@code-yeongyu/senpi 2026.10.25-1 → 2026.10.25-3

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 (226) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/bundle/chunks/{args-JXULI4EH.js → args-B444OQBK.js} +1 -1
  3. package/dist/bundle/chunks/{auth-4NUNUSTF.js → auth-C77UQA55.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-RHHBXIRN.js → chunk-4WZRVZNX.js} +1 -1
  5. package/dist/bundle/chunks/chunk-6O4SEW2W.js +5 -0
  6. package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-YDCRA5ES.js → chunk-7W7OJDIY.js} +4 -4
  8. package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
  9. package/dist/bundle/chunks/{chunk-FWXMMMCV.js → chunk-C5UFJ7B3.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-7IRLFYNM.js → chunk-CNL6QSUB.js} +4 -4
  11. package/dist/bundle/chunks/{chunk-BTNYLNWZ.js → chunk-FFQOEC76.js} +5 -5
  12. package/dist/bundle/chunks/{chunk-VOBD6FQG.js → chunk-FHXR4ZAC.js} +1 -1
  13. package/dist/bundle/chunks/chunk-H3HNMY2W.js +13 -0
  14. package/dist/bundle/chunks/{chunk-BMYNMUVZ.js → chunk-HVJI5T75.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-EWOEVVQT.js → chunk-JA2R2ZSE.js} +1 -1
  16. package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
  17. package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
  18. package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
  19. package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
  20. package/dist/bundle/chunks/{chunk-EYBDBR7V.js → chunk-MJ5NNWX4.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-RVYSJBZ7.js → chunk-O3Q2ET6W.js} +2 -5
  22. package/dist/bundle/chunks/chunk-O5RYI3IW.js +2 -0
  23. package/dist/bundle/chunks/{chunk-SGDOZT6B.js → chunk-ODTGC6YU.js} +1 -1
  24. package/dist/bundle/chunks/chunk-P3M2PF6K.js +27 -0
  25. package/dist/bundle/chunks/chunk-SWJXRC2I.js +5 -0
  26. package/dist/bundle/chunks/{chunk-QH2HSCMX.js → chunk-TTU2RNVX.js} +3 -3
  27. package/dist/bundle/chunks/{chunk-PXKF2RHD.js → chunk-UFJ2K7Q7.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-7UUOGZKR.js → chunk-UQNRW3VU.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
  30. package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
  31. package/dist/bundle/chunks/{chunk-7WAJCESX.js → chunk-WLHNEUGD.js} +1 -1
  32. package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
  33. package/dist/bundle/chunks/{chunk-URBJS24R.js → chunk-YRF4I43T.js} +35 -35
  34. package/dist/bundle/chunks/chunk-Z3UYNSVR.js +3 -0
  35. package/dist/bundle/chunks/cli-main-HEKFFB5S.js +3 -0
  36. package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
  37. package/dist/bundle/chunks/dispatch-NYAULNVW.js +9 -0
  38. package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
  39. package/dist/bundle/chunks/host-command-Z3WPHJOW.js +2 -0
  40. package/dist/bundle/chunks/host-lifecycle-ONQIMWMX.js +3 -0
  41. package/dist/bundle/chunks/host-lifecycle.js +9 -7
  42. package/dist/bundle/chunks/host-session-command-J65ENSV5.js +20 -0
  43. package/dist/bundle/chunks/{host-socket-request-ZDJGSSZO.js → host-socket-request-3CWE4265.js} +1 -1
  44. package/dist/bundle/chunks/interactive-mode-DEPQC6TX.js +2 -0
  45. package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
  46. package/dist/bundle/chunks/{multi-session-host-M7HVUMRH.js → multi-session-host-YI2WZOET.js} +12 -9
  47. package/dist/bundle/chunks/{package-manager-cli-FSR5P6YL.js → package-manager-cli-GHZPJMRF.js} +1 -1
  48. package/dist/bundle/chunks/{role-M2MTVACJ.js → role-JVSBNKGE.js} +1 -1
  49. package/dist/bundle/chunks/{rotation-stream-U7YQMC7H.js → rotation-stream-ZPVCFOVD.js} +1 -1
  50. package/dist/bundle/chunks/rpc-mode-ORHBGBBV.js +2 -0
  51. package/dist/bundle/chunks/{rpc-monitor-source-OL5KU6NO.js → rpc-monitor-source-FFEQS744.js} +1 -1
  52. package/dist/bundle/chunks/{runtime-session-CLNODAYE.js → runtime-session-V5OO7ZRM.js} +1 -1
  53. package/dist/bundle/chunks/{schedule-command-XBW4EOG5.js → schedule-command-YWG6D6ZF.js} +1 -1
  54. package/dist/bundle/chunks/session-control-endpoint-36HAQSQO.js +3 -0
  55. package/dist/bundle/chunks/{session-picker-KQUFWDQC.js → session-picker-25H5KNYP.js} +1 -1
  56. package/dist/bundle/chunks/session-worker.js +285 -271
  57. package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
  58. package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
  59. package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
  60. package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
  61. package/dist/bundle/cli.js +2 -2
  62. package/dist/bundle/host-supervisor.js +9 -7
  63. package/dist/bundle/index.js +1 -1
  64. package/dist/bundle/rpc-entry.js +1 -1
  65. package/dist/bundle/runtime-manifest.json +1 -1
  66. package/dist/cli/args.d.ts +2 -0
  67. package/dist/cli/args.js +9 -0
  68. package/dist/cli/host-command-args.d.ts +1 -0
  69. package/dist/cli/host-command-args.js +7 -1
  70. package/dist/cli/host-command.js +3 -1
  71. package/dist/core/agent-session.d.ts +1 -0
  72. package/dist/core/agent-session.js +50 -16
  73. package/dist/core/engine-turn-limit.d.ts +2 -0
  74. package/dist/core/engine-turn-limit.js +7 -1
  75. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +5 -0
  76. package/dist/core/extensions/builtin/goal/index.js +1 -0
  77. package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
  78. package/dist/core/extensions/builtin/goal/stop-pause.js +2 -1
  79. package/dist/core/extensions/builtin/loop/session-hold.js +2 -1
  80. package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
  81. package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
  82. package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
  83. package/dist/core/extensions/builtin/mcp/connection.js +46 -1
  84. package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
  85. package/dist/core/extensions/builtin/mcp/elicitation.js +56 -2
  86. package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
  87. package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
  88. package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
  89. package/dist/core/extensions/builtin/mcp/health.js +5 -0
  90. package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
  91. package/dist/core/extensions/builtin/mcp/index.js +2 -0
  92. package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
  93. package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
  94. package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
  95. package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
  96. package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
  97. package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
  98. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
  99. package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
  100. package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
  101. package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
  102. package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
  103. package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
  104. package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
  105. package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
  106. package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
  107. package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
  108. package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
  109. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
  110. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
  111. package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
  112. package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
  113. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
  114. package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
  115. package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
  116. package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
  117. package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
  118. package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
  119. package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
  120. package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
  121. package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
  122. package/dist/core/extensions/builtin/mcp/resources.js +19 -4
  123. package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
  124. package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
  125. package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
  126. package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
  127. package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
  128. package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
  129. package/dist/core/extensions/builtin/mcp/service.js +79 -4
  130. package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
  131. package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
  132. package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
  133. package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
  134. package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
  135. package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
  136. package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
  137. package/dist/core/extensions/runner.js +3 -2
  138. package/dist/core/extensions/types.d.ts +2 -0
  139. package/dist/core/lifecycle-ledger.d.ts +1 -0
  140. package/dist/core/lifecycle-ledger.js +7 -2
  141. package/dist/core/lifecycle-record.js +3 -0
  142. package/dist/core/lifecycle-runtime-facts.js +3 -0
  143. package/dist/core/lifecycle-session-binding.d.ts +11 -1
  144. package/dist/core/lifecycle-session-binding.js +35 -4
  145. package/dist/core/lifecycle-stop.d.ts +41 -0
  146. package/dist/core/lifecycle-stop.js +92 -0
  147. package/dist/core/mcp-form-types.d.ts +89 -0
  148. package/dist/core/mcp-form-types.js +23 -0
  149. package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
  150. package/dist/core/mcp-mrtr-presentations.js +17 -0
  151. package/dist/core/run-resume-turn.d.ts +1 -0
  152. package/dist/core/run-resume-turn.js +4 -3
  153. package/dist/core/session-lifecycle-types.d.ts +15 -0
  154. package/dist/core/session-lifecycle.d.ts +17 -1
  155. package/dist/core/session-lifecycle.js +40 -2
  156. package/dist/core/stop-resume-directive.d.ts +1 -1
  157. package/dist/core/stop-resume-directive.js +10 -1
  158. package/dist/main.js +5 -1
  159. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  160. package/dist/modes/interactive/components/extension-selector.js +4 -0
  161. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  162. package/dist/modes/interactive/interactive-mode.js +35 -8
  163. package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
  164. package/dist/modes/interactive/mcp-local-presenter.js +109 -0
  165. package/dist/modes/interactive/pause-footer.d.ts +5 -0
  166. package/dist/modes/interactive/pause-footer.js +2 -0
  167. package/dist/modes/rpc/connection-handler.d.ts +1 -0
  168. package/dist/modes/rpc/connection-handler.js +7 -1
  169. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  170. package/dist/modes/rpc/host-capabilities.js +2 -1
  171. package/dist/modes/rpc/host-ensure-start.js +13 -2
  172. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  173. package/dist/modes/rpc/host-ensure.js +1 -0
  174. package/dist/modes/rpc/host-handoff.d.ts +2 -0
  175. package/dist/modes/rpc/host-handover-wire.d.ts +2 -0
  176. package/dist/modes/rpc/host-handover-wire.js +1 -0
  177. package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
  178. package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
  179. package/dist/modes/rpc/host-reservations.js +3 -2
  180. package/dist/modes/rpc/host-runner.d.ts +2 -0
  181. package/dist/modes/rpc/host-runner.js +8 -6
  182. package/dist/modes/rpc/host-stalled-evidence.js +2 -1
  183. package/dist/modes/rpc/host-successor.js +11 -1
  184. package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
  185. package/dist/modes/rpc/mcp-form-dialog.js +159 -0
  186. package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
  187. package/dist/modes/rpc/mcp-form-worker.js +167 -0
  188. package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
  189. package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
  190. package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
  191. package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
  192. package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
  193. package/dist/modes/rpc/mcp-presenter-control.js +330 -0
  194. package/dist/modes/rpc/multi-session-host.d.ts +3 -0
  195. package/dist/modes/rpc/multi-session-host.js +36 -1
  196. package/dist/modes/rpc/session-binding.js +6 -1
  197. package/dist/modes/rpc/session-event-writer.d.ts +14 -0
  198. package/dist/modes/rpc/session-event-writer.js +128 -1
  199. package/dist/modes/rpc/session-router-routing.js +1 -0
  200. package/dist/modes/rpc/session-worker-client.d.ts +3 -0
  201. package/dist/modes/rpc/session-worker-client.js +89 -0
  202. package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
  203. package/dist/modes/rpc/session-worker.js +9 -0
  204. package/dist/modes/rpc/tool-child/group-ownership.js +4 -2
  205. package/dist/utils/process-identity.d.ts +7 -0
  206. package/dist/utils/process-identity.js +32 -5
  207. package/docs/extensions.md +5 -0
  208. package/docs/mcp.md +20 -3
  209. package/docs/rpc.md +111 -0
  210. package/package.json +6 -6
  211. package/dist/bundle/chunks/chunk-3VOG6WFU.js +0 -27
  212. package/dist/bundle/chunks/chunk-7ASLQ243.js +0 -2
  213. package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
  214. package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
  215. package/dist/bundle/chunks/chunk-II22E3CV.js +0 -3
  216. package/dist/bundle/chunks/chunk-J5P2VL3Y.js +0 -5
  217. package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
  218. package/dist/bundle/chunks/chunk-XUFUKDKJ.js +0 -5
  219. package/dist/bundle/chunks/cli-main-TABBP55A.js +0 -3
  220. package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
  221. package/dist/bundle/chunks/host-command-OQR4RDTM.js +0 -2
  222. package/dist/bundle/chunks/host-lifecycle-U36U7JTE.js +0 -3
  223. package/dist/bundle/chunks/host-session-command-IGB27ZPJ.js +0 -20
  224. package/dist/bundle/chunks/interactive-mode-DGQNSXUA.js +0 -2
  225. package/dist/bundle/chunks/rpc-mode-SZAJNKKL.js +0 -2
  226. package/dist/bundle/chunks/session-control-endpoint-WEF3XTJR.js +0 -3
@@ -16,6 +16,9 @@ export interface SessionWorkerCallbacks {
16
16
  failure: (error: string) => void;
17
17
  }
18
18
  export declare class SessionWorkerClient {
19
+ private privateBinding?;
20
+ private stopPrivateBinding?;
21
+ private readonly privateOperations;
19
22
  /** The worker's own route to the main-thread Bun.WebView service; released when the worker exits. */
20
23
  private readonly webviewBroker;
21
24
  readonly worker: Worker;
@@ -8,11 +8,13 @@ import { roleConnection } from "./connection-roles.js";
8
8
  import { subscriptionLimits } from "../../core/subscription-limits/limit-store.js";
9
9
  import { SessionWorkerRequests } from "./session-worker-requests.js";
10
10
  import { acknowledge, acknowledgeGrant } from "./session-worker-signals.js";
11
+ import { randomUUID } from "node:crypto";
11
12
  const compiledWorkerEntry = typeof SENPI_RPC_SESSION_WORKER_ENTRY === "string"
12
13
  ? SENPI_RPC_SESSION_WORKER_ENTRY
13
14
  : "./src/modes/rpc/session-worker.ts";
14
15
  export class SessionWorkerClient {
15
16
  constructor(callbacks) {
17
+ this.privateOperations = new Map();
16
18
  /** The worker's own route to the main-thread Bun.WebView service; released when the worker exits. */
17
19
  this.webviewBroker = createWebViewBroker();
18
20
  this.worker = new Worker(isBunBinary
@@ -35,6 +37,11 @@ export class SessionWorkerClient {
35
37
  this.callbacks = callbacks;
36
38
  this.exited = new Promise((resolve, reject) => {
37
39
  this.worker.once("exit", () => {
40
+ this.stopPrivateBinding?.();
41
+ this.privateBinding?.dispose();
42
+ for (const operation of this.privateOperations.values())
43
+ operation.operation.abort();
44
+ this.privateOperations.clear();
38
45
  if (!this.stopped && !this.closeTimer)
39
46
  this.fail("session_worker_exited");
40
47
  this.stopped = true;
@@ -111,9 +118,12 @@ export class SessionWorkerClient {
111
118
  capabilities: options.capabilities ?? [],
112
119
  connection: writer.currentConnection(),
113
120
  roles: options.connectionRoles?.rolesOf(roleConnection(writer.currentConnection())),
121
+ mcpFormsEnforced: writer.mcpPresenters !== undefined,
114
122
  });
115
123
  return {
116
124
  handle: async (command) => {
125
+ if (writer.handleMcpForm(command))
126
+ return;
117
127
  const connection = writer.currentConnection();
118
128
  if ("type" in command &&
119
129
  command.type === "extension_ui_response" &&
@@ -177,6 +187,85 @@ export class SessionWorkerClient {
177
187
  return;
178
188
  }
179
189
  switch (message.type) {
190
+ case "mcp_form_bind": {
191
+ this.stopPrivateBinding?.();
192
+ this.privateBinding?.dispose();
193
+ const writer = this.writer;
194
+ const sessionId = this.sessionId;
195
+ if (!writer?.mcpPresenters || !sessionId) {
196
+ this.fail("private_form_unavailable");
197
+ return;
198
+ }
199
+ const bound = writer.bindMcpForms(sessionId, message.durableId, {}, () => { });
200
+ if (bound instanceof Promise) {
201
+ this.fail("private_form_unavailable");
202
+ return;
203
+ }
204
+ this.privateBinding = writer.getMcpFormBinding(sessionId);
205
+ const binding = this.privateBinding;
206
+ if (!binding) {
207
+ this.fail("private_form_unavailable");
208
+ return;
209
+ }
210
+ const unsubscribe = binding.subscribe?.(() => this.post({
211
+ type: "mcp_form_availability",
212
+ epoch: binding.epoch,
213
+ version: binding.version?.(),
214
+ }));
215
+ this.stopPrivateBinding = () => {
216
+ unsubscribe?.();
217
+ bound?.();
218
+ };
219
+ this.post({ type: "mcp_form_bound", id: message.id, epoch: binding.epoch, version: binding.version?.() });
220
+ return;
221
+ }
222
+ case "mcp_form_ready": {
223
+ const binding = this.privateBinding;
224
+ this.post({
225
+ type: "mcp_form_availability",
226
+ id: message.id,
227
+ epoch: message.epoch,
228
+ version: binding?.epoch === message.epoch ? binding.version?.() : undefined,
229
+ });
230
+ return;
231
+ }
232
+ case "mcp_form_release":
233
+ if (this.privateBinding?.epoch === message.epoch)
234
+ this.stopPrivateBinding?.();
235
+ return;
236
+ case "mcp_form_cancel": {
237
+ const operation = this.privateOperations.get(message.id);
238
+ if (operation?.epoch === message.epoch)
239
+ operation[message.lifetime].abort();
240
+ return;
241
+ }
242
+ case "mcp_form_create": {
243
+ const binding = this.privateBinding;
244
+ const presenter = binding?.epoch === message.epoch && binding.version?.() === message.version
245
+ ? binding.capture()
246
+ : undefined;
247
+ const operation = new AbortController();
248
+ const presentation = new AbortController();
249
+ this.privateOperations.set(message.id, { operation, presentation, epoch: message.epoch });
250
+ const fence = new Int32Array(message.operationFence);
251
+ const result = presenter?.present({
252
+ ...message.input,
253
+ operationSignal: operation.signal,
254
+ presentationSignal: presentation.signal,
255
+ operationCurrent: () => Atomics.load(fence, 0) === 0,
256
+ });
257
+ void Promise.resolve(result).then((outcome) => {
258
+ this.privateOperations.delete(message.id);
259
+ this.post({
260
+ type: "mcp_form_settlement",
261
+ id: message.id,
262
+ epoch: message.epoch,
263
+ settlementId: randomUUID(),
264
+ outcome: outcome ?? { kind: "processing_failure", source: "host_termination", humanAdmission: false },
265
+ });
266
+ });
267
+ return;
268
+ }
180
269
  case "prepared":
181
270
  case "ready":
182
271
  case "result": {
@@ -1,4 +1,5 @@
1
1
  import type { BrowserEngine } from "../../core/browser-engine.ts";
2
+ import type { McpFormInput, McpFormOutcome } from "../../core/mcp-form-types.ts";
2
3
  import type { PromptSurface } from "../../core/dynamic-prompt/types.ts";
3
4
  import type { MirroredLimits } from "../../core/subscription-limits/limit-store.ts";
4
5
  import type { CliRuntimeConfiguration } from "../../main.ts";
@@ -52,6 +53,23 @@ export type HostToSessionWorker = {
52
53
  connection?: string;
53
54
  /** Roles the connection explicitly declared on the host (senpi#3811); the isolate adopts them. */
54
55
  roles?: readonly string[];
56
+ mcpFormsEnforced?: boolean;
57
+ } | {
58
+ type: "mcp_form_bound";
59
+ id: string;
60
+ epoch: string;
61
+ version?: string;
62
+ } | {
63
+ type: "mcp_form_availability";
64
+ epoch: string;
65
+ version?: string;
66
+ id?: string;
67
+ } | {
68
+ type: "mcp_form_settlement";
69
+ id: string;
70
+ epoch: string;
71
+ settlementId: string;
72
+ outcome: McpFormOutcome;
55
73
  } | {
56
74
  type: "command";
57
75
  request: number;
@@ -84,6 +102,29 @@ export type HostToSessionWorker = {
84
102
  type: "close";
85
103
  };
86
104
  export type SessionWorkerToHost = {
105
+ type: "mcp_form_bind";
106
+ id: string;
107
+ durableId: string;
108
+ } | {
109
+ type: "mcp_form_ready";
110
+ id: string;
111
+ epoch: string;
112
+ } | {
113
+ type: "mcp_form_release";
114
+ epoch: string;
115
+ } | {
116
+ type: "mcp_form_cancel";
117
+ id: string;
118
+ epoch: string;
119
+ lifetime: "operation" | "presentation";
120
+ } | {
121
+ type: "mcp_form_create";
122
+ id: string;
123
+ epoch: string;
124
+ version: string;
125
+ operationFence: SharedArrayBuffer;
126
+ input: Omit<McpFormInput, "operationSignal" | "operationCurrent" | "presentationSignal">;
127
+ } | {
87
128
  type: "prepared";
88
129
  request: number;
89
130
  sessionPath: string;
@@ -23,6 +23,7 @@ import { resolveMovedProfile } from "./session-registry-moved-path.js";
23
23
  import { createWorkerCredit } from "./session-worker-credit.js";
24
24
  import { SESSION_WORKER_LIMITS, } from "./session-worker-protocol.js";
25
25
  import { createSessionWorkerRegistry } from "./session-worker-registry.js";
26
+ import { McpFormWorker } from "./mcp-form-worker.js";
26
27
  if (isBunBinary) {
27
28
  const { registerBunRuntimeModules } = await import("../../bun/runtime-modules.js");
28
29
  registerBunRuntimeModules();
@@ -33,6 +34,8 @@ const port = parentPort;
33
34
  if (!port)
34
35
  throw new Error("Session worker requires a parent port");
35
36
  const send = (message) => port.postMessage(message);
37
+ const privateForms = new McpFormWorker(send);
38
+ let privateFormsEnforced = false;
36
39
  function failWorker(error) {
37
40
  send({ type: "failure", error });
38
41
  process.exit(1);
@@ -41,6 +44,9 @@ const { exchange, installWriteReservation } = createWorkerCredit(send, failWorke
41
44
  // The host's key for the same file, so a deleted directory neither throws here nor misses (senpi#2285).
42
45
  installWriteReservation(canonicalSessionPath);
43
46
  class WorkerEventWriter extends SessionEventWriter {
47
+ bindMcpForms(_sessionId, durableId, ui, admit) {
48
+ return privateFormsEnforced ? privateForms.bind(durableId, ui, admit) : undefined;
49
+ }
44
50
  constructor() {
45
51
  super(() => { });
46
52
  this.workerSecurityBridge = true;
@@ -174,6 +180,7 @@ async function handle(message) {
174
180
  if (message.roles !== undefined)
175
181
  connectionRoles.adopt(roleConnection(message.connection), message.roles);
176
182
  const bindingEntry = entry;
183
+ privateFormsEnforced = message.mcpFormsEnforced === true;
177
184
  const createBinding = () => createRpcSessionBinding(message.sessionId, bindingEntry, writer, () => send({ type: "request_close" }), {
178
185
  capabilities: message.capabilities,
179
186
  connectionRoles,
@@ -262,6 +269,8 @@ async function handle(message) {
262
269
  // A turn in this isolate reports into its own store; the host mirrors it so its clients see it (senpi#3695).
263
270
  subscriptionLimits.subscribeMirror((limits) => send({ type: "subscription_limits", limits }));
264
271
  port.on("message", (message) => {
272
+ if (privateForms.receive(message))
273
+ return;
265
274
  void handle(message).catch((cause) => {
266
275
  const error = cause instanceof Error ? cause.message : String(cause);
267
276
  if (error.startsWith("session_worker_")) {
@@ -7,7 +7,7 @@
7
7
  * own group. A `detached` entry is never swept; the command explicitly opted out of the child's lifetime.
8
8
  */
9
9
  import { execFile } from "node:child_process";
10
- import { processIsLive, processStartTimeMs, readProcessStartTime, sameProcessStartMs, } from "../../../utils/process-identity.js";
10
+ import { processIsRunning, processStartTimeMs, readProcessStartTime, sameProcessStartMs, } from "../../../utils/process-identity.js";
11
11
  export async function classifyGroup(group, ownGroup, probes) {
12
12
  if (group.lifetime === "detached")
13
13
  return "detached";
@@ -70,7 +70,9 @@ function groupHasMembers(pgid) {
70
70
  }
71
71
  export const defaultGroupProbes = {
72
72
  groupHasMembers,
73
- isAlive: processIsLive,
73
+ // An exited, unreaped leader (state Z) has no start identity left to compare, so it must read as exited:
74
+ // its orphaned members keep the group, and the group is swept (senpi#3020, senpi#4119).
75
+ isAlive: processIsRunning,
74
76
  startOf: (pid) => readProcessStartTime(pid),
75
77
  ownGroup: () => processGroupOf(process.pid),
76
78
  signalGroup: (pgid, signal) => {
@@ -48,6 +48,13 @@ timeZone?: "UTC"): Promise<ProcessIdentityResult>;
48
48
  export declare function stopValidatedPid(pidFile: DaemonPidFile, signal: NodeJS.Signals): Promise<void>;
49
49
  export declare function waitForGone(pidFile: DaemonPidFile, timeoutMs: number): Promise<boolean>;
50
50
  export declare function readProcessStartTime(pid: number, platform?: NodeJS.Platform, timeoutMs?: number): Promise<string | undefined>;
51
+ /**
52
+ * `processIsLive` minus processes that have exited but are not reaped yet (state Z): a SIGKILLed host
53
+ * stays a zombie until its parent's loop, or init, waits on it, and `kill(pid, 0)` still succeeds for it
54
+ * (senpi#4119). One `ps` snapshot decides; a probe that fails any other way reads as running, so a
55
+ * starved probe on a live process is never taken for a dead one (#1609).
56
+ */
57
+ export declare function processIsRunning(pid: number): boolean;
51
58
  export declare function processIsLive(pid: number): boolean;
52
59
  /**
53
60
  * Wait for a spawned pid's process identity.
@@ -1,4 +1,4 @@
1
- import { execFile } from "node:child_process";
1
+ import { execFile, execFileSync } from "node:child_process";
2
2
  const WINDOWS_FILETIME_EPOCH = 116444736000000000n;
3
3
  export const PROCESS_START_TOLERANCE_MS = 3_000;
4
4
  /** FILETIME and C/localized lstart/ISO identities share one millisecond representation. */
@@ -80,7 +80,7 @@ export class ProcessIdentityUnreadableError extends Error {
80
80
  * probe is retried within a bounded budget and only then surfaces as
81
81
  * ProcessIdentityUnreadableError. It never leaks the raw probe error as a verdict.
82
82
  */
83
- export async function processMatchesPidFile(pidFile, readStartTime = readProcessStartTime, isLive = processIsLive, retry = {}) {
83
+ export async function processMatchesPidFile(pidFile, readStartTime = readProcessStartTime, isLive = processIsRunning, retry = {}) {
84
84
  // A record without an identity guard answers only the liveness half of the question: a pid
85
85
  // that is gone is gone, and a live one stays unknown so it is never claimed or signalled.
86
86
  if (pidFile.processStartTime === null) {
@@ -129,7 +129,9 @@ timeZone) {
129
129
  `$process = Get-CimInstance Win32_Process -Filter "ProcessId=${String(pid)}" -ErrorAction Stop; if ($null -eq $process) { Write-Output '__SENPI_ABSENT__'; exit 0 }; $process.CreationDate.ToFileTimeUtc().ToString("D", [Globalization.CultureInfo]::InvariantCulture)`,
130
130
  ],
131
131
  }
132
- : { executable: "ps", args: ["-o", "lstart=", "-p", String(pid)] };
132
+ : // State and start time from ONE snapshot: an exited, unreaped process (state Z) keeps its start
133
+ // time but runs nothing, so it has no identity to match (senpi#4119).
134
+ { executable: "ps", args: ["-o", "stat=,lstart=", "-p", String(pid)] };
133
135
  return new Promise((resolve) => {
134
136
  const effectiveTimeoutMs = timeoutMs ?? (platform === "win32" ? 1_000 : undefined);
135
137
  execFile(command.executable, command.args, {
@@ -151,9 +153,13 @@ timeZone) {
151
153
  resolve({ kind: "error", error });
152
154
  return;
153
155
  }
154
- const output = stdout.trim();
155
- if (output === "__SENPI_ABSENT__")
156
+ const snapshot = stdout.trim();
157
+ if (snapshot === "__SENPI_ABSENT__")
156
158
  return resolve({ kind: "absent" });
159
+ const state = platform === "win32" ? "" : (snapshot.split(/\s+/, 1)[0] ?? "");
160
+ if (state.startsWith("Z"))
161
+ return resolve({ kind: "absent" });
162
+ const output = platform === "win32" ? snapshot : snapshot.slice(state.length).trim();
157
163
  if (!output || (platform === "win32" && !/^\d+$/.test(output)))
158
164
  return resolve({ kind: "error", error: new Error("invalid process identity output") });
159
165
  resolve({ kind: "present", identity: timeZone && platform !== "win32" ? `${output} UTC` : output });
@@ -191,6 +197,27 @@ export async function readProcessStartTime(pid, platform = process.platform, tim
191
197
  return result.kind === "present" ? result.identity : undefined;
192
198
  });
193
199
  }
200
+ /**
201
+ * `processIsLive` minus processes that have exited but are not reaped yet (state Z): a SIGKILLed host
202
+ * stays a zombie until its parent's loop, or init, waits on it, and `kill(pid, 0)` still succeeds for it
203
+ * (senpi#4119). One `ps` snapshot decides; a probe that fails any other way reads as running, so a
204
+ * starved probe on a live process is never taken for a dead one (#1609).
205
+ */
206
+ export function processIsRunning(pid) {
207
+ if (!processIsLive(pid))
208
+ return false;
209
+ if (process.platform === "win32")
210
+ return true;
211
+ try {
212
+ const state = execFileSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8", timeout: 2_000 });
213
+ return !state.trim().startsWith("Z");
214
+ }
215
+ catch (error) {
216
+ if (error instanceof Error && "status" in error && error.status === 1)
217
+ return false;
218
+ return true;
219
+ }
220
+ }
194
221
  export function processIsLive(pid) {
195
222
  try {
196
223
  process.kill(pid, 0);
@@ -3507,6 +3507,11 @@ if (confirmed) {
3507
3507
 
3508
3508
  #### Manual Dismissal with AbortSignal
3509
3509
 
3510
+ Local terminal `select`, `confirm`, and `input` dialogs also accept
3511
+ `ephemeral: true`. Their visible contents remain local; they emit no generic
3512
+ blocked events, replacement labels, identifiers, or pending markers. This option is
3513
+ not an RPC field or permission to present MCP input on another session.
3514
+
3510
3515
  For more control (e.g., to distinguish timeout from user cancel), use `AbortSignal`:
3511
3516
 
3512
3517
  ```typescript
package/docs/mcp.md CHANGED
@@ -393,9 +393,26 @@ above.
393
393
  notice.
394
394
  - Every server prompt registers as `/mcp:<server>:<prompt>`; invoking it
395
395
  collects the prompt's arguments and drops the rendered text into the editor.
396
- - Servers may ask questions mid-call (elicitation, form mode): senpi walks the
397
- requested fields through input dialogs; in non-interactive runs the request
398
- is declined cleanly.
396
+ - Legacy servers may ask form questions mid-call through the existing terminal
397
+ or RPC dialog channel.
398
+ - On MCP 2026-07-28 connections, `tools/call`, `resources/read`, and `prompts/get`
399
+ can return `input_required`. Senpi collects form input privately in the
400
+ originating terminal session, lets you review and edit it, and continues the
401
+ operation with a new request ID. Only the final result reaches the model or editor.
402
+ Continue does not submit a form: even empty forms require Submit. Decline is
403
+ an explicit human refusal; dismissal of a presented form is Cancel.
404
+ - RPC, print, and other runs without an originating terminal presenter do not
405
+ advertise modern elicitation. An unsolicited input request terminates the
406
+ operation locally without fabricating an answer or retrying incomplete input.
407
+ State-only continuations need no human and remain supported.
408
+ - Each operation allows eight input rounds and five minutes of total consent
409
+ time, including queueing. The overall deadline also includes network time
410
+ and cannot be extended by progress. Caller cancellation, session closure,
411
+ or observed authorization invalidation dismisses pending input without a retry.
412
+ Authorization uses the same observed-revocation boundary as ordinary MCP
413
+ requests. Unsupported client features terminate locally.
414
+ - Continuation state and drafts are not persisted or resumed after restart.
415
+ MRTR does not enable the Tasks extension.
399
416
 
400
417
  ## Auth
401
418
 
package/docs/rpc.md CHANGED
@@ -159,6 +159,17 @@ The shared `SessionLifecycle` type is exported by `@code-yeongyu/senpi`:
159
159
  with reason `provider_error`, `tool_error`, `task_error`, `lost`, or `interrupted`.
160
160
  - A standing agent self-settle intent adds `settleDecision: { decisionId }` to the
161
161
  persisted `done` seal, independently of the completion label.
162
+ - Optional `lastStop: { cause, runId, at, epoch, deadline?, guard?, todo?, abortSource?, persisted? }`
163
+ says why the run `runId` stopped. `cause` is `deadline`, `goal_guard`, `todo_guard`, `aborted`,
164
+ or `no_wake`; `deadline` is `inactivity` or `max_runtime`, `guard` is the goal guard reason as
165
+ recorded, `todo` is `cap`, and `abortSource` is `user`, `system`, or `provider`. `persisted: false`
166
+ appears only when the deadline-stop entry could not be written and is independent of the
167
+ snapshot's `persisted`. `lastStop` describes the last run's historical stop, so a `no_wake`
168
+ stop remains even if a wake source later appears. It is cleared when the next run starts
169
+ and never names a later run. A fact refresh with no bound turn end never sets `lastStop`;
170
+ between runs, a refresh while the last end is still bound may set or update that run's stop.
171
+ Goal guards are captured during their bound end's own processing, never inherited from
172
+ another run. Provider errors and policy rejections carry no `lastStop`, even if a deadline fired.
162
173
 
163
174
  Clients must not notify or settle while `restoring` is true or `persisted` is false.
164
175
  Use `completionId` to deduplicate root completion/failure, not `agent_settled` or
@@ -5425,6 +5436,106 @@ A provider holds logins of one kind. If it had an OAuth login, the key replaces
5425
5436
 
5426
5437
  ## Extension UI Protocol
5427
5438
 
5439
+ Without presenter enforcement, MCP 2026-07-28 multi-round-trip form answering is terminal-only in v1.
5440
+ RPC-originated modern requests advertise no elicitation and terminate locally
5441
+ if input is required; they never fabricate a decline or retry incomplete input.
5442
+ Client roles and capability declarations do not enable this form channel.
5443
+ Existing legacy MCP dialogs and their RPC request/response shapes remain unchanged.
5444
+
5445
+ ### Opt-in private MCP forms
5446
+
5447
+ A multi-session RPC host or interactive terminal can opt in with `--mcp-presenter-enforcement`. This is a
5448
+ deployment option, not a project setting. It defaults to off; existing desktop and
5449
+ gateway clients keep their current behavior until they implement the private
5450
+ presenter protocol. Without this option, terminal forms retain their existing queue,
5451
+ legacy origin rules, and legacy stderr diagnostics. Enforced terminal forms also
5452
+ queue, and a collector failure ends only its own form, not the terminal's grant.
5453
+ All UI contexts in one terminal session binding share its presenter authority.
5454
+ Without enforcement, each real select/input answer retains its existing human
5455
+ admission timing; the form collector does not add a second final admission.
5456
+ Ephemeral selects also keep the scheduled `requestRender()` path without enforcement.
5457
+ With enforcement, only the authority's winning whole-form action admits once.
5458
+ The host advertises `mcp_form_presenter_v1` only when enforcement is enabled;
5459
+ the capability reports protocol support, not authority to present or answer a form.
5460
+
5461
+ The native launch owner enrolls an Ed25519 public key through an inherited private
5462
+ bootstrap channel. Descriptor 4 is reserved for this channel; descriptor 3 remains
5463
+ the separate owner-lifetime channel. Enrollment and management proofs sign the
5464
+ host's exact returned transcript. No secret is passed in an argument, environment
5465
+ variable, launch specification, or session state. Reusing a host does not enroll the
5466
+ caller, and a successor requires fresh enrollment and fresh grants.
5467
+ An invalid or lost bootstrap channel leaves the host without a presenter; it does
5468
+ not terminate the shared daemon or its lifetime channel. The helper remains lazy
5469
+ to preserve the supervisor's startup import budget, with import and relay failures
5470
+ contained at that boundary. Conditional idle handover
5471
+ is owned by the running host after the CLI returns: that host relays the successor's
5472
+ fresh challenge over the existing authenticated launch-owner channel. The CLI's
5473
+ bootstrap stream is never serialized into `begin_handover`.
5474
+
5475
+ `mcp_presenter_context` returns the caller's current connection nonce and attachment
5476
+ context. These identifiers confer no authority. Acquisition and revocation use the
5477
+ separate authenticated management endpoint, with a fresh challenge for each
5478
+ `mcp_presenter_assign`, `mcp_presenter_acquire`, or `mcp_presenter_revoke` command.
5479
+ Enrollment alone assigns no sessions. Before acquisition, the trusted native
5480
+ supervisor signs `mcp_presenter_assign` with `sessionId`, the current `bindingEpoch`,
5481
+ and `purpose: "mcp_form"` for a session it opened, or one the user explicitly adopted
5482
+ through the trusted host-owner UI. Ordinary RPC attachment cannot assign a session.
5483
+ Assignment belongs only to that enrolled principal and live binding; re-enrollment
5484
+ or binding replacement requires a new assignment. It is not persisted.
5485
+ An acquisition also requires the connection's `interactive_user` and
5486
+ `mcp_form_presenter_v1` capabilities, and binds the
5487
+ current host, session, binding epoch, connection nonce, attachment ID and epoch, and
5488
+ `purpose: "mcp_form"`. Only one holder is active for a session binding.
5489
+ Concurrent forms, including worker-origin forms, wait in the host's abort-aware
5490
+ FIFO for that holder. Waiting consumes the original deadline; timeout, cancellation,
5491
+ or revocation before display never fabricates a human response.
5492
+ Enforced legacy connections observe presenter readiness and reconnect when their
5493
+ negotiated form capability becomes stale, including a grant issued after connect.
5494
+ Revoking the last ready holder removes the capability on the new connection.
5495
+ A shared physical connection advertises forms when at least one attached owner is
5496
+ ready; each request still requires its own origin's grant. An ungranted owner cannot
5497
+ borrow another owner's presenter. Readiness changes during initialization are
5498
+ rechecked before the connection is published. Modern MRTR connections do not
5499
+ reconnect for this purpose because their capability metadata is per request.
5500
+
5501
+ Legacy elicitation has no reliable parent tool-call identifier in the received SDK
5502
+ handler metadata. With multiple `tools/call` operations in flight on one physical
5503
+ legacy client, enforced elicitation fails closed with JSON-RPC `-32603` and
5504
+ `data.reason: "ambiguous_legacy_origin"`. The error explains that concurrent legacy
5505
+ calls make the origin ambiguous and recommends sequential calls or an MRTR server.
5506
+ No form is displayed and no human answer is admitted. Without enforcement, legacy
5507
+ clients keep the SDK request method, origin handling, and concurrent-call behavior.
5508
+
5509
+ Worker binding waits at most five seconds for the host's acknowledgement; expiry
5510
+ fails with `private_form_unavailable`, removes the pending waiter, and releases any
5511
+ late host binding. Worker readiness events feed the same legacy renegotiation path.
5512
+ Under enforcement, server stderr remains available as redacted diagnostic records
5513
+ with allowlisted system error codes. Arbitrary text is omitted because a server can
5514
+ echo private messages, schema labels, or answers; ordinary legacy stderr is unchanged.
5515
+
5516
+ The granted connection receives `mcp_form_request` privately. Its envelope contains
5517
+ `sessionId`, `bindingEpoch`, `attachmentEpoch`, `grantId`, `dialogId`, `dialogToken`,
5518
+ and `formRevision`; the request also carries `server` (`label` and `source`),
5519
+ `message`, `schema`, and `deadlineAtMs`. The presenter returns that exact envelope
5520
+ with `mcp_form_presented` only after rendering, then `mcp_form_response` with
5521
+ `action: "accept" | "decline" | "cancel"` and accepted `content` when applicable.
5522
+ `mcp_form_sync` uses the current session, binding epoch, attachment epoch, and grant
5523
+ to recover the holder's still-live form. It is not a public replay API.
5524
+
5525
+ Unknown and stolen-dialog submissions from a non-holder both return
5526
+ `presenter_not_authorized` before private dialog lookup. The host validates a whole
5527
+ form before admitting a human answer, and a synchronous terminal latch accepts only
5528
+ one outcome. `mcp_form_resolved` carries the session, grant, dialog, and a reason:
5529
+ `answered`, `declined`, `cancelled`, `processing_failed`, or `operation_aborted`.
5530
+
5531
+ No presenter, or loss before actual presentation, is a processing failure. Only
5532
+ explicit human refusal produces decline. Dismissal after presentation is cancel;
5533
+ host timeout or presentation loss after presentation also cancels, without human
5534
+ admission. Operation abort terminates the operation. Private records, schemas,
5535
+ tokens, drafts, and answers never enter general extension UI events, session
5536
+ snapshots, replay, or ordinary output queues. Native desktop registration, WSL
5537
+ relay, and a gateway form page require separate client implementation.
5538
+
5428
5539
  Extensions can request user interaction via `ctx.ui.select()`, `ctx.ui.confirm()`, etc. In RPC mode, these are translated into a request/response sub-protocol on top of the base command/event flow.
5429
5540
 
5430
5541
  There are two categories of extension UI methods:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi",
3
- "version": "2026.10.25-1",
3
+ "version": "2026.10.25-3",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -71,9 +71,9 @@
71
71
  },
72
72
  "dependencies": {
73
73
  "@earendil-works/chord": "0.99.1",
74
- "@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.25-1",
75
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.25-1",
76
- "@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.25-1",
74
+ "@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.25-3",
75
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.25-3",
76
+ "@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.25-3",
77
77
  "@silvia-odwyer/photon-node": "0.3.4",
78
78
  "chalk": "6.0.0",
79
79
  "cross-spawn": "7.0.6",
@@ -92,8 +92,8 @@
92
92
  "yaml": "2.9.1",
93
93
  "@anthropic-ai/claude-agent-sdk": "0.3.306",
94
94
  "@anthropic-ai/sdk": "0.127.0",
95
- "@code-yeongyu/senpi-codemode": "2026.10.25-1",
96
- "@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.25-1",
95
+ "@code-yeongyu/senpi-codemode": "2026.10.25-3",
96
+ "@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.25-3",
97
97
  "@modelcontextprotocol/sdk": "1.32.1",
98
98
  "@mozilla/readability": "0.6.0",
99
99
  "linkedom": "0.18.13",