Skip to content

Route lifecycle CLI through daemon - #307

Merged
TraderSamwise merged 3 commits into
masterfrom
chore/core-sidecar-next-32
Jul 4, 2026
Merged

Route lifecycle CLI through daemon#307
TraderSamwise merged 3 commits into
masterfrom
chore/core-sidecar-next-32

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • add daemon text routes for spawn/stop/kill/fork that forward to project-service lifecycle APIs
  • fast-path installed shim lifecycle commands without starting Node when the daemon is healthy
  • update command ownership inventory and no-spawn/stale-fallback coverage

Verification

  • sh -n scripts/installed-aimux-shim.sh
  • git diff --check
  • yarn vitest run src/daemon.test.ts src/installed-shim.test.ts src/core-command-ownership.test.ts
  • yarn typecheck
  • yarn lint
  • yarn build
  • yarn vitest run

Summary by CodeRabbit

  • New Features

    • Added support for lifecycle actions, including spawn, stop, kill, and fork, with updated routing and text/JSON responses.
    • Expanded command handling so these lifecycle actions can be invoked through the app’s existing command flow.
  • Bug Fixes

    • Improved error handling and response behavior for lifecycle requests, including clearer failures when a session or project state is unavailable.
    • Tightened routing so lifecycle actions only work in the appropriate local context.

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jul 4, 2026 5:35pm

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f4c930e-fff0-493d-b28f-5a66a35a2115

📥 Commits

Reviewing files that changed from the base of the PR and between 5e98a22 and 29113b1.

📒 Files selected for processing (4)
  • scripts/installed-aimux-shim.sh
  • src/daemon.test.ts
  • src/daemon.ts
  • src/installed-shim.test.ts
📝 Walkthrough

Walkthrough

Adds lifecycle spawn/stop/kill/fork commands end-to-end: new daemon text routes proxying to per-project services, new API route contracts and text-payload renderers, corresponding installed shim CLI commands with a query-forwarding POST helper, updated ownership tests/documentation, and new tests across daemon and shim suites.

Changes

Lifecycle Commands End-to-End

Layer / File(s) Summary
Route contract and text payload types
src/core-command-contract.ts, src/core-text.ts
Adds four lifecycle route paths to CORE_API_ROUTES and defines lifecycle payload interfaces plus renderCoreLifecycle*Lines renderer functions.
Daemon lifecycle route handlers and proxying
src/daemon.ts
Adds postProjectServiceJson, textError, requiredQuery helpers; implements spawn/stop/kill/fork handlers that proxy to project services, enforce loopback-only access via LOCAL_LIFECYCLE_TEXT_ROUTES, and wires routes into routeRequest.
Daemon lifecycle tests
src/daemon.test.ts
Adds tests for lifecycle route request/response payloads, error propagation, metadata directory creation, and relay-actor loopback rejection.
Installed shim lifecycle CLI commands
scripts/installed-aimux-shim.sh
Adds aimux_post_query_text_route helper and aimux_try_lifecycle_spawn/stop/kill/fork functions, replacing prior dispatch with lifecycle command routing.
Shim and ownership tests for lifecycle commands
src/installed-shim.test.ts, src/core-command-ownership.test.ts
Expands mocked curl fixtures and adds lifecycle test cases; updates shim-fast-path inventory entries.
Command ownership inventory documentation
docs/command-ownership-inventory.md
Updates ownership/status tables documenting lifecycle commands and revised routing notes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as Installed Shim
  participant Daemon
  participant ProjectService

  CLI->>Daemon: POST /core/lifecycle/spawn-text (project, tool, worktree)
  Daemon->>Daemon: validate required query params
  Daemon->>Daemon: check loopback-only access
  Daemon->>ProjectService: POST agents.spawn (JSON, timeout)
  ProjectService-->>Daemon: {ok, sessionId, status}
  Daemon->>Daemon: renderCoreLifecycleSpawnLines(payload)
  Daemon-->>CLI: text or JSON lines
Loading

Possibly related PRs

  • TraderSamwise/aimux#272: Both PRs affect daemon interaction with per-project service metadata endpoints that the new lifecycle routes rely on.
  • TraderSamwise/aimux#287: Both PRs update src/daemon.test.ts mocking of project-service calls invoked by lifecycle/launcher routing.
  • TraderSamwise/aimux#298: Both PRs modify scripts/installed-aimux-shim.sh command-dispatch logic for shim fast-path routing.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: routing lifecycle CLI commands through the daemon.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/core-sidecar-next-32

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

