@code-yeongyu/senpi 2026.11.2-3 → 2026.11.3-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/bundle/chunks/{anthropic-messages-WUPLDYD6.js → anthropic-messages-AFWQB3KN.js} +1 -1
  3. package/dist/bundle/chunks/{args-AOI4NEA6.js → args-O3WS4WCO.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-O4GRBX4R.js → chunk-3WJ2DE2G.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-LMIETUPG.js → chunk-4BF4FLT6.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-T5ZJ2Q4O.js → chunk-4SENPCEA.js} +1 -1
  7. package/dist/bundle/chunks/chunk-5DZD623T.js +448 -0
  8. package/dist/bundle/chunks/{chunk-TXOR5BQL.js → chunk-6BSMXZKQ.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-VRWJJMHO.js → chunk-7565XQAU.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-IWM5QRAE.js → chunk-7KLEFHSU.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-FO56LSUK.js → chunk-BNIZP4OS.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-L3V65XW7.js → chunk-CJWSQ6CK.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-QLTQ3UUI.js → chunk-DAPBNRN5.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-GIY7PGOH.js → chunk-DPBPO5OU.js} +1 -1
  15. package/dist/bundle/chunks/chunk-ESQBKTNG.js +2 -0
  16. package/dist/bundle/chunks/{chunk-N4TSAHRL.js → chunk-EVTTLPIY.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-NYXXKS6D.js → chunk-F4OHTL2K.js} +3 -2
  18. package/dist/bundle/chunks/{chunk-TVMW2SUP.js → chunk-FJ2UCBOA.js} +2 -2
  19. package/dist/bundle/chunks/{chunk-KVPRQ5KN.js → chunk-FLM6GZPT.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-P4AT7SBE.js → chunk-HBCNDC53.js} +1 -1
  21. package/dist/bundle/chunks/chunk-MOH5HOHX.js +2 -0
  22. package/dist/bundle/chunks/{chunk-7FUO655J.js → chunk-MOOFLKLI.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-IHOQR7TU.js → chunk-PX7FF5LE.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-QAM6AXA2.js → chunk-QTQ3ES2S.js} +12 -9
  25. package/dist/bundle/chunks/{chunk-FJSJUVQQ.js → chunk-TOOB6OLG.js} +3 -3
  26. package/dist/bundle/chunks/{chunk-662SCMTH.js → chunk-VPDQ6MOL.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-BW4YXS7X.js → chunk-WCRNJYHO.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-ZSVWKOAJ.js → chunk-ZNLCKKF6.js} +170 -226
  29. package/dist/bundle/chunks/{cli-main-534A3WSC.js → cli-main-XM5FWY3Z.js} +1 -1
  30. package/dist/bundle/chunks/{credential-accounts-OAK2E5O2.js → credential-accounts-YZYSKA7X.js} +1 -1
  31. package/dist/bundle/chunks/{health-write-EB4CPHXN.js → health-write-45CX64T2.js} +1 -1
  32. package/dist/bundle/chunks/{help-fast-path-U2PSNNWO.js → help-fast-path-3EDGRKQM.js} +1 -1
  33. package/dist/bundle/chunks/{host-command-WJIMRBGN.js → host-command-PZ4VYQIW.js} +1 -1
  34. package/dist/bundle/chunks/{host-session-command-PWVPQ2OV.js → host-session-command-OJFARE5Q.js} +1 -1
  35. package/dist/bundle/chunks/{interactive-mode-NPHFDPGH.js → interactive-mode-7JSJVVIS.js} +1 -1
  36. package/dist/bundle/chunks/{login-health-7G2DZVOV.js → login-health-YOPTYRTU.js} +1 -1
  37. package/dist/bundle/chunks/{main-I6FJAFUP.js → main-3YGJ2O4I.js} +1 -1
  38. package/dist/bundle/chunks/{multi-session-host-7E26R5JF.js → multi-session-host-N2RQ7JSZ.js} +4 -4
  39. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  40. package/dist/bundle/chunks/{openai-codex-responses-VNBMMIL3.js → openai-codex-responses-YTWLG4OL.js} +1 -1
  41. package/dist/bundle/chunks/{openai-completions-JLR32APM.js → openai-completions-TIFDYHZL.js} +1 -1
  42. package/dist/bundle/chunks/{openai-images-LEBNERSM.js → openai-images-OAEMY4RG.js} +1 -1
  43. package/dist/bundle/chunks/{openai-responses-JCDUNMHA.js → openai-responses-L35NIG5Y.js} +1 -1
  44. package/dist/bundle/chunks/{package-manager-cli-CBSDV36M.js → package-manager-cli-JPNA27E5.js} +1 -1
  45. package/dist/bundle/chunks/{role-LP6LVNCY.js → role-45Y7PQFC.js} +1 -1
  46. package/dist/bundle/chunks/{rotation-stream-ZDCNHIBE.js → rotation-stream-WXWJKJZN.js} +1 -1
  47. package/dist/bundle/chunks/{rpc-mode-GBBTN2WP.js → rpc-mode-Z7MN2ZWA.js} +1 -1
  48. package/dist/bundle/chunks/{run-attached-tui-PVLUHHJX.js → run-attached-tui-TI6MELEI.js} +1 -1
  49. package/dist/bundle/chunks/{schedule-command-W45NYTTK.js → schedule-command-OZ5ESMIT.js} +1 -1
  50. package/dist/bundle/chunks/{session-control-endpoint-LFLUNALC.js → session-control-endpoint-5YKBYJOO.js} +1 -1
  51. package/dist/bundle/chunks/{session-picker-OZUINLD7.js → session-picker-CMRV72VA.js} +1 -1
  52. package/dist/bundle/chunks/session-worker.js +324 -376
  53. package/dist/bundle/chunks/{state-store-SZARKD36.js → state-store-Q47AYMRI.js} +1 -1
  54. package/dist/bundle/cli.js +2 -2
  55. package/dist/bundle/index.js +1 -1
  56. package/dist/bundle/rpc-entry.js +1 -1
  57. package/dist/bundle/runtime-manifest.json +1 -1
  58. package/dist/core/agent-session.d.ts +8 -0
  59. package/dist/core/agent-session.js +68 -60
  60. package/dist/core/credential-pool/classify.d.ts +3 -2
  61. package/dist/core/credential-pool/classify.js +10 -3
  62. package/dist/core/dynamic-prompt/build.d.ts +2 -1
  63. package/dist/core/dynamic-prompt/build.js +1 -2
  64. package/dist/core/dynamic-prompt/handoff.d.ts +1 -1
  65. package/dist/core/dynamic-prompt/handoff.js +9 -9
  66. package/dist/core/dynamic-prompt/identity.js +1 -1
  67. package/dist/core/dynamic-prompt/intent-gate.d.ts +1 -1
  68. package/dist/core/dynamic-prompt/intent-gate.js +8 -8
  69. package/dist/core/dynamic-prompt/policies.d.ts +1 -1
  70. package/dist/core/dynamic-prompt/policies.js +5 -7
  71. package/dist/core/dynamic-prompt/style.js +5 -5
  72. package/dist/core/dynamic-prompt/tool-section.d.ts +5 -0
  73. package/dist/core/dynamic-prompt/tool-section.js +8 -6
  74. package/dist/core/dynamic-prompt/verification.d.ts +6 -6
  75. package/dist/core/dynamic-prompt/verification.js +12 -12
  76. package/dist/core/dynamic-prompt/working-task.d.ts +1 -1
  77. package/dist/core/dynamic-prompt/working-task.js +3 -3
  78. package/dist/core/dynamic-prompt/workstation.js +1 -1
  79. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.d.ts +13 -0
  80. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.js +64 -2
  81. package/dist/core/extensions/builtin/anthropic-subscription/session-stream.js +5 -1
  82. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-attempt.d.ts +7 -0
  83. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-attempt.js +28 -1
  84. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.d.ts +2 -0
  85. package/dist/core/extensions/builtin/bash-timeout/index.d.ts +1 -1
  86. package/dist/core/extensions/builtin/bash-timeout/index.js +3 -28
  87. package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts +0 -11
  88. package/dist/core/extensions/builtin/bash-timeout/timeout.js +0 -11
  89. package/dist/core/extensions/builtin/imagegen/index.d.ts +0 -1
  90. package/dist/core/extensions/builtin/imagegen/index.js +0 -11
  91. package/dist/core/extensions/builtin/prompt-preset/execution-tooling.d.ts +5 -5
  92. package/dist/core/extensions/builtin/prompt-preset/execution-tooling.js +5 -5
  93. package/dist/core/extensions/builtin/prompt-preset/glm-5.d.ts +1 -1
  94. package/dist/core/extensions/builtin/prompt-preset/glm-5.js +1 -1
  95. package/dist/core/extensions/builtin/terminal/extension.js +8 -1
  96. package/dist/core/extensions/builtin/terminal/notify.js +9 -3
  97. package/dist/core/extensions/builtin/terminal/prompt.d.ts +7 -1
  98. package/dist/core/extensions/builtin/terminal/prompt.js +27 -60
  99. package/dist/core/extensions/builtin/terminal/tools/bash.js +10 -2
  100. package/dist/core/extensions/builtin/todotools/normalize.d.ts +20 -3
  101. package/dist/core/extensions/builtin/todotools/normalize.js +251 -5
  102. package/dist/core/extensions/builtin/todotools/prompt.d.ts +2 -2
  103. package/dist/core/extensions/builtin/todotools/prompt.js +5 -5
  104. package/dist/core/extensions/builtin/todotools/tools/todo.js +9 -8
  105. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js +0 -5
  106. package/dist/core/extensions/builtin/websearch/index.js +9 -4
  107. package/dist/core/extensions/builtin/websearch/websearch/tool.d.ts +2 -0
  108. package/dist/core/extensions/builtin/websearch/websearch/tool.js +3 -1
  109. package/dist/core/model-runtime.js +1 -1
  110. package/dist/core/provider-failure.d.ts +6 -0
  111. package/dist/core/provider-failure.js +6 -2
  112. package/dist/core/retry-fallback/exhaustion.d.ts +18 -0
  113. package/dist/core/retry-fallback/exhaustion.js +20 -0
  114. package/dist/core/system-prompt.js +1 -1
  115. package/dist/core/tools/bash.d.ts +3 -1
  116. package/dist/core/tools/bash.js +14 -4
  117. package/dist/core/tools/edit.d.ts +1 -1
  118. package/dist/core/tools/edit.js +1 -6
  119. package/dist/core/tools/grep/index.js +2 -3
  120. package/dist/core/tools/powershell.d.ts +1 -1
  121. package/dist/core/tools/powershell.js +1 -1
  122. package/dist/core/tools/read.d.ts +1 -1
  123. package/dist/core/tools/read.js +1 -1
  124. package/dist/core/tools/write.d.ts +1 -1
  125. package/dist/core/tools/write.js +1 -1
  126. package/dist/engine-trust/shell-trust.d.ts +36 -0
  127. package/dist/engine-trust/shell-trust.js +44 -0
  128. package/dist/engine-trust/transport-failure.d.ts +12 -0
  129. package/dist/engine-trust/transport-failure.js +48 -0
  130. package/dist/modes/interactive/interactive-mode.js +9 -3
  131. package/dist/modes/print-mode.js +2 -1
  132. package/dist/modes/rpc/compact-catalog-families.d.ts +5 -2
  133. package/dist/modes/rpc/compact-catalog-families.js +5 -2
  134. package/dist/modes/rpc/compact-catalog-registry.d.ts +9 -1
  135. package/dist/modes/rpc/compact-catalog-registry.js +9 -2
  136. package/dist/modes/rpc/compact-recovery-manifest-kinds.d.ts +17 -0
  137. package/dist/modes/rpc/compact-recovery-manifest-kinds.js +21 -0
  138. package/dist/modes/rpc/compact-recovery-manifest-shape.d.ts +7 -0
  139. package/dist/modes/rpc/compact-recovery-manifest.js +11 -3
  140. package/dist/modes/rpc/compact-socket-commands.js +6 -0
  141. package/dist/modes/rpc/connection-handler.js +21 -5
  142. package/dist/modes/rpc/connection-question-bridge.d.ts +3 -1
  143. package/dist/modes/rpc/connection-question-bridge.js +4 -1
  144. package/dist/modes/rpc/connection-roles.d.ts +8 -0
  145. package/dist/modes/rpc/connection-roles.js +18 -0
  146. package/dist/modes/rpc/method-classes-schema-session.d.ts +4 -0
  147. package/dist/modes/rpc/method-classes-schema-session.js +1 -0
  148. package/dist/modes/rpc/method-classes-schema.d.ts +4 -0
  149. package/dist/modes/rpc/protocol-identity.js +2 -0
  150. package/dist/modes/rpc/remote-origin.d.ts +31 -0
  151. package/dist/modes/rpc/remote-origin.js +58 -0
  152. package/dist/modes/rpc/rpc-protocol-info-types.d.ts +8 -0
  153. package/dist/modes/rpc/rpc-question-types.d.ts +7 -0
  154. package/dist/modes/rpc/rpc-question-types.js +0 -3
  155. package/dist/modes/rpc/rpc-session-command-types.d.ts +3 -0
  156. package/dist/modes/rpc/session-registry.js +12 -3
  157. package/dist/modes/rpc/session-worker-client.js +2 -0
  158. package/dist/modes/rpc/session-worker-protocol.d.ts +3 -0
  159. package/dist/modes/rpc/session-worker.js +2 -0
  160. package/docs/engine-socket/fixtures/admission-routes/c-baseline.json +3 -3
  161. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  162. package/docs/engine-socket/fixtures/admission-routes/i-reset-stream.json +3 -3
  163. package/docs/engine-socket/fixtures/recovery-manifest/index.json +32 -0
  164. package/docs/engine-socket/fixtures/recovery-manifest/one-partial-family.json +118 -0
  165. package/docs/rpc.md +93 -3
  166. package/package.json +6 -6
  167. package/dist/bundle/chunks/chunk-7UC4W2T2.js +0 -2
  168. package/dist/bundle/chunks/chunk-R23WXN4L.js +0 -448
  169. package/dist/bundle/chunks/chunk-W5NLCRB3.js +0 -2
