@staix/agent-hub 0.12.21 → 0.12.23
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 +29 -0
- package/README.md +27 -12
- package/docs/agent-notes/adapters.md +15 -7
- package/docs/agent-notes/benchmarks.md +4 -1
- package/docs/agent-notes/budget.md +4 -0
- package/docs/agent-notes/bus.md +2 -1
- package/docs/agent-notes/daemon.md +12 -1
- package/docs/agent-notes/local-worker.md +4 -1
- package/docs/agent-notes/tasks.md +2 -1
- package/docs/agent-notes/tests.md +4 -0
- package/docs/events.md +17 -4
- package/docs/operations.md +478 -41
- package/docs/quickstart.md +14 -6
- package/docs/security.md +23 -11
- package/docs/smoke.md +133 -7
- package/docs/specs/2026-09-19-agent-hub-design.md +115 -15
- package/docs/specs/2026-09-20-upgrade-recovery-design.md +84 -8
- package/docs/verification/2026-10-10-cli-output-phase1.md +309 -0
- package/docs/verification/2026-10-10-cli-output-phase2.md +672 -0
- package/docs/verification/2026-10-11-0.12.23.md +90 -0
- package/docs/verified.json +32 -17
- package/package.json +1 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +14 -3
- package/src/adapters/acp-quota.ts +13 -0
- package/src/adapters/acp.ts +105 -17
- package/src/adapters/codex-appserver.ts +70 -35
- package/src/adapters/local-worker.ts +44 -10
- package/src/adapters/pi.ts +158 -19
- package/src/cli/bench.ts +2 -1
- package/src/cli/console-state.ts +28 -22
- package/src/cli/console.ts +20 -10
- package/src/cli/help.ts +25 -19
- package/src/cli/identity.ts +3 -3
- package/src/cli/init.ts +1 -1
- package/src/cli/launch.ts +182 -7
- package/src/cli/main.ts +409 -177
- package/src/cli/output.ts +363 -0
- package/src/cli/preview.ts +2 -1
- package/src/cli/recovery-package.ts +17 -0
- package/src/cli/reset.ts +3 -0
- package/src/cli/status-lines.ts +0 -28
- package/src/cli/terminal-recovery.ts +27 -1
- package/src/cli/upgrade-interactive.ts +297 -0
- package/src/cli/upgrade-runtime.ts +80 -9
- package/src/cli/upgrade.ts +50 -15
- package/src/hub/board.ts +2 -0
- package/src/hub/budget.ts +39 -7
- package/src/hub/bus.ts +15 -14
- package/src/hub/child-process.ts +11 -0
- package/src/hub/conductor.ts +23 -11
- package/src/hub/config-trust.ts +16 -5
- package/src/hub/conflicts.ts +2 -0
- package/src/hub/context-window.ts +8 -1
- package/src/hub/crash.ts +2 -1
- package/src/hub/daemon.ts +483 -83
- package/src/hub/db-timeout.ts +3 -0
- package/src/hub/delivery-journal.ts +2 -1
- package/src/hub/envelope.ts +1 -1
- package/src/hub/events.ts +4 -1
- package/src/hub/execution-budget.ts +2 -0
- package/src/hub/facts.ts +5 -4
- package/src/hub/git.ts +32 -0
- package/src/hub/hub-tools.ts +1 -1
- package/src/hub/lifecycle.ts +10 -4
- package/src/hub/manager.ts +18 -5
- package/src/hub/peers.ts +7 -5
- package/src/hub/permission-mode.ts +14 -0
- package/src/hub/project.ts +2 -1
- package/src/hub/recovery-error.ts +31 -0
- package/src/hub/recovery-store.ts +43 -4
- package/src/hub/registry.ts +2 -1
- package/src/hub/routing.ts +114 -13
- package/src/hub/settings.ts +392 -0
- package/src/hub/snapshots.ts +4 -2
- package/src/hub/start-mode.ts +48 -0
- package/src/hub/tasks.ts +158 -43
- package/src/hub/terminal-open.ts +64 -0
- package/src/hub/ui.ts +25 -13
- package/src/local/deny.ts +71 -2
- package/src/local/sandbox.ts +47 -6
- package/src/local/tools.ts +25 -6
- package/src/memory/recall.ts +2 -2
- package/src/models/mlx.ts +2 -1
- package/src/pi/extension.ts +43 -20
- package/src/pi/tool-receipts.ts +19 -2
- package/src/ui/index.html +172 -21
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Agent-hub 0.12.23 verification
|
|
2
|
+
|
|
3
|
+
Release preparation for 0.12.23, performed on 2026-10-11 with Bun 1.4.2 on macOS. The release changes no file under `src/`, `test/` or `scripts/` relative to main at `94b489d`: it carries the version, the rebuilt plugin bundle, the changelog section, the document review below and the stamps in `docs/verified.json`. Production application is verified separately after publication.
|
|
4
|
+
|
|
5
|
+
## What the release holds
|
|
6
|
+
|
|
7
|
+
Seventeen pull requests merged since `v0.12.22` (`deeae76..94b489d`): settings and start modes (#269, pull requests #290 and #292), the assignment delivery fixes (#276, #297), the sandbox follow-ups and the hub's one git helper (#281), Kimi's context reading (#285 phase 1), the Codex steer and dashboard start fixes (#296, #298), readable command output (#284, pull requests #291, #299 and #314), the SQLite busy timeouts and the named retryable restart outcome (#303, pull requests #305 and #313), the ACP cancellation wait (#304), the Kimi quota refusal pause and handoff (#306) the macOS timing fixtures (#287), and the console's Left and Right tab keys, Events colours and unrepeated context readings (#309, pull request #312).
|
|
8
|
+
|
|
9
|
+
Control protocol stays 16 (`PROTOCOL` in `src/hub/control-client.ts`) and the events schema stays 1 (`EVENTS_SCHEMA` in `src/hub/events.ts`). The new console-role requests (`settings_get`, `settings_set`, `settings_undo`, `settings_preview`, `peer_start`) are answered as unknown by an older hub, and the new fields are optional.
|
|
10
|
+
|
|
11
|
+
## Changelog
|
|
12
|
+
|
|
13
|
+
Sixteen of the seventeen pull requests have their entry in the 0.12.23 section; #291 and #299 share the #284 entry, and #305 and #313 each have their own #303 entry. #314, merged while this release was prepared, corrects how the unreleased #284 output splits a file name that holds combining marks or joined emoji; it is a clause of the #284 entry. The #309 line arrived under Unreleased with the merge of #312 and was moved into the section. No entry describes work that is not merged. The section heading carries no date, as no earlier section does. A closing line records the protocol and schema.
|
|
14
|
+
|
|
15
|
+
## Document review
|
|
16
|
+
|
|
17
|
+
`node scripts/check-docs.mjs` listed 13 stale documents at `bce5d00`. For each, the source commits since its stamp were read as the changed lines of `src/`, `scripts/`, `templates/`, `package.json` and `.github/`, with the commit messages, and the document was read against the code at `bce5d00`. All 13 are restamped; none is deferred. The corrections are in `88d4920`.
|
|
18
|
+
|
|
19
|
+
`origin/main` then moved by #314 (`76f1695`, `src/cli/output.ts` and its test) and was merged into the branch with a merge commit, `002abf4`. Its change was read against the five documents it made stale again: none describes the split at that level and nothing needed a correction.
|
|
20
|
+
|
|
21
|
+
`origin/main` moved once more by #312 (`94b489d`: `src/cli/console-state.ts`, `src/cli/console.ts` and the context line of the tail in `src/hub/daemon.ts`), merged in `e3313dd`. It made ten documents stale. Its own text in the operations guide ("Operator console and panels", "Native context readings") was read against the reducer, the paste branch and the tail's stamp and is accurate; README, the quickstart, the security guide and the six agent notes say nothing the change touches. No correction was needed and the ten are stamped at that merge commit.
|
|
22
|
+
|
|
23
|
+
| Document | Source commits | Changed | Stamp |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| `README.md` | 16 | yes | `e3313dd` |
|
|
26
|
+
| `docs/security.md` | 16 | yes | `e3313dd` |
|
|
27
|
+
| `docs/operations.md` | 17 | yes | `e3313dd` |
|
|
28
|
+
| `docs/quickstart.md` | 16 | yes | `e3313dd` |
|
|
29
|
+
| `docs/agent-notes/adapters.md` | 12 | scope line only | `e3313dd` |
|
|
30
|
+
| `docs/agent-notes/benchmarks.md` | 5 | no | `88d4920` |
|
|
31
|
+
| `docs/agent-notes/budget.md` | 9 | yes | `e3313dd` |
|
|
32
|
+
| `docs/agent-notes/bus.md` | 9 | no | `e3313dd` |
|
|
33
|
+
| `docs/agent-notes/daemon.md` | 12 | yes | `e3313dd` |
|
|
34
|
+
| `docs/agent-notes/local-worker.md` | 2 | scope line only | `88d4920` |
|
|
35
|
+
| `docs/agent-notes/models.md` | 1 | no | `88d4920` |
|
|
36
|
+
| `docs/agent-notes/tasks.md` | 12 | no | `e3313dd` |
|
|
37
|
+
| `docs/agent-notes/tests.md` | 17 | no | `e3313dd` |
|
|
38
|
+
|
|
39
|
+
README, the operations guide and the quickstart cover `package.json`, so their stamp has to follow the version commit (`62318e1`); the merge commit `e3313dd` does. Both stamps (`e3313dd` and `88d4920`) name commits of the release branch: the release pull request is merged with a merge commit, never squashed or rebased.
|
|
40
|
+
|
|
41
|
+
### What changed
|
|
42
|
+
|
|
43
|
+
- `README.md`, `docs/quickstart.md`: the dashboard also starts a peer in its start mode and shows the settings, and what an ordinary session may change (#269). The unified dashboard's Start opens a TUI only through a terminal provider; both pages said TUIs are launched from a terminal only.
|
|
44
|
+
- `docs/security.md`:
|
|
45
|
+
- `terminal.open` is in `MACHINE_LOCAL` (`src/hub/config-trust.ts`) and was missing from the list of machine-local fields under "Trust boundaries".
|
|
46
|
+
- "A conductor may ... start supported headless peers" contradicted the page's own start-mode paragraph: `conductorStart` starts any startable peer in the mode a person set.
|
|
47
|
+
- The list of what the browser receives omitted the settings rows and the start plans (`settingsSnapshot`, `startPlans` in `src/hub/daemon.ts`).
|
|
48
|
+
- `docs/operations.md`: the authority paragraph of "Settings" omitted that an ordinary session may set a start mode back to `tui` (the registry's `floor`, `settingRefusal` in `src/hub/settings.ts`). The version, the protocol-16 source lists and the upgrade commands follow the release.
|
|
49
|
+
- `docs/agent-notes/daemon.md`: two rules the changes introduced and no note held. Every `new Database(` under `src/` is followed by its own named `busy_timeout`, enforced by `test/db-busy-timeout.test.ts` (#303). The manager's `start_peer` deadline is matched by the exact text `ControlClient.request` resolves with, and the reopen guard and the retry hold share `PEER_START_RETRY_MS` (#298).
|
|
50
|
+
- `docs/agent-notes/budget.md`: Kimi's occupancy is a context reading only, and a gate crossing sends a checkpoint request to Claude and Codex only (#285); a quota resume notice is published before the unread notices it replaces are withdrawn (#306).
|
|
51
|
+
- `AGENTS.md` and the scope lines of the daemon, adapters and local-worker notes name the files this release added (`start-mode.ts`, `terminal-open.ts`, `recovery-error.ts`, `db-timeout.ts`, `git.ts`), and `docs/verified.json` covers them, with `ui.ts` and `settings.ts` for the daemon note.
|
|
52
|
+
|
|
53
|
+
### What was checked and found accurate
|
|
54
|
+
|
|
55
|
+
- `docs/operations.md`: "Command output" against `src/cli/help.ts` and the flag handling in `src/cli/main.ts`; "Settings" and "Start modes" against `src/hub/settings.ts`, `src/hub/start-mode.ts`, `src/hub/terminal-open.ts` and `hubStart`; the assignment delivery paragraph against `assignOwner` and `undeliveredNotice`; the Kimi quota paragraphs against `src/adapters/acp-quota.ts`, `Budget.report` and `Bus.failed`; the `database_busy` paragraph against `src/hub/recovery-error.ts` and `runRecovery`; the Kimi context reading against `acpContext` and the adapter's idle acceptance; the dashboard Start timings against `src/hub/manager.ts`. The ACP cancellation wait (#304) needs no operator text: the daemon's bus always has a journal, and the wait applies to a bus without one.
|
|
56
|
+
- `docs/security.md`: the settings and start-mode sections against the registry, `NEVER_EDITABLE`, the dashboard session window (`SETTINGS_MS`) and `personEnv`; the sandbox paragraph against `hubWriteRegexes` and `moduleGitDirs`.
|
|
57
|
+
- `adapters.md`: the steer rule (#296), the occupancy rule (#285), the cancellation wait (#304), the quota contract (#306), the `stop(reason)` return of journal-less work and the terminal environment rule (#269), each against `src/adapters/acp.ts`, `src/adapters/codex-appserver.ts` and `src/hub/child-process.ts`.
|
|
58
|
+
- `tasks.md`: the `assignOwner` rule against `src/hub/tasks.ts` at the head (#276, #297), and the overlay rule against `checkOverlay`.
|
|
59
|
+
- `bus.md`: the quota flag changes `Bus.failed` only for a paused peer and is stated in `budget.md`; nothing in the note is contradicted.
|
|
60
|
+
- `local-worker.md`: the submodule rules and the git helper against `src/local/deny.ts`, `src/local/sandbox.ts` and `src/hub/git.ts`.
|
|
61
|
+
- `benchmarks.md`: the two #304 paragraphs against the adapter's cancellation deadline. `models.md`: its one commit renames a busy-timeout constant.
|
|
62
|
+
- `tests.md`: against the workflow change, the ACP fake's held trailing updates and the harness of the new test files (`settings`, `start-modes`, `recovery-busy`, `hub-git`, `output-cli`).
|
|
63
|
+
|
|
64
|
+
### What was not checked
|
|
65
|
+
|
|
66
|
+
- The 5,600 changed lines under `test/` were not read assertion by assertion: for `tests.md` the fakes, the helpers and the opening of each new test file were read.
|
|
67
|
+
- `src/ui/index.html` was read for the start controls only; the Settings section's rendering was not compared with the guides line by line.
|
|
68
|
+
- The table-rendering code of `src/cli/output.ts` was not compared with the wrapping and banding sentences of "Command output"; `docs/verification/2026-10-10-cli-output-phase1.md` and `-phase2.md` record that check.
|
|
69
|
+
- Sections of the operations guide that none of the merged commits touch in substance were not re-read in full.
|
|
70
|
+
- `docs/events.md`, `docs/smoke.md` and the design spec are outside `docs/verified.json` and were not reviewed.
|
|
71
|
+
|
|
72
|
+
## Checks
|
|
73
|
+
|
|
74
|
+
All at the head of the branch after the merge of #312 and the last restamp, unless a line says otherwise.
|
|
75
|
+
|
|
76
|
+
- `node scripts/check-docs.mjs`: `docs: OK (0 stale documents)`.
|
|
77
|
+
- `bun x tsc --noEmit`: clean (exit 0).
|
|
78
|
+
- `bun scripts/build.mjs --check`: the bundle and the plugin manifest are fresh (exit 0).
|
|
79
|
+
- `scripts/check.sh`, admitted through `dot admit` and run without the agent session's environment markers, on the tree of the commit that holds this restamp (the merge `e3313dd` plus documents only): 1972 tests across 132 files in 311 s, 1972 pass, 0 fail, 26,811 expectations, no daemon leak; `check: OK`. This is the first full run on #312 and #314 together. An earlier run at `7ebd860`, before either merge, had 1953 pass and 0 fail.
|
|
80
|
+
- `git diff origin/main...HEAD -- src test scripts` is empty.
|
|
81
|
+
- CI at `e80c7af` (run 38104252746), first attempt: `check` on macOS (1972 pass, 0 fail, `check: OK`), `check` on Ubuntu (1941 pass, 31 skips, 0 fail, `check: OK`) and `seeded guards` (`seeded: OK (6 sequential guards, 13.237 s)`). The commit after `e80c7af` adds only this line; its own run is recorded on the pull request.
|
|
82
|
+
|
|
83
|
+
## Not proven live (human-only)
|
|
84
|
+
|
|
85
|
+
No live leg was run for this release. `docs/smoke.md` lists what stays unverified against real agents, among it: the settings leg and the start-mode legs (#269), Kimi 2.1.1's `usage_update` arrival through the hub (#285), the native measurement owed for #304, and the interactive upgrade legs (#272). An upgrade of a running 0.12.22 hub to this release has not been run.
|
|
86
|
+
|
|
87
|
+
## Left for the owner
|
|
88
|
+
|
|
89
|
+
- `test/upgrade-cli.test.ts` gained `withRestartDiagnostics` and the `AHUB_RESTART_DIAGNOSTICS_DIR` variable CI sets (#303). No note says a new installed-layout restart fixture should use it; whether that is a rule for `tests.md` is undecided.
|
|
90
|
+
- `docs/operations.md` still says "From an installed CLI older than 0.12.22, start it once by hand" and titles the start-mode migration "from 0.12.22 and earlier"; both are historical and were left.
|
package/docs/verified.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"documents": {
|
|
4
4
|
"README.md": {
|
|
5
|
-
"verifiedAgainst": "
|
|
5
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
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": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
15
15
|
"paths": [
|
|
16
16
|
"src/",
|
|
17
17
|
"templates/"
|
|
18
18
|
]
|
|
19
19
|
},
|
|
20
20
|
"docs/operations.md": {
|
|
21
|
-
"verifiedAgainst": "
|
|
21
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
22
22
|
"paths": [
|
|
23
23
|
"package.json",
|
|
24
24
|
"src/",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"docs/quickstart.md": {
|
|
38
|
-
"verifiedAgainst": "
|
|
38
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
39
39
|
"paths": [
|
|
40
40
|
"package.json",
|
|
41
41
|
"src/",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
45
|
"docs/agent-notes/adapters.md": {
|
|
46
|
-
"verifiedAgainst": "
|
|
46
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
47
47
|
"paths": [
|
|
48
48
|
"src/adapters/",
|
|
49
49
|
"src/pi/",
|
|
@@ -55,11 +55,14 @@
|
|
|
55
55
|
"src/hub/daemon.ts",
|
|
56
56
|
"src/hub/permission-mode.ts",
|
|
57
57
|
"src/hub/tool-refusal.ts",
|
|
58
|
-
"src/cli/identity.ts"
|
|
58
|
+
"src/cli/identity.ts",
|
|
59
|
+
"src/local/tools.ts",
|
|
60
|
+
"src/hub/start-mode.ts",
|
|
61
|
+
"src/hub/terminal-open.ts"
|
|
59
62
|
]
|
|
60
63
|
},
|
|
61
64
|
"docs/agent-notes/benchmarks.md": {
|
|
62
|
-
"verifiedAgainst": "
|
|
65
|
+
"verifiedAgainst": "88d49207d57a43e8beed8dbf95ad4a0283495b43",
|
|
63
66
|
"paths": [
|
|
64
67
|
"scripts/benchmarks/",
|
|
65
68
|
"test/benchmarks/",
|
|
@@ -71,7 +74,7 @@
|
|
|
71
74
|
]
|
|
72
75
|
},
|
|
73
76
|
"docs/agent-notes/budget.md": {
|
|
74
|
-
"verifiedAgainst": "
|
|
77
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
75
78
|
"paths": [
|
|
76
79
|
"src/hub/budget.ts",
|
|
77
80
|
"src/cli/statusline-tee.ts",
|
|
@@ -83,7 +86,7 @@
|
|
|
83
86
|
]
|
|
84
87
|
},
|
|
85
88
|
"docs/agent-notes/bus.md": {
|
|
86
|
-
"verifiedAgainst": "
|
|
89
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
87
90
|
"paths": [
|
|
88
91
|
"src/hub/bus.ts",
|
|
89
92
|
"src/hub/envelope.ts",
|
|
@@ -95,7 +98,7 @@
|
|
|
95
98
|
]
|
|
96
99
|
},
|
|
97
100
|
"docs/agent-notes/daemon.md": {
|
|
98
|
-
"verifiedAgainst": "
|
|
101
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
99
102
|
"paths": [
|
|
100
103
|
"src/hub/daemon.ts",
|
|
101
104
|
"src/hub/control-client.ts",
|
|
@@ -118,21 +121,32 @@
|
|
|
118
121
|
"src/cli/console-state.ts",
|
|
119
122
|
"src/cli/identity.ts",
|
|
120
123
|
"src/cli/identity-audit.ts",
|
|
121
|
-
"src/hub/registry.ts"
|
|
124
|
+
"src/hub/registry.ts",
|
|
125
|
+
"src/cli/main.ts",
|
|
126
|
+
"src/cli/launch.ts",
|
|
127
|
+
"src/hub/child-process.ts",
|
|
128
|
+
"src/hub/bus.ts",
|
|
129
|
+
"src/hub/ui.ts",
|
|
130
|
+
"src/hub/settings.ts",
|
|
131
|
+
"src/hub/start-mode.ts",
|
|
132
|
+
"src/hub/terminal-open.ts",
|
|
133
|
+
"src/hub/recovery-error.ts",
|
|
134
|
+
"src/hub/db-timeout.ts"
|
|
122
135
|
]
|
|
123
136
|
},
|
|
124
137
|
"docs/agent-notes/local-worker.md": {
|
|
125
|
-
"verifiedAgainst": "
|
|
138
|
+
"verifiedAgainst": "88d49207d57a43e8beed8dbf95ad4a0283495b43",
|
|
126
139
|
"paths": [
|
|
127
140
|
"src/adapters/local-worker.ts",
|
|
128
141
|
"src/local/",
|
|
129
142
|
"src/memory/capture.ts",
|
|
130
143
|
"src/hub/facts.ts",
|
|
131
|
-
"src/hub/permission-mode.ts"
|
|
144
|
+
"src/hub/permission-mode.ts",
|
|
145
|
+
"src/hub/git.ts"
|
|
132
146
|
]
|
|
133
147
|
},
|
|
134
148
|
"docs/agent-notes/models.md": {
|
|
135
|
-
"verifiedAgainst": "
|
|
149
|
+
"verifiedAgainst": "88d49207d57a43e8beed8dbf95ad4a0283495b43",
|
|
136
150
|
"paths": [
|
|
137
151
|
"src/models/",
|
|
138
152
|
"src/hub/inference.ts",
|
|
@@ -141,7 +155,7 @@
|
|
|
141
155
|
]
|
|
142
156
|
},
|
|
143
157
|
"docs/agent-notes/tasks.md": {
|
|
144
|
-
"verifiedAgainst": "
|
|
158
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
145
159
|
"paths": [
|
|
146
160
|
"src/hub/tasks.ts",
|
|
147
161
|
"src/hub/board.ts",
|
|
@@ -158,7 +172,7 @@
|
|
|
158
172
|
]
|
|
159
173
|
},
|
|
160
174
|
"docs/agent-notes/tests.md": {
|
|
161
|
-
"verifiedAgainst": "
|
|
175
|
+
"verifiedAgainst": "e3313ddb1673edc91fb567793e42723d441e53f2",
|
|
162
176
|
"paths": [
|
|
163
177
|
"test/",
|
|
164
178
|
"scripts/check.sh",
|
|
@@ -172,7 +186,8 @@
|
|
|
172
186
|
"src/hub/child-process.ts",
|
|
173
187
|
"src/switchyard/sidecar.ts",
|
|
174
188
|
"src/local/sandbox.ts",
|
|
175
|
-
"src/models/mlx.ts"
|
|
189
|
+
"src/models/mlx.ts",
|
|
190
|
+
"src/cli/upgrade-interactive.ts"
|
|
176
191
|
]
|
|
177
192
|
}
|
|
178
193
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-hub",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.23",
|
|
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",
|
|
@@ -15215,7 +15215,18 @@ import { join as join2 } from "path";
|
|
|
15215
15215
|
// src/hub/project.ts
|
|
15216
15216
|
import { existsSync, lstatSync, readFileSync, realpathSync } from "fs";
|
|
15217
15217
|
import { basename, dirname, isAbsolute, join, resolve } from "path";
|
|
15218
|
+
|
|
15219
|
+
// src/hub/git.ts
|
|
15218
15220
|
import { spawnSync } from "child_process";
|
|
15221
|
+
var SAFE_CONFIG = ["core.fsmonitor=false", "core.hooksPath=/dev/null", "diff.external=", "core.pager=cat", "credential.helper=", "core.askpass="];
|
|
15222
|
+
var hubGitArgv = (args) => ["git", "--no-pager", ...SAFE_CONFIG.flatMap((c) => ["-c", c]), ...args];
|
|
15223
|
+
var hubGitEnv = (env = process.env) => ({ ...env, GIT_TERMINAL_PROMPT: "0" });
|
|
15224
|
+
function hubGitSync(args, options) {
|
|
15225
|
+
const argv = hubGitArgv(args);
|
|
15226
|
+
return spawnSync(argv[0], argv.slice(1), { ...options, env: hubGitEnv(options.env ?? process.env) });
|
|
15227
|
+
}
|
|
15228
|
+
|
|
15229
|
+
// src/hub/project.ts
|
|
15219
15230
|
var canonical = (path) => {
|
|
15220
15231
|
const absolute = resolve(path);
|
|
15221
15232
|
try {
|
|
@@ -15226,7 +15237,7 @@ var canonical = (path) => {
|
|
|
15226
15237
|
}
|
|
15227
15238
|
};
|
|
15228
15239
|
function gitRoot(dir) {
|
|
15229
|
-
const result =
|
|
15240
|
+
const result = hubGitSync(["-C", dir, "rev-parse", "--show-toplevel"], { encoding: "utf8" });
|
|
15230
15241
|
if (result.status !== 0)
|
|
15231
15242
|
return;
|
|
15232
15243
|
const root = result.stdout.trim();
|
|
@@ -15404,7 +15415,7 @@ class ControlClient {
|
|
|
15404
15415
|
// package.json
|
|
15405
15416
|
var package_default = {
|
|
15406
15417
|
name: "@staix/agent-hub",
|
|
15407
|
-
version: "0.12.
|
|
15418
|
+
version: "0.12.23",
|
|
15408
15419
|
description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
|
|
15409
15420
|
license: "MIT",
|
|
15410
15421
|
type: "module",
|
|
@@ -15509,7 +15520,7 @@ var CONDUCTOR_TOOLS = [
|
|
|
15509
15520
|
tool("hub_task_show", "Read a task's public view and history, its done summary and check result included. PII tasks remain stubs. Only the conductor and the task's current owner and reviewer may use this tool.", { id }, ["id"]),
|
|
15510
15521
|
tool("hub_task_assign", "Move a task to another peer, as the conductor, or redirect a task you proposed while it is proposed and nobody accepted it. A task that still waits gets the peer as its reserved owner. When you have an explicit capabilities list, the conductor needs assign capability for any assign, the proposer only to hand the task to another peer.", { id, peer: str }, ["id", "peer"]),
|
|
15511
15522
|
tool("hub_task_escalate", "Escalate a task through the normal task flow, as the conductor. Requires assign capability when explicitly listed.", { id }, ["id"]),
|
|
15512
|
-
tool("hub_peer_start", "Start local, kimi or headless
|
|
15523
|
+
tool("hub_peer_start", "Start local, kimi, pi, claude or codex in the start mode a person set for it; you cannot choose the mode. A headless peer starts at once. A TUI peer (claude, and pi and codex by default) opens in a terminal with a fixed command; when the hub cannot open a terminal the call fails with the command for the person to run.", { peer: { type: "string", enum: ["local", "kimi", "pi", "claude", "codex"] } }, ["peer"]),
|
|
15513
15524
|
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"]),
|
|
15514
15525
|
tool("hub_peer_release", "Release only the conductor hold you placed. Never lifts a person's hold or a budget pause.", { peer: str }, ["peer"])
|
|
15515
15526
|
];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Provider refusal observed from Kimi Code CLI 2.1.1 on 2026-10-11. No weekly/monthly or Qwen text is verified. */
|
|
2
|
+
export const KIMI_2_1_1_5H_REFUSAL_PREFIX = "Authentication required: 403 You've reached your 5-hour usage limit. Your quota will reset when the current 5-hour window ends.";
|
|
3
|
+
export interface ACPQuotaRefusal {
|
|
4
|
+
id: "5h";
|
|
5
|
+
used: 1;
|
|
6
|
+
windowMins: 300;
|
|
7
|
+
source: "kimi ACP usage-limit refusal";
|
|
8
|
+
}
|
|
9
|
+
/** Identity and turn/effect fencing are the caller's responsibility; an arbitrary 'quota' error is not a known refusal. */
|
|
10
|
+
export function acpQuotaRefusal(message: string): ACPQuotaRefusal | undefined {
|
|
11
|
+
if (!message.startsWith(KIMI_2_1_1_5H_REFUSAL_PREFIX)) return undefined;
|
|
12
|
+
return { id: "5h", used: 1, windowMins: 300, source: "kimi ACP usage-limit refusal" };
|
|
13
|
+
}
|
package/src/adapters/acp.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
|
-
import { renderDigest, replyAudience, replyParent, type Envelope, type PeerId } from "../hub/envelope.ts";
|
|
3
|
+
import { renderDigest, replyAudience, replyParent, sanitize, type Envelope, type PeerId } from "../hub/envelope.ts";
|
|
4
|
+
import { acpQuotaRefusal, type ACPQuotaRefusal } from "./acp-quota.ts";
|
|
4
5
|
import { KIMI_MODE_IDS, type PermissionMode } from "../hub/permission-mode.ts";
|
|
5
6
|
import { BasePeer } from "../hub/peers.ts";
|
|
6
7
|
import { peerChildEnv, stopOwnedProcess, trackGroup } from "../hub/child-process.ts";
|
|
@@ -66,6 +67,8 @@ export interface AcpOptions {
|
|
|
66
67
|
/** Optional launch environment; recovery authority is always removed before spawn. */
|
|
67
68
|
env?: NodeJS.ProcessEnv;
|
|
68
69
|
watchdogMs?: number;
|
|
70
|
+
/** Bound for a journal-less watchdog cancellation to report its prompt; defaults to the 30 s ACP handshake cap. */
|
|
71
|
+
cancelWaitMs?: number;
|
|
69
72
|
/** ACP stdio MCP servers for the session (the hub's task tools). */
|
|
70
73
|
mcpServers?: { name: string; command: string; args: string[]; env: { name: string; value: string }[] }[];
|
|
71
74
|
/** Appended to the standing instruction of the first delivery (role contract). */
|
|
@@ -75,7 +78,10 @@ export interface AcpOptions {
|
|
|
75
78
|
/** The prompt rejected or ended without normal completion, even when it streamed partial text.
|
|
76
79
|
* Same shape as Pi's onTurnFailure; a stale cancelled turn never reports. */
|
|
77
80
|
onTurnFailure?: (envs: Envelope[], reason: string) => Promise<void> | void;
|
|
78
|
-
|
|
81
|
+
/** The sessionId is the adapter's own: the daemon binds a context reading to it, not to a lookup of its own (#285). */
|
|
82
|
+
onUsageDiagnostic?: (observation: ACPUsageDiagnostic, sessionId: string) => void;
|
|
83
|
+
/** Return true only when the current-session refusal has an authoritative quota pause; otherwise hold delivery. */
|
|
84
|
+
onQuotaRefusal?: (window: ACPQuotaRefusal) => boolean;
|
|
79
85
|
/** Resolve with an optionId, or undefined to cancel. Absent = every request is cancelled.
|
|
80
86
|
* A request whose payload could not be resolved is titled as such and carries no session-wide allow option. */
|
|
81
87
|
onPermission?: (req: PermissionRequest) => Promise<string | undefined>;
|
|
@@ -128,8 +134,12 @@ export class AcpPeer extends BasePeer {
|
|
|
128
134
|
private readonly toolTitles = new Map<string, string>(); // the title a call was announced with, per call id (#138)
|
|
129
135
|
private primed = false;
|
|
130
136
|
private turn = 0; // generation: a prompt cancelled by the watchdog must not touch the turn that followed it
|
|
137
|
+
private stopping = false;
|
|
131
138
|
private activeDeliveryId: string | undefined;
|
|
139
|
+
private activeLegacy: { turn: number; envs: Envelope[] } | undefined;
|
|
132
140
|
private deliveryAccepted = false;
|
|
141
|
+
private cancelWait: { turn: number; successor: number; timer: ReturnType<typeof setTimeout> } | undefined;
|
|
142
|
+
private promptActivity = false;
|
|
133
143
|
|
|
134
144
|
constructor(
|
|
135
145
|
id: PeerId,
|
|
@@ -144,6 +154,7 @@ export class AcpPeer extends BasePeer {
|
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
async start(): Promise<void> {
|
|
157
|
+
this.stopping = false;
|
|
147
158
|
const [bin, ...args] = this.opts.cmd;
|
|
148
159
|
// 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.
|
|
149
160
|
const proc = spawn(bin!, args, { cwd: this.opts.cwd, env: peerChildEnv(this.id, { ...process.env, ...(this.opts.env ?? {}) }), stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
@@ -222,24 +233,35 @@ export class AcpPeer extends BasePeer {
|
|
|
222
233
|
this.permissionMode = mode;
|
|
223
234
|
}
|
|
224
235
|
|
|
225
|
-
async stop(): Promise<void> {
|
|
226
|
-
|
|
236
|
+
async stop(reason?: string): Promise<void> {
|
|
237
|
+
this.stopping = true;
|
|
238
|
+
this.clearCancelWait();
|
|
239
|
+
const legacy = this.activeLegacy;
|
|
240
|
+
this.activeLegacy = undefined;
|
|
241
|
+
// Journal-less work has no durable receipt: return it before a requested stop
|
|
242
|
+
// invalidates its generation, while stopping still prevents a drain.
|
|
243
|
+
if (legacy?.turn === this.turn) this.onFailed?.(legacy.envs);
|
|
244
|
+
if (reason !== undefined) this.turn++; // a requested stop cannot escalate or complete the cancelled turn
|
|
245
|
+
if (this.activeDeliveryId) this.delivery({ id: this.activeDeliveryId, state: "needs_review", reason: reason ?? "ACP session stopped before settlement" });
|
|
227
246
|
this.activeDeliveryId = undefined;
|
|
228
247
|
const proc = this.proc;
|
|
229
|
-
if (!proc) return;
|
|
248
|
+
if (!proc) { this.setState("offline"); return; }
|
|
230
249
|
await stopOwnedProcess(proc, { group: true }); // also when it exited: what it left in its group fails the stop
|
|
231
250
|
if (this.proc === proc) this.proc = undefined;
|
|
251
|
+
this.setState("offline");
|
|
232
252
|
}
|
|
233
253
|
|
|
234
254
|
/** Resolves once the prompt is in flight; the turn result arrives on its own. */
|
|
235
255
|
async deliver(envs: Envelope[], deliveryId?: string): Promise<void> {
|
|
236
|
-
if (this.state !== "idle") {
|
|
256
|
+
if (this.state !== "idle" || this.stopping) {
|
|
237
257
|
if (deliveryId) this.delivery({ id: deliveryId, state: "failed_safe", reason: `${this.id} is ${this.state}` });
|
|
238
258
|
throw new Error(`${this.id} is ${this.state}`);
|
|
239
259
|
}
|
|
240
260
|
const turn = ++this.turn;
|
|
241
261
|
this.activeDeliveryId = deliveryId;
|
|
262
|
+
this.activeLegacy = deliveryId ? undefined : { turn, envs };
|
|
242
263
|
this.deliveryAccepted = false;
|
|
264
|
+
this.promptActivity = false;
|
|
243
265
|
this.chunks = [];
|
|
244
266
|
this.setState("busy");
|
|
245
267
|
// session/prompt answers only when the turn ends, so deliver resolves now and failures come back through onFailed.
|
|
@@ -247,6 +269,9 @@ export class AcpPeer extends BasePeer {
|
|
|
247
269
|
prompt
|
|
248
270
|
.then((result) => {
|
|
249
271
|
if (turn !== this.turn) return; // superseded: these chunks belong to a later turn
|
|
272
|
+
// This prompt has reported. A synchronous observer stop must not return
|
|
273
|
+
// completed work as if its native request were still in flight.
|
|
274
|
+
if (this.activeLegacy?.turn === turn) this.activeLegacy = undefined;
|
|
250
275
|
this.observeUsage(result, "prompt_result");
|
|
251
276
|
this.primed = true;
|
|
252
277
|
const body = this.chunks.join("").trim();
|
|
@@ -259,13 +284,35 @@ export class AcpPeer extends BasePeer {
|
|
|
259
284
|
}
|
|
260
285
|
})
|
|
261
286
|
.catch((e: Error) => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
287
|
+
if (turn !== this.turn) {
|
|
288
|
+
if (this.cancelWait?.turn === turn) this.opts.log?.(`[${this.id}] cancelled prompt rejected: ${sanitize(e.message)}`);
|
|
289
|
+
return; // cancelled prompt errors are discarded, like their late results
|
|
290
|
+
}
|
|
291
|
+
const reason = sanitize(e.message);
|
|
292
|
+
this.opts.log?.(`[${this.id}] prompt failed: ${reason}`);
|
|
293
|
+
const quota = !this.stopping && this.kimiAgent ? acpQuotaRefusal(e.message) : undefined;
|
|
294
|
+
if (quota) {
|
|
295
|
+
let paused = false;
|
|
296
|
+
try { paused = this.opts.onQuotaRefusal?.(quota) === true; }
|
|
297
|
+
catch (error) { this.opts.log?.(`[${this.id}] quota pause failed: ${sanitize(error instanceof Error ? error.message : String(error))}`); }
|
|
298
|
+
const safe = paused && !this.deliveryAccepted && !this.promptActivity;
|
|
299
|
+
if (deliveryId && this.activeDeliveryId === deliveryId) this.delivery({ id: deliveryId, state: safe ? "failed_safe" : "needs_review", reason, ...(safe ? { quota: true as const } : {}) });
|
|
300
|
+
else if (!deliveryId && this.activeLegacy?.turn === turn) { this.activeLegacy = undefined; this.onFailed?.(envs, safe ? { quota: true } : undefined); }
|
|
301
|
+
return; // quota relay owns reassignment, not the ordinary turn-failure path
|
|
302
|
+
}
|
|
303
|
+
this.reportTurnFailure(envs, reason);
|
|
304
|
+
if (deliveryId && this.activeDeliveryId === deliveryId) this.delivery({ id: deliveryId, state: "needs_review", reason });
|
|
305
|
+
else if (!deliveryId && this.activeLegacy?.turn === turn) { this.activeLegacy = undefined; this.onFailed?.(envs); }
|
|
266
306
|
})
|
|
267
307
|
.finally(() => {
|
|
268
|
-
|
|
308
|
+
const waiting = this.cancelWait;
|
|
309
|
+
if (waiting?.turn === turn) {
|
|
310
|
+
this.clearCancelWait();
|
|
311
|
+
if (!this.stopping && this.turn === waiting.successor && this.state === "busy") this.setState("idle");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (turn === this.turn && !this.stopping && this.state === "busy") this.setState("idle");
|
|
315
|
+
if (this.activeLegacy?.turn === turn) this.activeLegacy = undefined;
|
|
269
316
|
if (turn === this.turn && this.activeDeliveryId === deliveryId) this.activeDeliveryId = undefined;
|
|
270
317
|
});
|
|
271
318
|
}
|
|
@@ -280,6 +327,10 @@ export class AcpPeer extends BasePeer {
|
|
|
280
327
|
}
|
|
281
328
|
|
|
282
329
|
protected override onWatchdog(): void {
|
|
330
|
+
if (this.state !== "busy") return;
|
|
331
|
+
if (this.cancelWait) { this.clearWatchdog(); return; }
|
|
332
|
+
const cancelledTurn = this.turn;
|
|
333
|
+
this.activeLegacy = undefined; // the cancelled prompt is discarded, never returned by a later stop
|
|
283
334
|
const durable = !!this.activeDeliveryId;
|
|
284
335
|
if (this.activeDeliveryId) this.delivery({ id: this.activeDeliveryId, state: "needs_review", reason: "ACP turn watchdog timeout" });
|
|
285
336
|
this.activeDeliveryId = undefined;
|
|
@@ -288,12 +339,31 @@ export class AcpPeer extends BasePeer {
|
|
|
288
339
|
// ACP updates carry only a session ID, not a turn ID. After a durable turn times out,
|
|
289
340
|
// keep the adapter offline until it is restarted so late chunks cannot contaminate a new turn.
|
|
290
341
|
if (durable) this.setState("offline");
|
|
291
|
-
else
|
|
342
|
+
else {
|
|
343
|
+
this.clearWatchdog(); // waiting for cancellation is not a second inactive model turn
|
|
344
|
+
const successor = this.turn;
|
|
345
|
+
const timer = setTimeout(() => {
|
|
346
|
+
const waiting = this.cancelWait;
|
|
347
|
+
if (!waiting || waiting.turn !== cancelledTurn || waiting.successor !== successor) return;
|
|
348
|
+
this.clearCancelWait();
|
|
349
|
+
if (!this.stopping && this.turn === successor) {
|
|
350
|
+
this.setState("offline"); // keep queued work until restart
|
|
351
|
+
this.opts.log?.(`[${this.id}] cancelled prompt did not report within the cancellation deadline; peer offline, queued work retained; stop and restart the peer to resume delivery`);
|
|
352
|
+
}
|
|
353
|
+
}, this.opts.cancelWaitMs ?? HANDSHAKE_MS);
|
|
354
|
+
timer.unref?.();
|
|
355
|
+
this.cancelWait = { turn: cancelledTurn, successor, timer };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private clearCancelWait(): void {
|
|
360
|
+
if (this.cancelWait) clearTimeout(this.cancelWait.timer);
|
|
361
|
+
this.cancelWait = undefined;
|
|
292
362
|
}
|
|
293
363
|
|
|
294
364
|
private observeUsage(value: unknown, source: ACPUsageDiagnostic["source"]): void {
|
|
295
365
|
const observation = normalizeACPUsage(value, source);
|
|
296
|
-
this.opts.onUsageDiagnostic?.(observation);
|
|
366
|
+
this.opts.onUsageDiagnostic?.(observation, this.sessionId);
|
|
297
367
|
if (observation.availability === "known") this.opts.onTokens?.(observation.total!, this.sessionId);
|
|
298
368
|
}
|
|
299
369
|
|
|
@@ -304,6 +374,7 @@ export class AcpPeer extends BasePeer {
|
|
|
304
374
|
}
|
|
305
375
|
|
|
306
376
|
private down(reason: string): void {
|
|
377
|
+
this.clearCancelWait();
|
|
307
378
|
this.opts.log?.(`[${this.id}] ${reason}`);
|
|
308
379
|
for (const p of this.pending.values()) p.reject(new Error(reason));
|
|
309
380
|
this.pending.clear();
|
|
@@ -313,19 +384,34 @@ export class AcpPeer extends BasePeer {
|
|
|
313
384
|
}
|
|
314
385
|
|
|
315
386
|
private onLine(line: string): void {
|
|
387
|
+
if (this.stopping) return; // buffered native callbacks cannot recreate approvals after peer_stop
|
|
316
388
|
let msg: any;
|
|
317
389
|
try {
|
|
318
390
|
msg = JSON.parse(line);
|
|
319
391
|
} catch {
|
|
320
392
|
return; // not protocol output
|
|
321
393
|
}
|
|
322
|
-
if (this.state === "busy") this.touch();
|
|
394
|
+
if (this.state === "busy" && !this.cancelWait) this.touch();
|
|
323
395
|
|
|
324
396
|
if (msg.method === "session/update") {
|
|
325
397
|
if (msg.params?.sessionId && msg.params.sessionId !== this.sessionId) return;
|
|
326
|
-
if (this.state !== "busy") return;
|
|
327
|
-
this.acceptDelivery();
|
|
328
398
|
const u = msg.params?.update;
|
|
399
|
+
// Occupancy is the session's, not the turn's: 2.1.1's source text emits usage_update after the prompt
|
|
400
|
+
// resolves, when the peer may already be idle (read from the source, not observed live; #285). So a
|
|
401
|
+
// context-used update is accepted while idle too, but then it must name the current session; in a turn the
|
|
402
|
+
// lenient session check above stands, as before. Every other update stays behind the busy gate, and a
|
|
403
|
+
// watchdog-cancelled turn's late chunks still lose their turn (the prompt result path is turn-fenced); an
|
|
404
|
+
// occupancy reading carries no turn state.
|
|
405
|
+
if (u?.sessionUpdate === "usage_update" && this.state !== "offline") {
|
|
406
|
+
const observation = normalizeACPUsage(u, "usage_update");
|
|
407
|
+
if (observation.shape === "context-used") {
|
|
408
|
+
if ((this.state === "busy" && !this.cancelWait) || msg.params?.sessionId === this.sessionId) this.opts.onUsageDiagnostic?.(observation, this.sessionId);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (this.state !== "busy" || this.cancelWait) return;
|
|
413
|
+
this.promptActivity = true;
|
|
414
|
+
this.acceptDelivery();
|
|
329
415
|
if (u?.sessionUpdate === "agent_message_chunk" && u.content?.type === "text") this.chunks.push(u.content.text);
|
|
330
416
|
// The permission request that follows may carry no `rawInput` (Kimi 2.0.1 does not), and then the
|
|
331
417
|
// console would be asked to approve a bare tool name. Keep what the call said it would run (issue #31).
|
|
@@ -344,6 +430,8 @@ export class AcpPeer extends BasePeer {
|
|
|
344
430
|
}
|
|
345
431
|
else if (u?.sessionUpdate === "usage_update") this.observeUsage(u, "usage_update");
|
|
346
432
|
} else if (msg.method === "session/request_permission") {
|
|
433
|
+
if (this.cancelWait) { this.send({ jsonrpc: "2.0", id: msg.id, result: { outcome: { outcome: "cancelled" } } }); return; }
|
|
434
|
+
if (this.state === "busy" && (!msg.params?.sessionId || msg.params.sessionId === this.sessionId)) this.promptActivity = true;
|
|
347
435
|
void this.answerPermission(msg);
|
|
348
436
|
} else if (msg.method && msg.id !== undefined) {
|
|
349
437
|
this.send({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: "method not supported by agent-hub" } });
|
|
@@ -395,7 +483,7 @@ export class AcpPeer extends BasePeer {
|
|
|
395
483
|
} finally {
|
|
396
484
|
clearInterval(alive); // also when there is no handler at all
|
|
397
485
|
}
|
|
398
|
-
const valid = options.some((o) => o.optionId === picked);
|
|
486
|
+
const valid = turn === this.turn && !this.stopping && options.some((o) => o.optionId === picked);
|
|
399
487
|
this.send({
|
|
400
488
|
jsonrpc: "2.0",
|
|
401
489
|
id: msg.id,
|