@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: 2

🧹 Nitpick comments (4)
scripts/installed-aimux-shim.sh (2)

135-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate status-classification logic with aimux_post_text_route.

aimux_post_query_text_route (135-169) repeats almost the entire body of aimux_post_text_route (101-133) — only the curl invocation differs (plain POST vs. --get with extra args). Consider extracting the shared temp-file/trap/status-classification logic into a common helper parameterized by the curl invocation.

🤖 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 `@scripts/installed-aimux-shim.sh` around lines 135 - 169, The status handling
and temp-file/trap cleanup in aimux_post_query_text_route duplicates
aimux_post_text_route almost exactly, so refactor the shared body into a common
helper and keep only the curl invocation differences (plain POST versus --get
with extra args) in the route-specific functions. Use the existing function
names aimux_post_text_route and aimux_post_query_text_route to locate the
duplicated logic, then parameterize the helper so both functions reuse the same
response classification and cleanup flow.

289-504: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Repeated flag-parsing boilerplate across lifecycle handlers.

--tool, --project, --worktree, --json/--no-open parsing blocks are duplicated nearly verbatim across aimux_try_lifecycle_spawn, _stop, _kill, and _fork. A shared parsing helper (or a data-driven loop) would reduce this ~250-line surface and the risk of divergent bugs like the ones above.

🤖 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 `@scripts/installed-aimux-shim.sh` around lines 289 - 504, The lifecycle
handlers contain repeated command-line parsing logic, so the issue is duplicated
flag handling across aimux_try_lifecycle_spawn, aimux_try_lifecycle_stop,
aimux_try_lifecycle_kill, and aimux_try_lifecycle_fork. Refactor the shared
parsing for --tool, --project, --worktree, --json, and --no-open into a reusable
helper or a data-driven parser, then have each lifecycle function call it and
keep only the route-specific arguments and required positional values.
src/daemon.test.ts (1)

573-696: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good coverage for the happy paths; consider adding a case for the ensureProject-failure/unhandled-exception path.

These tests cover success and one project-service error (404), but none currently exercise ensureProject() throwing (e.g., via coreActorMock.failStartFor) through a lifecycle text route. Given the related concern raised in daemon.ts (uncaught exception yielding a JSON body instead of text/plain), a regression test here would lock in the fix.

🤖 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 `@src/daemon.test.ts` around lines 573 - 696, Add a regression test around the
lifecycle text routes in daemon.test.ts that forces ensureProject() to fail, for
example by using coreActorMock.failStartFor and then calling a lifecycle text
endpoint through AimuxDaemon.routeRequest. Verify the response stays text/plain
with the expected plain-text error body instead of falling back to a JSON
unhandled-exception response, and anchor the test near the existing
lifecycleSpawnText/lifecycleStopText cases so the ensureProject path is covered.
src/daemon.ts (1)

418-453: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

stop/kill implicitly start a project service that wasn't running.

postProjectServiceJson unconditionally calls ensureProject() before proxying, so aimux stop/aimux kill against a project with no running service will spin one up just to immediately fail with "session not found" from the downstream agents API — an unnecessary and surprising side effect for a stop/kill action.

