@staix/agent-hub 0.12.16 → 0.12.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +4 -4
  3. package/docs/agent-notes/adapters.md +1 -0
  4. package/docs/agent-notes/bus.md +2 -0
  5. package/docs/agent-notes/tasks.md +3 -1
  6. package/docs/agent-notes/tests.md +1 -0
  7. package/docs/events.md +37 -3
  8. package/docs/operations.md +149 -10
  9. package/docs/quickstart.md +3 -3
  10. package/docs/security.md +28 -1
  11. package/docs/smoke.md +93 -0
  12. package/docs/specs/2026-09-19-agent-hub-design.md +176 -1
  13. package/docs/verification/2026-10-09-agent-shell-t0.md +123 -0
  14. package/docs/verified.json +26 -17
  15. package/package.json +1 -1
  16. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  17. package/plugins/agent-hub/server.js +17 -5
  18. package/src/adapters/acp.ts +2 -2
  19. package/src/adapters/claude-channel.ts +3 -2
  20. package/src/adapters/codex-appserver.ts +3 -2
  21. package/src/adapters/local-worker.ts +16 -12
  22. package/src/cli/console-state.ts +279 -0
  23. package/src/cli/console.ts +224 -0
  24. package/src/cli/facts-hook.ts +8 -1
  25. package/src/cli/identity-audit.ts +66 -0
  26. package/src/cli/identity.ts +54 -0
  27. package/src/cli/launch.ts +15 -2
  28. package/src/cli/main.ts +75 -32
  29. package/src/cli/tail-render.ts +17 -0
  30. package/src/cli/upgrade-runtime.ts +1 -1
  31. package/src/hub/attribution.ts +20 -0
  32. package/src/hub/board.ts +6 -2
  33. package/src/hub/bus.ts +88 -10
  34. package/src/hub/child-process.ts +11 -0
  35. package/src/hub/conductor.ts +196 -0
  36. package/src/hub/control-client.ts +3 -3
  37. package/src/hub/daemon.ts +388 -51
  38. package/src/hub/envelope.ts +1 -1
  39. package/src/hub/events.ts +10 -4
  40. package/src/hub/hub-tools.ts +13 -0
  41. package/src/hub/report.ts +162 -4
  42. package/src/hub/supervision.ts +152 -0
  43. package/src/hub/tasks.ts +30 -15
  44. package/src/hub/usage.ts +37 -2
  45. package/src/pi/launch.ts +2 -1
@@ -1343,6 +1343,181 @@ whether to continue the native session or restart; this change provides no
1343
1343
  automatic session replacement. Status, tail and dashboard expose readings with
1344
1344
  source, measurement time and freshness beside quota information.
1345
1345
 
1346
- The control contract is protocol 14. Recovery sources 9 through 13 remain
1346
+ Release 0.12.16 uses control protocol 14. Recovery sources 9 through 13 remain
1347
1347
  supported; protocol 13 identifies releases 0.12.4 through 0.12.15, while
1348
1348
  0.12.16 uses protocol 14.
