Skip to content

Wire orchestrator mode through the daemon + unify new-chat draft state#544

Merged
arul28 merged 3 commits into
mainfrom
ade/chat-20260608-131732-7c97805b
Jun 8, 2026
Merged

Wire orchestrator mode through the daemon + unify new-chat draft state#544
arul28 merged 3 commits into
mainfrom
ade/chat-20260608-131732-7c97805b

Conversation

@arul28

@arul28 arul28 commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Orchestrator runs (lead/worker/validator) crashed in every real build — the orchestration IPC handlers dereferenced in-process services (orchestrationService/laneService/agentChatService) that are null in runtime-backed mode (shouldUseInProcessProjectRuntime() is NODE_ENV==="test" only). Same bug class as the recurring runtime-backed null-service issue.
  • Port the handler logic into a shared createOrchestrationDomainService (apps/desktop/src/main/services/orchestration/orchestrationDomain.ts), used by both the in-process IPC path and a new "orchestration" daemon action domain (registry allowlist). Construct the service in apps/ade-cli/src/bootstrap.ts and stream its events via pushEvent("orchestrator", …).
  • Rewrite the preload bridge to route every call through callProjectRuntimeActionOr("orchestration", …); subscribe to the runtime event stream (structural toOrchestrationRuntimeEvent detection) while keeping the legacy in-process broadcast for test mode.
  • Collapse the 3rd chat-orchestrator WorkDraftKind into an orthogonal orchestratorEnabled boolean on a single unified draft bucket, so prompt/model/lane/orchestrator-flag stay stable across every toggle. CLI forces orchestrator off; auto-create-lane is now allowed while orchestrator is enabled. Legacy chat-orchestrator draftKind is migrated in normalizeWorkProjectViewState.

Test plan

  • Desktop tsc --noEmit clean (Node 22)
  • ade-cli tsc --noEmit clean (Node 22)
  • Desktop eslint: 0 errors
  • New orchestrationDomain.test.ts — 11 cases (spawn/inject gates, retry-on-etag, cleanup, runCreate stitch) green
  • Scoped renderer/main suites green (AgentChatPane, registry+orchestrationService, terminals/lanes)
  • CI green
  • Manual: launch orchestrator run in a packaged/runtime-backed build (the path that previously threw "orchestration service is not initialised")

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Orchestrator mode is now available as a standalone feature toggle, separate from chat and CLI draft kinds for improved clarity and control.
  • Improvements

    • Streamlined orchestration service integration across the application with enhanced event handling and agent lifecycle management capabilities.

Greptile Summary