package/docs/rpc.md CHANGED
@@ -789,6 +789,32 @@ synchronous cut requests refuse while that asynchronous reservation is pending.
789
789
  `base_file_replaced`, `base_file_truncated`, `base_file_missing` or `base_fingerprint_mismatch`, never stale rows. With a base present, the transcript, opaque and source families are
790
790
  complete: their members are the catalog's rows of this epoch plus the base stream's rows of their family.
791
791
 
792
+ **Recovery manifest (`componentRef: "manifest"`, senpi#5397, RULING-128).** Paged under a token with
793
+ `componentRef: "manifest"` (one page), the manifest is one JSON object `{format: "senpi-recovery-manifest", version: 1, socketEpoch, admissionId, routeKind, session,
794
+ sessionEpoch, generation, sequence, historyVersion, token, leaf, activeMessage, turn, checkpointDigest, catalogRoot,
795
+ catalogRootLength, requiredKinds}`. `checkpointDigest` is the `root`-domain digest of the catalog root node that
796
+ `catalogRoot` names. `requiredKinds` is the sorted (code-unit order), de-duplicated list of the catalog families this
797
+ cut's root requires: its `complete` families only; a family the engine produced only partially for this cut (an
798
+ unavailable transcript leaves `transcript`, `opaque` and `source` partial, a refused client correlation leaves
799
+ `identity` partial) is left out. The cut's `families/families` registry component carries the same list as its own
800
+ `requiredKinds`, so the list is part of the content `checkpointDigest` covers; a client that reads the registry can
801
+ compare the two. The field exists only in this flag-on compact manifest; legacy and flag-off output is unchanged.
802
+ A client:
803
+
804
+ - reads `requiredKinds` verbatim and opens exactly those families, never deriving the list from a built-in family
805
+ list or `CATALOG_FAMILIES`;
806
+ - refuses a manifest without `requiredKinds` (an older engine) as `manifest_missing_required_kinds`, with no
807
+ fallback to a built-in list;
808
+ - refuses a manifest naming a kind it does not know as `manifest_unknown_required_kind`, never ignoring it, so a
809
+ family a newer engine adds cannot leave a root half opened;
810
+ - refuses a manifest whose `requiredKinds` differs in any way (a kind added, dropped, repeated or reordered) from the
811
+ complete families of the cut's `families/families` registry component, read and verified against the root entry
812
+ `checkpointDigest` covers, as `manifest_required_kinds_mismatch`. The digest covers that component, not the
813
+ manifest field, so this comparison is what binds the list to the cut. senpi's reference reader is
814
+ `readRequiredKinds` (`compact-recovery-manifest-kinds.ts`).
815
+
816
+ The golden `docs/engine-socket/fixtures/recovery-manifest/` is a manifest with one partial family excluded.
817
+
792
818
  **S14 repair policy (RULING-84 and RULING-87).** Success carries `data.repair`; a typed recovery failure carries
