Skip to content

feat(server): autonomOS-aware statusline for spawned CC sessions - #162

Merged
aterrylu merged 1 commit into
mainfrom
terry/statusline-config
May 4, 2026
Merged

feat(server): autonomOS-aware statusline for spawned CC sessions#162
aterrylu merged 1 commit into
mainfrom
terry/statusline-config

Conversation

@aterrylu

@aterrylu aterrylu commented May 4, 2026

Copy link
Copy Markdown
Owner

Summary

Inject an autonomOS-aware statusline into every spawned Claude Code session via inline --settings JSON. The renderer reads CC's stdin payload (model, ctx %, cost, branch) and queries the local autonomOS server for hierarchy, then renders an adaptive identity line that auto-detects role from manager + directReports.

What it looks like

auto + manager (TeamLead with 3 children):
[TeamLead@autonomos · ↑Dispatcher · ↓3 reports]
⚡Opus  ▓▓░░░░░░░░ 18%  $0.42  ⏱8m05s

auto + worker (no children):
[Worker@autonomos · ↑TeamLead]
⚡Opus  ▓▓▓▓░░░░░░ 45%  $0.18  ⏱2m05s  🌿 terry/feat-x

auto + standalone (no manager, no children):
[Agent@autonomos · standalone]
⚡Opus  ▓▓▓▓░░░░░░ 45%  $0.18  ⏱2m05s

