Skip to content

fix(core): Enable Pi agent to successfully use slash commands: Accumulate multi-chunk AI commands before parsing#1581

Merged
Wirasm merged 10 commits into
coleam00:devfrom
b1skit:commandTruncationFix
May 12, 2026
Merged

fix(core): Enable Pi agent to successfully use slash commands: Accumulate multi-chunk AI commands before parsing#1581
Wirasm merged 10 commits into
coleam00:devfrom
b1skit:commandTruncationFix

Conversation

@b1skit

@b1skit b1skit commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: When an AI provider emits an orchestrator command (/invoke-workflow or /register-project) across multiple streaming chunks, the orchestrator stopped accumulating text as soon as the command prefix was detected — so the command body never arrived, parse failed, and the workflow was silently not dispatched. A second independent bug affected batch mode: filterToolIndicators joins chunks with '\n\n---\n\n' before splitting on '\n\n', injecting bare separator lines that break cross-chunk command patterns at parse time.

  • Why it matters: Any provider that emits smaller text deltas (e.g. Pi, or any provider under load) would silently fail to dispatch AI-generated workflow invocations and project registrations, with no error shown to the user. The same response that triggered a workflow in one run would do nothing in another.

  • What changed: Both handleStreamMode and handleBatchMode in orchestrator-agent.ts now accumulate assistant chunks until the complete command pattern is present, then stop. A commandFullyParsed flag decouples accumulation from streaming suppression — commandDetected still gates UI output, but commandFullyParsed gates the push. Batch-mode command parsing now uses assistantMessages.join('') (raw concatenation) instead of filterToolIndicators output. Four module-level regex constants replace inline literals duplicated across both modes. Six regression tests cover stream and batch mode for both commands, including split-across-chunks and trailing-chunk scenarios.

  • What did not change (scope boundary): Workflow routing semantics, workflow execution, provider APIs, command syntax, the filterToolIndicators function itself, user-visible message formatting for non-command responses, and all other orchestrator paths.

UX Journey

Before

User                   Archon (stream mode)             AI Provider
────                   ────────────────────             ───────────
sends request ──────▶  sends prompt to provider ──────▶ streams response
                       chunk 1: "/invoke-workflow "
                       ← commandDetected = true
                       chunk 2: "fix-bug "             (dropped — not pushed)
                       chunk 3: "--project my-proj"    (dropped — not pushed)
                       result chunk arrives
                       parse: allMessages = ["/invoke-workflow "]
                       no match → workflowInvocation = null
user sees nothing ◀─── workflow not dispatched (silent failure)

After

User                   Archon (stream mode)             AI Provider
────                   ────────────────────             ───────────
sends request ──────▶  sends prompt to provider ──────▶ streams response
                       chunk 1: "/invoke-workflow "
                       [commandDetected = true]
                       [commandFullyParsed = false → still accumulating]
                       chunk 2: "fix-bug "             [pushed — accumulating]
                       chunk 3: "--project my-proj"    [pushed — full pattern matches]
                       [commandFullyParsed = true → stop accumulating]
                       result chunk arrives
                       parse: allMessages.join('') = "/invoke-workflow fix-bug --project my-proj"
                       match → workflowInvocation resolved
user sees dispatch ◀── workflow dispatched correctly

Architecture Diagram

Before

packages/core/src/orchestrator/orchestrator-agent.ts
  ├─ handleStreamMode()
  │   ├─ commandDetected flag controls BOTH streaming suppression AND accumulation
  │   └─ post-detection chunks silently dropped from allMessages[]
  └─ handleBatchMode()
      ├─ same commandDetected conflation
      └─ parseOrchestratorCommands(filterToolIndicators(assistantMessages))
          └─ filterToolIndicators inserts '\n\n---\n\n' separators → breaks cross-chunk parse

packages/core/src/orchestrator/orchestrator-agent.test.ts
  └─ no coverage for split-command-body scenarios

After

[~] packages/core/src/orchestrator/orchestrator-agent.ts
  ├─ [+] INVOKE_WORKFLOW_PREFIX_RE  (module-level constant)
  ├─ [+] REGISTER_PROJECT_PREFIX_RE (module-level constant)
  ├─ [+] INVOKE_WORKFLOW_FULL_RE    (module-level constant)
  ├─ [+] REGISTER_PROJECT_FULL_RE   (module-level constant)
  ├─ [~] handleStreamMode()
  │   ├─ [+] commandFullyParsed flag — stops accumulation once complete pattern present
  │   ├─ commandDetected still gates UI output (unchanged semantics)
  │   └─ allMessages[] accumulates until commandFullyParsed, then freezes
  └─ [~] handleBatchMode()
      ├─ [+] commandFullyParsed flag (same logic as stream mode)
      ├─ allChunks.push() unconditional (all chunks recorded for debug log)
      ├─ assistantMessages.push() gated on !commandFullyParsed
      ├─ MAX_BATCH_ASSISTANT_CHUNKS truncation gated on !commandFullyParsed
      └─ parseOrchestratorCommands(assistantMessages.join(''), ...)
          └─ raw concatenation bypasses filterToolIndicators separator injection

[~] packages/core/src/orchestrator/orchestrator-agent.test.ts
  └─ [+] describe block: 'handleMessage — multi-chunk command accumulation (regression)'
      ├─ stream mode — register-project split across 3 chunks
      ├─ batch mode — register-project split across 3 chunks
      ├─ stream mode — invoke-workflow split across 2 chunks
      ├─ batch mode — invoke-workflow split across 2 chunks
      ├─ stream mode — command in single chunk still works (non-regression)
      └─ stream mode — pre-command text streamed, post-command chunks suppressed

Connection inventory:

From To Status Notes
handleStreamMode allMessages[] accumulation modified Now gated on !commandFullyParsed instead of !commandDetected
handleStreamMode platform.sendMessage unchanged Still gated on !commandDetected
handleBatchMode assistantMessages[] accumulation modified Now gated on !commandFullyParsed
handleBatchMode allChunks[] accumulation modified Now unconditional (was inside !commandDetected)
handleBatchMode parseOrchestratorCommands modified Input changed from filterToolIndicators(assistantMessages) to assistantMessages.join('')
handleBatchMode filterToolIndicators unchanged Still used for user-visible finalMessage
parseOrchestratorCommands workflow dispatch unchanged Contract unchanged
INVOKE_WORKFLOW_PREFIX_RE / REGISTER_PROJECT_PREFIX_RE both modes new Extracted from inline literals
INVOKE_WORKFLOW_FULL_RE / REGISTER_PROJECT_FULL_RE both modes new New — full-command stop condition

Label Snapshot

  • Risk: risk: low
  • Size: size: S
  • Scope: core|tests
  • Module: core:orchestrator

Change Metadata

  • Change type: bug
  • Primary scope: core

Linked Issue

Validation Evidence (required)

bun run validate

Result: 3574 pass, 0 fail across all packages. Exit code 0. All six checks passed:
check:bundled, check:bundled-skill, type-check, lint (0 warnings), format:check, test.