793
819
  `errorData.repair`, using the same `RecoveryRepairAdviceSchema` as the client package's
794
820
  `RecoveryRepairController`. Advice contains `state` (`ok`, `repair_needed`, or `storage_unavailable`),
@@ -1318,7 +1344,8 @@ parsing a version string:
1318
1344
  "profile_id": "ca7a4943…",
1319
1345
  "core": { "extensions": [], "multi_session": true, "session_runtime": "in-process" }
1320
1346
  },
1321
- "agentDir": "/home/me/.senpi/agent"
1347
+ "agentDir": "/home/me/.senpi/agent",
1348
+ "method_classes": { "version": 1, "digest": "71d68465…" }
1322
1349
  }
1323
1350
  ```
1324
1351
 
@@ -1341,6 +1368,11 @@ parsing a version string:
1341
1368
  - `agentDir` - the absolute agent directory the host reads and writes. A generation handoff is refused
1342
1369
  `foreign_agent_dir` when it differs from the caller's own agent dir (see "Generation handoff"). Hosts
1343
1370
  released before the field omit it.
1371
+ - `method_classes` - the inbound method-class table this engine runs: `version` is the class-meaning version
1372
+ and `digest` the sha256 (hex) of the table's sorted `type=class\n` lines (`type=dynamic@<n>` for a
1373
+ record-dependent entry). A client that classifies records with its own copy of the table uses that copy only
1374
+ while its digest equals this one, and otherwise treats every record as unclassified. Hosts released before
1375
+ the field omit it.
1344
1376
  - `memory_pressure` - multi-session hosts only: `true` while this host's memory sampler reads its memory footprint
1345
1377
  above `SENPI_RPC_HOST_RSS_WARN_MB`, else `false`. Not identity and never an admission signal; `senpi host status`
1346
1378
  reports it per endpoint. Classic hosts and hosts released before the field omit it.
@@ -2311,7 +2343,7 @@ An `effect_status` is enqueued on the subscribing connection AFTER the record th
2311
2343
 
2312
2344
  ### Client information
2313
2345
 
2314
- Clients may send `set_client_info` with `{ sessionId?, width, capabilities? }`. `capabilities` registers the connection's
2346
+ Clients may send `set_client_info` with `{ sessionId?, width, capabilities?, remoteOrigin? }` (`remoteOrigin`: see "Remote origin"). `capabilities` registers the connection's
2315
2347
  opt-in client capabilities (see [Client capabilities](#client-capabilities)); a name the host does not know is ignored,
2316
2348
  never refused. Registration belongs to the connection that sent it: socket disposal removes it, and clients must
2317
2349
  re-register `capabilities` after every reconnect. `width` is accepted for compatibility and has no effect: the host
@@ -3295,7 +3327,7 @@ names a valid preset.
3295
3327
  | `attach_session` | `sessionId`, `durableSessionId`, `expectedHostInstance?` | `{ sessionId, attached: true }` | Live-only, identity-fenced attachment; never opens/resumes or changes the launch profile. Capability `attach_session`. |
3296
3328
  | `release_attachment` | `sessionId` | `{ deferred: boolean }` | Releases exactly one caller hold with disconnect semantics; `deferred` is true only when settlement owns the final release, and false while draining. Capability `release_attachment`. |
3297
3329
  | `reclaim_session` | `sessionPath`, `durableSessionId`, `expectedHostInstance` | `{ reclaimed: true, sessionId }` | Atomic attach-and-claim of every caller hold; refuses other connections before aborting. Capability `reclaim_session`. |
3298
- | `get_protocol_info` | - | `{ protocolVersion: 1, serverVersion: string, capabilities: string[], mode: "classic"\|"multi", instanceId: string, generation: number, engineVersion: string, engineOrdinal: [y, m, d, n, epoch], launch_profile: { profile_id, core }, memory_pressure?: boolean }` | Answered in BOTH modes; side-effect-free; the capability probe. Multi-session hosts include `multi_session`, `session_held` (the active foreign-holder admission guard, in both runtimes), `retain_on_disconnect`, `session_kind`, `session_context`, `auto_title_per_session`, `durable_session_id`, `moved_path_guard` (senpi resolves and guards paths the OmO desktop moved with its data home), `prompt_surface` and `prompt_surface_chat` plus the negotiated launch capabilities. Those are HOST capabilities (a client never sends them) and are advertised only in multi-session mode, where the host owns the attachment refcount and the per-session launch profile. The identity fields are described under "Host identity" above; compatibility is decided from `protocolVersion`, `capabilities` and `engineOrdinal`, NEVER from `serverVersion`. |
3330
+ | `get_protocol_info` | - | `{ protocolVersion: 1, serverVersion: string, capabilities: string[], mode: "classic"\|"multi", instanceId: string, generation: number, engineVersion: string, engineOrdinal: [y, m, d, n, epoch], launch_profile: { profile_id, core }, method_classes: { version, digest }, memory_pressure?: boolean }` | Answered in BOTH modes; side-effect-free; the capability probe. Multi-session hosts include `multi_session`, `session_held` (the active foreign-holder admission guard, in both runtimes), `retain_on_disconnect`, `session_kind`, `session_context`, `auto_title_per_session`, `durable_session_id`, `moved_path_guard` (senpi resolves and guards paths the OmO desktop moved with its data home), `prompt_surface` and `prompt_surface_chat` plus the negotiated launch capabilities. Those are HOST capabilities (a client never sends them) and are advertised only in multi-session mode, where the host owns the attachment refcount and the per-session launch profile. The identity fields are described under "Host identity" above; compatibility is decided from `protocolVersion`, `capabilities` and `engineOrdinal`, NEVER from `serverVersion`. |
3299
3331
  | `open_session` | `sessionPath?`, `cwd?`, `provider?`, `modelId?`, `thinkingLevel?`, `permissionPreset?`, `retain_on_disconnect?`, `kind?`, `context?`, `auto_title?`, `durableSessionId?`, `promptSurface?`, `browserEngine?`, `retryFallback?`, `env?` (all optional; paths MUST be absolute) | `{ sessionId, state: RpcSessionState, attached?: true }` | `sessionPath` = today's `--session` semantics (open-if-exists else create persisting there, `session-manager.ts:926-940`); `provider`/`modelId` applied only on create (resume restores the session's model — mirrors `SenpiSessionRuntime.ts:198-200`); params form the immutable launch profile (D8). When the path is already held by a fully-open session, the open ATTACHES to it: same routing handle, `attached: true`, one more attachment counted; the runtime is torn down only when the last attachment closes. Idle sessions past the eviction window are closed by the host itself. `retain_on_disconnect: true` (default false) makes a dropped connection DETACH from this session instead of closing it — see "Retained sessions" below. `kind` (default `interactive`) and the opaque `context` map are described under "Session kind and context" above; both are stored frozen for the session's life and never influence auth, model or resource resolution. |
3300
3332
  | `close_session` | `sessionId` | `{}` | Refused with `unknown_session` when the requesting connection never attached to that handle (a close releases the CALLER's attachment, and `list_sessions` publishes every handle). Otherwise aborts active work, awaits agent idle + settled persistence for up to the host grace window (default 10s), then quarantines any worker that has not exited without releasing its path reservation; its response is the LAST record tagged with that handle for the first closer — no events after (test-pinned). An admitted concurrent close joins the same teardown and receives its own successful response; output saturation rejects admission with the bounded close-overflow/resync notice described above. |
3301
3333
  | `list_sessions` | `include_workers?` (default false) | `{ sessions: [{ sessionId, durableSessionId, sessionPath, cwd, name, status, attachments, retain_on_disconnect, kind, context? }] }` | Includes `opening`/`closing` entries. `retain_on_disconnect` is true when the session outlives its last attachment (an open or attach asked for it; retention is never revoked), so no client's release or disconnect can end it. Internally quarantined workers remain externally `closing` until exit. `attachments` is the session's live client attachment count; `0` on an `open` row is a retained session with no client attached. Every row carries `kind`. Rows with `kind: "worker"` are omitted unless `include_workers: true`, and `context` is published ONLY on that listing — a default listing carries no `context` at all. |
@@ -3511,6 +3543,11 @@ Strict FIFO per session; one total stdout order; cross-session order unspecified
3511
3543
 
3512
3544
  ### Duplicate/idempotency
3513
3545
 
3546
+ If claim removal outlives an in-process close's grace deadline, a reopen waits for that
3547
+ in-flight removal even after the closing entry has been deleted. This wait uses the
3548
+ existing bounded pending-claim wait; a stuck removal still refuses with
3549
+ `session_path_in_use` and `errorData.reason: "claim_in_progress"`.
3550
+
3514
3551
  Duplicate `open_session` while a path reservation is held by a fully-open session → ATTACH (`attached: true`, same handle), including when that session is retained with zero attachments; while held by an `opening` entry (or an internally quarantined one) → `session_path_in_use`. A `closing` entry is a session that is ENDING, not one in use: on the in-process runtime (the `--listen` socket host default) the open WAITS for that teardown - bounded by the close grace window that bounds the teardown itself - and then opens the file fresh, so reopening a path after a close never depends on how long disposal takes. The worker runtime still answers `session_path_in_use` there, because a worker's teardown ends with an OS thread exit that no host deadline bounds. A path whose owner has already replaced it with another session file is no longer held: that open allocates a new worker and resumes the file. `close_session` releases one attachment; the runtime is disposed only when the last attachment closes. A close for an entry already `closing` joins its in-flight teardown. `close_session` on unknown/already-closed → `unknown_session` error, and so is a `close_session` from a connection that never attached to that handle — it releases nothing and leaves the session untouched. The grace window is configurable by the host through `SENPI_RPC_CLOSE_GRACE_MS`. Request `id`s are client-owned; the server echoes them without dedup.
3515
3552
 
3516
3553
  ## Protocol Overview
@@ -5446,6 +5483,7 @@ Events are streamed to stdout as JSON lines during agent operation. Events do no
5446
5483
  | `compaction_end` | Compaction completes |
5447
5484
  | `auto_retry_start` | Auto-retry begins (after transient error) |
5448
5485
  | `auto_retry_end` | Auto-retry completes (success or final failure) |
5486
+ | `retry_fallback_exhausted` | Every model in the fallback chain failed this turn (`chainKey`, `lastError`, optional `primaryError`, `failures`) |
5449
5487
  | `summarization_retry_scheduled` | Retry scheduled for a transient compaction or branch-summary summarization error |
5450
5488
  | `summarization_retry_attempt_start` | Retried summarization request starts |
5451
5489
  | `summarization_retry_finished` | Summarization retry loop completes |
@@ -5727,6 +5765,22 @@ When the run ended with a provider error (the last assistant message has `stopRe
5727
5765
 