This PR fixes orchestrator runs crashing in runtime-backed builds by extracting the IPC handler logic into a shared createOrchestrationDomainService, wiring it through both the in-process IPC path and a new "orchestration" daemon action domain, and collapsing the legacy "chat-orchestrator" draft kind into an orthogonal orchestratorEnabled boolean on the unified "chat" draft bucket.

  • Orchestration domain unification: orchestrationDomain.ts introduces a single service factory with SAFE_RUN_ID path-traversal guards, one-retry etag conflict handling, and session cleanup on failure; used identically by the IPC handlers (registerIpc.ts) and the daemon registry (registry.ts).
  • Preload bridge rewrite: orchestrationBridge.ts routes every call through callProjectRuntimeActionOr(\"orchestration\", …) and subscribes to the daemon runtime event stream with a per-subscription source latch, while keeping the legacy in-process broadcast as a fallback validated by the same toOrchestrationRuntimeEvent guard.
  • Draft state migration: WorkDraftKind loses \"chat-orchestrator\"; normalizeWorkProjectViewState migrates persisted state to the new orchestratorEnabled boolean; setOrchestratorEnabled in useWorkSessions correctly forces draftKind to \"chat\" on enable and clears the flag on CLI switch.

Confidence Score: 5/5

Safe to merge; the null-service crash is correctly fixed across both delivery paths and the draft state migration is backward-compatible.

The core fix (shared OrchestrationDomainService used by both IPC and daemon paths) is well-tested with 11 unit cases. The draft-kind migration in normalizeWorkProjectViewState correctly handles legacy 'chat-orchestrator' persistence. The only findings are a per-call domain service allocation in the daemon registry path and a slightly lighter event guard in one test file — neither affects runtime correctness.

apps/desktop/src/main/services/adeActions/registry.ts — buildOrchestrationDomainService allocates a new object per daemon action call; consider applying the same caching strategy used in registerIpc.ts.

Important Files Changed

Filename Overview
apps/desktop/src/main/services/orchestration/orchestrationDomain.ts New shared domain service centralising all orchestration logic; safe runId validation via SAFE_RUN_ID regex, one-retry etag conflict pattern, and session cleanup on failure.
apps/desktop/src/main/services/orchestration/orchestrationDomain.test.ts 11 focused unit tests covering spawn gates, etag-conflict retry, orphan session cleanup, inject modes, and runCreate best-effort stitch; solid coverage of the new domain service.
apps/desktop/src/main/services/adeActions/registry.ts Adds 'orchestration' to the daemon action allowlist and wires buildOrchestrationDomainService; new service object is created on every getAdeActionDomainServices call (unlike the cached IPC path).
apps/desktop/src/main/services/ipc/registerIpc.ts IPC handlers collapsed to thin delegation through getOrchestrationDomain() with correct per-context caching; code reduction is significant and correct.
apps/desktop/src/preload/orchestrationBridge.ts Bridge correctly routes through callProjectRuntimeActionOr with legacy IPC fallback; source-latch in subscribe is safe given the documented runtime/legacy mutual exclusivity.
apps/desktop/src/preload/preload.ts Adds toOrchestrationRuntimeEvent guard with kind-specific field checks (heartbeat/lifecycle) and wires registerRemoteOrchestrationEventCallback into the existing event pump pattern.
apps/desktop/src/renderer/state/appStore.ts WorkDraftKind drops 'chat-orchestrator'; normalizeWorkProjectViewState correctly migrates legacy draftKind to orchestratorEnabled boolean, CLI forces orchestrator off.
apps/ade-cli/src/bootstrap.ts Instantiates orchestrationService in CLI daemon, forwards events via pushEvent('orchestrator', …), and wires it into agentChatService and disposal chain.
apps/desktop/src/renderer/components/terminals/useWorkSessions.ts Adds orchestratorEnabled state and setOrchestratorEnabled callback; correctly forces draftKind to 'chat' when enabling and forces orchestratorEnabled off when switching to CLI.
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx Removes chat-orchestrator draftKind, adds orchestratorEnabled prop; createSession, sendMessage, isOrchestratorDraft references all correctly updated.

Sequence Diagram

sequenceDiagram
    participant Renderer
    participant OrchestrationBridge
    participant Preload
    participant Daemon as Daemon (runtime-backed)
    participant IPC as Desktop IPC (in-process/test)
    participant OrchestratorDomain as OrchestrationDomainService
    participant OrchestratorSvc as OrchestrationService

    Renderer->>OrchestrationBridge: spawnAgent(args)
    OrchestrationBridge->>Preload: callProjectRuntimeActionOr("orchestration", "spawnAgent", args, fallback)

    alt Runtime-backed (production)
        Preload->>Daemon: "run_ade_action {domain:"orchestration", action:"spawnAgent"}"
        Daemon->>OrchestratorDomain: spawnAgent(arg)
        OrchestratorDomain->>OrchestratorSvc: getManifestForRun / manifestPatch
        OrchestratorDomain-->>Daemon: "{sessionId, etag}"
        Daemon-->>Preload: result
        Daemon--)Preload: pushEvent("orchestrator", payload)
        Preload--)Renderer: remoteOrchestrationEventCallbacks (runtime source)
    else In-process / test
        Preload->>IPC: ipcRenderer.invoke(IPC.orchestrationSpawnAgent, args)
        IPC->>OrchestratorDomain: getOrchestrationDomain().spawnAgent(arg)
        OrchestratorDomain->>OrchestratorSvc: getManifestForRun / manifestPatch
        OrchestratorDomain-->>IPC: "{sessionId, etag}"
        IPC-->>Preload: result
        IPC--)Renderer: ipcRenderer.on(IPC.orchestrationEvent) (legacy source)
    end

    Preload-->>OrchestrationBridge: result
    OrchestrationBridge-->>Renderer: "{sessionId, etag}"
Loading

