@selesai/code 0.13.39 → 0.13.40
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/dist/core/intercom-rpc.d.ts +135 -0
- package/dist/core/intercom-rpc.js +64 -0
- package/dist/core/release-rpc.d.ts +137 -0
- package/dist/core/release-rpc.js +77 -0
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.js +3 -0
- package/dist/core/subagent-rpc.d.ts +110 -0
- package/dist/core/subagent-rpc.js +60 -0
- package/dist/extensions/agent-browser.ts +9 -0
- package/dist/extensions/pi-intercom/README.md +15 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +277 -13
- package/dist/extensions/pi-intercom/broker/client.ts +6 -2
- package/dist/extensions/pi-intercom/broker/mailbox-persistence.test.ts +523 -0
- package/dist/extensions/pi-intercom/broker/paths.test.ts +4 -4
- package/dist/extensions/pi-intercom/broker/paths.ts +4 -2
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +1 -3
- package/dist/extensions/pi-intercom/extension-api.ts +10 -0
- package/dist/extensions/pi-intercom/index.ts +188 -8
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +280 -0
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/release-readiness.test.ts +116 -0
- package/dist/extensions/pi-intercom/release-readiness.ts +93 -0
- package/dist/extensions/pi-subagents/docs/extension-api.md +8 -0
- package/dist/extensions/pi-subagents/docs/observability.md +8 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +51 -1
- package/dist/extensions/pi-subagents/src/extension/observability.ts +164 -0
- package/dist/extensions/pi-subagents/src/extension/release-readiness.ts +367 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +8 -1
- package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +7 -1
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +1 -44
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +6 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +13 -3
- package/dist/extensions/pi-subagents/src/runs/background/result-delivery-ownership.ts +269 -7
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +11 -10
- package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +15 -3
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +64 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -1
- package/dist/extensions/pi-subagents/src/shared/owner-record.ts +107 -0
- package/dist/extensions/pi-subagents/src/shared/process-identity.ts +87 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +9 -0
- package/dist/extensions/pi-subagents/test/integration/async-attention-clearing.test.ts +191 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +63 -0
- package/dist/extensions/pi-subagents/test/integration/stop-all-background.test.ts +142 -0
- package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/observability.test.ts +198 -0
- package/dist/extensions/pi-subagents/test/unit/process-identity.test.ts +75 -0
- package/dist/extensions/pi-subagents/test/unit/release-readiness.test.ts +715 -0
- package/dist/extensions/pi-subagents/test/unit/result-takeover.test.ts +332 -0
- package/dist/extensions/tokenin-onboarding.ts +4 -2
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -0
- package/dist/main.d.ts +6 -0
- package/dist/main.js +1 -1
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/rpc/intercom-bridge.d.ts +14 -0
- package/dist/modes/rpc/intercom-bridge.js +75 -0
- package/dist/modes/rpc/intercom-bridge.test.d.ts +1 -0
- package/dist/modes/rpc/intercom-bridge.test.js +65 -0
- package/dist/modes/rpc/release-bridge.d.ts +35 -0
- package/dist/modes/rpc/release-bridge.js +159 -0
- package/dist/modes/rpc/release-bridge.test.d.ts +1 -0
- package/dist/modes/rpc/release-bridge.test.js +347 -0
- package/dist/modes/rpc/rpc-client.d.ts +34 -10
- package/dist/modes/rpc/rpc-client.js +45 -2
- package/dist/modes/rpc/rpc-intercom-transport.test.d.ts +1 -0
- package/dist/modes/rpc/rpc-intercom-transport.test.js +85 -0
- package/dist/modes/rpc/rpc-mode.d.ts +1 -13
- package/dist/modes/rpc/rpc-mode.js +239 -8
- package/dist/modes/rpc/rpc-release-transport.test.d.ts +1 -0
- package/dist/modes/rpc/rpc-release-transport.test.js +329 -0
- package/dist/modes/rpc/rpc-subagent-transport.test.d.ts +1 -0
- package/dist/modes/rpc/rpc-subagent-transport.test.js +60 -0
- package/dist/modes/rpc/rpc-types.d.ts +12 -2
- package/dist/modes/rpc/rpc-types.js +0 -6
- package/dist/modes/rpc/subagent-bridge.d.ts +23 -0
- package/dist/modes/rpc/subagent-bridge.js +93 -0
- package/dist/modes/rpc/test-fixtures/intercom-host.d.ts +1 -0
- package/dist/modes/rpc/test-fixtures/intercom-host.js +13 -0
- package/docs/rpc-subagent.schema.json +491 -0
- package/docs/rpc.md +210 -1
- package/package.json +2 -1
package/docs/rpc.md
CHANGED
|
@@ -186,6 +186,7 @@ Response:
|
|
|
186
186
|
"thinkingLevel": "medium",
|
|
187
187
|
"isStreaming": false,
|
|
188
188
|
"isCompacting": false,
|
|
189
|
+
"isRetrying": false,
|
|
189
190
|
"steeringMode": "all",
|
|
190
191
|
"followUpMode": "one-at-a-time",
|
|
191
192
|
"sessionFile": "/path/to/session.jsonl",
|
|
@@ -198,7 +199,7 @@ Response:
|
|
|
198
199
|
}
|
|
199
200
|
```
|
|
200
201
|
|
|
201
|
-
The `model` field is a full [Model](#model) object or `null`. The `sessionName` field is the display name set via `set_session_name`, or omitted if not set.
|
|
202
|
+
The `model` field is a full [Model](#model) object or `null`. `isRetrying` is true while an auto-retry is in flight, including the backoff sleep between attempts (`isStreaming` can be false then). The `sessionName` field is the display name set via `set_session_name`, or omitted if not set.
|
|
202
203
|
|
|
203
204
|
#### get_messages
|
|
204
205
|
|
|
@@ -857,6 +858,214 @@ Optional `themeName` selects the export theme:
|
|
|
857
858
|
|
|
858
859
|
Response: `{"command": "share_gist", "success": true, "data": {"url": "https://github.com/user/...", "gistUrl": "https://gist.github.com/..."}}`
|
|
859
860
|
|
|
861
|
+
### Subagent observability (Desktop / headless clients)
|
|
862
|
+
|
|
863
|
+
`subagent_status` is a dedicated **read-only** command. It does not invoke an LLM, the `subagent` tool, a slash command, or the generic internal RPC/event bus. It reads the loaded extension's current native foreground controls/history and tracker-fed async/fleet state. No arbitrary user paths are scanned. Spawn/manage/steer/stop/resume are **not** exposed by this stdio contract.
|
|
864
|
+
|
|
865
|
+
```json
|
|
866
|
+
{"type":"subagent_status","id":"status-1","version":1}
|
|
867
|
+
{"type":"response","id":"status-1","command":"subagent_status","success":true,"data":{"version":1,"available":true,"capabilities":{"snapshot":true,"events":true},"sessionId":"host-session-id","epoch":"bind-uuid","revision":2,"fleet":{"version":1,"entries":[{"key":"fleet-1","agent":"worker","startedAt":1733234500000,"tokens":{"input":12,"output":3,"total":15}}],"totalActive":1,"topLevelAsyncCapacity":{"used":1,"limit":3},"omitted":0},"runs":[{"key":"node-opaque","runId":"run-1","status":"running","startedAt":1733234500000,"children":[{"key":"node-child-opaque","agent":"worker","status":"running","currentTool":"read","tokens":{"input":12,"output":3,"total":15},"children":[],"omittedChildren":0}],"omittedChildren":0}],"omitted":{"runs":0,"children":0}}}
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
The exact machine-readable JSON Schema for commands, success responses, events, snapshots, fleet and recursive nodes is [rpc-subagent.schema.json](rpc-subagent.schema.json); TypeScript definitions are in [`src/core/subagent-rpc.ts`](../src/core/subagent-rpc.ts). `version:1` is required; missing/unknown versions, extra command fields, non-string/overlong IDs and malformed backend projections fail with normal `success:false` responses. Optional `id` is at most 128 characters without CR/LF. Unknown future wire fields should be ignored by clients.
|
|
871
|
+
|
|
872
|
+
Missing extension (also after disposal) is explicit, **not** an empty healthy fleet:
|
|
873
|
+
|
|
874
|
+
```json
|
|
875
|
+
{"type":"response","command":"subagent_status","success":true,"data":{"version":1,"available":false,"capabilities":{"snapshot":true,"events":false},"sessionId":"host-session-id","epoch":"bind-uuid","revision":1,"fleet":{"version":1,"entries":[],"totalActive":0,"topLevelAsyncCapacity":{"used":0,"limit":0},"omitted":0},"runs":[],"omitted":{"runs":0,"children":0}}}
|
|
876
|
+
{"type":"response","command":"subagent_status","success":false,"error":"Unsupported subagent_status version; expected 1"}
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
An initial `subagent_event` is emitted after extension binding, including when unavailable, and again for every session switch/rebind/reload. Each event is a **full replacement snapshot**, identical to the authoritative status projection at its revision, not a patch:
|
|
880
|
+
|
|
881
|
+
```json
|
|
882
|
+
{"type":"subagent_event","version":1,"sessionId":"host-session-id","epoch":"bind-uuid","revision":1,"snapshot":{"version":1,"available":true,"capabilities":{"snapshot":true,"events":true},"sessionId":"host-session-id","epoch":"bind-uuid","revision":1,"fleet":{"version":1,"entries":[],"totalActive":0,"topLevelAsyncCapacity":{"used":0,"limit":0},"omitted":0},"runs":[],"omitted":{"runs":0,"children":0}}}
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
Scope and recovery:
|
|
886
|
+
- `sessionId` is the **bare host parent SDK session ID**, never an artifact path or broker endpoint. Internally, native run ownership can use a session-file path; the adapter filters on that native identity without exposing it as a node identity.
|
|
887
|
+
- `epoch` is a fresh host-binding UUID shared with Intercom's nested `scope`. Rebinding the same session/reloading rotates it. Revision starts at 1 after binding and increases monotonically on projection changes; querying unchanged status does not advance it. A status query may publish a fresh changed snapshot immediately. Native progress changes are sampled/coalesced at 250 ms while subscribed; identical snapshots emit nothing.
|
|
888
|
+
- Desktop should retain the current `(sessionId, epoch)` from the current binding/status, reject obsolete scopes and revisions, and replace the whole projection. Reconnect by requesting status; events are not a durable log. Late callbacks from disposed bindings are ignored and subscriptions/timers are released. Parent `agent_end` is **not** child completion: async work remains active until its own runtime lifecycle changes.
|
|
889
|
+
- `available:true` means the native observation backend is loaded, not that any child, model, or task is healthy. `capabilities.snapshot` is always true; `events` is true only with the backend. Unavailable snapshots contain zero counters as unavailable placeholders, not evidence of an idle fleet.
|
|
890
|
+
|
|
891
|
+
Node semantics and bounds:
|
|
892
|
+
- `runs` holds bounded native foreground controls, retained foreground child rows, and current/recent async records. It is a runtime display window, **not a durable run ledger**. Foreground retained child rows have the parent `runId`; async records use their native async run ID. A `workflowId` is only present when native workflow ownership is known. `key` is opaque; reconcile keys only inside the current epoch, never parse them or use paths as IDs. Fleet keys are a separate active-child display namespace; do not join fleet keys to run keys.
|
|
893
|
+
- `status` is the native lifecycle: `queued|pending|running|complete|completed|failed|partial|paused|stopped|rejected|detached`. Optional `activity` is separate: `waiting_supervisor` comes only from an actual registered pending native supervisor request; `needs_attention` comes only from native control activity. Clearing a pending request removes the wait hint. Do not infer activity from parent streaming, elapsed time, output text or tool names.
|
|
894
|
+
- Optional numeric `startedAt`, `updatedAt`, `endedAt` are native millisecond timestamps. `progress` is a bounded latest-output/description projection (256 characters), `currentTool` is at most 96 characters, `agent` 96, `error` 256, `reason` 128. No arguments, transcripts, arbitrary artifacts, or paths-as-identity are included; display strings can naturally mention paths and are not a secret-redaction API.
|
|
895
|
+
- `tokens`, when genuinely present, is `{input,output,total,window?,windowPeak?}` with nonnegative safe integers. Do not invent missing counters. Fleet retains the existing DTO's zero-counter fallback for absent usage; run/node tokens are omitted if unknown.
|
|
896
|
+
- Optional `outcome:{status,success?,exitCode?}` is only copied from an explicit native `execution` projection. A lifecycle `complete`/`completed`, terminal timestamp, exit code in other metadata, or output presence does **not** establish task success. `processTerminal:{state,observedAt?,reason?}` independently copies native runner proof (`pending|observed|unknown|not-started`); never infer it from task outcome or lifecycle. Both may be absent, and a failed task may have observed process closure.
|
|
897
|
+
- Fleet has at most 16 entries and 256 candidate records. `totalActive` counts native active children before the display bound; `omitted = totalActive - entries.length`, including malformed display rows. `topLevelAsyncCapacity.limit:0` means that native opt-in cap is disabled.
|
|
898
|
+
- At most 32 run roots, 64 total run/child nodes, 16 immediate children per node, and child depth 4 are emitted. The serialized projection reserves 1 KiB inside a 64 KiB budget for wire scope/envelope. Overflow/malformed records are omitted. `omitted.runs` counts excluded root candidates; every emitted node has `omittedChildren`, counting excluded **immediate child candidates** (not recursively enumerating hidden descendants). `omitted.children` is the sum of these counts over emitted nodes. Entire roots can be removed to meet the byte budget. These counts describe this projection window only, not expired native history or undiscovered artifacts.
|
|
899
|
+
|
|
900
|
+
TypeScript: `RpcClient.subagentStatus(): Promise<SubagentRpcSnapshot>` and `RpcEvent`/`SubagentRpcEvent` are exported with `SubagentRpcCommand`, `SubagentRpcResponse`, `SubagentRpcNode`, `SubagentRpcFleet`, `SubagentRpcTokens`, `SubagentRpcLifecycle`, and `RpcObservationScope` from `@selesai/code`. The dedicated internal source bridge is not an external extension-bus RPC invocation API.
|
|
901
|
+
|
|
902
|
+
### Release readiness (Desktop / headless clients)
|
|
903
|
+
|
|
904
|
+
`release_readiness` answers one question authoritatively: **is it safe to stop this process right now, and if not, why?** A host that spawns one `--mode rpc` child per session uses it to decide when an idle child may be released (gracefully stopped) without losing background subagent runs, intercom asks, armed session-only schedules, queued prompts, open extension dialogs or retrying turns. It is read-only and never invokes a model. Call it from events (navigate away, turn end, background result delivered), not on a timer.
|
|
905
|
+
|
|
906
|
+
```json
|
|
907
|
+
{"type":"release_readiness","id":"r-1","version":1}
|
|
908
|
+
{"type":"response","id":"r-1","command":"release_readiness","success":true,"data":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid","safe":false,"contributors":["subagents","intercom","scheduler"],"blockers":[{"source":"subagents","kind":"async_run","id":"run-1","detail":"Async single running: worker","since":1733234500000}]}}
|
|
909
|
+
{"type":"response","command":"release_readiness","success":false,"error":"Unsupported release_readiness version; expected 1"}
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
- `version` must be `1`; unknown keys and an `id` longer than 128 characters or containing line breaks are rejected with a failure response. After a session switch/reload the previous binding answers `"Release readiness session ended"`.
|
|
913
|
+
- `safe` is `true` **only** when `blockers` is empty. Treat any failure response as "not safe".
|
|
914
|
+
- `sessionId` and `epoch` are the same scope as `subagent_status`/Intercom events. Drop answers whose `(sessionId, epoch)` is obsolete; the epoch rotates on `new_session`, `switch_session`, fork/clone, import and reload.
|
|
915
|
+
- `contributors` lists the extension contributors that answered (`subagents`, `intercom`, `scheduler`). **`safe: true` with an empty `contributors` list means no extension reported**, e.g. the extensions are not loaded. Core blockers are always evaluated. A host that requires extension coverage should check the list.
|
|
916
|
+
- **Fail closed.** A contributor that throws, returns malformed blockers, or fails while reconciling a run yields a `contributor_error` blocker instead of silently answering safe. Over-long contributions are truncated with a `contributor_error` marker (64 blockers per contributor, 256 total).
|
|
917
|
+
- A dead background runner is **not** a blocker: it is repaired into a failed result first and then reported as `undelivered_result` until that result is delivered to the session.
|
|
918
|
+
|
|
919
|
+
Blocker fields: `source` (`core`, `subagents`, `intercom`, `scheduler`, or another extension name), `kind` (stable token), optional `id`, human-readable `detail`, optional `since` (epoch ms; for `armed_schedule` it is the next run time).
|
|
920
|
+
|
|
921
|
+
| source | kind | Reported when |
|
|
922
|
+
|---|---|---|
|
|
923
|
+
| core | `streaming` | an agent run (or its post-run continuation) is active |
|
|
924
|
+
| core | `compacting` | compaction or branch summarization is running |
|
|
925
|
+
| core | `retrying` | an auto-retry is in flight (also `isRetrying` in `get_state`) |
|
|
926
|
+
| core | `queued_messages` | steering/follow-up messages are queued |
|
|
927
|
+
| core | `pending_tool_calls` | tool calls are in flight |
|
|
928
|
+
| core | `pending_bash` | a `bash` command is running or its output awaits flushing |
|
|
929
|
+
| core | `extension_dialog` | an extension UI request awaits an `extension_ui_response` |
|
|
930
|
+
| core | `compaction_prompt_queue` | prompts are buffered until compaction ends |
|
|
931
|
+
| core | `prompt_in_flight` | a `prompt` command has not finished processing |
|
|
932
|
+
| core | `session_transition` | a session switch/new/fork/reload/rebind is in progress |
|
|
933
|
+
| core | `releasing` | a `release` command has been committed and the process is shutting down (see [`release`](#release-atomic-release-if-safe)) |
|
|
934
|
+
| subagents | `async_run` | a background run is queued/running, has live nested descendants, or a retained nested route may still be live (`id` = run id, `since` = start) |
|
|
935
|
+
| subagents | `foreground_run` | a foreground subagent run is in progress |
|
|
936
|
+
| subagents | `undelivered_result` | a repaired/finished run's result, a queued completion notification, or a persisted result owned by this process has not been delivered |
|
|
937
|
+
| intercom | `inbound_ask` | an ask is awaiting our reply (`id` = message id) |
|
|
938
|
+
| intercom | `outbound_ask` | our blocking ask awaits a reply |
|
|
939
|
+
| intercom | `outbound_message` | an outbox send from another extension has not settled |
|
|
940
|
+
| scheduler | `armed_schedule` | a **session-only** schedule holds an armed timer in this process (it would be lost on exit). Project schedules persist on disk and never block. An armed schedule whose record cannot be read is reported too (fail closed) |
|
|
941
|
+
| scheduler | `scheduled_run_pending` | a scheduled run (of any schedule kind) is still awaiting completion |
|
|
942
|
+
| any | `contributor_error` | a contributor failed (fail closed) |
|
|
943
|
+
|
|
944
|
+
Extension contract (internal, not an external RPC API): the host emits `release:readiness:v1` on the session event bus with `{version:1, sessionId, contribute(name, blockers)}`. Listeners call `contribute` synchronously, many contributors are accepted, and listeners must ignore requests whose `sessionId` is not the session they host (`answerReleaseReadiness` from `@selesai/code` implements the gate and the fail-closed try/catch).
|
|
945
|
+
|
|
946
|
+
TypeScript: `RpcClient.releaseReadiness(): Promise<ReleaseReadiness>`; `ReleaseReadinessCommand`, `ReleaseReadinessResponse`, `ReleaseReadiness` and `ReleaseBlocker` are exported types.
|
|
947
|
+
|
|
948
|
+
#### `release`: atomic release-if-safe
|
|
949
|
+
|
|
950
|
+
Asking `release_readiness` and then closing stdin is racy: stdin EOF shuts the process down immediately, so work that starts between the "safe" answer and the EOF (a schedule timer fires, an intercom message triggers a turn, a background result arrives) is lost. `release` closes that window by making the check and the commitment one step inside the process.
|
|
951
|
+
|
|
952
|
+
```json
|
|
953
|
+
{"type":"release","id":"r-2","version":1}
|
|
954
|
+
{"type":"response","id":"r-2","command":"release","success":true,"data":{"released":true,"readiness":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid","safe":true,"contributors":["subagents","intercom","scheduler"],"blockers":[]}}}
|
|
955
|
+
{"type":"response","id":"r-3","command":"release","success":true,"data":{"released":false,"readiness":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid","safe":false,"contributors":["subagents","intercom","scheduler"],"blockers":[{"source":"core","kind":"pending_bash","detail":"Bash command running"}]}}}
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
- Request fields and validation are identical to `release_readiness` (`version` must be `1`, no unknown keys, `id` at most 128 characters without line breaks); a malformed request gets a failure response and nothing is committed.
|
|
959
|
+
- The process computes readiness exactly as `release_readiness` does (core blockers plus every extension contributor) and, in the same synchronous step, decides:
|
|
960
|
+
- **Releasable** = `safe` **and** at least one contributor answered. The response is `released: true`. The process then rejects every later command with `success:false, error:"Session is releasing"` and, once the response is flushed, takes the normal graceful shutdown path: extensions receive `session_shutdown`, **nothing is aborted**, and the exit code is `0`. Because releasable implies idle, there is no in-flight turn to lose. Stdin EOF after a committed release is harmless.
|
|
961
|
+
- **Not releasable**: `released: false` with the full `readiness` (the blockers). The process keeps running normally and nothing changed.
|
|
962
|
+
- **Nobody answered is not released.** `safe: true` with an empty `contributors` list (extensions not loaded) yields `released: false`, because the process cannot prove that no background work exists. The `readiness` in the response shows `safe: true, contributors: []`; hosts that want to stop such a process anyway must do so themselves.
|
|
963
|
+
- After a committed release: `release` is answered idempotently with `released: true` (and the committed `readiness`); `release_readiness` answers `safe: false` with a core `releasing` blocker; every other command fails with `Session is releasing`. `extension_ui_response` lines are still routed.
|
|
964
|
+
- Residual window: work that bypasses the RPC command loop (extension timers such as session-only schedules, intercom-triggered turns, background results) is not refused by the flag; between the commit and the process exit (the stdout flush plus extension disposal, typically milliseconds) such work would be cut off by the shutdown. This is the same disposal that stdin EOF triggers, but without the unbounded gap between the readiness answer and the EOF.
|
|
965
|
+
- Prefer `release` over `release_readiness` + closing stdin. For CLIs that predate it (the `release` command fails with `Unknown command: release`), fall back to `release_readiness` and, only when `safe` with a non-empty `contributors`, close stdin.
|
|
966
|
+
|
|
967
|
+
TypeScript: `RpcClient.release(): Promise<ReleaseResult>`; `ReleaseCommand`, `ReleaseResponse` and `ReleaseResult` are exported types.
|
|
968
|
+
|
|
969
|
+
#### `stop_all_background`: kill switch for "Quit anyway"
|
|
970
|
+
|
|
971
|
+
`release` and `release_readiness` never stop work. `stop_all_background` is the opposite: **one destructive command that leaves nothing of this session running or armed.** It is intended only for an explicit user decision such as a "Quit anyway" button after a quit warning built from `release_readiness` blockers; never call it from automatic release, idle, cap or navigation logic. Afterwards the host exits the child gently (close stdin) or kills it.
|
|
972
|
+
|
|
973
|
+
```json
|
|
974
|
+
{"type":"stop_all_background","id":"k-1","version":1}
|
|
975
|
+
{"type":"response","id":"k-1","command":"stop_all_background","success":true,"data":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid","stopped":[{"source":"core","kind":"turn","detail":"Aborted in-flight agent turn","ok":true},{"source":"scheduler","kind":"schedule","id":"nightly","detail":"Paused session-only schedule nightly and cleared its timer (schedule.resume re-arms it)","ok":true},{"source":"subagents","kind":"async_run","id":"run-1","detail":"Stopped Async single (worker) run-1","ok":true},{"source":"intercom","kind":"inbound_ask","id":"m-7","detail":"cannot cancel inbound: ask from planner is awaiting our reply and keeps waiting","ok":false,"error":"cannot cancel inbound"}],"remaining":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid","safe":false,"contributors":["subagents","intercom","scheduler"],"blockers":[{"source":"intercom","kind":"inbound_ask","id":"m-7","detail":"Ask from planner is awaiting our reply"}]}}}
|
|
976
|
+
{"type":"response","command":"stop_all_background","success":false,"error":"Unsupported stop_all_background version; expected 1"}
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
- Request fields and validation are identical to `release_readiness` and `release` (`version` must be `1`, no unknown keys, `id` at most 128 characters without line breaks). `success:false` is returned **only** for a malformed request, while the session is releasing (`error:"Session is releasing"`, like every other command after a committed `release`), or when the binding was replaced during the stop. Individual failures never fail the command: they are `ok:false` items.
|
|
980
|
+
- `stopped` lists everything that was running, armed or pending when the command arrived, one item per thing: `{source, kind, id?, detail, ok, error?}`. `ok:true` means it is no longer running/armed/pending; `ok:false` carries a short `error` (`timeout`, `cannot cancel inbound`, `foreign_session`, or the failure text). An idle session yields `stopped: []`. Items are ordered by contributor: `core`, then extensions in registration order.
|
|
981
|
+
- `remaining` is the same `ReleaseReadiness` that `release_readiness` returns, computed **after** the stop: show it to explain what could not be stopped. Note that a run which was just stopped usually still appears as `undelivered_result` (its `stopped` result has not been delivered to the parent); that is not running work.
|
|
982
|
+
- **Idempotent.** A second call finds nothing left to stop: `stopped` only repeats things that cannot be stopped (inbound asks, runs of another session), usually `[]`.
|
|
983
|
+
- **Bounded.** The core abort and every extension's stop start together (synchronously inside the call) and are awaited under ONE total budget of about 5 seconds. A part that has not finished by then is reported `ok:false, error:"timeout"` and the response is sent anyway; its stop request stays in effect.
|
|
984
|
+
|
|
985
|
+
What is stopped:
|
|
986
|
+
|
|
987
|
+
| Source | What | How |
|
|
988
|
+
| --- | --- | --- |
|
|
989
|
+
| core | the in-flight agent turn, auto-retry, compaction and branch summarization | the same path as the `abort` command (`abortBranchSummary`, `abortCompaction`, `session.abort()`), plus: a running `bash` command is killed, and queued steer/follow-up messages and prompts buffered until compaction ends are dropped first so nothing restarts the turn after the abort |
|
|
990
|
+
| core / subagents | **foreground** subagent runs | they belong to the turn: the abort signal kills their child processes. The subagents handler only waits (within the budget) for them to leave and reports each as `foreground_run` |
|
|
991
|
+
| subagents | every live **async / detached** run of this session (`async_run`), runs a scheduled run is still awaiting (`scheduled_run`, the `scheduled_run_pending` blockers) and live nested descendants (`nested_run`) | the same machinery as the subagents RPC `stop` method: a stop request file is written to the run's control inbox (`<run dir>/control/stop-requests/`). The detached runner consumes it by itself, ends with status `stopped`, and exits; nothing depends on the CLI process staying alive, and the request is written before the call's first asynchronous step, so exiting right after the response is safe. Each item is `ok:true` once the runner's status is terminal |
|
|
992
|
+
| scheduler | **session-only** schedules armed in this process | **paused** (persisted) and their timers cleared, so they neither fire now nor on the next launch (`catchUp: "latest"` cannot revive a paused schedule). Pause rather than delete: it is reversible with `schedule.resume`, keeps the history and works while a run is active (delete refuses then) |
|
|
993
|
+
| intercom | this session's blocking **outbound ask** | broker `cancel_ask` (drops the ask edge and its pending-ask record), the blocked tool call/RPC ask returns `Cancelled: session background work was stopped`, and the recipient is asked to drop the message (`cancel_message`, best-effort; the detail says whether it was reached) |
|
|
994
|
+
|
|
995
|
+
What is **not** stopped:
|
|
996
|
+
|
|
997
|
+
- **Project schedules** (decision D11): they live on the project and re-arm on any launch. An armed timer whose record cannot be read is left alone and reported `ok:false` (it might be a project schedule).
|
|
998
|
+
- **Inbound intercom asks** waiting on this session: the receiver cannot cancel the asker's ask. They are reported `ok:false, error:"cannot cancel inbound"` and stay in `remaining`. Outbound messages from other extensions that have not settled (`outbound_message`) and pending extension dialogs are also left alone.
|
|
999
|
+
- **Runs of another session** that happen to be listed in this process are never touched (`ok:false, error:"foreign_session"`).
|
|
1000
|
+
- **Undelivered results** of stopped runs: they are delivered when the session is resumed (ownership takeover).
|
|
1001
|
+
- A wedged detached runner that never consumes its stop file: the control channel is file based on purpose (no PID signalling, a PID cannot be proven to belong to the runner), so it is reported `timeout` and stays in `remaining`; the host may still kill the process tree it knows about.
|
|
1002
|
+
- New work started during the stop window (for example a new prompt sent by the host in parallel) is not prevented; send the command, wait for the response, then exit.
|
|
1003
|
+
|
|
1004
|
+
TypeScript: `RpcClient.stopAllBackground(): Promise<StopAllBackgroundResult>`; `StopAllBackgroundCommand`, `StopAllBackgroundResponse`, `StopAllBackgroundResult` and `StopAllBackgroundItem` are exported types. Extension contract (internal): the host emits `release:stop-all:v1` with `{version:1, sessionId, deadline, contribute(name, items | Promise<items>)}`; extensions register a handler next to their `release:readiness:v1` contributor (`answerStopAllBackground` from `@selesai/code` implements the session gate and the fail-closed try/catch), start the stop synchronously and settle by `deadline`.
|
|
1005
|
+
|
|
1006
|
+
### Intercom (Desktop / headless clients)
|
|
1007
|
+
|
|
1008
|
+
These dedicated commands use the loaded `pi-intercom` extension's existing local broker connection. They do not invoke a model, run `/intercom` (a TUI overlay), or expose arbitrary extension events/tools. They work while the agent is idle or busy. `list_sessions` lists saved transcripts; `intercom_list` lists **live broker-connected endpoints**, including self, within the current routing scope.
|
|
1009
|
+
|
|
1010
|
+
| Command | Fields | Successful `data` |
|
|
1011
|
+
| --- | --- | --- |
|
|
1012
|
+
| `intercom_status` | none | `{enabled, connected, sessionId, name?, scope:{version:1,sessionId,epoch}}` (snapshot; does not initiate a connection) |
|
|
1013
|
+
| `intercom_list` | none | `{sessionId, sessions: SessionInfo[]}` |
|
|
1014
|
+
| `intercom_pending` | none | `{asks: [{from, message, receivedAt}]}` (unresolved inbound asks) |
|
|
1015
|
+
| `intercom_send` | `to`, `message`, `attachments?`, `replyTo?` | Delivery object below |
|
|
1016
|
+
| `intercom_ask` | `to`, `message`, `attachments?`, `replyTo?`, `timeoutMs?` | Delivery object plus `reply: Message` |
|
|
1017
|
+
| `intercom_reply` | `message`, `to?`, `replyTo?`, `attachments?` | Delivery object with exact `replyTo` |
|
|
1018
|
+
|
|
1019
|
+
Targets use existing name/full-ID/unique-ID-prefix resolution; ambiguous and self targets fail. Prefer full IDs from a fresh live list. Text and targets must be non-empty strings; attachments use `{type: "file"|"snippet"|"context", name, content, language?}`. `reply` uses the current turn's inbound message or single pending ask; specify `replyTo` when there are multiple asks. Ordinary `send` infers the sole pending ask from its target, with the same active-turn misdirection guard as the model tool.
|
|
1020
|
+
|
|
1021
|
+
```json
|
|
1022
|
+
{"id":"status-1","type":"intercom_status"}
|
|
1023
|
+
{"id":"peers-1","type":"intercom_list"}
|
|
1024
|
+
{"id":"send-1","type":"intercom_send","to":"peer-full-id","message":"Build finished."}
|
|
1025
|
+
{"id":"send-1","type":"response","command":"intercom_send","success":true,"data":{"messageId":"msg-1","to":"peer-full-id","delivered":true,"delivery":"socket_delivered","retryable":false,"outcomeKnown":true}}
|
|
1026
|
+
{"id":"ask-1","type":"intercom_ask","to":"peer-full-id","message":"Approve the change?","timeoutMs":60000}
|
|
1027
|
+
{"id":"ask-1","type":"response","command":"intercom_ask","success":true,"data":{"messageId":"question-1","to":"peer-full-id","delivered":true,"delivery":"socket_delivered","retryable":false,"outcomeKnown":true,"reply":{"id":"answer-1","timestamp":1733234567890,"replyTo":"question-1","content":{"text":"Approved."}}}}
|
|
1028
|
+
{"id":"pending-1","type":"intercom_pending"}
|
|
1029
|
+
{"id":"reply-1","type":"intercom_reply","replyTo":"question-2","message":"Proceed."}
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
RPC `id` correlates the terminal command response, not the Intercom message. `data.messageId` correlates delivery/receipts and `reply.replyTo`; `data.to` is the resolved target. Delivery is `socket_delivered` or `queued`; it does **not** mean the recipient model processed the message. Delivery failures return `success:false` and an error (diagnostic delivery events can include `code`, `reason`, `retryable`, `outcomeKnown`).
|
|
1033
|
+
|
|
1034
|
+
`ask` waits without an LLM turn and requires a live recipient. Its `timeoutMs` is an integer from 1 to 600000, default 600000; only one shared ask waiter (RPC or model tool) is allowed per session. Concurrent asks fail rather than replacing it. A timeout is **not cancellation** of already delivered work. Other bridge operations are bounded to 30 seconds (with a one-second host cleanup margin). Replacement, reload, shutdown, and disconnect settle pending operations; stale session events/results are discarded.
|
|
1035
|
+
|
|
1036
|
+
The extension's confirmation policy is unchanged: with `confirmSend:true`, ordinary and inferred sends emit `extension_ui_request` with `method:"confirm"`; respond with a matching `extension_ui_response`. Refusal, missing UI, or a 30-second confirmation timeout fails closed. Explicit `replyTo`, `reply`, and `ask` skip confirmation, as in the existing Intercom tool. A missing extension fails promptly with `Intercom extension unavailable`; a disabled extension reports `enabled:false` in status and rejects other operations.
|
|
1037
|
+
|
|
1038
|
+
#### Structured Intercom events
|
|
1039
|
+
|
|
1040
|
+
These stream even without an active prompt and while the agent is busy:
|
|
1041
|
+
|
|
1042
|
+
Every Intercom event and `intercom_status.data` includes **`scope: {version:1, sessionId: string|null, epoch: string}`**. `scope.sessionId` is the host parent SDK session ID (as in `get_state`), never a session-file path or broker endpoint ID. Existing top-level `sessionId` is unchanged: self broker endpoint in status/connection, departing peer endpoint in `intercom_session_left`. `from.id`, `session.id`, and list IDs also remain broker endpoints. `scope.epoch` is the host binding UUID shared with subagent snapshots; it rotates on every rebind/reload, including rebinding the same session. Reject events from an obsolete `(scope.sessionId, scope.epoch)` pair. Broker reconnects within the same binding do not rotate the epoch; connection events report reconnect state. Status does not initiate a broker connection.
|
|
1043
|
+
|
|
1044
|
+
```json
|
|
1045
|
+
{"id":"status-1","type":"response","command":"intercom_status","success":true,"data":{"enabled":true,"connected":true,"sessionId":"broker-self-id","scope":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid"}}}
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
| Event | Payload |
|
|
1049
|
+
| --- | --- |
|
|
1050
|
+
| `intercom_connection` | `enabled`, `connected`, `sessionId`, `reason?` |
|
|
1051
|
+
| `intercom_session_joined` | `session: SessionInfo` |
|
|
1052
|
+
| `intercom_session_left` | `sessionId` |
|
|
1053
|
+
| `intercom_presence` | `session: SessionInfo` (name, cwd, model, lifecycle status, optional context usage) |
|
|
1054
|
+
| `intercom_message` | `from: SessionInfo`, `message: Message` (including attachments and threading) |
|
|
1055
|
+
| `intercom_delivery` | `result: {type:"delivered"|"delivery_failed", messageId, delivery, retryable, outcomeKnown, code?, reason?}` |
|
|
1056
|
+
| `intercom_receipt` | `from: SessionInfo`, `receipt: {messageId, status, timestamp, detail?}` |
|
|
1057
|
+
|
|
1058
|
+
```json
|
|
1059
|
+
{"type":"intercom_connection","enabled":true,"connected":true,"sessionId":"self-id","scope":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid"}}
|
|
1060
|
+
{"type":"intercom_session_left","sessionId":"peer-full-id","scope":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid"}}
|
|
1061
|
+
{"type":"intercom_message","from":{"id":"peer-full-id","name":"reviewer","cwd":"/project","model":"model-id","pid":123,"startedAt":1733234500000,"lastActivity":1733234567890},"message":{"id":"question-2","timestamp":1733234567890,"expectsReply":true,"content":{"text":"Can I proceed?"}},"scope":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid"}}
|
|
1062
|
+
{"type":"intercom_receipt","from":{"id":"peer-full-id","cwd":"/project","model":"model-id","pid":123,"startedAt":1733234500000,"lastActivity":1733234567890},"receipt":{"messageId":"msg-1","status":"injected","timestamp":1733234567890},"scope":{"version":1,"sessionId":"host-session-id","epoch":"bind-uuid"}}
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
Incoming events are deduplicated per sender/message ID and include replies consumed by an ask waiter. They do not replace the normal custom-message/transcript stream; avoid rendering both as separate copies. Inbound triggering remains governed by `inboundTrigger`: idle recipients may start a turn and busy RPC recipients receive steering at the next safe boundary. Receipt statuses include `receiver_received`, `acknowledged`, `injected`, `queued`, `expired`, `cancelled`, `superseded`, and `cancellation_requested`.
|
|
1066
|
+
|
|
1067
|
+
TypeScript: `RpcClient` provides `intercomStatus`, `intercomList`, `intercomSend`, `intercomAsk`, `intercomReply`, `intercomPending`; `onEvent` accepts the exported `RpcEvent` union, including `IntercomRpcEvent` and extension UI requests. The `IntercomRpcCommand`, `IntercomRpcDataMap`, delivery/status/message option types are exported from `@selesai/code`.
|
|
1068
|
+
|
|
860
1069
|
### Commands
|
|
861
1070
|
|
|
862
1071
|
#### get_commands
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selesai/code",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.40",
|
|
4
4
|
"description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"test": "vitest run src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts src/extensions/capability-gateway/routing.test.ts src/core/settings-manager-auto-handoff.test.ts src/core/remote-catalog-provider.test.ts src/extensions/jev-advisory-memory.test.ts src/extensions/jev-advisory-recommendations.test.ts src/extensions/jev-ask-tool.test.ts src/extensions/jev-advisory-lifecycle.test.ts src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/pi-graft src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-search.test.ts src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/core/system-prompt.test.ts src/core/session-format.test.ts test/settings-manager-compaction.test.ts test/suite/agent-session-runtime.test.ts test/suite/agent-session-prompt.test.ts test/suite/agent-session-queue.test.ts test/suite/agent-session-retry-events.test.ts test/suite/agent-session-compaction.test.ts test/suite/agent-session-compaction-model-overrides.test.ts test/suite/agent-session-bash-persistence.test.ts test/suite/agent-session-model-extension.test.ts test/clipboard.test.ts test/clipboard-command.test.ts test/clipboard-image.test.ts test/clipboard-image-bmp-conversion.test.ts test/clipboard-image-native-errors.test.ts src/cli/args.test.ts",
|
|
59
59
|
"test:coverage": "vitest run --coverage src/__tests__/model-registry-completion.test.ts src/extensions/jev/decisions.test.ts src/extensions/capability-gateway/routing.test.ts src/core/settings-manager-auto-handoff.test.ts src/core/remote-catalog-provider.test.ts src/extensions/jev-advisory-memory.test.ts src/extensions/jev-advisory-recommendations.test.ts src/extensions/jev-ask-tool.test.ts src/extensions/jev-advisory-lifecycle.test.ts src/extensions/undo.test.ts src/extensions/tps.test.ts src/extensions/agent-browser.test.ts src/extensions/auto-session-name.test.ts src/extensions/context-compaction-reminder.test.ts src/extensions/copy-turn.test.ts src/extensions/grep-app/index.test.ts src/extensions/inline-skills.test.ts src/extensions/rtk.test.ts src/extensions/handoff-new.test.ts src/extensions/question/tests src/extensions/ponytail/test src/__tests__/tokenin-search.test.ts src/__tests__/tokenin-onboarding.test.ts src/__tests__/model-registry-defaults.test.ts src/core/system-prompt.test.ts src/core/session-format.test.ts test/settings-manager-compaction.test.ts test/suite/agent-session-runtime.test.ts test/suite/agent-session-prompt.test.ts test/suite/agent-session-queue.test.ts test/suite/agent-session-retry-events.test.ts test/suite/agent-session-compaction.test.ts test/suite/agent-session-compaction-model-overrides.test.ts test/suite/agent-session-bash-persistence.test.ts test/suite/agent-session-model-extension.test.ts test/clipboard.test.ts test/clipboard-command.test.ts test/clipboard-image.test.ts test/clipboard-image-bmp-conversion.test.ts test/clipboard-image-native-errors.test.ts src/cli/args.test.ts",
|
|
60
60
|
"prepare": "npm run build",
|
|
61
|
+
"build:native": "bun scripts/build-native.ts",
|
|
61
62
|
"build": "npm run clean && tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js dist/rpc-entry.js && npm run copy-assets",
|
|
62
63
|
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && shx mkdir -p dist/defaults && shx cp src/defaults/* dist/defaults/ && shx mkdir -p dist/extensions && node scripts/copy-extensions.mjs && shx mkdir -p dist/themes && shx cp -r src/themes/. dist/themes/ && shx mkdir -p dist/skills && shx cp -r src/skills/. dist/skills/"
|
|
63
64
|
},
|