5728
5766
  `unknown` is used when the error carries no diagnostic or its metadata matches no row; its `reason` is the generic `the provider returned an unexpected error`, and the provider's code or status, when it sent one, is only in `httpStatus` and `code`. No `reason` or `action` text carries a code or status. When `resetsAt` is set, `reason` ends with that time in the host's local time zone. A bare 429 stays `unknown` because status alone cannot tell a rate limit from exhausted quota. An `unknown` failure the provider answered with HTTP 404 is `model_unavailable` instead: a model endpoint returns 404 for a model id this account cannot use, as the ChatGPT subscription backend does for a model not yet rolled out to the plan. Retry and fallback decisions do not read `failure`.
5729
5767
 
5768
+ A `network` failure whose `code` is a TLS chain-verification code (`UNABLE_TO_GET_ISSUER_CERT_LOCALLY`, `UNABLE_TO_GET_ISSUER_CERT`, `UNABLE_TO_VERIFY_LEAF_SIGNATURE`, `SELF_SIGNED_CERT_IN_CHAIN`, `DEPTH_ZERO_SELF_SIGNED_CERT` or `CERT_UNTRUSTED`), in an engine that adopted a fallback trust generation (its operating-system certificate import failed), also carries `trust`:
5769
+
5770
+ ```json
5771
+ {
5772
+ "category": "network",
5773
+ "provider": "anthropic",
5774
+ "model": "claude-sonnet-4-5",
5775
+ "code": "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
5776
+ "reason": "the connection to the provider failed",
5777
+ "action": "check connectivity",
5778
+ "trust": { "code": "trust-tls-failed-under-fallback", "reason": "helper-missing", "action": "reinstall-omo" }
5779
+ }
5780
+ ```
5781
+
5782
+ `reason` is the generation's fallback reason and `action` the id of its repair action. A hostname mismatch, an expired certificate, any other network failure, and an engine whose import completed never carry `trust`. The code comes from the transport error's structured `code`, never from its message. The first such failure for each reason in a process also appends one record to `diagnostics.jsonl` beside the generation file; neither the record nor `trust` holds a host name, a path or a certificate.
5783
+
5730
5784
  A turn that never reached the provider because no API key or sign-in is saved for it (the message carries auth resolution's `Provider is not configured: <id>` sentinel) reports `auth`, with a `reason` naming the provider and an `action` to connect it or choose another model; it carries no `httpStatus` or `code`.
5731
5785
 
5732
5786
  `request_refused` is an `auth` failure whose credential (same provider, endpoint and key) served a response within the last 10 minutes: the provider refused this one request, and the key may still be valid. If failures persist, re-authenticate the named provider: a recently working key may have been revoked. The failed assistant message carries `credentialServedRecently: true` and keeps the provider's own `errorMessage`. The in-memory record drops expired entries on each record/check and retains at most 1,024 credentials, evicting the least recently served first. Header-based, ADC and SigV4 authentication do not supply an `apiKey`, so their fingerprint reduces to provider + baseUrl; different credentials on that endpoint share the record.
@@ -5988,6 +6042,30 @@ On final failure (max retries exceeded):
5988
6042
  }