Comments Outside Diff (1)

  1. apps/desktop/src/renderer/components/chat/AgentChatPane.tsx, line 1379-1382 (link)

    P2 Orchestrator composer draft silently abandoned on upgrade

    normalizeWorkDraftStorageKind now unconditionally returns "work-start", which is correct for the new unified bucket. However, users who typed a prompt in the old "chat-orchestrator" composer (which was stored under a key built from "chat-orchestrator") will silently lose that draft after upgrading because the new code reads from "work-start". The appStore.ts migration correctly converts the persisted draftKind state, but there is no corresponding migration for the localStorage composer draft key. If the UI rebuilds the storage key from the already-migrated state, the old draft becomes unreachable. Consider reading from the old key once on first boot and copying it into the "work-start" bucket.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
    Line: 1379-1382
    
    Comment:
    **Orchestrator composer draft silently abandoned on upgrade**
    
    `normalizeWorkDraftStorageKind` now unconditionally returns `"work-start"`, which is correct for the new unified bucket. However, users who typed a prompt in the old `"chat-orchestrator"` composer (which was stored under a key built from `"chat-orchestrator"`) will silently lose that draft after upgrading because the new code reads from `"work-start"`. The `appStore.ts` migration correctly converts the persisted `draftKind` state, but there is no corresponding migration for the localStorage composer draft key. If the UI rebuilds the storage key from the already-migrated state, the old draft becomes unreachable. Consider reading from the old key once on first boot and copying it into the `"work-start"` bucket.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src/main/services/adeActions/registry.ts:868-878
**Per-call domain service allocation on the daemon path**

`buildOrchestrationDomainService` constructs a fresh closure object on every `getAdeActionDomainServices` invocation. The IPC path fixed this with `cachedOrchestrationDomain` keyed by context — the same caching strategy would work here. Since the domain service is stateless (all state lives in the three underlying services), a simple module-level cache keyed by `runtime` identity is enough.

```suggestion
let cachedRegistryOrchestrationDomain: {
  runtime: AdeRuntime;
  domain: OpaqueService;
} | null = null;

function buildOrchestrationDomainService(runtime: AdeRuntime): OpaqueService | null {
  const orchestrationService = runtime.orchestrationService;
  const laneService = runtime.laneService;
  const agentChatService = runtime.agentChatService;
  if (!orchestrationService || !laneService || !agentChatService) return null;
  if (cachedRegistryOrchestrationDomain?.runtime === runtime) {
    return cachedRegistryOrchestrationDomain.domain;
  }
  const domain = createOrchestrationDomainService({
    orchestrationService,
    laneService: { getLaneWorktreePath: (laneId: string) => laneService.getLaneWorktreePath(laneId) },
    agentChatService,
  }) as unknown as OpaqueService;
  cachedRegistryOrchestrationDomain = { runtime, domain };
  return domain;
}
```

### Issue 2 of 2
apps/desktop/src/renderer/components/orchestration/OrchestrationPanel.test.tsx:366-385
**Test `parseLegacyEvent` diverges from production guard**

The inline `parseLegacyEvent` in this test doesn't enforce the `ORCHESTRATION_EVENT_KINDS` allowlist or the per-kind required fields (`sessionId`/`lastHeartbeatAt` for `"heartbeat"`, `status` for `"lifecycle"`). A test event with `kind: "bogus"` or a malformed heartbeat would be accepted here but rejected by `toOrchestrationRuntimeEvent` in production. Importing and reusing `toOrchestrationRuntimeEvent` directly would close the gap.

Reviews (3): Last reviewed commit: "Address review: guard orchestration runI..." | Re-trigger Greptile

Orchestrator runs (lead/worker/validator) crashed in every real build
because the orchestration IPC handlers dereferenced in-process services
(orchestrationService/laneService/agentChatService) that are null in
runtime-backed mode. Port the handler logic into a shared
createOrchestrationDomainService, register an "orchestration" daemon
action domain, construct the service in ade-cli bootstrap, and route the
preload bridge + event stream through the daemon.

Collapse the 3rd "chat-orchestrator" WorkDraftKind into an orthogonal
orchestratorEnabled boolean on a single unified draft bucket so prompt,
model, lane and orchestrator flag stay stable across every toggle; CLI
forces it off; allow auto-create-lane while orchestrator is enabled.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview Jun 8, 2026 8:58pm

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds orchestration service integration across the ADE stack, from CLI bootstrap through desktop IPC/preload layers, then refactors the work draft system to separate an orthogonal orchestratorEnabled flag from the narrowed workDraftKind. The change introduces a new orchestration domain service that coordinates run operations and agent spawning, updates the preload bridge to support dependency injection, and cascades state changes through work session hooks and UI components.