New regression tests in orchestrator-agent.test.ts:

  • handleMessage — multi-chunk command accumulation (regression) — 6 tests, all pass

  • Evidence provided: bun run validate exit 0, all test suites green

  • If any command is intentionally skipped, explain why: N/A — full suite ran

Security Impact (required)

  • New permissions/capabilities? (No)
  • New external network calls? (No)
  • Secrets/tokens handling changed? (No)
  • File system access scope changed? (No)

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Database migration needed? (No)

Human Verification (required)

What was personally validated beyond CI:

  • Verified scenarios: Split /invoke-workflow across 2 chunks (stream and batch); split /register-project across 3 chunks (stream and batch); single-chunk command (non-regression); pre-command prose streamed correctly, post-command trailing chunks suppressed; existing orchestrator.test.ts test 'silences further output after /invoke-workflow detected' continues to pass with no modification.
  • Edge cases checked: Trailing chunk arriving after complete command present (stops accumulating, project-name token not corrupted); command fully present in first chunk (sets commandFullyParsed immediately, same behavior as before); batch mode filterToolIndicators output still used for user-visible non-command prose.
  • What was not verified: Live end-to-end test with Pi provider streaming real multi-chunk output; browser-level manual workflow dispatch through Web UI.

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: handleStreamMode and handleBatchMode in orchestrator-agent.ts — the two AI response processing paths. All platforms route through one of these two functions.
  • Potential unintended effects: For responses that happen to contain /invoke-workflow or /register-project as prose examples (not commands), streaming suppression already fired before this change and continues to fire. The commandFullyParsed stop condition means accumulation halts once a valid full-command pattern matches — if the AI emits command-shaped prose that also matches the full pattern, it will be parsed and dispatched. This was already true before the change; the new stop condition does not widen the surface.
  • Guardrails/monitoring for early detection: Existing command dispatch tests plus the 6 new regression tests cover the primary failure modes. The commandDetected + commandFullyParsed state machine is straightforward enough to audit by inspection.

Rollback Plan (required)

  • Fast rollback command/path: Revert this commit (git revert HEAD). No schema, config, or data changes to undo.
  • Feature flags or config toggles (if any): None.
  • Observable failure symptoms after rollback: AI-generated /invoke-workflow and /register-project commands from providers that emit small chunks (e.g. Pi) silently fail to dispatch — user sees no workflow started, no error message.

Risks and Mitigations

  • Risk: commandFullyParsed full-pattern regexes diverge from parseOrchestratorCommands parser regexes over time, causing accumulation to stop before the parser can match.

    • Mitigation: The stop-condition constants (INVOKE_WORKFLOW_FULL_RE, REGISTER_PROJECT_FULL_RE) are module-level and co-located with the parser. They are intentionally conservative (require fewer tokens than the parser to guarantee the parser will match). The regression tests exercise both the accumulation stop and the downstream parse in a single assertion, so any divergence would break the tests.
  • Risk: A provider emits the command prefix (/invoke-workflow ) but the turn ends before the full command arrives — commandDetected has suppressed all output, commandFullyParsed is never set, and the post-loop parse returns null. The user receives no response.

    • Mitigation: This silent-swallow scenario predates this PR (it existed in the original commandDetected logic). It is not introduced by this change and is out of scope. It is noted here for future work.

Summary by CodeRabbit

  • Bug Fixes

    • More robust command handling for streamed and batched assistant output: reconstructs commands split across chunks, strips markdown decorators, preserves quoted paths and synthesized prompts, suppresses command-trigger/post-command assistant text, and emits missing assistant tail text when later fragments complete a stream.
  • Tests

    • Added regression and streaming/batch tests for multi‑chunk commands, delayed prompts, single‑chunk non‑regression, session capture, and expanded test mocks for codebase operations.

Copilot AI review requested due to automatic review settings May 5, 2026 05:00
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds conservative full-command detection and continued accumulation for AI-generated orchestrator slash-commands so commands split across streamed assistant chunks are reconstructed and parsed in both stream and batch modes; emits corrective assistant tail chunks for partially streamed assistant output. Tests and mocks updated to cover multi-chunk command reconstruction and tail-completion cases.

Changes

Multi-Chunk Command Parsing and Accumulation

Layer / File(s) Summary
Command Detection Constants & Helpers
packages/core/src/orchestrator/orchestrator-agent.ts
Adds prefix regexes and full-command matchers (INVOKE_WORKFLOW_FULL_RE, REGISTER_PROJECT_FULL_RE), normalizeCommandText, and isCommandFullyParsed for conservative multi-chunk parsing.
Normalization & Parsing Input
packages/core/src/orchestrator/orchestrator-agent.ts
parseOrchestratorCommands now strips markdown bold/italic around slash-command lines and derives remainingMessage/preamble from normalized text; batch parsing uses concatenated assistantMessages.join('').
Stream Mode Accumulation & Suppression
packages/core/src/orchestrator/orchestrator-agent.ts
handleStreamMode suppresses frontend streaming once a command prefix is detected but continues accumulating assistant chunks until the command is fully parsed.
Batch Mode Accumulation, Truncation & Caps
packages/core/src/orchestrator/orchestrator-agent.ts
handleBatchMode records allChunks unconditionally, avoids shifting/truncating while command tokens are incomplete, and enforces MAX_BATCH_TOTAL_CHUNKS using allChunks.
Registration Response Handling
packages/core/src/orchestrator/orchestrator-agent.ts
handleProjectRegistrationResult normalizes the full response before extracting/sending the user-visible pre-command text.
Tests & Mocks
packages/core/src/orchestrator/orchestrator-agent.test.ts, packages/core/src/orchestrator/orchestrator.test.ts, packages/core/src/handlers/command-handler.test.ts
Adds parse/regression tests for markdown-decorated commands, quoted-path handling, and a handleMessage — multi-chunk command accumulation suite validating stream/batch /register-project and /invoke-workflow splits and prompt-late chunks; refactors mockCreateCodebase, adds mockListCodebases, and adjusts a test newline to trigger full-command detection.

Bridge: Streaming Tail Completion

Layer / File(s) Summary
Assembled Assistant Extraction
packages/providers/src/community/pi/event-bridge.ts
Adds extractLastAssistantText(messages) to concatenate text blocks from the last assistant message in a Pi transcript.
Per-turn Tracking & Subscription Updates
packages/providers/src/community/pi/event-bridge.ts
Tracks currentTurnText and finalAssembledText, updating them on turn_start and agent_end events in the bridge subscription.
Tail Completion Emission
packages/providers/src/community/pi/event-bridge.ts
Before yielding the terminal result, if the assembled assistant text is longer than the streamed prefix and the prefix matches, emits a corrective assistant chunk with the missing suffix and appends it to the structured-output buffer when enabled.
Tests: Tail Completion & Structured Output
packages/providers/src/community/pi/event-bridge.test.ts
Adds streaming tail completion tests covering corrective-chunk emission on truncation, non-emission when streaming is complete or mismatched, per-turn buffering reset, and structured-output incorporation of corrective tail.