5989
6043
  ```
5990
6044
 
6045
+ ### retry_fallback_exhausted
6046
+
6047
+ Emitted when the turn's model fallback chain runs out: the primary and every fallback rung failed.
6048
+
6049
+ ```json
6050
+ {
6051
+ "type": "retry_fallback_exhausted",
6052
+ "chainKey": "anthropic/claude-opus",
6053
+ "lastError": "Connection error.",
6054
+ "primaryError": { "model": "anthropic/claude-opus", "reason": "billing", "error": "400 out of extra usage" },
6055
+ "failures": [
6056
+ { "model": "anthropic/claude-opus", "reason": "billing", "error": "400 out of extra usage" },
6057
+ { "model": "openai/gpt", "reason": "transient", "error": "Connection error." }
6058
+ ]
6059
+ }
6060
+ ```
6061
+
6062
+ - `chainKey`: the `provider/model` key of the fallback chain that ran out.
6063
+ - `lastError`: the error of the rung that ran last.
6064
+ - `primaryError` (optional): the failure that started the fallback walk, the turn's first failed rung, as `{ model, reason, error }`. `reason` is `transient`, `refusal`, `hard-error` or `billing`.
6065
+ - `failures` (optional): every rung that failed this turn, in the order it ran, the primary first. An exact repeat of the previous entry is dropped.
6066
+
6067
+ `primaryError` and `failures` are present together when the session recorded at least one rung failure, and absent otherwise. Account usage-limit text in `lastError` and in each `error` is replaced by its account-limit description. Clients that ignore unknown fields keep working with `chainKey` and `lastError`.
6068
+
5991
6069
  ### summarization_retry_scheduled / summarization_retry_attempt_start / summarization_retry_finished
5992
6070
 
5993
6071
  Emitted when compaction or branch-summary summarization retries after a transient provider error. These events use the same retry settings as automatic assistant-turn retries.
@@ -6548,6 +6626,18 @@ Present one or more questions to the user. Requires the `question` client capabi
6548
6626
 
6549
6627
  Every `question` frame carries `raisedBy`: the `connectionId` whose command raised it (the same connection a `select`, `confirm`, `input` or `editor` raised at that point is delivered to; for a model tool call, the connection whose prompt started the turn), or `"agent"` when no connection's command started the work (for example a goal continuation, or a classic stdio session). A client uses it to tell a question it raised from one another connection raised; the frame still reaches every attached connection, and any of them may answer. The field is optional and additive: a client that ignores it is unaffected, and frames from older hosts omit it. The replayed frames in `pendingQuestions` carry the same value.
6550
6628
 
6629
+ #### Remote origin
6630
+
6631
+ A relay forwarder that drives a session on behalf of another device opens its own connection for that device and declares, in that connection's `set_client_info`, which device it speaks for:
6632
+
6633
+ ```json
6634
+ {"type":"set_client_info","width":80,"remoteOrigin":{"deviceId":"device-a","deviceName":"Laptop"}}
6635
+ ```
6636
+
6637
+ `deviceId` is required and `deviceName` optional; both are non-empty strings of at most 256 characters, and no other key is accepted (`remote_origin_invalid`). A connection's origin is fixed once declared: repeating the same value is accepted, and a different one is refused `remote_origin_fixed` and leaves the declared origin unchanged. A connection that never declares one is local.
6638
+
6639
+ Every `select`, `confirm`, `input`, `editor` and `question` frame raised by that connection's command (for a model tool call, the connection whose prompt started the turn) carries the declared value as `remoteOrigin`, next to `raisedBy`. The mirrored `ui_request_pending` carries it inside `request`, and so do replayed pending dialogs. A dialog raised by a local connection carries no `remoteOrigin`, so absence means local. A client shows the label (for example "Requested from Laptop") so that the person at the host can tell a dialog a remote device's turn raised from their own. `set_client_info` is a connection-local method: a forwarder never forwards a remote device's own `set_client_info`, so a remote device cannot set or change the label. A secret `input` goes only to the connection that raised it and is not labeled.
6640
+
6551
6641
  ```json