Changes

Orchestration Service & Draft Kind Separation

Layer / File(s) Summary
CLI Bootstrap Orchestration Service
apps/ade-cli/src/bootstrap.ts
Adds orchestration service instantiation, forwards its events to runtime buffer under orchestrator domain, and exposes the service on AdeRuntime. Agent chat service receives access via getOrchestrationService.
Orchestration Domain Service Implementation & Tests
apps/desktop/src/main/services/orchestration/orchestrationDomain.ts, orchestrationDomain.test.ts
New domain service factory createOrchestrationDomainService coordinates run bundle resolution and agent operations. Implements spawnAgent with manifest validation/patching/etag-retry, agentInject with payload-kind routing, runCreate with best-effort orchestration-field persistence, and lifecycle management. Comprehensive test coverage for spawn/inject/create flows, validation gates, and error handling.
Desktop IPC & Action Registry Wiring
apps/desktop/src/main/services/adeActions/registry.ts, apps/desktop/src/main/services/ipc/registerIpc.ts
Adds "orchestration" to action domains and allowlist. Refactors IPC handlers to delegate through domain service instead of inlining logic. Removes pathJoinOrchestration helper since bundle path handling moves to domain layer.
Preload Bridge Dependency Injection & Runtime Events
apps/desktop/src/preload/orchestrationBridge.ts, apps/desktop/src/preload/preload.ts
Refactors bridge to accept dependencies (callAction, subscribeRuntimeOrchestrationEvents) for abstract action routing. Adds orchestration-runtime event support with callback set, event parsing (validates runId, etag, kind), and dispatch. Context bridge wires custom callAction routing and event subscription hook.
Application State Refactoring
apps/desktop/src/renderer/state/appStore.ts
WorkDraftKind narrowed to "chat" | "cli". New orchestratorEnabled: boolean field added to WorkProjectViewState. Legacy state migration converts "chat-orchestrator" draftKind into orchestratorEnabled: true.
Work State Hooks
apps/desktop/src/renderer/components/terminals/useWorkSessions.ts, apps/desktop/src/renderer/components/lanes/useLaneWorkSessions.ts
useWorkSessions exposes orchestratorEnabled and setOrchestratorEnabled callback (forces draftKind: "chat" when enabling). useLaneWorkSessions includes orchestratorEnabled in state and hook return. Both disable orchestrator when switching to "cli".
AgentChatPane Composer Refactoring
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx, AgentChatPane.test.tsx
Narrows workDraftKind prop to "chat" | "cli" and adds orchestratorEnabled?: boolean. All orchestrator-specific logic (placeholder/label, session creation, draft launch gating, send interactionMode) now checks orchestratorEnabled instead of workDraftKind === "chat-orchestrator". Draft storage simplified to shared "work-start" bucket.
Work View UI Components
apps/desktop/src/renderer/components/terminals/WorkViewArea.tsx, WorkStartSurface.tsx, apps/desktop/src/renderer/components/lanes/LaneWorkPane.tsx, test files
WorkViewArea and WorkStartSurface accept orchestratorEnabled prop and forward through component hierarchy. Mode-selection pill active logic simplified to direct equality. LaneWorkPane threads prop from useLaneWorkSessions.
TerminalsPage Orchestrator Control
apps/desktop/src/renderer/components/terminals/TerminalsPage.tsx
Refactors orchestrator chat UI state from draft-kind switching to setOrchestratorEnabled(true/false). Effect listeners for ade:work:start-orchestrator-chat / ade:work:stop-orchestrator-chat now toggle orchestrator flag. WorkViewArea receives orchestratorEnabled prop with proper dependency tracking.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • arul28/ADE#361: Introduces core Work-tab Chat Orchestrator implementation with same orchestration service plumbing (domain service, IPC bridge, event subscription layers).

Suggested labels

desktop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: wiring orchestrator mode through the daemon and unifying the chat draft state by collapsing the chat-orchestrator variant into a single draft bucket.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/chat-20260608-131732-7c97805b

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.

@arul28 arul28 changed the title Chat 20260608 131732 Wire orchestrator mode through the daemon + unify new-chat draft state Jun 8, 2026
@arul28

arul28 commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

@copilot review but do not make fixes

Comment thread apps/desktop/src/main/services/ipc/registerIpc.ts
Comment thread apps/desktop/src/preload/preload.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: 6