Sequence Diagram

sequenceDiagram
    participant Provider as AI Provider
    participant Stream as Stream Handler
    participant Batch as Batch Handler
    participant Parser as Command Parser
    participant Dispatcher as Workflow Dispatcher
    participant Platform as Platform (UI)

    rect rgba(220,120,120,0.5)
    Note over Provider,Dispatcher: Before fix — prefix suppressed but accumulation stopped
    Provider->>Stream: Chunk 1: /invoke-workflow (prefix)
    Stream->>Platform: suppress output
    Provider->>Stream: Chunk 2: some-workflow
    Provider->>Stream: Chunk 3: --project X
    Stream->>Parser: Parse incomplete buffer
    Parser->>Dispatcher: no dispatch
    end

    rect rgba(120,220,120,0.5)
    Note over Provider,Dispatcher: After fix — accumulate until full command, then parse & dispatch
    Provider->>Stream: Chunk 1: /invoke-workflow (prefix)
    Stream->>Platform: suppress output
    Note over Stream: accumulate chunks until isCommandFullyParsed == true
    Provider->>Stream: Chunk 2: some-workflow
    Provider->>Stream: Chunk 3: --project X
    Stream->>Parser: Parse full accumulated text
    Parser->>Dispatcher: dispatch /invoke-workflow
    Dispatcher->>Platform: background workflow started (sessionId)
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • coleam00/Archon#1297: Modifies packages/providers/src/community/pi/event-bridge.ts and related assistant-chunk handling, overlapping with tail-completion logic.
  • coleam00/Archon#1116: Touches orchestrator agent/client/provider APIs that are referenced by the updated orchestrator-agent parsing/streaming logic.
  • coleam00/Archon#1065: Modifies handleMessage / orchestrator-agent behavior and may overlap with multi-chunk command accumulation fixes.

Poem

I stitch the chunks the stream forgot to keep,
I hide the slash and gather every part,
I whisper tails the broken stream would leap,
I strip the bold, preserve the quoted heart,
Then hop — the workflow wakes, a stitched-up art. 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main bug fix: enabling Pi agent to use slash commands by accumulating multi-chunk AI commands before parsing. This is the primary change in the PR.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering problem, why it matters, what changed, scope boundaries, UX journey, architecture diagrams, connection inventory, labels, change metadata, linked issues, validation evidence, security impact, compatibility, human verification, side effects, and rollback plan—all required sections are complete.
Linked Issues check ✅ Passed The PR fully addresses issue #1541 requirements: accumulates multi-chunk commands before parsing [#1541], suppresses UI output while accumulating [#1541], uses raw concatenation for batch parsing to avoid separator injection [#1541], adds regression tests for split commands [#1541], and verifies workflow dispatch and command suppression [#1541].
Out of Scope Changes check ✅ Passed All changes are scoped to handling orchestrator command accumulation and parsing: modifications to orchestrator-agent.ts/test.ts for command detection/parsing, test setup changes in command-handler.test.ts and event-bridge.test.ts, and event-bridge.ts for assistant text reconstruction. Workflow routing/execution, provider APIs, command syntax, and formatters remain unchanged, consistent with stated scope boundaries.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes orchestrator command dispatch reliability when /invoke-workflow and /register-project are streamed across multiple assistant chunks, by continuing to accumulate assistant text past prefix detection (while still suppressing command output), adjusting batch parsing to use raw concatenation, and adding regression coverage.

Changes:

  • Add module-level prefix/full regex constants and a commandFullyParsed flag to decouple UI suppression from command accumulation in both stream and batch modes.
  • In batch mode, parse orchestrator commands from assistantMessages.join('') instead of the filterToolIndicators(...)-formatted output to avoid separator-induced parse breaks.
  • Add regression tests covering split-across-chunks behavior for both commands in stream and batch modes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
packages/core/src/orchestrator/orchestrator-agent.ts Updates stream/batch accumulation logic and batch parsing input; introduces shared regex constants.
packages/core/src/orchestrator/orchestrator-agent.test.ts Adds regression tests for multi-chunk command accumulation and refactors a mock for reuse/reset.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/orchestrator/orchestrator-agent.ts Outdated
Comment thread packages/core/src/orchestrator/orchestrator-agent.ts
Comment thread packages/core/src/orchestrator/orchestrator-agent.ts Outdated
Comment thread packages/core/src/orchestrator/orchestrator-agent.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/core/src/orchestrator/orchestrator-agent.ts (1)

962-1000: 💤 Low value

Optional: extract the full-pattern check to remove 4× duplication.

The block

if (INVOKE_WORKFLOW_FULL_RE.test(accumulated) || REGISTER_PROJECT_FULL_RE.test(accumulated)) {
  commandFullyParsed = true;
}

appears twice in handleStreamMode (lines 982–987, 994–999) and twice in handleBatchMode (lines 1157–1162, 1166–1170). A small helper would reduce repetition and centralize the "is the command fully captured?" decision alongside the regex constants near line 100.

♻️ Suggested helper
+const isCommandFullyParsed = (s: string): boolean =>
+  INVOKE_WORKFLOW_FULL_RE.test(s) || REGISTER_PROJECT_FULL_RE.test(s);
+
+const hasCommandPrefix = (s: string): boolean =>
+  INVOKE_WORKFLOW_PREFIX_RE.test(s) || REGISTER_PROJECT_PREFIX_RE.test(s);

Then both modes collapse to:

