Wire orchestrator mode through the daemon + unify new-chat draft state#544
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
📝 WalkthroughWalkthroughThis 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 ChangesOrchestration Service & Draft Kind Separation
🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
@copilot review but do not make fixes |
There was a problem hiding this comment.
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 liftLane hook exposes orchestrator state but cannot enable it
Line 608 only mutates
orchestratorEnabledin the"cli" -> falsedirection, and Line 854 returns the flag without any setter. After removingchat-orchestratoras 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 winGate orchestrator-lead behavior to fresh drafts only.
Line 6423 and Line 7823 key off the raw
orchestratorEnabledprop. After this refactor that flag is surface state, not session state, so a staletruevalue can make a normal chat creation/send path run asorchestrator-leadand even callorchestration.runCreatefor the wrong session. Reuse the stricter draft guard you already model later withisOrchestratorDraft(or a shared helper) here, and only preserveorchestrator-leadfor real sessions whenselectedSession?.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 winAdd a regression test for invalid
runIdpath segments.Please add a case that calls a domain method with
runIdcontaining 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 winAssert
orchestratorEnabledis actually forwarded toWorkStartSurface.The current mock drops props, so this suite won’t catch a regression where
WorkViewAreastops passingorchestratorEnabled.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
📒 Files selected for processing (19)
apps/ade-cli/src/bootstrap.tsapps/desktop/src/main/services/adeActions/registry.tsapps/desktop/src/main/services/ipc/registerIpc.tsapps/desktop/src/main/services/orchestration/orchestrationDomain.test.tsapps/desktop/src/main/services/orchestration/orchestrationDomain.tsapps/desktop/src/preload/orchestrationBridge.tsapps/desktop/src/preload/preload.tsapps/desktop/src/renderer/components/chat/AgentChatPane.test.tsxapps/desktop/src/renderer/components/chat/AgentChatPane.tsxapps/desktop/src/renderer/components/lanes/LaneWorkPane.tsxapps/desktop/src/renderer/components/lanes/useLaneWorkSessions.tsapps/desktop/src/renderer/components/orchestration/OrchestrationPanel.test.tsxapps/desktop/src/renderer/components/terminals/TerminalsPage.tsxapps/desktop/src/renderer/components/terminals/WorkStartSurface.test.tsxapps/desktop/src/renderer/components/terminals/WorkStartSurface.tsxapps/desktop/src/renderer/components/terminals/WorkViewArea.test.tsxapps/desktop/src/renderer/components/terminals/WorkViewArea.tsxapps/desktop/src/renderer/components/terminals/useWorkSessions.tsapps/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>
|
@codex review |
There was a problem hiding this comment.
💡 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".
…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>
|
@codex review |
|
Codex Review: Didn't find any major issues. Nice work! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Summary
orchestrationService/laneService/agentChatService) that arenullin runtime-backed mode (shouldUseInProcessProjectRuntime()isNODE_ENV==="test"only). Same bug class as the recurring runtime-backed null-service issue.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 inapps/ade-cli/src/bootstrap.tsand stream its events viapushEvent("orchestrator", …).callProjectRuntimeActionOr("orchestration", …); subscribe to the runtime event stream (structuraltoOrchestrationRuntimeEventdetection) while keeping the legacy in-process broadcast for test mode.chat-orchestratorWorkDraftKindinto an orthogonalorchestratorEnabledboolean 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. Legacychat-orchestratordraftKind is migrated innormalizeWorkProjectViewState.Test plan
tsc --noEmitclean (Node 22)tsc --noEmitclean (Node 22)orchestrationDomain.test.ts— 11 cases (spawn/inject gates, retry-on-etag, cleanup, runCreate stitch) green🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
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 orthogonalorchestratorEnabledboolean on the unified"chat"draft bucket.orchestrationDomain.tsintroduces a single service factory withSAFE_RUN_IDpath-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).orchestrationBridge.tsroutes every call throughcallProjectRuntimeActionOr(\"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 sametoOrchestrationRuntimeEventguard.WorkDraftKindloses\"chat-orchestrator\";normalizeWorkProjectViewStatemigrates persisted state to the neworchestratorEnabledboolean;setOrchestratorEnabledinuseWorkSessionscorrectly forcesdraftKindto\"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
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}"Comments Outside Diff (1)
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx, line 1379-1382 (link)normalizeWorkDraftStorageKindnow 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". TheappStore.tsmigration correctly converts the persisteddraftKindstate, 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
Prompt To Fix All With AI
Reviews (3): Last reviewed commit: "Address review: guard orchestration runI..." | Re-trigger Greptile