Caution

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

⚠️ Outside diff range comments (2)
apps/desktop/src/renderer/components/lanes/useLaneWorkSessions.ts (1)

608-617: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Lane hook exposes orchestrator state but cannot enable it

Line 608 only mutates orchestratorEnabled in the "cli" -> false direction, and Line 854 returns the flag without any setter. After removing chat-orchestrator as a draft kind, this leaves lane-scoped flows without a direct way to enter orchestrator mode from this hook API.

Suggested fix
   const showDraftKind = useCallback((nextKind: WorkDraftKind) => {
     setViewState((prev) => ({
       ...prev,
       draftKind: nextKind,
       // CLI has no orchestrator form — switching to it forces the flag off.
       orchestratorEnabled: nextKind === "cli" ? false : prev.orchestratorEnabled,
       activeItemId: null,
       selectedItemId: null,
     }));
   }, [setViewState]);

+  const setOrchestratorEnabled = useCallback((enabled: boolean) => {
+    setViewState((prev) => ({
+      ...prev,
+      orchestratorEnabled: enabled,
+      draftKind: enabled ? "chat" : prev.draftKind,
+      activeItemId: null,
+      selectedItemId: null,
+    }));
+  }, [setViewState]);
+
   return {
     lane: currentLane,
     loading,
     sessions,
     visibleSessions,
     gridLayoutId,
     activeItemId: laneViewState.activeItemId,
     draftKind: laneViewState.draftKind,
     orchestratorEnabled: laneViewState.orchestratorEnabled,
+    setOrchestratorEnabled,
     showDraftKind,
     setActiveItemId,
     closeTab,

As per coding guidelines, apps/desktop/**/*.{ts,tsx,js} should keep interface changes and renderer usage in sync when behavior contracts evolve.

Also applies to: 846-855

🤖 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 `@apps/desktop/src/renderer/components/lanes/useLaneWorkSessions.ts` around
lines 608 - 617, The hook useLaneWorkSessions currently only forces
orchestratorEnabled to false when switching to "cli" via showDraftKind and does
not expose any setter to enable/disable orchestrator mode; update the hook so
callers can both read and set orchestratorEnabled. Specifically, in
useLaneWorkSessions adjust showDraftKind to only clear orchestratorEnabled when
switching into "cli" (leave other transitions untouched), and add and return an
explicit setter (e.g., setOrchestratorEnabled or include setViewState in the
returned API) so consumers can enable orchestrator mode programmatically; locate
the showDraftKind function and the hook's return value (the block that returns
view state around orchestratorEnabled) and update them accordingly.

Source: Coding guidelines

apps/desktop/src/renderer/components/chat/AgentChatPane.tsx (1)

6422-6443: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate orchestrator-lead behavior to fresh drafts only.

Line 6423 and Line 7823 key off the raw orchestratorEnabled prop. After this refactor that flag is surface state, not session state, so a stale true value can make a normal chat creation/send path run as orchestrator-lead and even call orchestration.runCreate for the wrong session. Reuse the stricter draft guard you already model later with isOrchestratorDraft (or a shared helper) here, and only preserve orchestrator-lead for real sessions when selectedSession?.interactionMode === "orchestrator-lead".

Also applies to: 7820-7827

🤖 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 `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx` around lines
6422 - 6443, The orchestrator-lead gating currently uses the surface state flag
orchestratorEnabled which can be stale; change both places that check
orchestratorEnabled (including where you build orchestratorOverrides for
window.ade.agentChat.create and the follow-up orchestration allocation block) to
use the stricter draft/session-level guard — e.g. use isOrchestratorDraft (or
selectedSession?.interactionMode === "orchestrator-lead") instead of
orchestratorEnabled so only actual drafts/sessions marked orchestrator-lead get
the override and call orchestration.runCreate.
🧹 Nitpick comments (2)
apps/desktop/src/main/services/orchestration/orchestrationDomain.test.ts (1)

115-337: ⚡ Quick win

Add a regression test for invalid runId path segments.

Please add a case that calls a domain method with runId containing traversal (../) and asserts rejection. That prevents silent reintroduction of path-escape behavior in IPC-facing orchestration calls.