♻️ Suggested approach: make ensureProject opt-in per call site
   private async postProjectServiceJson(
     projectRoot: string,
     routePath: string,
     body: Record<string, unknown>,
+    opts: { ensureProject?: boolean } = {},
   ): Promise<...> {
     const resolvedRoot = this.resolveProjectRoot(projectRoot);
     try {
-      await this.ensureProject(resolvedRoot);
+      if (opts.ensureProject !== false) await this.ensureProject(resolvedRoot);
       const endpoint = loadMetadataEndpointByProjectId(getProjectIdFor(resolvedRoot));
       if (!endpoint)
         return { ok: false, response: this.textError(503, `project service unavailable for ${resolvedRoot}`) };
       ...

Then pass { ensureProject: false } from lifecycleStopTextRoute/lifecycleKillTextRoute.

🤖 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 `@src/daemon.ts` around lines 418 - 453, The lifecycle stop/kill paths are
unexpectedly starting a project service because postProjectServiceJson always
calls ensureProject() before proxying. Update postProjectServiceJson to make
project initialization opt-in (for example via an ensureProject flag or
similar), and keep the default behavior only for call sites that need it. Then
adjust lifecycleStopTextRoute and lifecycleKillTextRoute to pass the
non-starting behavior so stop/kill only proxy to an existing service and do not
spin one up.
🤖 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 `@src/daemon.ts`:
- Around line 418-453: The lifecycle route in postProjectServiceJson lets
ensureProject() throw before the local try/catch, which breaks the text/plain
error contract. Move the ensureProject(resolvedRoot) call inside the try block
in src/daemon.ts so any startup or actor failures are converted through this
method’s existing textError(502, ...) path, keeping all postProjectServiceJson
failures consistent with the other route branches. After updating, verify the
daemon behavior through a local build/install and aimux restart as requested.
- Around line 455-542: The lifecycle text route handlers treat upstream JSON as
a success before verifying required response fields, which can render undefined
values. In lifecycleSpawnTextRoute, lifecycleStopTextRoute,
lifecycleKillTextRoute, and lifecycleForkTextRoute, validate
result.json.sessionId, status, previousStatus, and threadId before building the
payload or calling renderCoreLifecycle*Lines; if any field is missing or
malformed, return an error response instead of success. Use the existing payload
types and the postProjectServiceJson result handling to keep the checks close to
the response parsing.

---

Nitpick comments:
In `@scripts/installed-aimux-shim.sh`:
- Around line 135-169: The status handling and temp-file/trap cleanup in
aimux_post_query_text_route duplicates aimux_post_text_route almost exactly, so
refactor the shared body into a common helper and keep only the curl invocation
differences (plain POST versus --get with extra args) in the route-specific
functions. Use the existing function names aimux_post_text_route and
aimux_post_query_text_route to locate the duplicated logic, then parameterize
the helper so both functions reuse the same response classification and cleanup
flow.
- Around line 289-504: The lifecycle handlers contain repeated command-line
parsing logic, so the issue is duplicated flag handling across
aimux_try_lifecycle_spawn, aimux_try_lifecycle_stop, aimux_try_lifecycle_kill,
and aimux_try_lifecycle_fork. Refactor the shared parsing for --tool, --project,
--worktree, --json, and --no-open into a reusable helper or a data-driven
parser, then have each lifecycle function call it and keep only the
route-specific arguments and required positional values.

In `@src/daemon.test.ts`:
- Around line 573-696: Add a regression test around the lifecycle text routes in
daemon.test.ts that forces ensureProject() to fail, for example by using
coreActorMock.failStartFor and then calling a lifecycle text endpoint through
AimuxDaemon.routeRequest. Verify the response stays text/plain with the expected
plain-text error body instead of falling back to a JSON unhandled-exception
response, and anchor the test near the existing
lifecycleSpawnText/lifecycleStopText cases so the ensureProject path is covered.

In `@src/daemon.ts`:
- Around line 418-453: The lifecycle stop/kill paths are unexpectedly starting a
project service because postProjectServiceJson always calls ensureProject()
before proxying. Update postProjectServiceJson to make project initialization
opt-in (for example via an ensureProject flag or similar), and keep the default
behavior only for call sites that need it. Then adjust lifecycleStopTextRoute
and lifecycleKillTextRoute to pass the non-starting behavior so stop/kill only
proxy to an existing service and do not spin one up.
🪄 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: de861cbb-802c-4f7e-987b-8d0ef2deab6c

📥 Commits

Reviewing files that changed from the base of the PR and between abb369c and 5e98a22.

📒 Files selected for processing (8)
  • docs/command-ownership-inventory.md
  • scripts/installed-aimux-shim.sh
  • src/core-command-contract.ts
  • src/core-command-ownership.test.ts
  • src/core-text.ts
  • src/daemon.test.ts
  • src/daemon.ts
  • src/installed-shim.test.ts

Comment thread src/daemon.ts
Comment thread src/daemon.ts Outdated
@TraderSamwise

Copy link
Copy Markdown
Owner Author

Independent review findings resolved in 29113b1:

  • Fixed JSON lifecycle body parsing so open:false is honored instead of defaulting to open:true.
  • Fixed local lifecycle text routes so browser-origin requests are rejected; these routes are shim-only, while web/app clients continue using the project-service APIs.

Verification: typecheck, lint, build, focused lifecycle/shim tests, and full vitest suite passed.

@TraderSamwise
TraderSamwise merged commit 6701f62 into master Jul 4, 2026
3 checks passed
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