1349
+
1350
+ ## Operator console and conductor (issues #190, #191, #193, #194, #195)
1351
+
1352
+ `ahub console` owns one authenticated console connection. It shares tail's
1353
+ renderer, renders a DECSTBM stream and footer, and provides optional alternate
1354
+ screen panels. `up` opens it only with terminal input and output, unless
1355
+ `--no-console` is given. Tail remains a plain stream. Console commands use an
1356
+ allowlisted argv with stdin closed; lifecycle and native launches are excluded.
1357
+ Allow options require confirmation and never interpret nonempty input as an
1358
+ approval. Deny is direct. Pending requests include expiry and are withdrawn by
1359
+ `permission_closed` on any answer or cancellation. Answer audits have no title.
1360
+ Panels expose Peers, Approvals, Tasks, Queue and Events with bounded polling and
1361
+ Unicode cell widths, fall back below 80x24, and restore terminal state on exit.
1362
+
1363
+ Console semantic colors (issue #201) use spans with a fixed terminal-native
1364
+ palette: cyan information, bold cyan active/selected labels, green availability
1365
+ and success, yellow attention/waiting, red failure/intervention, and restrained
1366
+ bright-black metadata. Offline peers, ordinary titles, action details and body
1367
+ lines use the default foreground. Existing labels, selection markers and
1368
+ confirmation prompts remain sufficient without color. Stream headers use event
1369
+ structure for their tone; body text is never parsed for meaning or passed
1370
+ through as terminal styling. Denial requests and observed expiry/cancellation
1371
+ are red; an answered remote closure lacks option-kind metadata and is not
1372
+ guessed to be a denial.
1373
+
1374
+ `--color=auto|always|never` affects styling only. Auto requires terminal input
1375
+ and output, a non-dumb TERM, and absent/empty NO_COLOR. Always overrides those
1376
+ checks even for redirected stream output, without enabling panels or raw mode;
1377
+ never disables styling. Invalid values fail with usage text before connection.
1378
+ Plain `renderConsole` remains available, while `renderConsoleLines` exposes
1379
+ semantic spans. Geometry is computed from sanitized plain Unicode text before
1380
+ painting. `paint` sanitizes every span, inserts only fixed palette SGR sequences
1381
+ and resets each styled span. Interactive exits, signals, disconnect and errors
1382
+ retain the console restoration sequence. No new dependencies, protocol changes,
1383
+ polling or redraw triggers are added. Tail, logs and machine-readable output
1384
+ remain unchanged. Native light/dark readability and idle-CPU observations are
1385
+ separate smoke evidence, not inferred from fake-terminal tests.
1386
+
1387
+ Agent shell CLI calls connect as the detected peer in tools mode. Hub launches
1388
+ set `AGENTHUB_PEER_ID`; the pinned installed Codex shell injects
1389
+ `CODEX_THREAD_ID` after environment filtering, and Claude uses `CLAUDECODE`.
1390
+ Malformed or conflicting markers fail closed. Console-only commands are denied
1391
+ before connecting, with no as-user escape. Refusals enter a bounded ids-only
1392
+ local audit spool which a running daemon consumes; this preserves the no-connect
1393
+ rule while making refusals visible on the console and in the log. A stopped
1394
+ daemon cannot show a live notice; it consumes remaining records on startup.
1395
+ This does not establish a security boundary against an unrestricted shell
1396
+ which can read the token. Native source evidence and live probe results remain
1397
+ separate in the smoke ledger.
1398
+
1399
+ Exactly one explicit conductor role may be configured. Default-allow
1400
+ capabilities do not grant it; role authority is checked on each operation and
1401
+ refreshed on a subsequent connection. Shared MCP tools provide public status,
1402
+ public task history, actor-preserving assign/escalate, headless local/Kimi/Pi
1403
+ start, and conductor-owned persistent holds. TUI starts return launch commands.
1404
+ Assign/escalate need `assign` if the peer has a capabilities entry. Approval
1405
+ answers, durable queue resolution, budget overrides and lifecycle remain human
1406
+ operations. A conductor cannot release human or budget holds. Audit events are
1407
+ ids-only and report counts their actions.
1408
+
1409
+ The conductor feed defaults to own tasks, also supports all and off, and uses
1410
+ the existing bus digest window. Repeated queued task/kind milestones replace
1411
+ the earlier pending notice; accepted or in-flight deliveries are preserved.
1412
+ Structured milestones carry public titles or PII stubs, never raw history
1413
+ notes or check output. Only aged approval summaries and needs-review delivery
1414
+ ids are important. Role/feed revocation withdraws pending feed notices. A
1415
+ completed task set produces one round notice until a new task joins; subsequent
1416
+ rounds count only newly joined tasks. Pure status supervision queues wait for
1417
+ the existing digest deadline rather than flushing at the ordinary batch-count
1418
+ threshold. Mixed traffic retains the ordinary admission behavior. The existing
1419
+ 10-original digest ceiling and 200-entry queue ceiling still bound admission;
1420
+ larger windows may require multiple bounded deliveries.
1421
+
1422
+ Supervision cost counts completed native turns that received feed notices,
1423
+ with whole-turn token readings when available. Other work may share a turn;
1424
+ these readings are not per-notice token attribution. Missing measurements are
1425
+ unknown. Native TUI conductor, sandbox and feed-on smoke results must be recorded
1426
+ as observed outcomes, separately from unit/fake protocol tests.
1427
+
1428
+ Managed Claude launches with turn-free facts, task-idle sweeps or a conductor
1429
+ role install native observation hooks. A conductor receives them even when facts
1430
+ injection and task-idle sweeps are disabled; ordinary non-opt-in launches remain
1431
+ unchanged. SessionStart registers the native
1432
+ session and UserPromptSubmit starts observation; PreToolUse keeps a tool turn
1433
+ active and Stop closes it. Explicit caller settings remain authoritative and
1434
+ produce a warning when they replace these hooks. An ordinary terminal records a
1435
+ private launcher identity without creating an Orca terminal-recovery record.
1436
+ The facts control request accepts session/start/pre/post/stop phases and carries
1437
+ nativeInstanceId and nativeLaunchId alongside sessionId and transcriptPath.
1438
+ The command hook forwards launcher identity only for its matching state directory
1439
+ and peer; library calls targeting another hub do not inherit that identity.
1440
+ The daemon fences observations to its current instance and launcher, validates
1441
+ the session transcript, and rejects stale stop/post observations. Native prompt
1442
+ text is never included in those requests or events.
1443
+
1444
+ A genuine native Stop requires the current daemon/launcher binding and an actual
1445
+ assistant end_turn transcript message at or after the native turn's first start,
1446
+ distinct from the completed-message baseline recorded at that start. Later tool
1447
+ activity does not move this turn-start boundary. Missing start evidence or a
1448
+ start from another session, launch or channel claim leaves completion unknown.
1449
+ Accepted completion consumes that start before the peer becomes idle, so another
1450
+ message cannot reuse it. A valid bound Stop request receives one prompt
1451
+ acknowledgement with pending=true, within the existing two-second hook deadline.
1452
+ The acknowledgement records observation only, never completion. After the hook
1453
+ can return, a deferred observer waits up to 1200 monotonic milliseconds for its
1454
+ transcript append to become visible. Each read and final consumption revalidate
1455
+ the captured start, session,
1456
+ launch, peer and claim. A seen previous-turn baseline still waits while a new
1457
+ current start exists; a consumed duplicate remains a no-op. Timeout, malformed or
1458
+ oversized evidence and superseded context leave completion unknown. This wait
1459
+ does not retry a user action or relax authority.
1460
+ The observer sends no second request reply and never changes global hook settings.
1461
+ The live harness also binds its final receipt to the current private launch id.
1462
+ Its opaque
1463
+ deduplication id binds session, launch and message; transport replacement or new
1464
+ activity cannot turn a replay into another completion. Unbound or idless legacy
1465
+ Stop events cannot certify completion or finish supervision. A current private
1466
+ launcher marker takes precedence over an older terminal-recovery launch id for
1467
+ native observation, while preserving the recovery records and their authority.
1468
+ Claude turn reports
1469
+ prefer unique native Stop events; historical logical-state counts are labelled
1470
+ as such, and an unobserved completion remains unknown. Channel idle, watchdog
1471
+ expiry, board approval and a tool reply do not independently establish native
1472
+ completion. The live harness waits for the current fixture/instance/session's
1473
+ final transcript end_turn and turn_duration after its last review before
1474
+ reporting a completed case or terminating its native TUI. It also requires the
1475
+ matching opaque native completion event, an idle peer and settled delivery rows.
1476
+
1477
+ These additions use control protocol 15. Supported recovery sources include
1478
+ protocol 14 (0.12.16) alongside the previous source protocols.
1479
+
1480
+ For release 0.12.17 only, the user explicitly accepted deferral of real Kimi ACP
1481
+ new-tool invocation and ordinary-role refusal T0 evidence on 2026-10-09. The
1482
+ managed OAuth account rejected its prompt with HTTP 403 for the weekly quota
1483
+ before any tool event; reset time is unknown and no configured native alternative
1484
+ was found. This prerequisite remains unverified. Actual Claude/Codex conductor
1485
+ workflows, Claude shell/channel observations, shared MCP/fake-adapter authority
1486
+ checks, real Kimi ACP initialization/session creation and full source gates are
1487
+ separately qualified. The deferral authorizes no purchase, credentials,
1488
+ provider/configuration change, account retry or authority relaxation. Once quota
1489
+ is available under an authorized account, the same isolated read-only status-tool
1490
+ and ordinary-role refusal probe must record a native tool event and daemon result
1491
+ before this Kimi prerequisite can be marked verified.
1492
+
1493
+ ## Amendment: per-task usage attribution (issue #200)
1494
+
1495
+ Task usage is attributed by a rule at write time, never by a proportional guess:
1496
+
1497
+ - A turn whose original delivery names exactly one distinct positive `refs.task` uses that id and `attribution: "delivery"`, even when the peer does not own the task. Two distinct delivery task ids fall through to the next rule.
1498
+ - Otherwise a peer with exactly one owned `in_progress` task uses that id and `attribution: "single_open"`.
1499
+ - Otherwise the record carries `attribution: "unattributed"` and no task id.
1500
+
1501
+ The bus calls `onDeliver(peer, originals)` immediately before `peer.deliver`,
1502
+ after retaining the original delivery. The daemon consumes pending delivery
1503
+ identity during the synchronous busy/turn-start transition and clears it on
1504
+ delivery admission/failure. A user-started native turn cannot inherit an older
1505
+ delivery. Tokens and usage use the rule at write time; turn ends preserve the
1506
+ start-time attribution. Local worker usage carries its request-bound route
1507
+ policy task when one exists. Relay usage would use a route decision's task when
1508
+ present; the current daemon has no such relay usage writer, so collection is
1509
+ unchanged. Attributed PII records carry only a task id and `pii: true`.
1510
+
1511
+ `ahub report --by task` and `--by task --json` use only events, never the board or
1512
+ task text. They report latest task class/outcome, attributed turn ends, first
1513
+ accept-to-first-approval wall time (unknown while not approved or without both
1514
+ boundaries), per-peer token increments and provider counters, and class rollups.
1515
+ Usage deduplication uses peer/source/id. Missing counters stay unknown, distinct
1516
+ from reported zero, and known-record counts identify measured subsets.
1517
+
1518
+ Unattributed token and usage-record shares always appear. Older records without
1519
+ `attribution` stay in a distinct `before attribution` bucket, never redistributed
1520
+ to a task; both buckets appear even when empty. A zero denominator has unknown
1521
+ share. Export carries the new fields without task text. No prices or savings
1522
+ counterfactuals are derived. These additive fields retain events schema 1 and
1523
+ the existing control protocol. Plain `ahub report` remains unchanged.
@@ -0,0 +1,123 @@
1
+ # Agent shell identity T0, issues #193 and #194
2
+
3
+ ## Evidence collected
4
+
5
+ - Installed CLI reports `codex-cli 0.146.0` (`codex --version`, 2026-10-09). `codex app-server --help` supports configuration overrides and WebSocket listeners; its example names `shell_environment_policy.inherit=all`.
6
+ - The [version-pinned native environment implementation](https://github.com/openai/codex/blob/rust-v0.146.0/codex-rs/protocol/src/shell_environment.rs) applies inheritance, default exclusions, custom exclusions, explicit values, and `include_only` in that order. It then injects `CODEX_THREAD_ID`. `AGENTHUB_PEER_ID` is an ordinary inherited variable and can be removed by filtering. The native thread marker is injected after filtering, which supplies a fallback without widening the user's environment policy.
7
+ - The [version-pinned core wrapper](https://github.com/openai/codex/blob/rust-v0.146.0/codex-rs/core/src/exec_env.rs) explicitly documents thread-marker injection even with `include_only`.
8
+ - The [current configuration reference](https://learn.chatgpt.com/docs/config-file/config-reference) describes inheritance and filtering, and now describes a `filters` map alongside legacy `exclude` and `include_only`. Current documentation is not evidence that every new option exists in installed 0.146.0.
9
+ - The hub's Codex adapter passes a child environment even without optional launch values. It now sets its own marker and removes stale vendor markers inherited from the caller. Recovery authority remains scrubbed by `childEnv`.
10
+
11
+ ## Native Codex observations (2026-10-09)
12
+
13
+ - Real installed `codex exec --json --model gpt-5.5 --sandbox workspace-write`
14
+ completed a single fixed Python shell probe in an isolated git fixture.
15
+ With explicit `inherit=all`, the hub marker, native thread marker and harmless
16
+ canary were present; a Claude marker was absent. The disposable loopback
17
+ responder was reachable by the harness, while the native shell request failed
18
+ with `URLError`, errno 1. No sandbox or network exception was requested.
19
+ - A second real run used `inherit=none`, an explicit PATH and
20
+ `include_only=["PATH"]`. The hub marker, native marker and canary still appeared
21
+ in the observed shell. This does not establish that filtering removed them;
22
+ the difference from the pinned environment construction is unresolved in this
23
+ installed host environment. The fallback is supported by source and marker
24
+ presence, without widening the caller's configured policy.
25
+ - A real native MCP call under `workspace-write` completed `agent-hub.hub_status`
26
+ against the candidate bundle and an isolated conductor daemon. Native JSONL
27
+ recorded the MCP call completing, and the daemon independently recorded a
28
+ codex `conduct/status` event. Only this exact hub tool was approved through the
29
+ existing per-tool `approval_mode=approve` configuration; its daemon role check
30
+ still applied. This proves MCP access separately from the blocked shell path.
31
+ - The first run with the user's configured `gpt-6.1-sol` failed before tools:
32
+ that slug was rejected by the installed ChatGPT-account endpoint. The probe's
33
+ explicit model selection did not change the user's configuration.
34
+
35
+ ## Native Claude observations (2026-10-09)
36
+
37
+ - The final observer proof used source `20ad5e6`. Its read-only feed-off
38
+ continuation verified one completed native turn, 263,413 cached-inclusive
39
+ tokens, a matching opaque daemon Stop receipt, native idle and no pending
40
+ deliveries. The fresh feed-own run verified two actual owner completions and
41
+ independent file reviews, nine native finals matched by nine daemon Stops,
42
+ 27 unique usage records and 1,888,242 tokens. Eight completed turns contained
43
+ supervision, totalling 1,292,922 tokens. The receipt matched the final native
44
+ message, session, launcher and instance; earlier incomplete captures below
45
+ were not retroactively marked complete.
46
+
47
+ - In the original captures, Claude Code 2.1.295, Opus 5.5, ran the candidate MCP tools in real PTYs
48
+ after its five-hour quota reset. Both feed-off and feed-own fixtures started
49
+ local and headless Pi, proposed two tasks, reassigned Beta to Pi, held and
50
+ released both peers, and independently read the exact ALPHA/BETA files before
51
+ approving their tasks. The console independently audited four allow-once
52
+ answers in each fixture, covering the two writes and bounded file checks.
53
+ - The real feed-off TUI displayed inbound review requests; the feed-own TUI
54
+ displayed actual supervision and review pushes. MCP success and channel
55
+ delivery were observed separately. A transient startup warning about the
56
+ server name did not prevent the later observed channel deliveries.
57
+ - In the feed-own native TUI, the operator submitted the fixed Python probe
58
+ through `!`. Its actual command output was `AGENTHUB_PEER_ID=true`,
59
+ `CLAUDECODE=true`, `CLAUDE_CODE_SESSION_ID=true`, `CODEX_THREAD_ID=false`.
60
+ This is shell output captured before Claude's interpretation of it.
61
+ - A subsequent feed-off run pinned to `da2ebbc` executed the same fixed probe
62
+ through the model's actual native Bash tool and separately through `!`.
63
+ Both actual command outputs showed the same three Claude/hub markers present
64
+ and `CODEX_THREAD_ID` absent. This closes the separate model-shell observation;
65
+ neither command printed environment values.
66
+ - The original harness accepted hub idle too early and stopped both last
67
+ review turns before a native final answer. Native transcripts independently
68
+ show two completed turns in feed-off and five in feed-own, with the last
69
+ assistant message still `tool_use` in each case. The board approvals are real;
70
+ complete final-turn verification and production supervision accounting were
71
+ incomplete for those captures. The harness now requires the current daemon instance, its session,
72
+ the fixture-specific transcript, a final `end_turn` and `turn_duration` after
73
+ the last actual review. Missing measurements remain unknown.
74
+ - The `da2ebbc` run verified the final native message UUID, message id,
75
+ `end_turn`, subsequent `turn_duration`, daemon instance, session and launcher.
76
+ Its complete transcript contains five completed turns and 21 unique usage
77
+ records, totalling 1,451,782 tokens including cached input. The daemon certified
78
+ only one native Stop, however. Its log refused the other completions as an
79
+ unavailable completed transcript message or completion predating the current
80
+ turn; the final refusal occurred before cleanup. A normal review receipt
81
+ remained queued. Actual task/file/final-answer evidence is valid, while full
82
+ runtime accounting and delivery settlement remain incomplete.
83
+ - The `6034dd9` read-only continuation preserved both approved tasks and exact
84
+ files. It completed one native turn with four unique usage records and 261,483
85
+ cached-inclusive tokens, but the matching daemon Stop remained absent. After
86
+ a bounded retry, the Stop handler logged unavailable completion at
87
+ 01:39:04.087 UTC; the native stop summary/duration appeared at 01:39:04.095 UTC.
88
+ With no overlapping operator prompt, this strongly supports a transcript
89
+ visibility barrier while the native hook waits. The strict harness timed out
90
+ incomplete and stopped its owned processes. The final message's thinking/text
91
+ rows carried identical usage counters in these observed transcripts; this
92
+ observation does not replace the final text/duration requirement.
93
+
94
+ ## Plain Claude and Kimi observations (2026-10-09)
95
+
96
+ - Plain Claude 2.1.295, launched with the candidate MCP configuration but no
97
+ development-channel flag, completed one native `hub_status` call with an
98
+ independent daemon Claude status audit. Its only native tool calls were
99
+ ToolSearch and that status call. A unique operator push was bridge-accepted,
100
+ but no native pushed user row or response appeared in the observed
101
+ 20.075-second window. This bounded negative observation is not a universal
102
+ claim about channel support. Actual channel-enabled review/supervision pushes
103
+ were observed separately in the conductor cases. The plain probe and cleanup
104
+ took 56.724 seconds and made no model file, shell, task or settings changes.
105
+ - Real Kimi Code CLI 2.1.1 answered ACP `initialize` and created native session
106
+ `session_17eeb9dc-38d3-4685-b1aa-3db6a89b465c`. Its single prompt failed with
107
+ protocol error `-32000`, HTTP 403, for the managed account's weekly usage
108
+ limit before any requested tool event. No task-list/status result or ordinary
109
+ peer's conductor refusal was obtained. The owning CLI's safe provider list
110
+ showed only `managed:kimi-code`, type `kimi`, four models, OAuth, default
111
+ `kimi-code/k3`. No distinct configured provider was found. The reset time
112
+ remains unknown; no purchase, credential/configuration change or model retry
113
+ was attempted. Owned native processes and the fixture daemon were stopped.
114
+
115
+ ## Release disposition and remaining bounded live probe
116
+
117
+ - On 2026-10-09 the user explicitly approved release 0.12.17 with only the Kimi native new-tool and ordinary-role refusal T0 evidence deferred. Kimi coverage remains unverified. This decision defers no other source/native gate and authorizes no purchase, credentials, provider/configuration change, account retry or authority relaxation.
118
+ - Kimi ACP MCP: after native provider access is available under an authorized account, request the same read-only task list/status and ordinary-role refusal probes in isolated sessions. Verify actual native tool events/results and the daemon reply before marking this prerequisite verified. The existing quota failure proves neither new-tool access nor refusal behavior.
119
+
120
+ ## Verification limits
121
+
122
+ - Codex shell and MCP probes used the root agent's serial resource slot. Claude `!`, model shell, enabled-channel pushes and the bounded plain-session comparison were observed in subsequent serial native cases. Kimi new-tool access remains unverified because its real account rejected the prompt before tools.
123
+ - New unit and fake-adapter tests are prepared but have not been run by this worker. The root agent owns the final immutable-head checks.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "documents": {
4
4
  "README.md": {
5
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
5
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
6
6
  "paths": [
7
7
  "package.json",
8
8
  "src/",
@@ -11,14 +11,14 @@
11
11
  ]
12
12
  },
13
13
  "docs/security.md": {
14
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
14
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
15
15
  "paths": [
16
16
  "src/",
17
17
  "templates/"
18
18
  ]
19
19
  },
20
20
  "docs/operations.md": {
21
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
21
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
22
22
  "paths": [
23
23
  "package.json",
24
24
  "src/",
@@ -30,11 +30,12 @@
30
30
  ".github/workflows/check.yml",
31
31
  "scripts/ci-gates.mjs",
32
32
  "scripts/ci-reuse.mjs",
33
- ".github/workflows/release.yml"
33
+ ".github/workflows/release.yml",
34
+ "scripts/smoke-conductor.ts"
34
35
  ]
35
36
  },
36
37
  "docs/quickstart.md": {
37
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
38
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
38
39
  "paths": [
39
40
  "package.json",
40
41
  "src/",
@@ -42,7 +43,7 @@
42
43
  ]
43
44
  },
44
45
  "docs/agent-notes/adapters.md": {
45
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
46
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
46
47
  "paths": [
47
48
  "src/adapters/",
48
49
  "src/pi/",
@@ -55,7 +56,7 @@
55
56
  ]
56
57
  },
57
58
  "docs/agent-notes/benchmarks.md": {
58
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
59
+ "verifiedAgainst": "8b7d7254f1bb5602ec5d658e7814e236f8fb43e7",
59
60
  "paths": [
60
61
  "scripts/benchmarks/",
61
62
  "test/benchmarks/",
@@ -67,7 +68,7 @@
67
68
  ]
68
69
  },
69
70
  "docs/agent-notes/budget.md": {
70
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
71
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
71
72
  "paths": [
72
73
  "src/hub/budget.ts",
73
74
  "src/cli/statusline-tee.ts",
@@ -79,18 +80,19 @@
79
80
  ]
80
81
  },
81
82
  "docs/agent-notes/bus.md": {
82
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
83
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
83
84
  "paths": [
84
85
  "src/hub/bus.ts",
85
86
  "src/hub/envelope.ts",
86
87
  "src/hub/limits.ts",
87
88
  "src/hub/delivery-journal.ts",
88
89
  "src/hub/inference.ts",
89
- "src/hub/daemon.ts"
90
+ "src/hub/daemon.ts",
91
+ "src/hub/supervision.ts"
90
92
  ]
91
93
  },
92
94
  "docs/agent-notes/daemon.md": {
93
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
95
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
94
96
  "paths": [
95
97
  "src/hub/daemon.ts",
96
98
  "src/hub/control-client.ts",
@@ -107,11 +109,16 @@
107
109
  "src/cli/recovery-package.ts",
108
110
  "src/cli/facts-hook.ts",
109
111
  "src/pi/process-signature.ts",
110
- "src/hub/context-window.ts"
112
+ "src/hub/context-window.ts",
113
+ "src/hub/conductor.ts",
114
+ "src/cli/console.ts",
115
+ "src/cli/console-state.ts",
116
+ "src/cli/identity.ts",
117
+ "src/cli/identity-audit.ts"
111
118
  ]
112
119
  },
113
120
  "docs/agent-notes/local-worker.md": {
114
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
121
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
115
122
  "paths": [
116
123
  "src/adapters/local-worker.ts",
117
124
  "src/local/",
@@ -120,7 +127,7 @@
120
127
  ]
121
128
  },
122
129
  "docs/agent-notes/models.md": {
123
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
130
+ "verifiedAgainst": "8b7d7254f1bb5602ec5d658e7814e236f8fb43e7",
124
131
  "paths": [
125
132
  "src/models/",
126
133
  "src/hub/inference.ts",
@@ -129,7 +136,7 @@
129
136
  ]
130
137
  },
131
138
  "docs/agent-notes/tasks.md": {
132
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
139
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
133
140
  "paths": [
134
141
  "src/hub/tasks.ts",
135
142
  "src/hub/board.ts",
@@ -138,11 +145,13 @@
138
145
  "src/hub/task-sweep.ts",
139
146
  "src/cli/launch.ts",
140
147
  "src/cli/main.ts",
141
- "src/cli/preview.ts"
148
+ "src/cli/preview.ts",
149
+ "src/hub/conductor.ts",
150
+ "src/hub/supervision.ts"
142
151
  ]
143
152
  },
144
153
  "docs/agent-notes/tests.md": {
145
- "verifiedAgainst": "2f6274d007d47aa3db0976c4989021520c03a3ef",
154
+ "verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
146
155
  "paths": [
147
156
  "test/",
148
157
  "scripts/check.sh",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.12.16",
3
+ "version": "0.12.18",
4
4
  "description": "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-hub",
3
- "version": "0.12.16",
3
+ "version": "0.12.18",
4
4
  "description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
5
5
  "author": {
6
6
  "name": "Young Joon Lee",
@@ -15291,8 +15291,8 @@ function projectContext(cwd, env = process.env) {
15291
15291
  function stateDirFor(cwd) {
15292
15292
  return projectContext(cwd).stateDir;
15293
15293
  }
15294
- var PROTOCOL = 14;
15295
- var RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, PROTOCOL];
15294
+ var PROTOCOL = 15;
15295
+ var RECOVERY_SOURCE_PROTOCOLS = [9, 10, 11, 12, 13, 14, PROTOCOL];
15296
15296
  function readControl(stateDir) {
15297
15297
  try {
15298
15298
  const status = JSON.parse(readFileSync2(join2(stateDir, "status.json"), "utf8"));
@@ -15403,7 +15403,7 @@ class ControlClient {
15403
15403
  // package.json
15404
15404
  var package_default = {
15405
15405
  name: "@staix/agent-hub",
15406
- version: "0.12.16",
15406
+ version: "0.12.18",
15407
15407
  description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
15408
15408
  license: "MIT",
15409
15409
  type: "module",
@@ -15503,7 +15503,18 @@ var TASK_TOOLS = [
15503
15503
  tool("hub_remember", "Save a decision, finding, contract or fail to the memory all agents share (claude-mem); the other agents also get it with their next message. A fail is an approach you tried that does not work, and why: the most useful note, it stops the others spending their quota on it. Do not retry what a fail note rules out without new evidence. Conclusions worth recalling, not chatter.", { text: str, title: str, kind: { type: "string", enum: [...NOTE_KINDS] }, task: id }, ["text"])
15504
15504
  ];
15505
15505
  var TASK_TOOL_NAMES = new Set(TASK_TOOLS.map((t) => t.name));
15506
+ var CONDUCTOR_TOOLS = [
15507
+ tool("hub_status", "Inspect public team state, holds, quota windows, task counts and pending approval peer/tool/age. Only the configured conductor may use this tool.", {}),
15508
+ tool("hub_task_show", "Read a task's public view and history. PII tasks remain stubs. Only the conductor may use this tool.", { id }, ["id"]),
15509
+ tool("hub_task_assign", "Move a task to another peer, as the conductor. Requires assign capability when the conductor has an explicit capabilities list.", { id, peer: str }, ["id", "peer"]),
15510
+ tool("hub_task_escalate", "Escalate a task through the normal task flow, as the conductor. Requires assign capability when explicitly listed.", { id }, ["id"]),
15511
+ tool("hub_peer_start", "Start local, kimi or headless pi. Claude, Codex and Pi TUI requests return a command for the person to run, without launching a terminal.", { peer: { type: "string", enum: ["local", "kimi", "pi", "claude", "codex"] }, mode: { type: "string", enum: ["headless", "tui"] } }, ["peer"]),
15512
+ tool("hub_peer_hold", "Hold a peer's deliveries as the conductor. This hold is separate from the person's hold and budget pauses.", { peer: str }, ["peer"]),
15513
+ tool("hub_peer_release", "Release only the conductor hold you placed. Never lifts a person's hold or a budget pause.", { peer: str }, ["peer"])
15514
+ ];
15515
+ var CONDUCTOR_TOOL_NAMES = new Set(CONDUCTOR_TOOLS.map((t) => t.name));
15506
15516
  var ROLE_TEXT = {
15517
+ conductor: "conductor: plan and split work into tasks with owners, watch the team with hub_status, move stalled work, and ensure every task is reviewed (review it yourself only if you also hold reviewer). Report results and open decisions to the person. Do not implement tasks you handed out. Never ask a peer to answer an approval; ask the person for human-only actions. You may release only holds you placed, never human holds or budget pauses.",
15507
15518
  planner: "planner: break work into tasks with hub_task_propose (one outcome each, the right class, paths in refs, and after: [ids] for work that must wait for other tasks) instead of doing everything yourself.",
15508
15519
  implementer: "implementer: accept tasks assigned to you, do them, and finish with hub_task_done (summary: what changed, why, and the check you ran with its result; refs). Decline what you cannot do. Before starting work nobody assigned you, claim it with hub_task_propose naming yourself as owner, with the paths in refs. With a claim or an accept, give a plan: the files, symbols and signatures you will change and where new code goes.",
15509
15520
  verifier: "verifier: run the checks a task names and report what passed and what did not in hub_task_done.",
@@ -15690,7 +15701,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
15690
15701
  inputSchema: { type: "object", properties: { delivery_id: { type: "string" }, delivery_generation: { type: "string" } }, required: ["delivery_id", "delivery_generation"], additionalProperties: false }
15691
15702
  }
15692
15703
  ],
15693
- ...TASK_TOOLS
15704
+ ...TASK_TOOLS,
15705
+ ...CONDUCTOR_TOOLS
15694
15706
  ]
15695
15707
  }));
15696
15708
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
@@ -15720,7 +15732,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
15720
15732
  const sent = `sent to: ${res.targets.join(", ") || "(no other peers attached)"}`;
15721
15733
  return text(typeof res.notice === "string" ? `${sent}; ${res.notice}` : sent);
15722
15734
  }
15723
- if (TASK_TOOL_NAMES.has(name)) {
15735
+ if (TASK_TOOL_NAMES.has(name) || CONDUCTOR_TOOL_NAMES.has(name)) {
15724
15736
  if (!hub)
15725
15737
  return text(offline());
15726
15738
  const res = await hub.request({ t: "task", op: name, args: args ?? {} });
@@ -2,7 +2,7 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
2
  import { createInterface } from "node:readline";
3
3
  import { renderDigest, replyAudience, replyParent, type Envelope, type PeerId } from "../hub/envelope.ts";
4
4
  import { BasePeer } from "../hub/peers.ts";
5
- import { childEnv, stopOwnedProcess, trackGroup } from "../hub/child-process.ts";
5
+ import { peerChildEnv, stopOwnedProcess, trackGroup } from "../hub/child-process.ts";
6
6
 
7
7
  export interface PermissionOption {
8
8
  optionId: string;
@@ -135,7 +135,7 @@ export class AcpPeer extends BasePeer {
135
135
  async start(): Promise<void> {
136
136
  const [bin, ...args] = this.opts.cmd;
137
137
  // Its own process group, stopped as a whole (#115, as Codex's in #113): an agent CLI may be a launcher with a native child.
138
- const proc = spawn(bin!, args, { cwd: this.opts.cwd, env: childEnv({ ...process.env, ...(this.opts.env ?? {}) }), stdio: ["pipe", "pipe", "pipe"], detached: true });
138
+ const proc = spawn(bin!, args, { cwd: this.opts.cwd, env: peerChildEnv(this.id, { ...process.env, ...(this.opts.env ?? {}) }), stdio: ["pipe", "pipe", "pipe"], detached: true });
139
139
  this.proc = proc;
140
140
  trackGroup(proc);
141
141
  proc.on("error", (e) => this.down(`spawn failed: ${e.message}`));
@@ -8,7 +8,7 @@ import { readFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import { ControlClient, stateDirFor } from "../hub/control-client.ts";
10
10
  import { VERSION } from "../version.ts";
11
- import { DEFAULT_ROLES, roleContract, TASK_TOOL_NAMES, TASK_TOOLS } from "../hub/hub-tools.ts";
11
+ import { CONDUCTOR_TOOLS, CONDUCTOR_TOOL_NAMES, DEFAULT_ROLES, roleContract, TASK_TOOL_NAMES, TASK_TOOLS } from "../hub/hub-tools.ts";
12
12
  import { frame, replyParent, sanitize, HUB_MESSAGE_INSTRUCTION, type Envelope } from "../hub/envelope.ts";
13
13
 
14
14
  // A native session is pinned at launch. Unlike a new CLI invocation after `cd`,
@@ -201,6 +201,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
201
201
  },
202
202
  ]),
203
203
  ...TASK_TOOLS,
204
+ ...CONDUCTOR_TOOLS,
204
205
  ],
205
206
  }));
206
207
 
@@ -225,7 +226,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
225
226
  const sent = `sent to: ${res.targets.join(", ") || "(no other peers attached)"}`;
226
227
  return text(typeof res.notice === "string" ? `${sent}; ${res.notice}` : sent);
227
228
  }
228
- if (TASK_TOOL_NAMES.has(name)) {
229
+ if (TASK_TOOL_NAMES.has(name) || CONDUCTOR_TOOL_NAMES.has(name)) {
229
230
  if (!hub) return text(offline());
230
231
  const res = await hub.request({ t: "task", op: name, args: args ?? {} });
231
232
  return text(res.ok ? res.text : `error: ${res.error}`);
@@ -3,7 +3,7 @@ import type { Server, ServerWebSocket } from "bun";
3
3
  import { renderDigest, replyAudience, replyParent, type Envelope, type PeerId } from "../hub/envelope.ts";
4
4
  import { BasePeer } from "../hub/peers.ts";
5
5
  import { codexContext, type ContextReading } from "../hub/context-window.ts";
6
- import { childEnv, stopOwnedProcess, trackGroup } from "../hub/child-process.ts";
6
+ import { peerChildEnv, stopOwnedProcess, trackGroup } from "../hub/child-process.ts";
7
7
 
8
8
  export interface CodexOptions {
9
9
  /** Port the TUI attaches to: `codex --enable tui_app_server --remote ws://127.0.0.1:<proxyPort>`. */
@@ -305,9 +305,10 @@ export class CodexPeer extends BasePeer {
305
305
  throw new Error(`port ${port} already answers /healthz: an app-server the hub does not own is running (orphan from a crashed hub?)`);
306
306
  }
307
307
  let gone = "";
308
+ const env = peerChildEnv("codex", { ...process.env, ...(this.opts.env ?? {}) });
308
309
  this.proc = spawn(this.opts.bin ?? "codex", ["app-server", "--listen", `ws://127.0.0.1:${port}`, ...(this.opts.extraArgs ?? [])], {
309
310
  cwd: this.opts.cwd,
310
- env: childEnv({ ...process.env, ...(this.opts.env ?? {}) }),
311
+ env,
311
312
  stdio: ["ignore", "ignore", "pipe"],
312
313
  detached: true, // its own process group, stopped as a whole (#113): `codex` is a launcher with a native child
313
314
  });