🤖 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 `@apps/desktop/src/main/services/orchestration/orchestrationDomain.test.ts`
around lines 115 - 337, Add a regression test that calls a domain method with a
runId containing traversal segments (e.g. "../R-1") and asserts it rejects; for
example, copy the pattern of existing tests and add one that calls
h.service.spawnAgent({ ...SPAWN_ARG, runId: "../R-1" }) (or
h.service.agentInject with injectReq("../R-1")) after arranging a valid manifest
via h.orchestrationService.getManifestForRun, then await
expect(...).rejects.toThrow(...) and assert that no downstream
agent/orchestration calls (e.g. h.agentChatService.createSession or
h.orchestrationService.manifestPatch / h.agentChatService.steer) were invoked to
ensure path-escape is blocked early.
apps/desktop/src/renderer/components/terminals/WorkViewArea.test.tsx (1)

117-119: ⚡ Quick win

Assert orchestratorEnabled is actually forwarded to WorkStartSurface.

The current mock drops props, so this suite won’t catch a regression where WorkViewArea stops passing orchestratorEnabled.

Proposed test hardening
 vi.mock("./WorkStartSurface", () => ({
-  WorkStartSurface: () => <div data-testid="work-start-surface" />,
+  WorkStartSurface: ({ orchestratorEnabled = false }: { orchestratorEnabled?: boolean }) => (
+    <div
+      data-testid="work-start-surface"
+      data-orchestrator-enabled={String(orchestratorEnabled)}
+    />
+  ),
 }));
   it("keeps the Chat start mode selected for orchestrator drafts", () => {
@@
     expect(screen.getByRole("button", { name: "Chat" }).className).toContain("ade-work-tab-active");
     expect(screen.getByRole("button", { name: "CLI" }).className).not.toContain("ade-work-tab-active");
+    expect(screen.getByTestId("work-start-surface").getAttribute("data-orchestrator-enabled")).toBe("true");
   });

As per coding guidelines, apps/desktop/**/*.{ts,tsx,js}: Keep IPC contracts, preload types, shared types, and renderer usage in sync whenever an interface changes.

Also applies to: 343-345

🤖 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 `@apps/desktop/src/renderer/components/terminals/WorkViewArea.test.tsx` around
lines 117 - 119, The test mock for WorkStartSurface currently discards props so
it can't detect whether WorkViewArea forwards orchestratorEnabled; change the
mock implementation used in WorkViewArea.test.tsx to accept and surface props
(e.g., a functional mock component that takes props including
orchestratorEnabled and renders an element with a test id and a data attribute
or text reflecting orchestratorEnabled) and then add an assertion that rendering
WorkViewArea results in the mocked WorkStartSurface receiving/reflecting the
orchestratorEnabled value; update references to WorkStartSurface and the test
assertions accordingly so regressions in WorkViewArea forwarding are caught.

Source: Coding guidelines

🤖 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 `@apps/desktop/src/main/services/ipc/registerIpc.ts`:
- Around line 6581-6584: The IPC handler for orchestrationSpawnAgent now assumes
arg.leadSessionId is present but the shared type OrchestrationSpawnAgentRequest
(in shared/types/orchestration.ts) doesn’t include it; update the code to keep
contracts in sync by either adding leadSessionId to the shared
OrchestrationSpawnAgentRequest type everywhere that type is used, or change the
handler to treat leadSessionId as optional (e.g., remove the required
augmentation and pass only the fields defined in OrchestrationSpawnAgentRequest)
before calling getOrchestrationDomain().spawnAgent; ensure the symbol
orchestrationSpawnAgent, the type OrchestrationSpawnAgentRequest, and the call
site getOrchestrationDomain().spawnAgent are updated consistently so
preload/renderer code and domain service receive a matching payload.

In `@apps/desktop/src/main/services/orchestration/orchestrationDomain.ts`:
- Around line 56-57: The bundlePathFor function builds a filesystem path using
an unvalidated runId which allows path traversal; fix by validating and
normalizing runId before composing the path in bundlePathFor: reject or sanitize
inputs that contain path separators or traversal sequences (e.g. only allow a
whitelist regex like /^[A-Za-z0-9._-]+$/ or use path.basename and compare to the
original), then compute path.join(laneService.getLaneWorktreePath(laneId),
".ade", "orchestration", safeRunId); apply the same validation/sanitization to
all other places that use runId (the other usages referenced in the review) so
no read/write can escape the intended .ade/orchestration root.