Architecture

  • No CLI flag exists for statusline — CC's --settings accepts inline JSON, autonomos already uses this for hook relay. New statusLine key piggybacks on the existing inline payload (one extra JSON property).
  • Renderer ships as plain .mjs at packages/server/src/providers/statusline.mjs. No build step — uses only built-in node modules + global fetch. Same source-tree-shipping pattern as channel-server/dist.mjs but doesn't need bundling because it has no external deps.
  • Single API call (GET /api/sessions) covers identity lookup AND report counting. The LIST endpoint enriches each entry with manager/template/project from PersistedSession; the SINGLE endpoint (/api/sessions/:id) does NOT, so we don't use it.
  • Refresh interval: 5s so hierarchy stays current during idle (catches new spawns, kills, set_manager reorgs without requiring an assistant message).
  • Replaces personal CC statusline for spawned sessions only (CC's --settings overrides ~/.claude/settings.json for object-typed fields). Toggle off → falls back to personal config.

Failure-mode contract

The script must NEVER crash CC's terminal (per CC docs: non-zero exit or no output → blank statusline). All boundaries degrade gracefully:

  • Server unreachable → [autonomos · offline] + activity-line-only
  • Outside autonomos (no env) → [autonomos] + activity
  • stdin parse failure → activity uses defaults
  • Top-level catch → static [autonomos] fallback

Files

File Change
packages/server/src/settings.ts Add statusLine?: { enabled: boolean } to AppSettings
packages/server/src/routes/settings.ts Mask + accept statusLine in REST handler
packages/server/src/providers/claude-code.ts Inject statusLine into existing inline --settings JSON when enabled (default on)
packages/server/src/providers/statusline.mjs (NEW) Renderer (~140 LOC plain ES module)
packages/server/src/providers/statusline.d.mts (NEW) TypeScript declarations for the .mjs (test-only)
packages/dashboard/src/plugins/settings/SettingsStatusBarItem.tsx Toggle UI (mirrors autoTrust pattern)
packages/server/src/__tests__/statusline.test.ts (NEW) 30 unit tests

Test plan

  • formatHierarchy across all 4 hierarchy states (standalone / worker / top / middle)
  • buildBar boundary clamping (0%, 100%, >100, <0, null/undefined)
  • formatDuration minute/second padding + null handling
  • formatActivity with all fields, empty stdin, missing branch, null nested fields
  • getAutonomosMeta against mocked fetch: errors, non-array response, session-not-found, manager + reports counting, exited-session exclusion, missing-name fallback, null-vs-undefined manager
  • All 303 tests pass + type-check clean + lint clean
  • Visual confirmation: spawn a real CC session, verify statusline renders in dashboard terminal pane (deferred to first interactive session post-merge)
  • Toggle off → verify fallback to personal CC statusline (manual)

Notes

  • One bug found and fixed during QA: formatHierarchy({manager: null, directReports: -1}) was emitting [X] instead of [X · standalone] because the standalone check was === 0 instead of <= 0. Test caught it on first run.
  • Code review caught a shell-injection concern: command: \node ${PATH}`was unquoted. Fixed byJSON.stringify(STATUSLINE_SCRIPT) for proper shell quoting against install paths with spaces/quotes/$`/backticks.
  • Two correctness bugs caught by silent-failure-hunter: I had assumed /api/org returns a flat array with manager fields and /api/sessions/:id returns enriched session — both wrong. Fixed by switching to the LIST endpoint which has the correct enriched shape.
  • Gemini CLI deferred: only has showStatusInTitle (terminal-title icons), no command-script statusline.
  • Codex CLI deferred: has /statusline slash command but no custom-command support — see openai/codex#17827.

🤖 Generated with Claude Code

Inject an adaptive statusline into every Claude Code session spawned by
autonomos via the inline `--settings` JSON. Renderer auto-detects role
from hierarchy (manager + direct-report count) and renders one of:

  [Dispatcher@autonomos · ↓3 reports]
  [TeamLead@autonomos · ↑Dispatcher · ↓2 reports]
  [Worker@autonomos · ↑TeamLead]
  [Agent@autonomos · standalone]

Plus an activity line with model, ctx %, cost, duration, branch.

Refresh every 5s so hierarchy stays current during idle. Single GET
/api/sessions covers both identity lookup and report counting (the LIST
endpoint enriches with persisted manager/template/project; the SINGLE
endpoint does not, so we don't use it here).

Settings: `statusLine.enabled` toggle in the dashboard settings panel,
default ON. When disabled, falls back to the user's personal
`~/.claude/settings.json` statusLine.

Renderer ships as a plain `.mjs` next to claude-code.ts (no build step,
no external deps — only built-in node modules + global fetch). Path is
JSON.stringified into the inline statusLine.command for safe shell
quoting against install paths with spaces.

30 unit tests cover formatHierarchy, formatActivity, buildBar,
formatDuration, and getAutonomosMeta against mocked fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@aterrylu
aterrylu force-pushed the terry/statusline-config branch from f40eea1 to 70ca6ad Compare May 4, 2026 06:33
@aterrylu
aterrylu marked this pull request as ready for review May 4, 2026 06:33

@nox-0x nox-0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. Well-structured feature with a solid failure-mode contract — the statusline renderer is defensive by design and the 30-unit test suite covers all edge cases. No blocking issues.

@aterrylu
aterrylu merged commit 9e25100 into main May 4, 2026
1 check passed
@aterrylu
aterrylu deleted the terry/statusline-config branch May 4, 2026 07:15
aterrylu added a commit that referenced this pull request May 6, 2026
The statusline renderer shipped in #162 had two bugs that would have
made the hierarchy line silently non-functional in production. Both
were caught by visual QA against the live prod server post-merge —
unit tests with mocked fetch couldn't detect them.

1. Missing Authorization header. /api/sessions requires
   `Authorization: Bearer ${AUTONOMOS_TOKEN}` (the same token the
   channel-server uses). Without it, the renderer always got 401,
   getAutonomosMeta returned null, and every spawned agent's identity
   line was `[autonomos · offline]` instead of the proper hierarchy.

2. Wrong field match. AUTONOMOS_SESSION_ID is the autonomOS managed
   session id (the `id` field on /api/sessions entries), not Claude's
   internal session id (`claudeSessionId`). The renderer matched on
   the wrong field, so even if auth had been correct, find() would
   always have returned undefined.

Verified against the live :3100 server with three real sessions:

  [StatuslineConfig@autonomOS · ↑TeamLead@autonomOS]
  [TeamLead@autonomOS · ↑Dispatcher · ↓4 reports]
  [Dispatcher · ↓5 reports]

Added 2 tests covering the auth header behavior (sent when token
provided, omitted when absent). Updated existing tests' mock data
to use `id` instead of `claudeSessionId` so the field semantics are
asserted correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
aterrylu added a commit that referenced this pull request May 6, 2026
* fix(server): statusline auth + correct session id field

The statusline renderer shipped in #162 had two bugs that would have
made the hierarchy line silently non-functional in production. Both
were caught by visual QA against the live prod server post-merge —
unit tests with mocked fetch couldn't detect them.

1. Missing Authorization header. /api/sessions requires
   `Authorization: Bearer ${AUTONOMOS_TOKEN}` (the same token the
   channel-server uses). Without it, the renderer always got 401,
   getAutonomosMeta returned null, and every spawned agent's identity
   line was `[autonomos · offline]` instead of the proper hierarchy.

2. Wrong field match. AUTONOMOS_SESSION_ID is the autonomOS managed
   session id (the `id` field on /api/sessions entries), not Claude's
   internal session id (`claudeSessionId`). The renderer matched on
   the wrong field, so even if auth had been correct, find() would
   always have returned undefined.

Verified against the live :3100 server with three real sessions:

  [StatuslineConfig@autonomOS · ↑TeamLead@autonomOS]
  [TeamLead@autonomOS · ↑Dispatcher · ↓4 reports]
  [Dispatcher · ↓5 reports]

Added 2 tests covering the auth header behavior (sent when token
provided, omitted when absent). Updated existing tests' mock data
to use `id` instead of `claudeSessionId` so the field semantics are
asserted correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(statusline): colors, project layout, security sanitization

Visual + correctness iteration on top of the auth fix:

Layout (matching Terry's personal CC statusline pattern):
- Activity line now leads with project (blue) → branch (green) → cost
  (yellow) → model (magenta) → ctx bar (threshold green/yellow/red) →
  duration (dim grey). No two adjacent segments share a color.
- │ vertical separators between activity segments (was double space).
- Centralized ANSI palette at top of file for easy tweaking.

New resolvers:
- resolveProject: 3-tier fallback chain — meta.project → @-suffix on
  agent name → basename of CC's project_dir. Strict project_dir-first
  preference (current_dir is excluded so subdirs don't mislabel).
- resolveBranch: synchronous git fallback when CC's stdin doesn't include
  a branch (common case for non-worktree sessions). Bounded by 100ms
  timeout and GIT_CEILING_DIRECTORIES so a misconfigured cwd can't make
  git stat-storm up the ancestor chain.

Security:
- ANSI/control characters stripped from agent name and manager name in
  getAutonomosMeta. Prevents a malicious peer agent named "evil\x1b[2J"
  from clearing the user's terminal on every 5s refresh tick.

Compat with main (PR #165):
- /api/sessions endpoint replaced by /api/agents.
- Manager refs are now by UUID (managerId) not name. Renderer resolves
  the manager's display name by looking up the referenced agent in the
  same response. Direct-report counting now compares managerId === me.id.

Tests: 42 unit tests (was 30) cover format helpers, color verification,
meta resolver against the new /api/agents shape, sanitization, and
manager-by-id resolution including the dangling-reference case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* style(statusline): biome formatter pass

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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.

2 participants