@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.
- package/CHANGELOG.md +14 -0
- package/README.md +4 -4
- package/docs/agent-notes/adapters.md +1 -0
- package/docs/agent-notes/bus.md +2 -0
- package/docs/agent-notes/tasks.md +3 -1
- package/docs/agent-notes/tests.md +1 -0
- package/docs/events.md +37 -3
- package/docs/operations.md +149 -10
- package/docs/quickstart.md +3 -3
- package/docs/security.md +28 -1
- package/docs/smoke.md +93 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +176 -1
- package/docs/verification/2026-10-09-agent-shell-t0.md +123 -0
- package/docs/verified.json +26 -17
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +17 -5
- package/src/adapters/acp.ts +2 -2
- package/src/adapters/claude-channel.ts +3 -2
- package/src/adapters/codex-appserver.ts +3 -2
- package/src/adapters/local-worker.ts +16 -12
- package/src/cli/console-state.ts +279 -0
- package/src/cli/console.ts +224 -0
- package/src/cli/facts-hook.ts +8 -1
- package/src/cli/identity-audit.ts +66 -0
- package/src/cli/identity.ts +54 -0
- package/src/cli/launch.ts +15 -2
- package/src/cli/main.ts +75 -32
- package/src/cli/tail-render.ts +17 -0
- package/src/cli/upgrade-runtime.ts +1 -1
- package/src/hub/attribution.ts +20 -0
- package/src/hub/board.ts +6 -2
- package/src/hub/bus.ts +88 -10
- package/src/hub/child-process.ts +11 -0
- package/src/hub/conductor.ts +196 -0
- package/src/hub/control-client.ts +3 -3
- package/src/hub/daemon.ts +388 -51
- package/src/hub/envelope.ts +1 -1
- package/src/hub/events.ts +10 -4
- package/src/hub/hub-tools.ts +13 -0
- package/src/hub/report.ts +162 -4
- package/src/hub/supervision.ts +152 -0
- package/src/hub/tasks.ts +30 -15
- package/src/hub/usage.ts +37 -2
- 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
|
-
|
|
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.
|
package/docs/verified.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"documents": {
|
|
4
4
|
"README.md": {
|
|
5
|
-
"verifiedAgainst": "
|
|
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": "
|
|
14
|
+
"verifiedAgainst": "4b44b92dd2dc4b5515b6e87627fe4bdfcd6742c3",
|
|
15
15
|
"paths": [
|
|
16
16
|
"src/",
|
|
17
17
|
"templates/"
|
|
18
18
|
]
|
|
19
19
|
},
|
|
20
20
|
"docs/operations.md": {
|
|
21
|
-
"verifiedAgainst": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "agent-hub",
|
|
3
|
-
"version": "0.12.
|
|
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 =
|
|
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.
|
|
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 ?? {} });
|
package/src/adapters/acp.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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 {
|
|
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
|
|
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
|
});
|