if (!commandDetected) {
  const accumulated = allMessages.join('');
  if (hasCommandPrefix(accumulated)) {
    commandDetected = true;
    if (isCommandFullyParsed(accumulated)) commandFullyParsed = true;
  } else {
    await platform.sendMessage(conversationId, msg.content);
  }
} else if (!commandFullyParsed) {
  if (isCommandFullyParsed(allMessages.join(''))) commandFullyParsed = true;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 962 -
1000, Extract the duplicated full-pattern check into a small helper (e.g.
isCommandFullyParsed(accumulated)) placed near the regex constants so it returns
true when INVOKE_WORKFLOW_FULL_RE.test(accumulated) ||
REGISTER_PROJECT_FULL_RE.test(accumulated); then replace the four duplicated
if-blocks in handleStreamMode and handleBatchMode with calls to
isCommandFullyParsed(allMessages.join('')) (or the accumulated variable) and set
commandFullyParsed = true when it returns true; keep the existing flow and only
centralize the regex logic into that helper.
packages/core/src/orchestrator/orchestrator-agent.test.ts (1)

1839-1868: 💤 Low value

Optional: strengthen the suppression test by asserting parsed command body, not just sendMessage calls.

This test verifies that extra trailing is not streamed via sendMessage, but doesn't directly assert the post-command chunk was excluded from accumulation/parsing. Since mockParseCommand is stubbed to return args: ['Foo', '/path', 'extra', 'trailing'], the actual default_cwd passed to createCodebase is decoupled from the regex parse — so a regression where commandFullyParsed failed to engage and the suppressed chunk was appended would still pass this test.

Consider asserting mockCreateCodebase was called with the parsed-from-regex value to lock in the accumulation behavior:

♻️ Stronger assertion
     // Full command was accumulated and parsed
-    expect(mockCreateCodebase).toHaveBeenCalled();
+    // The mocked parseCommand returns ['Foo','/path','extra','trailing'], but createCodebase
+    // is invoked through `/register-project ${name} ${path}` synthesized from the regex parse.
+    // If post-command suppression failed, the regex would see ' extra trailing' and the
+    // synthesized command path would include it. We assert the path stops at /path.
+    expect(mockCreateCodebase).toHaveBeenCalled();
+    // Optional: spy on handleRegisterProject input to assert the synthesized message
+    // ends with '/path' (not '/path extra trailing').
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.test.ts` around lines 1839
- 1868, Add an assertion that the accumulated/parsed command fed to
createCodebase matches the expected parsed args (i.e., that mockCreateCodebase
was invoked with the parsed default_cwd and not with the appended "extra
trailing" part) so the test verifies accumulation/parsing, not just suppression
of sendMessage; specifically, after calling handleMessage check
mockCreateCodebase (or the function that ultimately consumes the parsed command)
was called and inspect its argument(s) to assert default_cwd (or args) equals
the value returned by mockParseCommand (e.g., '/path' and
['Foo','/path','extra','trailing'] or assert the post-command text is not
present), using the existing mocks mockParseCommand and mockCreateCodebase to
locate where to add the assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 100-101: REGISTER_PROJECT_FULL_RE currently uses
/^\/register-project\s+\S+\s+\S+/m which treats the path as a single non-space
token and can flip commandFullyParsed prematurely when a path contains spaces
across chunks; change REGISTER_PROJECT_FULL_RE to anchor the path to the end of
line so it accepts spaces (e.g. use a pattern like
/^\/register-project\s+\S+\s+.*$/m or /^\/register-project\s+\S+\s+.+$/m) and
update related streaming tests to cover split-across-chunks input, or if quoted
paths are required by design, add an inline comment and documentation enforcing
quoted-paths to make the asymmetry with parseOrchestratorCommands explicit
(referencing REGISTER_PROJECT_FULL_RE, INVOKE_WORKFLOW_FULL_RE and
parseOrchestratorCommands).

---

Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator-agent.test.ts`:
- Around line 1839-1868: Add an assertion that the accumulated/parsed command
fed to createCodebase matches the expected parsed args (i.e., that
mockCreateCodebase was invoked with the parsed default_cwd and not with the
appended "extra trailing" part) so the test verifies accumulation/parsing, not
just suppression of sendMessage; specifically, after calling handleMessage check
mockCreateCodebase (or the function that ultimately consumes the parsed command)
was called and inspect its argument(s) to assert default_cwd (or args) equals
the value returned by mockParseCommand (e.g., '/path' and
['Foo','/path','extra','trailing'] or assert the post-command text is not
present), using the existing mocks mockParseCommand and mockCreateCodebase to
locate where to add the assertion.

In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 962-1000: Extract the duplicated full-pattern check into a small
helper (e.g. isCommandFullyParsed(accumulated)) placed near the regex constants
so it returns true when INVOKE_WORKFLOW_FULL_RE.test(accumulated) ||
REGISTER_PROJECT_FULL_RE.test(accumulated); then replace the four duplicated
if-blocks in handleStreamMode and handleBatchMode with calls to
isCommandFullyParsed(allMessages.join('')) (or the accumulated variable) and set
commandFullyParsed = true when it returns true; keep the existing flow and only
centralize the regex logic into that helper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2108524-99fa-43da-aad7-6544fe0ae54d

📥 Commits

Reviewing files that changed from the base of the PR and between 0ec7441 and 0ddeb7f.

📒 Files selected for processing (2)
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts

Comment thread packages/core/src/orchestrator/orchestrator-agent.ts Outdated
…etion

Co-authored-by: Copilot <copilot@github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/orchestrator/orchestrator-agent.test.ts (1)

1780-1816: ⚡ Quick win

Test gap: /invoke-workflow … --prompt split across chunks isn't covered.

The regression suite covers the bare /invoke-workflow assist --project my-project shape but never exercises a chunk boundary that lands between --project <name> and --prompt "…". Per prompt-builder.ts, the synthesized --prompt is mandatory and must be self-contained, so it's the most likely token to be dropped if accumulation stops too early. As noted in the comment on INVOKE_WORKFLOW_FULL_RE in orchestrator-agent.ts, the current full-pattern matches as soon as the project name is seen — a test like the one below would expose that gap and pin behavior once it's fixed.

♻️ Proposed additional test
+  test('stream mode — invoke-workflow with --prompt split across 3 chunks preserves synthesizedPrompt', async () => {
+    mockListCodebases.mockReturnValueOnce(Promise.resolve([makeCodebase('my-project')]));
+    mockDiscoverWorkflowsWithConfig.mockReturnValueOnce(
+      Promise.resolve({ workflows: [makeTestWorkflowWithSource({ name: 'assist' })], errors: [] })
+    );
+    mockSendQuery.mockImplementationOnce(async function* () {
+      yield { type: 'assistant', content: 'Running.\n\n/invoke-workflow assist --project my-project ' };
+      yield { type: 'assistant', content: '--prompt "Add dark mode support"' };
+      yield { type: 'result', sessionId: 'sess-1' };
+    });
+
+    const platform = makePlatform();
+    (platform.getStreamingMode as ReturnType<typeof mock>).mockReturnValue('stream');
+    await handleMessage(platform, 'conv-1', 'add dark mode');
+
+    expect(mockDispatchBackgroundWorkflow).toHaveBeenCalled();
+    const dispatchArgs = mockDispatchBackgroundWorkflow.mock.calls[0][0] as { originalMessage: string };
+    expect(dispatchArgs.originalMessage).toBe('Add dark mode support');
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.test.ts` around lines 1780
- 1816, The tests currently cover chunk splits before the project flag but not a
split that separates the required --prompt argument; add a new test in
orchestrator-agent.test.ts that mirrors the existing "split across 2 chunks"
cases but yields assistant chunks where the first chunk ends with '--project
my-project ' and the second chunk begins with '--prompt "some prompt text"' (use
mockSendQuery to produce those two assistant yields and a final result),
exercise handleMessage with both streaming and batch platform.getStreamingMode
values, and assert that the workflow is dispatched/executed according to the
intended behavior; reference the existing test structure and symbols
handleMessage, mockSendQuery, mockDispatchBackgroundWorkflow,
mockExecuteWorkflow, and the INVOKE_WORKFLOW_FULL_RE behavior in
orchestrator-agent.ts so the test targets the prompt-boundary regression
described.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Line 100: INVOKE_WORKFLOW_FULL_RE currently marks the command complete as soon
as the project token is matched, which can drop a following --prompt delivered
in a later chunk; update the detection so the command is considered "fully
parsed" only when a line terminator or a closing --prompt quote is present
(mirror the approach used for REGISTER_PROJECT_FULL_RE). Locate
INVOKE_WORKFLOW_FULL_RE and the isCommandFullyParsed/parseOrchestratorCommands
flow and change the regex/logic to require either a newline terminator or an
explicit --prompt closure before flipping isCommandFullyParsed, ensuring
synthesizedPrompt is captured for handleWorkflowInvocationResult instead of
falling back to originalMessage.

---

Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator-agent.test.ts`:
- Around line 1780-1816: The tests currently cover chunk splits before the
project flag but not a split that separates the required --prompt argument; add
a new test in orchestrator-agent.test.ts that mirrors the existing "split across
2 chunks" cases but yields assistant chunks where the first chunk ends with
'--project my-project ' and the second chunk begins with '--prompt "some prompt
text"' (use mockSendQuery to produce those two assistant yields and a final
result), exercise handleMessage with both streaming and batch
platform.getStreamingMode values, and assert that the workflow is
dispatched/executed according to the intended behavior; reference the existing
test structure and symbols handleMessage, mockSendQuery,
mockDispatchBackgroundWorkflow, mockExecuteWorkflow, and the
INVOKE_WORKFLOW_FULL_RE behavior in orchestrator-agent.ts so the test targets
the prompt-boundary regression described.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b27e2196-ee09-40ed-ab66-9053b2810ac2

📥 Commits

Reviewing files that changed from the base of the PR and between 0ddeb7f and fa09823.

📒 Files selected for processing (2)
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts

Comment thread packages/core/src/orchestrator/orchestrator-agent.ts Outdated
…t accumulation in multi-chunk messages

Co-authored-by: Copilot <copilot@github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/core/src/orchestrator/orchestrator-agent.test.ts (1)

1892-1931: 💤 Low value

Consider tightening this test to avoid masking real path-corruption behavior.

The mocked parseCommand returning clean ['Foo', '/path'] lets createCodebase succeed even though the accumulated command text is /register-project Foo /path extra trailing (trailing chunk concatenated into the unquoted path). The comment honestly notes this would fail existsSync in production, but the assertion expect(mockCreateCodebase).toHaveBeenCalledWith(expect.objectContaining({ default_cwd: '/path' })) reads as if the path was correctly isolated when it actually is not.

A safer formulation would be either (a) terminate chunk 2 with \n so commandFullyParsed fires and chunk 3 is genuinely suppressed (then assert default_cwd: '/path' truthfully), or (b) drop the mockParseCommand override and assert the suppression-only behavior on platform.sendMessage — leaving createCodebase call expectations out — so the test doesn't appear to validate path correctness it isn't actually validating. Optional, since the streaming-suppression invariant is what this test is really about.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.test.ts` around lines 1892
- 1931, The test currently masks real path-corruption because mockParseCommand
returns clean args while mockSendQuery yields a third " extra trailing" chunk
that would actually corrupt the reconstructed unquoted path; either (a) make the
second assistant chunk include a trailing "\n" (e.g. yield { type: 'assistant',
content: ' /register-project Foo /path\n' }) so commandFullyParsed becomes true
and the third chunk is suppressed (keep mockParseCommand and the createCodebase
assertion), or (b) remove the mockParseCommand override and drop the
createCodebase assertion so the test only asserts streaming behavior via
platform.sendMessage and does not pretend to validate path correctness (adjust
mockSendQuery accordingly); update the mockSendQuery yields and expectations to
match the chosen approach and keep references to handleMessage, mockSendQuery,
mockParseCommand, and mockCreateCodebase when editing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator-agent.test.ts`:
- Around line 1892-1931: The test currently masks real path-corruption because
mockParseCommand returns clean args while mockSendQuery yields a third " extra
trailing" chunk that would actually corrupt the reconstructed unquoted path;
either (a) make the second assistant chunk include a trailing "\n" (e.g. yield {
type: 'assistant', content: ' /register-project Foo /path\n' }) so
commandFullyParsed becomes true and the third chunk is suppressed (keep
mockParseCommand and the createCodebase assertion), or (b) remove the
mockParseCommand override and drop the createCodebase assertion so the test only
asserts streaming behavior via platform.sendMessage and does not pretend to
validate path correctness (adjust mockSendQuery accordingly); update the
mockSendQuery yields and expectations to match the chosen approach and keep
references to handleMessage, mockSendQuery, mockParseCommand, and
mockCreateCodebase when editing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2cbe714b-5b84-494d-8136-342fbb318de0

📥 Commits

Reviewing files that changed from the base of the PR and between fa09823 and 944ceee.

📒 Files selected for processing (3)
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/core/src/orchestrator/orchestrator.test.ts

b1skit and others added 3 commits May 5, 2026 08:41
…i-chunk messages

Co-authored-by: Copilot <copilot@github.com>
… truncation

Fixes two failure modes where the Pi provider silently drops /register-project and /invoke-workflow commands, leaving projects unregistered with no error shown to the user.

Mode A — Markdown bold wrapping (orchestrator-agent.ts)

Pi occasionally emits commands wrapped in markdown bold: **/register-project Name "/path"**. The existing prefix and full-command regexes require lines to start with /, so these were never detected.

Adds normalizeCommandText() which strips leading/trailing * characters from lines whose first non-asterisk character is /. Applied to all six detection and parse sites: isCommandFullyParsed, handleStreamMode, handleBatchMode, parseOrchestratorCommands (3 uses), and handleProjectRegistrationResult.

Mode B — Streaming truncation (event-bridge.ts)

Pi's text_delta events sometimes stop delivering characters mid-command (e.g. /register-project arrives but  SaberEngine "/path" never does as a delta). The complete text is present in agent_end.messages but was previously discarded.

Adds extractLastAssistantText() to read the fully-assembled text from the agent_end transcript. In bridgeSession, tracks the per-turn streamed length, and if the assembled text is strictly longer and starts with what was streamed, emits the missing suffix as a corrective assistant chunk before the result chunk — making it available to the orchestrator's accumulator.

Tests

5 new tests in orchestrator-agent.test.ts covering bold/italic stripping for both commands, including multiline responses and quoted paths
5 new tests in event-bridge.test.ts covering: truncation detected and emitted, no false emission when complete, mismatch guard, per-turn reset on turn_start, and assistantBuffer inclusion when wantsStructured
@b1skit

b1skit commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown
✅ Actions performed

Reviews resumed.

@b1skit

b1skit commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

Fix results: Pi agent can now successfully use the /register-project command

image image

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/core/src/orchestrator/orchestrator-agent.ts (1)

177-179: 💤 Low value

normalizeCommandText won't strip bold wrappers on indented lines.

The pattern /^\*+(\/[^\n]*?)\**$/gm anchors directly on * at line start, so **/register-project foo /path** (any leading whitespace, blockquote, list marker, etc.) won't be normalized and the prefix regex will still miss the line. If observed Pi outputs always emit bold-wrapped commands flush-left, this is fine; otherwise, allow optional leading whitespace and trim trailing whitespace too.

♻️ Optional: tolerate leading/trailing whitespace
-function normalizeCommandText(text: string): string {
-  return text.replace(/^\*+(\/[^\n]*?)\**$/gm, '$1');
-}
+function normalizeCommandText(text: string): string {
+  // Tolerate leading whitespace (blockquote/indent) and trailing whitespace
+  // around the bold/italic wrappers — some models indent their output.
+  return text.replace(/^[ \t]*\*+(\/[^\n]*?)\**[ \t]*$/gm, '$1');
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 177 - 179,
normalizeCommandText fails to strip bold wrappers when lines have leading
whitespace; update the function (normalizeCommandText) to allow optional leading
whitespace before the opening asterisks and to trim trailing whitespace after
the closing asterisks (e.g., accept /^\s*\*+(\/[^\n]*?)\**\s*$/gm or
alternatively apply .trim() to the captured text) so indented or spaced lines
like "   **/register-project foo /path**" are normalized correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 177-179: normalizeCommandText fails to strip bold wrappers when
lines have leading whitespace; update the function (normalizeCommandText) to
allow optional leading whitespace before the opening asterisks and to trim
trailing whitespace after the closing asterisks (e.g., accept
/^\s*\*+(\/[^\n]*?)\**\s*$/gm or alternatively apply .trim() to the captured
text) so indented or spaced lines like "   **/register-project foo /path**" are
normalized correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9bf69065-164b-4c2a-95be-98fd30f62ba1

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea14db and 3923c21.

📒 Files selected for processing (5)
  • packages/core/src/handlers/command-handler.test.ts
  • packages/core/src/orchestrator/orchestrator-agent.test.ts
  • packages/core/src/orchestrator/orchestrator-agent.ts
  • packages/providers/src/community/pi/event-bridge.test.ts
  • packages/providers/src/community/pi/event-bridge.ts

@b1skit b1skit changed the title fix(core): accumulate multi-chunk AI commands before parsing fix(core): Enable Pi agent to successfully use slash commands: Accumulate multi-chunk AI commands before parsing May 7, 2026
@b1skit

b1skit commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

@coleam00 @Wirasm Is there anything else needed to get this approved/merged?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/orchestrator/orchestrator-agent.ts (1)

1449-1474: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

indexOf('/register-project') may land on a prose mention rather than the actual command line.

parseOrchestratorCommands uses ^\/register-project with the /m flag, so only line-anchored occurrences are treated as the command. handleProjectRegistrationResult, however, slices on the first substring occurrence:

const regIndex = normalizedForExtraction.indexOf('/register-project');
const textBeforeReg = normalizedForExtraction.slice(0, regIndex).trim();

If the assistant explains itself before invoking — e.g.

You can use /register-project to register a project.
/register-project foo /path/to/foo

regIndex points at the prose mention on line 1, so the line containing the actual command is dropped from textBeforeReg, but the parser still dispatches based on line 2. The user message comes out truncated.

♻️ Suggested fix — anchor the lookup to the parsed command line
-  // Normalize before extraction so that Mode A's bold markers ('**') are
-  // stripped from the command line; otherwise textBeforeReg would include a
-  // trailing '**' when the model wrapped the command in markdown bold.
-  const normalizedForExtraction = normalizeCommandText(fullResponse);
-  const regIndex = normalizedForExtraction.indexOf('/register-project');
-  const textBeforeReg = normalizedForExtraction.slice(0, regIndex).trim();
+  // Normalize before extraction so that Mode A's bold markers ('**') are
+  // stripped from the command line; otherwise textBeforeReg would include a
+  // trailing '**' when the model wrapped the command in markdown bold.
+  const normalizedForExtraction = normalizeCommandText(fullResponse);
+  // Match line-anchored to avoid landing on a prose mention of "/register-project".
+  const regLineMatch = /^\/register-project\b/m.exec(normalizedForExtraction);
+  const regIndex = regLineMatch?.index ?? normalizedForExtraction.indexOf('/register-project');
+  const textBeforeReg = normalizedForExtraction.slice(0, regIndex).trim();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 1449 -
1474, handleProjectRegistrationResult currently uses
normalizedForExtraction.indexOf('/register-project') which can match a prose
mention; change the lookup to find the line-anchored command like
parseOrchestratorCommands does (e.g., search with a /^\s*\/register-project/m
style match) and use the match's index to compute textBeforeReg so only text
before the actual command line is sent; ensure you fall back safely if no
anchored match is found and keep the subsequent call to handleRegisterProject
using the exact matched command line.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 1449-1474: handleProjectRegistrationResult currently uses
normalizedForExtraction.indexOf('/register-project') which can match a prose
mention; change the lookup to find the line-anchored command like
parseOrchestratorCommands does (e.g., search with a /^\s*\/register-project/m
style match) and use the match's index to compute textBeforeReg so only text
before the actual command line is sent; ensure you fall back safely if no
anchored match is found and keep the subsequent call to handleRegisterProject
using the exact matched command line.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee4b66fd-db45-4843-a0cb-443312a81490

📥 Commits

Reviewing files that changed from the base of the PR and between 3923c21 and 54c5c77.

📒 Files selected for processing (1)
  • packages/core/src/orchestrator/orchestrator-agent.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
packages/core/src/orchestrator/orchestrator-agent.ts (3)

170-185: 💤 Low value

normalizeCommandText only strips asterisks — confirm Pi/Claude don't also wrap with backticks/underscores.

The docstring mentions "bold/italic decorators", but the regex only matches \*+. Markdown italic with underscores (_/register-project ..._) or inline code (\/register-project ...`) would still defeat both INVOKE_WORKFLOW_PREFIX_REandREGISTER_PROJECT_PREFIX_RE`. If the only observed bad-output mode from Pi was asterisks, this is fine as-is; otherwise consider broadening the pattern to cover the other two wrappers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 170 - 185,
normalizeCommandText currently only removes surrounding asterisks, but other
markdown wrappers (backticks or underscores) can also prevent
INVOKE_WORKFLOW_PREFIX_RE / REGISTER_PROJECT_PREFIX_RE and the full regexes from
matching; update normalizeCommandText to also strip a single pair of surrounding
backticks and/or underscores (and combinations like `_*...*_`) so lines whose
first non-wrapper char is '/' are normalized. Keep the normalization used by
isCommandFullyParsed (which tests INVOKE_WORKFLOW_FULL_RE and
REGISTER_PROJECT_FULL_RE) consistent with the prefix regexes by expanding the
replacement pattern to remove leading/trailing `, _, and * wrappers around the
/... command text while preserving internal characters.

1457-1467: 💤 Low value

Normalize-before-extract is correct, but the indexOf fallback can land on prose.

Normalizing first ensures the ** wrappers don't leak into textBeforeReg — good. The fallback normalizedForExtraction.indexOf('/register-project') at line 1463 is defensive but, by definition, will trigger only when the line-anchored regex misses, in which case the only available occurrence is non-line-anchored (i.e., almost certainly a prose mention like "use /register-project ..."). In that case we'd send the prose containing the command as the user-visible preamble and then attempt registration with whatever parseOrchestratorCommands captured — which itself is line-anchored, so registration should never be set in this branch in the first place. Consider replacing the fallback with an early return / log warning so the inconsistency is visible rather than silently producing an odd preamble.

♻️ Optional: make the no-line-match case explicit
-  const normalizedForExtraction = normalizeCommandText(fullResponse);
-  // Match line-anchored to avoid landing on a prose mention of "/register-project".
-  const regLineMatch = /^\/register-project\b/m.exec(normalizedForExtraction);
-  const regIndex = regLineMatch?.index ?? normalizedForExtraction.indexOf('/register-project');
-  const textBeforeReg = normalizedForExtraction.slice(0, regIndex).trim();
+  const normalizedForExtraction = normalizeCommandText(fullResponse);
+  // Match line-anchored to avoid landing on a prose mention of "/register-project".
+  const regLineMatch = /^\/register-project\b/m.exec(normalizedForExtraction);
+  if (!regLineMatch) {
+    getLog().warn({ conversationId }, 'orchestrator.register_project_line_not_found');
+  }
+  const regIndex = regLineMatch?.index ?? normalizedForExtraction.length;
+  const textBeforeReg = normalizedForExtraction.slice(0, regIndex).trim();

As per coding guidelines: "Use Pino logger factory with event naming format {domain}.{action}_{state}".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 1457 -
1467, The code currently falls back to
normalizedForExtraction.indexOf('/register-project') which will match prose and
produce an inconsistent preamble; change the logic in the block around
normalizeCommandText, regLineMatch and regIndex so that if the line-anchored
regex (/^\/register-project\b/m.exec) returns null you do not use indexOf but
instead log a warning and return early so no prose is sent; specifically, remove
the normalizedForExtraction.indexOf fallback, add an explicit branch when
regLineMatch is falsy that calls the module Pino logger (use event name like
"orchestrator.extract_no_line_match" per the pino format) and then return
without calling platform.sendMessage(conversationId, ...); keep
parseOrchestratorCommands behavior as-is.

1206-1256: 💤 Low value

Batch-mode gating and unconditional total-chunk cap look correct.

Pushing into allChunks unconditionally while gating assistantMessages accumulation/truncation on !commandFullyParsed correctly preserves the parse tokens, and enforcing MAX_BATCH_TOTAL_CHUNKS outside the commandDetected branch keeps the unconditional allChunks.push bounded. One minor caveat worth noting in a comment: if the AI never completes a detected command (no newline / closing quote ever arrives), assistantMessages will skip the MAX_BATCH_ASSISTANT_CHUNKS shift and grow up to MAX_BATCH_TOTAL_CHUNKS — that's still bounded by the outer cap, but it's a behavior change from “always cap assistant chunks” worth calling out for future maintainers.

Also applies to: 1299-1304

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 1206 -
1256, Add a clarifying comment near the loop handling
allChunks/assistantMessages (around allChunks, assistantMessages,
commandDetected, commandFullyParsed logic) that explains the edge case: once a
command is detected (commandDetected=true) we stop enforcing the
MAX_BATCH_ASSISTANT_CHUNKS shift, so if the AI never yields a complete command
(isCommandFullyParsed never true) assistantMessages can grow until the outer
MAX_BATCH_TOTAL_CHUNKS bound is hit; explicitly document this behavior and
rationale so future maintainers know why assistant chunk capping is conditional
and that total chunks still provide an overall cap.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 170-185: normalizeCommandText currently only removes surrounding
asterisks, but other markdown wrappers (backticks or underscores) can also
prevent INVOKE_WORKFLOW_PREFIX_RE / REGISTER_PROJECT_PREFIX_RE and the full
regexes from matching; update normalizeCommandText to also strip a single pair
of surrounding backticks and/or underscores (and combinations like `_*...*_`) so
lines whose first non-wrapper char is '/' are normalized. Keep the normalization
used by isCommandFullyParsed (which tests INVOKE_WORKFLOW_FULL_RE and
REGISTER_PROJECT_FULL_RE) consistent with the prefix regexes by expanding the
replacement pattern to remove leading/trailing `, _, and * wrappers around the
/... command text while preserving internal characters.
- Around line 1457-1467: The code currently falls back to
normalizedForExtraction.indexOf('/register-project') which will match prose and
produce an inconsistent preamble; change the logic in the block around
normalizeCommandText, regLineMatch and regIndex so that if the line-anchored
regex (/^\/register-project\b/m.exec) returns null you do not use indexOf but
instead log a warning and return early so no prose is sent; specifically, remove
the normalizedForExtraction.indexOf fallback, add an explicit branch when
regLineMatch is falsy that calls the module Pino logger (use event name like
"orchestrator.extract_no_line_match" per the pino format) and then return
without calling platform.sendMessage(conversationId, ...); keep
parseOrchestratorCommands behavior as-is.
- Around line 1206-1256: Add a clarifying comment near the loop handling
allChunks/assistantMessages (around allChunks, assistantMessages,
commandDetected, commandFullyParsed logic) that explains the edge case: once a
command is detected (commandDetected=true) we stop enforcing the
MAX_BATCH_ASSISTANT_CHUNKS shift, so if the AI never yields a complete command
(isCommandFullyParsed never true) assistantMessages can grow until the outer
MAX_BATCH_TOTAL_CHUNKS bound is hit; explicitly document this behavior and
rationale so future maintainers know why assistant chunk capping is conditional
and that total chunks still provide an overall cap.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 890e54cd-07c6-44cf-a044-870d8009478c

📥 Commits

Reviewing files that changed from the base of the PR and between 54c5c77 and c7b5278.

📒 Files selected for processing (1)
  • packages/core/src/orchestrator/orchestrator-agent.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 1469-1475: The current code returns when regLineMatch is falsy,
which can silently drop a valid /register-project dispatch; instead, remove the
early return so registration proceeds even if regLineMatch is missing, keep the
existing getLog().warn({ conversationId }, 'orchestrator.extract_no_line_match')
call, and ensure that when regLineMatch is absent you set/leave textBeforeReg
empty (or omit textBeforeReg from the downstream dispatch) before calling the
registration flow that parseOrchestratorCommands previously validated; update
the code paths around regLineMatch, textBeforeReg, and the /register-project
dispatch to continue processing while only skipping inclusion of textBeforeReg.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a1ce3b99-6737-4cf9-97e9-90ca8aca7ea4

📥 Commits

Reviewing files that changed from the base of the PR and between c7b5278 and efe22dd.

📒 Files selected for processing (1)
  • packages/core/src/orchestrator/orchestrator-agent.ts

Comment thread packages/core/src/orchestrator/orchestrator-agent.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/core/src/orchestrator/orchestrator-agent.ts (1)

170-179: 💤 Low value

Asymmetric markdown stripping — minor edge case.

The pattern /^\s*\*+(\/[^\n]*?)\**\s*$/gm requires at least one leading asterisk (\*+), so it normalizes symmetric (**/cmd ...**, */cmd ...*) and leading-only (**/cmd ...) cases, but a trailing-only emission (/register-project foo bar**) is left untouched and would leak ** into the path token captured by parseOrchestratorCommands's (.+)$. Symmetric bold is by far the common emission pattern, so this is unlikely to fire in practice; flagging for awareness only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/orchestrator/orchestrator-agent.ts` around lines 170 - 179,
The regex in normalizeCommandText requires at least one leading asterisk so it
misses trailing-only cases like "/register-project foo**"; change the quantifier
on the leading asterisks from \*+ to \** so lines with zero or more leading
asterisks are normalized (i.e., update the pattern in normalizeCommandText from
/^\s*\*+(\/[^\n]*?)\**\s*$/gm to use /^\s*\**(\/[^\n]*?)\**\s*$/gm) to strip
trailing-only, leading-only, and symmetric asterisks consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/orchestrator/orchestrator-agent.ts`:
- Around line 170-179: The regex in normalizeCommandText requires at least one
leading asterisk so it misses trailing-only cases like "/register-project
foo**"; change the quantifier on the leading asterisks from \*+ to \** so lines
with zero or more leading asterisks are normalized (i.e., update the pattern in
normalizeCommandText from /^\s*\*+(\/[^\n]*?)\**\s*$/gm to use
/^\s*\**(\/[^\n]*?)\**\s*$/gm) to strip trailing-only, leading-only, and
symmetric asterisks consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47412e40-ba53-418e-9573-b0712b76b80a

📥 Commits

Reviewing files that changed from the base of the PR and between efe22dd and df34071.

📒 Files selected for processing (1)
  • packages/core/src/orchestrator/orchestrator-agent.ts

@Wirasm

Wirasm commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Review Summary

Verdict: ready-to-merge

Clean, well-targeted bug fix. The core change accumulates multi-chunk AI streaming output until full /invoke-workflow and /register-project command patterns are detected — then stops. Six regression tests cover split-across-chunks, trailing-chunk, and markdown-decorated scenarios. CI is green at 3574 tests. One consistency gap between the diff and the in-repo handleProjectRegistrationResult should be patched before or shortly after merge.

Suggested fixes

  • orchestrator-agent.ts:1310 (handleProjectRegistrationResult): This function still uses the old indexOf('/register-project') extraction pattern instead of the normalized regLineMatch pattern shown in the diff (lines 1460–1480). When a Pi model emits **/register-project ...**, the preamble will include trailing ** characters. Replace the extraction body with the normalized version from the diff — keep the warn + proceed fallback so valid commands aren't silently dropped.

  • orchestrator-agent.ts — add dedicated describe('isCommandFullyParsed') tests covering: markdown bold stripped → true; unmatched leading asterisk → false; empty string → false; incomplete --prompt split → false; --prompt fully closed → true. The function is well-exercised transitively but deserves its own unit-level assertions.

  • event-bridge.test.ts — assert that getLog().warn('pi.event-bridge.streaming_tail_completed') fires in the streaming-truncated test. Adding { streamedLen, tailLen } in the assertion ensures a future refactor can't silently remove the warning.

Minor / nice-to-have

  • orchestrator-agent.ts:1023: Trim the comment to // Guard allChunks size unconditionally (allChunks grows for debug even when commandDetected).
  • orchestrator-agent.ts:1068: Remove the redundant "User-visible output still comes from filterToolIndicators" sentence.
  • orchestrator-agent.ts:1340–1356: Consolidate the two regLineMatch fallback comments into one: // Line-anchored to avoid landing on a prose mention of "/register-project". Fallback: if extraction fails, skip preamble but still execute registration (upstream parse already confirmed the command is valid).

Compliments

  • The 8 multi-chunk integration tests (stream/batch × register-project split, invoke-workflow split, --prompt in later chunk, trailing text suppression) are thorough and cover the full state machine.
  • extractLastAssistantText in event-bridge.ts has an excellent docstring that immediately tells a future reader why it exists and how the text vs. delta gap is used — exactly the kind of WHY comment this codebase needs.
  • The batch-mode comment explaining why assistantMessages stops shifting once commandDetected flips ("shifting the prefix away would break both the prefix and full-command regexes") captures a subtle invariant that would be very hard to debug if it broke.

Reviewed via maintainer-review-pr workflow (Pi/Minimax). Aspects run: code-review, error-handling, test-coverage, comment-quality.

@Wirasm Wirasm merged commit fe25450 into coleam00:dev May 12, 2026
4 checks passed
@Wirasm Wirasm mentioned this pull request May 12, 2026
cropse pushed a commit to cropse/Archon that referenced this pull request May 19, 2026
…late multi-chunk AI commands before parsing (coleam00#1581)

* fix: improve command accumulation handling for multi-chunk messages

* fix: enhance command parsing logic to prevent premature command completion

Co-authored-by: Copilot <copilot@github.com>

* fix: enhance INVOKE_WORKFLOW_FULL_RE regex to properly handle --prompt accumulation in multi-chunk messages

Co-authored-by: Copilot <copilot@github.com>

* fix: update command chunk handling to prevent path corruption in multi-chunk messages

Co-authored-by: Copilot <copilot@github.com>

* fix: add mock for listCodebases to enhance test coverage

* Fix Pi provider silent failures: markdown bold wrapping and streaming truncation

Fixes two failure modes where the Pi provider silently drops /register-project and /invoke-workflow commands, leaving projects unregistered with no error shown to the user.

Mode A — Markdown bold wrapping (orchestrator-agent.ts)

Pi occasionally emits commands wrapped in markdown bold: **/register-project Name "/path"**. The existing prefix and full-command regexes require lines to start with /, so these were never detected.

Adds normalizeCommandText() which strips leading/trailing * characters from lines whose first non-asterisk character is /. Applied to all six detection and parse sites: isCommandFullyParsed, handleStreamMode, handleBatchMode, parseOrchestratorCommands (3 uses), and handleProjectRegistrationResult.

Mode B — Streaming truncation (event-bridge.ts)

Pi's text_delta events sometimes stop delivering characters mid-command (e.g. /register-project arrives but  SaberEngine "/path" never does as a delta). The complete text is present in agent_end.messages but was previously discarded.

Adds extractLastAssistantText() to read the fully-assembled text from the agent_end transcript. In bridgeSession, tracks the per-turn streamed length, and if the assembled text is strictly longer and starts with what was streamed, emits the missing suffix as a corrective assistant chunk before the result chunk — making it available to the orchestrator's accumulator.

Tests

5 new tests in orchestrator-agent.test.ts covering bold/italic stripping for both commands, including multiline responses and quoted paths
5 new tests in event-bridge.test.ts covering: truncation detected and emitted, no false emission when complete, mismatch guard, per-turn reset on turn_start, and assistantBuffer inclusion when wantsStructured

* fix: improve command normalization to handle leading/trailing whitespace

* fix: improve command registration handling to avoid false positives in text extraction

* fix: enhance command accumulation logic to prevent token loss and improve error handling

* fix: improve command registration handling to prevent silent drops of valid commands

---------

Co-authored-by: Copilot <copilot@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pi provider split assistant chunks can prevent AI-generated /invoke-workflow from dispatching

3 participants