In `@apps/desktop/src/preload/orchestrationBridge.ts`:
- Around line 66-69: The listener currently forwards any object with a matching
runId to callback, weakening the IPC contract; update the listener to validate
payload against the shared OrchestrationEventPayload type using the existing
parser/type-guard (e.g., isOrchestrationEventPayload or
parseOrchestrationEventPayload) before calling callback, ensure you check
payload.runId === args.runId only after the guard succeeds, and change
callback's accepted parameter type to OrchestrationEventPayload so only
well-formed events reach renderer code (reference listener, callback,
OrchestrationEventPayload, args.runId).
- Around line 61-73: The orchestration events are being delivered twice because
both subscribeRuntimeOrchestrationEvents(...) and
ipcRenderer.on(IPC.orchestrationEvent, listener) are registered unconditionally;
fix by choosing one source or deduplicating before invoking callback: either (A)
determine backend mode from callAction("subscribe", args,
IPC.orchestrationSubscribe) and only register the runtime subscription or the
ipcRenderer listener (register the second only if the backend indicates
legacy/test mode), or (B) keep both but de-duplicate by a stable event
identifier (e.g., payload.eventId or payload.id) — store seen IDs per runId in a
Set and check/set before calling callback in both the runtime subscription
handler and the ipcRenderer listener; update unsubscribeRuntime/listener
teardown accordingly.

In `@apps/desktop/src/preload/preload.ts`:
- Around line 2922-2940: The toOrchestrationRuntimeEvent function currently only
performs generic checks and then casts payload to OrchestrationEventPayload;
update it to perform kind-specific validation before casting: inside
toOrchestrationRuntimeEvent (and using ORCHESTRATION_EVENT_KINDS), after
validating runId/etag/kind, add branching on payload.kind to validate required
fields for each variant (at minimum ensure for "heartbeat" that sessionId is a
non-empty string and lastHeartbeatAt is a valid timestamp/number/string as
expected, and for "lifecycle" that sessionId is a non-empty string and status is
one of the allowed lifecycle statuses) and return null if those checks fail,
only then cast and return payload as OrchestrationEventPayload so subscribers
never receive malformed heartbeat/lifecycle events.

In `@apps/desktop/src/renderer/state/appStore.ts`:
- Around line 269-275: During persisted-state migration in
normalizeWorkProjectViewState (the block computing draftKind/orchestratorEnabled
from candidate), enforce the invariant that draftKind "cli" always disables the
orchestrator: compute orchestratorEnabled by first deriving draftKind
(candidate.draftKind === "cli" ? "cli" : "chat") and then set
orchestratorEnabled to false whenever that derived draftKind === "cli";
otherwise preserve the existing logic (candidate.orchestratorEnabled === true ||
legacy draftKind "chat-orchestrator"). Ensure you update the orchestratorEnabled
calculation near the draftKind assignment so the CLI/orchestrator invariant
cannot be violated.

---