6552
6642
  {
6553
6643
  "type": "extension_ui_request",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi",
3
- "version": "2026.11.2-3",
3
+ "version": "2026.11.3-1",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -83,9 +83,9 @@
83
83
  },
84
84
  "dependencies": {
85
85
  "@earendil-works/chord": "0.99.1",
86
- "@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.11.2-3",
87
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.11.2-3",
88
- "@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.11.2-3",
86
+ "@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.11.3-1",
87
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.11.3-1",
88
+ "@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.11.3-1",
89
89
  "@silvia-odwyer/photon-node": "0.3.4",
90
90
  "ajv": "8.20.0",
91
91
  "chalk": "6.0.0",
@@ -106,8 +106,8 @@
106
106
  "yaml": "2.9.1",
107
107
  "@anthropic-ai/claude-agent-sdk": "0.3.306",
108
108
  "@anthropic-ai/sdk": "0.127.0",
109
- "@code-yeongyu/senpi-codemode": "2026.11.2-3",
110
- "@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.11.2-3",
109
+ "@code-yeongyu/senpi-codemode": "2026.11.3-1",
110
+ "@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.11.3-1",
111
111
  "@modelcontextprotocol/sdk": "1.32.1",
112
112
  "@mozilla/readability": "0.6.0",
113
113
  "linkedom": "0.18.13",
@@ -1,2 +0,0 @@
1
- import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
2
- import{peekProviderDiagnostic,siwcErrorForCode}from"./chunk-3SASTB6Y.js";var PROVIDER_DIAGNOSTIC_MAX_BYTES=512;function categoryForProviderCode(code){let siwc=siwcErrorForCode(code);if(siwc!==void 0)return siwc.category;switch(code){case"authentication_error":case"invalid_api_key":return"auth";case"rate_limit_error":case"rate_limit_exceeded":return"rate_limit";case"insufficient_quota":case"exceeded_current_quota_error":case"usage_limit_reached":case"usage_not_included":return"quota";case"context_length_exceeded":return"context_limit";case"invalid_request_error":return"invalid_request";case"overloaded_error":case"api_error":case"timeout_error":return"provider_unavailable";case"billing_error":case"permission_error":case"not_found_error":return"unknown";default:return}}function categoryForTransportCode(code){switch(code){case"ECONNRESET":case"ECONNREFUSED":case"ETIMEDOUT":case"ENOTFOUND":case"EAI_AGAIN":case"EPIPE":case"UND_ERR_SOCKET":case"UND_ERR_CONNECT_TIMEOUT":case"UND_ERR_HEADERS_TIMEOUT":case"UND_ERR_BODY_TIMEOUT":case"UND_ERR_CLOSED":case"APIConnectionError":case"APIConnectionTimeoutError":return"network";default:return}}function isProviderCodeStatusCompatible(code,status){let siwc=siwcErrorForCode(code);if(siwc!==void 0)return status===siwc.status;switch(code){case"authentication_error":case"invalid_api_key":return status===401;case"rate_limit_error":case"rate_limit_exceeded":case"insufficient_quota":case"exceeded_current_quota_error":case"usage_limit_reached":case"usage_not_included":return status===429;case"context_length_exceeded":case"invalid_request_error":return status===400;case"overloaded_error":case"api_error":case"timeout_error":return status>=500&&status<=599;case"billing_error":return status===402;case"permission_error":return status===403;case"not_found_error":return status===404;default:return!1}}var OPAQUE_PROVIDER_CODE=/^[a-z]+(?:[_.][a-z]+)*$/,OPAQUE_PROVIDER_CODE_MAX_LENGTH=64;function isOpaqueProviderCode(code){return code.length<=OPAQUE_PROVIDER_CODE_MAX_LENGTH&&OPAQUE_PROVIDER_CODE.test(code)&&categoryForProviderCode(code)===void 0}function isProviderDiagnosticResetsAt(value){return typeof value=="number"&&Number.isInteger(value)&&value>9466848e5&&value<41024448e5}function categoryForProviderStatus(status){return status===401?"auth":status===400?"invalid_request":status>=500&&status<=599?"provider_unavailable":"unknown"}function isProviderDiagnosticStatus(value){return typeof value=="number"&&Number.isInteger(value)&&value>=400&&value<=599}function isProviderDiagnosticCategory(value){switch(value){case"auth":case"rate_limit":case"quota":case"context_limit":case"invalid_request":case"provider_unavailable":case"network":case"unknown":return!0;default:return!1}}function buildProviderDiagnostic(input){let diagnostic={category:input.category,evidence:input.evidence};return input.httpStatus!==void 0&&(diagnostic.httpStatus=input.httpStatus),input.code!==void 0&&(diagnostic.code=input.code),input.route!==void 0&&(diagnostic.route=input.route),input.resetsAt!==void 0&&(input.category==="quota"||input.category==="rate_limit")&&(diagnostic.resetsAt=input.resetsAt),new TextEncoder().encode(JSON.stringify(diagnostic)).length<=PROVIDER_DIAGNOSTIC_MAX_BYTES?diagnostic:void 0}function classifyProviderFailure(facts){let statusSupplied=facts.status!==void 0&&facts.status!==null;if(statusSupplied&&!isProviderDiagnosticStatus(facts.status))return;let status=statusSupplied&&isProviderDiagnosticStatus(facts.status)?facts.status:void 0,category=facts.code===void 0?void 0:categoryForProviderCode(facts.code);if(facts.code!==void 0&&category!==void 0)return status!==void 0&&!isProviderCodeStatusCompatible(facts.code,status)?void 0:buildProviderDiagnostic({category,...status===void 0?{}:{httpStatus:status},code:facts.code,evidence:"structured_code"});if(status!==void 0)return buildProviderDiagnostic({category:categoryForProviderStatus(status),httpStatus:status,evidence:"structured_status"})}function readField(value,key){return Reflect.get(value,key)}function sanitizeProviderDiagnostic(value){try{if(typeof value!="object"||value===null||Array.isArray(value))return;let category=readField(value,"category"),evidence=readField(value,"evidence"),httpStatus=readField(value,"httpStatus"),code=readField(value,"code"),resetsAt=readField(value,"resetsAt"),route=readField(value,"route");if(route!==void 0&&route!=="siwc")return;let routed=route==="siwc"?{route}:{};if(!isProviderDiagnosticCategory(category)||httpStatus!==void 0&&!isProviderDiagnosticStatus(httpStatus)||resetsAt!==void 0&&!isProviderDiagnosticResetsAt(resetsAt))return;let reset=resetsAt===void 0?{}:{resetsAt};return evidence==="structured_status"?code!==void 0||!isProviderDiagnosticStatus(httpStatus)||category!==categoryForProviderStatus(httpStatus)?void 0:buildProviderDiagnostic({category,httpStatus,evidence,...reset,...routed}):evidence==="structured_transport"?httpStatus!==void 0||typeof code!="string"||categoryForTransportCode(code)!==category?void 0:buildProviderDiagnostic({category,code,evidence}):evidence!=="structured_code"||typeof code!="string"?void 0:isOpaqueProviderCode(code)?category!=="unknown"||httpStatus!==void 0?void 0:buildProviderDiagnostic({category,code,evidence}):categoryForProviderCode(code)!==category||httpStatus!==void 0&&!isProviderCodeStatusCompatible(code,httpStatus)?void 0:buildProviderDiagnostic({category,...httpStatus===void 0?{}:{httpStatus},code,evidence,...reset,...routed})}catch{return}}function readProviderDiagnostic(error){return sanitizeProviderDiagnostic(peekProviderDiagnostic(error))}export{PROVIDER_DIAGNOSTIC_MAX_BYTES,categoryForProviderCode,categoryForTransportCode,isOpaqueProviderCode,isProviderDiagnosticResetsAt,isProviderDiagnosticStatus,buildProviderDiagnostic,classifyProviderFailure,sanitizeProviderDiagnostic,readProviderDiagnostic};