Outside diff comments:
In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx`:
- Around line 6422-6443: The orchestrator-lead gating currently uses the surface
state flag orchestratorEnabled which can be stale; change both places that check
orchestratorEnabled (including where you build orchestratorOverrides for
window.ade.agentChat.create and the follow-up orchestration allocation block) to
use the stricter draft/session-level guard — e.g. use isOrchestratorDraft (or
selectedSession?.interactionMode === "orchestrator-lead") instead of
orchestratorEnabled so only actual drafts/sessions marked orchestrator-lead get
the override and call orchestration.runCreate.

In `@apps/desktop/src/renderer/components/lanes/useLaneWorkSessions.ts`:
- Around line 608-617: The hook useLaneWorkSessions currently only forces
orchestratorEnabled to false when switching to "cli" via showDraftKind and does
not expose any setter to enable/disable orchestrator mode; update the hook so
callers can both read and set orchestratorEnabled. Specifically, in
useLaneWorkSessions adjust showDraftKind to only clear orchestratorEnabled when
switching into "cli" (leave other transitions untouched), and add and return an
explicit setter (e.g., setOrchestratorEnabled or include setViewState in the
returned API) so consumers can enable orchestrator mode programmatically; locate
the showDraftKind function and the hook's return value (the block that returns
view state around orchestratorEnabled) and update them accordingly.

---

Nitpick comments:
In `@apps/desktop/src/main/services/orchestration/orchestrationDomain.test.ts`:
- Around line 115-337: Add a regression test that calls a domain method with a
runId containing traversal segments (e.g. "../R-1") and asserts it rejects; for
example, copy the pattern of existing tests and add one that calls
h.service.spawnAgent({ ...SPAWN_ARG, runId: "../R-1" }) (or
h.service.agentInject with injectReq("../R-1")) after arranging a valid manifest
via h.orchestrationService.getManifestForRun, then await
expect(...).rejects.toThrow(...) and assert that no downstream
agent/orchestration calls (e.g. h.agentChatService.createSession or
h.orchestrationService.manifestPatch / h.agentChatService.steer) were invoked to
ensure path-escape is blocked early.

In `@apps/desktop/src/renderer/components/terminals/WorkViewArea.test.tsx`:
- Around line 117-119: The test mock for WorkStartSurface currently discards
props so it can't detect whether WorkViewArea forwards orchestratorEnabled;
change the mock implementation used in WorkViewArea.test.tsx to accept and
surface props (e.g., a functional mock component that takes props including
orchestratorEnabled and renders an element with a test id and a data attribute
or text reflecting orchestratorEnabled) and then add an assertion that rendering
WorkViewArea results in the mocked WorkStartSurface receiving/reflecting the
orchestratorEnabled value; update references to WorkStartSurface and the test
assertions accordingly so regressions in WorkViewArea forwarding are caught.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 13c678cb-d6c5-48d0-9d4a-e049c928911f

📥 Commits

Reviewing files that changed from the base of the PR and between a9fddb4 and 036e0c6.

📒 Files selected for processing (19)
  • apps/ade-cli/src/bootstrap.ts
  • apps/desktop/src/main/services/adeActions/registry.ts
  • apps/desktop/src/main/services/ipc/registerIpc.ts
  • apps/desktop/src/main/services/orchestration/orchestrationDomain.test.ts
  • apps/desktop/src/main/services/orchestration/orchestrationDomain.ts
  • apps/desktop/src/preload/orchestrationBridge.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/components/chat/AgentChatPane.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/components/lanes/LaneWorkPane.tsx
  • apps/desktop/src/renderer/components/lanes/useLaneWorkSessions.ts
  • apps/desktop/src/renderer/components/orchestration/OrchestrationPanel.test.tsx
  • apps/desktop/src/renderer/components/terminals/TerminalsPage.tsx
  • apps/desktop/src/renderer/components/terminals/WorkStartSurface.test.tsx
  • apps/desktop/src/renderer/components/terminals/WorkStartSurface.tsx
  • apps/desktop/src/renderer/components/terminals/WorkViewArea.test.tsx
  • apps/desktop/src/renderer/components/terminals/WorkViewArea.tsx
  • apps/desktop/src/renderer/components/terminals/useWorkSessions.ts
  • apps/desktop/src/renderer/state/appStore.ts

Comment thread apps/desktop/src/main/services/ipc/registerIpc.ts
Comment thread apps/desktop/src/main/services/orchestration/orchestrationDomain.ts Outdated
Comment thread apps/desktop/src/preload/orchestrationBridge.ts
Comment thread apps/desktop/src/preload/orchestrationBridge.ts Outdated
Comment thread apps/desktop/src/preload/preload.ts
Comment thread apps/desktop/src/renderer/state/appStore.ts
… domain

- toOrchestrationRuntimeEvent now rejects heartbeat payloads missing
  sessionId/lastHeartbeatAt and lifecycle payloads with an invalid status,
  matching the per-kind validation the sibling runtime-event guards use.
- getOrchestrationDomain caches the domain service per owning context instead
  of allocating a fresh closure object on every in-process IPC call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@arul28

arul28 commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d0f553dcb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/ade-cli/src/bootstrap.ts
…ch event source

- Reject non-`[A-Za-z0-9_-]` runIds at bundlePathFor so a crafted runId can't
  escape the bundle root at the IPC/daemon boundary (+ regression test)
- Dispose orchestrationService on daemon teardown to close chokidar watchers
- CLI draft mode forces orchestrator off during persisted-state normalization
- Latch the orchestration subscribe onto the first delivering event source and
  validate legacy-broadcast payloads through the shared union before dispatch

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@arul28

arul28 commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@arul28 arul28 merged commit 0fb654b into main Jun 8, 2026
27 checks passed
@arul28 arul28 deleted the ade/chat-20260608-131732-7c97805b branch June 8, 2026 22:52
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.

1 participant