@code-yeongyu/senpi 2026.10.25-2 → 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.
- package/CHANGELOG.md +20 -0
- package/dist/bundle/chunks/{args-JXULI4EH.js → args-B444OQBK.js} +1 -1
- package/dist/bundle/chunks/{auth-4NUNUSTF.js → auth-C77UQA55.js} +1 -1
- package/dist/bundle/chunks/chunk-6O4SEW2W.js +5 -0
- package/dist/bundle/chunks/{chunk-Y5OTHMXO.js → chunk-7CNOCJCN.js} +1 -1
- package/dist/bundle/chunks/{chunk-JRXKLIEQ.js → chunk-7W7OJDIY.js} +4 -4
- package/dist/bundle/chunks/chunk-AX72VNRS.js +27 -0
- package/dist/bundle/chunks/{chunk-LYGAV6KU.js → chunk-C5UFJ7B3.js} +1 -1
- package/dist/bundle/chunks/{chunk-G2UETS2R.js → chunk-CNL6QSUB.js} +4 -4
- package/dist/bundle/chunks/{chunk-2EFVQWXZ.js → chunk-FFQOEC76.js} +3 -3
- package/dist/bundle/chunks/chunk-H3HNMY2W.js +13 -0
- package/dist/bundle/chunks/{chunk-W2BKZBYJ.js → chunk-HVJI5T75.js} +1 -1
- package/dist/bundle/chunks/{chunk-YN7ELQ43.js → chunk-JA2R2ZSE.js} +1 -1
- package/dist/bundle/chunks/chunk-JPGT64RV.js +2 -0
- package/dist/bundle/chunks/chunk-KXB5WIS5.js +4 -0
- package/dist/bundle/chunks/chunk-L73UYAH5.js +3 -0
- package/dist/bundle/chunks/chunk-LTU6UFEX.js +2 -0
- package/dist/bundle/chunks/{chunk-KUE4AQN6.js → chunk-MJ5NNWX4.js} +1 -1
- package/dist/bundle/chunks/{chunk-WFXUCVIR.js → chunk-O3Q2ET6W.js} +2 -5
- package/dist/bundle/chunks/{chunk-MXZPAUFB.js → chunk-ODTGC6YU.js} +1 -1
- package/dist/bundle/chunks/chunk-P3M2PF6K.js +27 -0
- package/dist/bundle/chunks/{chunk-VNN2V74Y.js → chunk-SWJXRC2I.js} +1 -1
- package/dist/bundle/chunks/{chunk-RXKNOEQQ.js → chunk-UQNRW3VU.js} +1 -1
- package/dist/bundle/chunks/{chunk-U67ML6CQ.js → chunk-WD6WBYC5.js} +1 -1
- package/dist/bundle/chunks/chunk-WJOT3DFL.js +2 -0
- package/dist/bundle/chunks/{chunk-XBUSVKWC.js → chunk-WLHNEUGD.js} +1 -1
- package/dist/bundle/chunks/chunk-YI7SPFDN.js +5 -0
- package/dist/bundle/chunks/{chunk-J2U5VDG7.js → chunk-YRF4I43T.js} +28 -28
- package/dist/bundle/chunks/chunk-Z3UYNSVR.js +3 -0
- package/dist/bundle/chunks/cli-main-HEKFFB5S.js +3 -0
- package/dist/bundle/chunks/{client-KWTLTKVV.js → client-WTYEICLH.js} +3 -3
- package/dist/bundle/chunks/dispatch-NYAULNVW.js +9 -0
- package/dist/bundle/chunks/{help-fast-path-ZHZGXAUG.js → help-fast-path-3Z26DQSV.js} +1 -1
- package/dist/bundle/chunks/host-command-Z3WPHJOW.js +2 -0
- package/dist/bundle/chunks/host-lifecycle-ONQIMWMX.js +3 -0
- package/dist/bundle/chunks/host-lifecycle.js +7 -5
- package/dist/bundle/chunks/host-session-command-J65ENSV5.js +20 -0
- package/dist/bundle/chunks/{host-socket-request-AS2OCRLP.js → host-socket-request-3CWE4265.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-DEPQC6TX.js +2 -0
- package/dist/bundle/chunks/mcp-presenter-bootstrap-XUSCQVRC.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-VTTY5MFT.js → multi-session-host-YI2WZOET.js} +12 -9
- package/dist/bundle/chunks/{package-manager-cli-DZFOC3MF.js → package-manager-cli-GHZPJMRF.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-U7YQMC7H.js → rotation-stream-ZPVCFOVD.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-ORHBGBBV.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-B5NQ4ZVH.js → rpc-monitor-source-FFEQS744.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-SLISTG4V.js → schedule-command-YWG6D6ZF.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-36HAQSQO.js +3 -0
- package/dist/bundle/chunks/{session-picker-465FVXQG.js → session-picker-25H5KNYP.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +278 -264
- package/dist/bundle/chunks/{stdio-IEC635SY.js → stdio-YXDKKKQZ.js} +1 -1
- package/dist/bundle/chunks/{streamableHttp-KJI5KE4K.js → streamableHttp-DKIBUWVY.js} +1 -1
- package/dist/bundle/chunks/{types-LLNV23LW.js → types-BJUEQ4II.js} +1 -1
- package/dist/bundle/chunks/zod-compat-RJDXCU7S.js +2 -0
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +7 -5
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +9 -0
- package/dist/cli/host-command-args.d.ts +1 -0
- package/dist/cli/host-command-args.js +7 -1
- package/dist/cli/host-command.js +3 -1
- package/dist/core/agent-session.js +22 -7
- package/dist/core/extensions/builtin/goal/index.js +1 -0
- package/dist/core/extensions/builtin/goal/lifecycle-facts.d.ts +3 -0
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/catalog.js +2 -0
- package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/connection.js +46 -1
- package/dist/core/extensions/builtin/mcp/elicitation.d.ts +6 -1
- package/dist/core/extensions/builtin/mcp/elicitation.js +56 -2
- package/dist/core/extensions/builtin/mcp/expose/call.js +6 -2
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/expose/session.js +7 -0
- package/dist/core/extensions/builtin/mcp/health.js +5 -0
- package/dist/core/extensions/builtin/mcp/host-registry.js +7 -1
- package/dist/core/extensions/builtin/mcp/index.js +2 -0
- package/dist/core/extensions/builtin/mcp/modern/client.d.ts +5 -0
- package/dist/core/extensions/builtin/mcp/modern/client.js +15 -0
- package/dist/core/extensions/builtin/mcp/modern/consent-queue.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +59 -0
- package/dist/core/extensions/builtin/mcp/modern/diagnostics.d.ts +4 -0
- package/dist/core/extensions/builtin/mcp/modern/diagnostics.js +39 -0
- package/dist/core/extensions/builtin/mcp/modern/dispatch.js +21 -11
- package/dist/core/extensions/builtin/mcp/modern/elicitation.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/modern/elicitation.js +145 -0
- package/dist/core/extensions/builtin/mcp/modern/errors.d.ts +21 -0
- package/dist/core/extensions/builtin/mcp/modern/errors.js +23 -0
- package/dist/core/extensions/builtin/mcp/modern/form-schema.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/modern/form-schema.js +153 -0
- package/dist/core/extensions/builtin/mcp/modern/http-fetch.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/modern/http-fetch.js +8 -2
- package/dist/core/extensions/builtin/mcp/modern/inputs.d.ts +11 -0
- package/dist/core/extensions/builtin/mcp/modern/inputs.js +65 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +11 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +109 -0
- package/dist/core/extensions/builtin/mcp/modern/request.d.ts +20 -0
- package/dist/core/extensions/builtin/mcp/modern/request.js +292 -0
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +54 -6
- package/dist/core/extensions/builtin/mcp/modern/transport.js +80 -14
- package/dist/core/extensions/builtin/mcp/modern/wire.js +11 -7
- package/dist/core/extensions/builtin/mcp/prompts.d.ts +5 -1
- package/dist/core/extensions/builtin/mcp/prompts.js +6 -1
- package/dist/core/extensions/builtin/mcp/request-origin.d.ts +47 -0
- package/dist/core/extensions/builtin/mcp/request-origin.js +59 -0
- package/dist/core/extensions/builtin/mcp/resources.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/resources.js +19 -4
- package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +3 -1
- package/dist/core/extensions/builtin/mcp/sdk.lazy.js +3 -1
- package/dist/core/extensions/builtin/mcp/service-register.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/service-register.js +3 -0
- package/dist/core/extensions/builtin/mcp/service-types.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/service.js +79 -4
- package/dist/core/extensions/builtin/mcp/shared-connection.js +36 -2
- package/dist/core/extensions/builtin/mcp/shared-lease.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/shared-lease.js +9 -0
- package/dist/core/extensions/builtin/mcp/transport-sdk.d.ts +3 -2
- package/dist/core/extensions/builtin/mcp/transport-sdk.js +56 -25
- package/dist/core/extensions/builtin/mcp/wrap.d.ts +7 -0
- package/dist/core/extensions/builtin/mcp/wrap.js +6 -0
- package/dist/core/extensions/runner.js +3 -2
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/lifecycle-ledger.d.ts +1 -0
- package/dist/core/lifecycle-ledger.js +7 -2
- package/dist/core/lifecycle-record.js +3 -0
- package/dist/core/lifecycle-session-binding.d.ts +11 -1
- package/dist/core/lifecycle-session-binding.js +29 -2
- package/dist/core/lifecycle-stop.d.ts +41 -0
- package/dist/core/lifecycle-stop.js +92 -0
- package/dist/core/mcp-form-types.d.ts +89 -0
- package/dist/core/mcp-form-types.js +23 -0
- package/dist/core/mcp-mrtr-presentations.d.ts +15 -0
- package/dist/core/mcp-mrtr-presentations.js +17 -0
- package/dist/core/session-lifecycle-types.d.ts +15 -0
- package/dist/core/session-lifecycle.d.ts +17 -1
- package/dist/core/session-lifecycle.js +40 -2
- package/dist/main.js +5 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
- package/dist/modes/interactive/components/extension-selector.js +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.js +34 -8
- package/dist/modes/interactive/mcp-local-presenter.d.ts +5 -0
- package/dist/modes/interactive/mcp-local-presenter.js +109 -0
- package/dist/modes/rpc/connection-handler.d.ts +1 -0
- package/dist/modes/rpc/connection-handler.js +7 -1
- package/dist/modes/rpc/host-capabilities.d.ts +1 -0
- package/dist/modes/rpc/host-capabilities.js +2 -1
- package/dist/modes/rpc/host-ensure-start.js +13 -2
- package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
- package/dist/modes/rpc/host-ensure.js +1 -0
- package/dist/modes/rpc/host-handoff.d.ts +2 -0
- package/dist/modes/rpc/host-handover-wire.d.ts +2 -0
- package/dist/modes/rpc/host-handover-wire.js +1 -0
- package/dist/modes/rpc/host-lifecycle-launch.d.ts +1 -0
- package/dist/modes/rpc/host-lifecycle-launch.js +43 -2
- package/dist/modes/rpc/host-runner.d.ts +2 -0
- package/dist/modes/rpc/host-runner.js +8 -6
- package/dist/modes/rpc/host-successor.js +11 -1
- package/dist/modes/rpc/mcp-form-dialog.d.ts +45 -0
- package/dist/modes/rpc/mcp-form-dialog.js +159 -0
- package/dist/modes/rpc/mcp-form-worker.d.ts +14 -0
- package/dist/modes/rpc/mcp-form-worker.js +167 -0
- package/dist/modes/rpc/mcp-presenter-authority.d.ts +53 -0
- package/dist/modes/rpc/mcp-presenter-authority.js +309 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.d.ts +8 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.js +111 -0
- package/dist/modes/rpc/mcp-presenter-control.d.ts +24 -0
- package/dist/modes/rpc/mcp-presenter-control.js +330 -0
- package/dist/modes/rpc/multi-session-host.d.ts +3 -0
- package/dist/modes/rpc/multi-session-host.js +36 -1
- package/dist/modes/rpc/session-binding.js +6 -1
- package/dist/modes/rpc/session-event-writer.d.ts +14 -0
- package/dist/modes/rpc/session-event-writer.js +128 -1
- package/dist/modes/rpc/session-router-routing.js +1 -0
- package/dist/modes/rpc/session-worker-client.d.ts +3 -0
- package/dist/modes/rpc/session-worker-client.js +89 -0
- package/dist/modes/rpc/session-worker-protocol.d.ts +41 -0
- package/dist/modes/rpc/session-worker.js +9 -0
- package/docs/extensions.md +5 -0
- package/docs/mcp.md +20 -3
- package/docs/rpc.md +111 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-B6TPQDBW.js +0 -5
- package/dist/bundle/chunks/chunk-CDRFFHLL.js +0 -4
- package/dist/bundle/chunks/chunk-HPCS4HM4.js +0 -27
- package/dist/bundle/chunks/chunk-KJZAY5YH.js +0 -27
- package/dist/bundle/chunks/chunk-OI4NMCKP.js +0 -3
- package/dist/bundle/chunks/chunk-SCVRKIFK.js +0 -2
- package/dist/bundle/chunks/cli-main-SWKFMTZQ.js +0 -3
- package/dist/bundle/chunks/dispatch-DEP4PEDZ.js +0 -10
- package/dist/bundle/chunks/host-command-BC6UWP33.js +0 -2
- package/dist/bundle/chunks/host-lifecycle-TN5NEGNF.js +0 -3
- package/dist/bundle/chunks/host-session-command-MGVBLBRV.js +0 -20
- package/dist/bundle/chunks/interactive-mode-SCSROHD5.js +0 -2
- package/dist/bundle/chunks/rpc-mode-TOXYD4XA.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-HFFMBALG.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_")) {
|
package/docs/extensions.md
CHANGED
|
@@ -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
|
-
-
|
|
397
|
-
|
|
398
|
-
|
|
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-
|
|
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-
|
|
75
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.25-
|
|
76
|
-
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.25-
|
|
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-
|
|
96
|
-
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.25-
|
|
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",
|
|
@@ -1,5 +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{attachMcpGrantPresentation,effectStatusRecord,sessionQuestionBridges}from"./chunk-JRXKLIEQ.js";import{RPC_ERROR_INVALID_SESSION_CONTEXT,RPC_ERROR_INVALID_SUBSCRIPTION,RPC_ERROR_SESSION_NOT_ATTACHED,RPC_ERROR_SUBSCRIPTION_LIMIT,RPC_ERROR_UNKNOWN_SUBSCRIPTION,RPC_ERROR_UNSUPPORTED_TRANSPORT}from"./chunk-GY5MR5KR.js";import{omitInlineMedia}from"./chunk-EHQXLHRB.js";import{MEDIA_PLACEHOLDERS_CAPABILITY,hostGeneration,hostInstanceId}from"./chunk-W2BKZBYJ.js";import{serializeJsonLine}from"./chunk-WFXUCVIR.js";import{AsyncLocalStorage}from"node:async_hooks";var RpcSecurityPresentations=class{#sessions=new Map;#attachments=new Map;constructor(fanout,inConnection,flush){this.fanout=fanout,this.inConnection=inConnection,this.flush=flush}bind(sessionId,durableId,notices,select){let previous=this.#sessions.get(sessionId);this.#stop(previous);let state={durableId,select,snapshot:notices.snapshot,registrations:new Map,grants:new Map,replayed:previous?.durableId===durableId?previous.replayed:new Map};this.#sessions.set(sessionId,state),state.stop=notices.subscribe(snapshot=>this.acceptSnapshot(sessionId,snapshot));for(let id of this.fanout.attachedConnections(sessionId))this.#register(id,state);return()=>{this.#sessions.get(sessionId)===state&&(this.#stop(state),this.#sessions.delete(sessionId))}}acceptSnapshot(sessionId,snapshot){let state=this.#sessions.get(sessionId);state===void 0&&(state={snapshot,registrations:new Map,grants:new Map,replayed:new Map},this.#sessions.set(sessionId,state)),state.snapshot=snapshot;for(let id of this.fanout.attachedConnections(sessionId))this.#replay(sessionId,id,snapshot)}attach(id,sessionId){let attached=this.#attachments.get(id)??new Set;if(attached.has(sessionId))return;attached.add(sessionId),this.#attachments.set(id,attached);let state=this.#sessions.get(sessionId);state!==void 0&&(this.#register(id,state),this.#replay(sessionId,id,state.snapshot))}detach(id,sessionId){this.#attachments.get(id)?.delete(sessionId),this.#attachments.get(id)?.size===0&&this.#attachments.delete(id);let state=this.#sessions.get(sessionId);state?.registrations.get(id)?.(),state?.registrations.delete(id),state?.replayed.delete(id),state!==void 0&&this.#cancelFor(state,id)}dropConnection(id){for(let sessionId of[...this.#attachments.get(id)??[]])this.detach(id,sessionId)}capabilitiesChanged(id){for(let sessionId of this.#attachments.get(id)??[]){let state=this.#sessions.get(sessionId);state!==void 0&&(this.#register(id,state),this.fanout.connectionHas(id,"grant_select")||this.#cancelFor(state,id))}}routeWorkerGrant(sessionId,requestId,preferred,fallbackCapabilities,cancel){let attached=this.fanout.attachedConnections(sessionId),connection=attached.includes(preferred??"")&&this.fanout.connectionHas(preferred,"grant_select")?preferred:attached.find(id=>this.fanout.connectionHas(id,"grant_select"));if(!(connection!==void 0||this.fanout.isEmpty()&&fallbackCapabilities.includes("grant_select")))return cancel(),{accepted:!1};let state=this.#sessions.get(sessionId);return state===void 0&&(state={snapshot:{generation:0,notices:[]},registrations:new Map,grants:new Map,replayed:new Map},this.#sessions.set(sessionId,state)),state.grants.set(requestId,{connection,cancel}),{accepted:!0,connection}}acceptWorkerResponse(sessionId,requestId,connection){let state=this.#sessions.get(sessionId),pending=state?.grants.get(requestId);if(pending!==void 0)return pending.connection!==connection||connection!==void 0&&!this.fanout.connectionHas(connection,"grant_select")?!1:(state?.grants.delete(requestId),!0)}settleGrant(sessionId,requestId){this.#sessions.get(sessionId)?.grants.delete(requestId)}dropSession(sessionId){this.#stop(this.#sessions.get(sessionId)),this.#sessions.delete(sessionId);for(let[id,sessions]of this.#attachments)sessions.delete(sessionId),sessions.size===0&&this.#attachments.delete(id)}#register(id,state){if(!this.fanout.connectionHas(id,"grant_select")){state.registrations.get(id)?.(),state.registrations.delete(id);return}if(state.registrations.has(id)||state.select===void 0||state.durableId===void 0)return;let select=state.select;state.registrations.set(id,attachMcpGrantPresentation(state.durableId,(title,choices,opts)=>this.inConnection(id,()=>select(title,choices,opts))))}#replay(sessionId,id,snapshot){let target=this.fanout.get(id);if(target===void 0)return;let state=this.#sessions.get(sessionId),revision=JSON.stringify(snapshot);if(state?.replayed.get(id)!==revision){state?.replayed.set(id,revision);for(let notice of snapshot.notices)target.actor.enqueue(serializeJsonLine({type:"extension_ui_request",id:crypto.randomUUID(),sessionId,method:"notify",notifyType:"warning",message:notice.message}));this.flush()}}#cancelFor(state,id){for(let[requestId,pending]of state.grants)pending.connection===id&&(state.grants.delete(requestId),pending.cancel())}#stop(state){state?.stop?.();for(let stop of state?.registrations.values()??[])stop();for(let pending of state?.grants.values()??[])pending.cancel()}};var blockedMsTotal=0;function recordLoopBlockedMs(driftMs){driftMs>0&&(blockedMsTotal+=driftMs)}function loopBlockedMark(){return blockedMsTotal}function loopBlockedMsSince(mark){return blockedMsTotal-mark}var EVENT_RESYNC_CAPABILITY="event_resync";function isCorrelatedReply(value){return typeof value=="object"&&value!==null&&"type"in value&&value.type==="response"&&"id"in value&&typeof value.id=="string"}var SocketPeerLivenessError=class extends Error{constructor(){super("socket peer missed its liveness ping"),this.name="SocketPeerLivenessError"}},SocketPeerRecovery=class{constructor(write,snapshot){this.awaitingPing=!1,this.pingBuffered=!1,this.replyLost=!1,this.write=write,this.snapshot=snapshot,this.pingBuffered=!0,this.write(serializeJsonLine({type:"rpc_ping"}))}get pending(){return this.reason!==void 0}shed(reason){this.reason===void 0&&(this.reason=reason,this.readSnapshot=this.snapshot(reason))}acknowledge(){this.awaitingPing=!1}loseReply(){this.replyLost=!0}ping(){if(this.awaitingPing)throw new SocketPeerLivenessError;this.awaitingPing=!0,this.pingBuffered||(this.pingBuffered=!0,this.write(serializeJsonLine({type:"rpc_ping"})))}takeSnapshot(){let reason=this.reason;this.reason=void 0,this.awaitingPing=!1,this.pingBuffered=!1;let snapshot=this.readSnapshot;this.readSnapshot=void 0;let gap=reason===void 0?[]:[{type:"resync_required",reason,replyLost:this.replyLost}];return this.replyLost=!1,[...gap,...snapshot?.()??[]]}};var DEFAULT_QUEUE_BYTES=64*1024*1024,DEFAULT_STALL_MS=3e4,SocketEventQueueStallError=class extends Error{constructor(pendingBytes,stallMs){super(`socket event queue stalled: peer did not drain ${pendingBytes} queued bytes within ${stallMs}ms`),this.name="SocketEventQueueStallError",this.pendingBytes=pendingBytes,this.stallMs=stallMs}},SocketEventQueueOverflowError=class extends Error{constructor(queuedBytes,incomingBytes,maxQueueBytes,incomingPreview){super(`socket event queue overflow: ${queuedBytes} queued + ${incomingBytes} incoming > ${maxQueueBytes} (incoming: ${incomingPreview})`),this.name="SocketEventQueueOverflowError",this.queuedBytes=queuedBytes,this.incomingBytes=incomingBytes,this.maxQueueBytes=maxQueueBytes,this.incomingPreview=incomingPreview}},SocketEventSinkActor=class{constructor(sink,onFailure,maxQueueBytes=DEFAULT_QUEUE_BYTES,stallMs=DEFAULT_STALL_MS){this.queue=[],this.queuedBytes=0,this.closed=!1,this.recoveryEnabled=!1,this.stopped=new AbortController,this.sink=sink,this.onFailure=onFailure,this.maxQueueBytes=maxQueueBytes,this.stallMs=stallMs}enableRecovery(snapshot){this.recoveryEnabled=!0,this.recovery??=new SocketPeerRecovery(line=>this.sink.writeRaw(line),snapshot),this.recovery.acknowledge()}disableRecovery(){this.recoveryEnabled=!1,this.recovery?.pending||(this.recovery=void 0)}enqueue(line,key,onWritten,demotedLine,reply=!1){if(this.closed)return;if(this.recovery?.pending&&!reply){onWritten?.();return}let bytes=Buffer.byteLength(line);if(this.recovery?.pending){this.queuedBytes+bytes<=this.maxQueueBytes?(this.queue.push({line,bytes,onWritten,reply}),this.queuedBytes+=bytes):(this.recovery.loseReply(),onWritten?.());return}if(key!==void 0){let existing=this.queue.find(entry=>entry.key===key);existing&&(existing.demotedLine!==void 0&&(this.queuedBytes-=existing.bytes,existing.line=existing.demotedLine,existing.bytes=Buffer.byteLength(existing.line),this.queuedBytes+=existing.bytes,existing.demotedLine=void 0),existing.key=void 0)}if(this.queuedBytes+bytes>this.maxQueueBytes){if(this.recovery&&this.recoveryEnabled){this.shed("overflow"),this.enqueue(line,void 0,onWritten,void 0,reply),this.drain();return}let overflow=new SocketEventQueueOverflowError(this.queuedBytes,bytes,this.maxQueueBytes,line.slice(0,120));this.closed=!0,this.queue.length=0,this.queuedBytes=0;try{this.sink.writeRaw(`${JSON.stringify({type:"overflow",error:"overflow, resync required"})}
|
|
3
|
-
`)}catch(cause){this.onFailure(cause)}this.onFailure(overflow);return}this.queue.push({line,bytes,key,demotedLine,onWritten,reply}),this.queuedBytes+=bytes,this.drain()}async flush(){for(;this.draining;)await this.draining;if(this.failure!==void 0)throw this.failure}waitForAcceptance(){return Promise.resolve()}close(){this.closed=!0,this.queue.length=0,this.queuedBytes=0,this.stopped.abort()}shed(reason){if(this.recovery?.pending)return;let replies=[];for(let entry of this.queue)entry.reply?replies.push(entry):entry.onWritten?.();this.queue.length=0;for(let reply of replies)this.queue.push(reply);this.queuedBytes=replies.reduce((bytes,entry)=>bytes+entry.bytes,0),this.recovery?.shed(reason)}waitForDrainOrStall(writtenBytes){return new Promise((resolve,reject)=>{let timer,blockedMark=loopBlockedMark(),cleanup=()=>{clearTimeout(timer),this.stopped.signal.removeEventListener("abort",onStop)},onStop=()=>{cleanup(),resolve()},onDeadline=()=>{let blockedMs=loopBlockedMsSince(blockedMark);if(blockedMs>0){blockedMark=loopBlockedMark(),timer=setTimeout(onDeadline,Math.min(blockedMs,this.stallMs));return}if(this.recovery&&this.recoveryEnabled){try{this.shed("stall"),this.recovery.ping(),blockedMark=loopBlockedMark(),timer=setTimeout(onDeadline,this.stallMs)}catch(cause){cleanup(),reject(cause)}return}let stall=new SocketEventQueueStallError(this.queuedBytes+writtenBytes,this.stallMs);try{this.sink.writeRaw(`${JSON.stringify({type:"overflow",error:"stalled, resync required"})}
|
|
4
|
-
`)}catch{}cleanup(),reject(stall)};timer=setTimeout(onDeadline,this.stallMs),this.stopped.signal.addEventListener("abort",onStop,{once:!0}),this.sink.waitForBackpressure().then(()=>{cleanup(),resolve()},cause=>{cleanup(),reject(cause)})})}drain(){return this.draining?this.draining:(this.draining=(async()=>{try{for(;!this.closed&&(this.queue.length>0||this.recovery?.pending);){if(this.recovery?.pending&&this.queue.length===0){if(await this.waitForDrainOrStall(0),this.closed)break;let snapshot=this.recovery.takeSnapshot();this.recoveryEnabled||(this.recovery=void 0);for(let record of snapshot){if(this.closed||this.recovery?.pending)break;let line=serializeJsonLine(record);this.sink.writeRaw(line),await this.waitForDrainOrStall(Buffer.byteLength(line))}continue}let entry=this.queue.shift();this.queuedBytes-=entry.bytes,this.sink.writeRaw(entry.line),await this.waitForDrainOrStall(entry.bytes),entry.onWritten?.()}}catch(cause){this.failure=cause,this.closed=!0,this.queue.length=0,this.queuedBytes=0,this.onFailure(cause)}})().finally(()=>{this.draining=void 0,!this.closed&&(this.queue.length>0||this.recovery?.pending)&&this.drain()}),this.draining)}};var BROADCAST_LIFECYCLE_RECORDS=new Set(["agent_start","agent_settled","agent_idle","session_opened","session_closed","session_lifecycle"]);function snapshotPlaceholderLine(record){if(record.placeholderLine!==void 0)return record.placeholderLine;let redacted=record.source===void 0?void 0:omitInlineMedia(record.source);return record.placeholderLine=redacted===void 0||redacted===record.source?record.line:serializeJsonLine(redacted),record.placeholderLine}var SessionEventFanout=class{constructor(){this.connections=new Map,this.sessionSnapshots=new Map,this.lifecycleSnapshots=new Map,this.pendingQuestions=new Map,this.connectionCapabilities=new Map,this.connectionSessions=new Map,this.registeredCapabilityConnections=new Set}registerConnection(id,connection,options={}){let actor=new SocketEventSinkActor(connection,cause=>{this.connections.get(id)?.actor===actor&&(this.connections.delete(id),this.connectionCapabilities.delete(id),this.connectionSessions.delete(id),this.registeredCapabilityConnections.delete(id),this.onConnectionClosed?.(id)),process.stderr.write(`senpi rpc connection ${id} event queue failed; closing: ${cause instanceof Error?cause.message:String(cause)}
|
|
5
|
-
`),connection.close?.()},options.maxQueueBytes,options.stallMs);this.connections.set(id,{connection,actor}),this.connectionCapabilities.set(id,new Set),this.connectionSessions.set(id,new Set)}unregisterConnection(id){let registered=this.connections.get(id);registered&&(registered.actor.close(),this.connections.delete(id),this.connectionCapabilities.delete(id),this.connectionSessions.delete(id),this.registeredCapabilityConnections.delete(id))}attachConnectionToSession(id,sessionId){if(!this.connections.has(id))return;let sessions=this.connectionSessions.get(id)??new Set;if(!sessions.has(sessionId)){sessions.add(sessionId),this.connectionSessions.set(id,sessions),this.replaySnapshot(id,sessionId);for(let frame of this.pendingQuestions.get(sessionId)?.values()??[])this.connections.get(id)?.actor.enqueue(serializeJsonLine({...frame,remainingMs:typeof frame.deadlineAtMs=="number"?Math.max(0,frame.deadlineAtMs-Date.now()):0}))}}detachConnectionFromSession(id,sessionId){this.connectionSessions.get(id)?.delete(sessionId)}setConnectionCapabilities(id,capabilities){let registered=this.connections.get(id);registered&&(this.connectionCapabilities.set(id,new Set(capabilities)),this.registeredCapabilityConnections.add(id),capabilities.includes(EVENT_RESYNC_CAPABILITY)?registered.actor.enableRecovery(reason=>{let attached=[...this.connectionSessions.get(id)??[]];return()=>this.resyncSnapshot(id,reason,attached)}):registered.actor.disableRecovery())}clearConnectionCapabilities(id){this.connections.has(id)&&(this.connectionCapabilities.set(id,new Set),this.registeredCapabilityConnections.delete(id))}hasRegisteredConnectionCapabilities(id){return this.registeredCapabilityConnections.has(id)}getConnectionCapabilities(id){if(this.registeredCapabilityConnections.has(id))return[...this.connectionCapabilities.get(id)??[]]}connectionHas(id,capability){return id===void 0?!1:this.connectionCapabilities.get(id)?.has(capability)??!1}targets(sessionId,targetId,isTargeted,recordType){return isTargeted?[targetId]:typeof recordType=="string"&&BROADCAST_LIFECYCLE_RECORDS.has(recordType)?this.connections.size>0?[...this.connections.keys()]:[void 0]:this.connections.size>0?[...this.connections.keys()].filter(id=>this.connectionSessions.get(id)?.has(sessionId)):[void 0]}get(id){return this.connections.get(id)}values(){return this.connections.values()}isEmpty(){return this.connections.size===0}broadcast(line){for(let{actor}of this.connections.values())actor.enqueue(line)}connectionIds(){return[...this.connections.keys()]}attachedConnections(sessionId){return[...this.connections.keys()].filter(id=>this.connectionSessions.get(id)?.has(sessionId))}pendingQuestionCount(sessionId){return this.pendingQuestions.get(sessionId)?.size??0}deliverToSession(sessionId,line){for(let[id,{actor}]of this.connections)this.connectionSessions.get(id)?.has(sessionId)&&actor.enqueue(line)}rememberSnapshot(sessionId,value,line,placeholderLine,source){if(value.type==="session_lifecycle"){this.lifecycleSnapshots.set(sessionId,{line,placeholderLine,source});return}if(value.type==="extension_ui_request"&&value.method==="question"&&typeof value.id=="string"){let pending=this.pendingQuestions.get(sessionId)??new Map;pending.set(value.id,{...value}),this.pendingQuestions.set(sessionId,pending);return}if(value.type==="question_resolved"&&typeof value.id=="string"){this.pendingQuestions.get(sessionId)?.delete(value.id);return}if(value.type==="question_updated"&&typeof value.id=="string"){let frame=this.pendingQuestions.get(sessionId)?.get(value.id);frame&&Object.assign(frame,{deadlineAtMs:value.deadlineAtMs,remainingMs:value.remainingMs});return}let event=value.assistantMessageEvent,record={line,placeholderLine,source};value.type==="message_start"||value.type==="message_update"&&event?.type==="text_start"?this.sessionSnapshots.set(sessionId,[record]):this.sessionSnapshots.has(sessionId)&&this.sessionSnapshots.get(sessionId)?.push(record),value.type==="message_end"&&this.sessionSnapshots.delete(sessionId)}forgetSession(sessionId){this.pendingQuestions.delete(sessionId),this.sessionSnapshots.delete(sessionId),this.lifecycleSnapshots.delete(sessionId)}replaySnapshot(id,sessionId){let actor=this.connections.get(id)?.actor;if(!actor)return;let placeholders=this.connectionHas(id,MEDIA_PLACEHOLDERS_CAPABILITY),lifecycle=this.lifecycleSnapshots.get(sessionId);lifecycle&&actor.enqueue(placeholders?snapshotPlaceholderLine(lifecycle):lifecycle.line);for(let record of this.sessionSnapshots.get(sessionId)??[])actor.enqueue(placeholders?snapshotPlaceholderLine(record):record.line)}resyncSnapshot(id,reason,attached){let placeholders=this.connectionHas(id,MEDIA_PLACEHOLDERS_CAPABILITY),current=this.connectionSessions.get(id)??new Set;return[...new Set([...attached,...current])].map(sessionId=>{let latest=(this.sessionSnapshots.get(sessionId)??[]).findLast(record2=>record2.source?.message!=null),frozen=latest===void 0?void 0:JSON.parse(latest.line),record={type:"resync_required",sessionId,reason,message:typeof frozen=="object"&&frozen!==null&&"message"in frozen?frozen.message:null,...current.has(sessionId)?{}:{closed:!0}};return placeholders?omitInlineMedia(record):record})}};var SessionOpenTurns=class{constructor(){this.open=new Map}note(sessionId,type){if(type==="agent_start")this.open.set(sessionId,(this.open.get(sessionId)??0)+1);else if(type==="agent_settled"){let left=(this.open.get(sessionId)??0)-1;left>0?this.open.set(sessionId,left):this.open.delete(sessionId)}}take(sessionId){let owed=this.open.get(sessionId)??0;return this.open.delete(sessionId),owed}};var SessionRecordAccounting=class{constructor(){this.count=0,this.bytes=0}append(queue,value,key,resolve,reject){let line=serializeJsonLine(value),byteLength=Buffer.byteLength(line),node={value,line,byteLength,key,previous:queue.tail,resolve,reject};return queue.tail?queue.tail.next=node:queue.head=node,queue.tail=node,this.count++,this.bytes+=byteLength,node}replace(node,value){let line=serializeJsonLine(value),byteLength=Buffer.byteLength(line);this.bytes+=byteLength-node.byteLength,node.value=value,node.line=line,node.byteLength=byteLength}unlink(queue,node){node.previous?node.previous.next=node.next:queue.head=node.next,node.next?node.next.previous=node.previous:queue.tail=node.previous,node.key&&queue.latestByKey.get(node.key)===node&&queue.latestByKey.delete(node.key),node.previous=void 0,node.next=void 0,this.count--,this.bytes-=node.byteLength}};function liveStatus(runtime,fanout){return fanout.pendingQuestions>0||runtime.pendingQuestions>0?"waiting_input":runtime.isCompacting?"compacting":runtime.isStreaming?"running":"idle"}function statusOf(entry,runtime,fanout,terminal){if(terminal!==void 0)return terminal.status;switch(entry.state){case"opening":return"opening";case"closing":case"quarantined":case"closed":return"closed";case"open":return runtime===void 0?"opening":liveStatus(runtime,fanout);default:{let exhaustive=entry.state;throw new Error(`unexpected session state ${String(exhaustive)}`)}}}function deriveSessionStatus(entry,runtime,fanout,terminal,cause){let status=statusOf(entry,runtime,fanout,terminal),reason=terminal?.reason??(status==="idle"&&cause==="abort"?"aborted":void 0);return{status,...reason!==void 0?{reason}:{},kind:entry.kind,cwd:entry.cwd,attachments:Math.max(0,entry.attachments),...entry.durableSessionId!==void 0?{durableSessionId:entry.durableSessionId}:{},...entry.sessionPath!==void 0?{sessionPath:entry.sessionPath}:{},...entry.name!==void 0?{name:entry.name}:{},...runtime?.model!==void 0?{model:{provider:runtime.model.provider,id:runtime.model.id}}:{},...runtime?.thinkingLevel!==void 0?{thinkingLevel:runtime.thinkingLevel}:{},pendingQueue:{steering:runtime?.steering??0,followUp:runtime?.followUp??0},context:entry.context}}function terminalOfLifecycle(record){let reason=typeof record.reason=="string"?record.reason:void 0;if(record.type==="session_parked")return{status:"unloaded",reason:reason==="unloaded"?"unload_session":"idle"};if(record.type==="session_closed")return reason==="handoff_parked"?{status:"unloaded",reason:"handoff"}:reason===void 0?{status:"closed"}:{status:"closed",reason}}function causeOfRecord(record){switch(record.type){case"agent_start":return"prompt";case"agent_end":return"agent_end";case"agent_idle":return"agent_idle";case"session_abort":return"abort";case"queue_update":return"queue";case"model_changed":case"thinking_level_changed":return"set_model";case"session_replaced":return"replace";case"session_parked":return"park";case"session_closed":return"close";case"question_resolved":return"question";case"extension_ui_request":return record.method==="question"?"question":void 0;case"compaction_start":case"compaction_end":return"compaction";default:return}}function sessionOpenedLines(record){let{context,...plain}=record;return{plain:serializeJsonLine(plain),withContext:serializeJsonLine(context===void 0?plain:{...plain,context})}}var RECORD_TYPE_PATTERN=/^[a-z][a-z0-9_]{0,63}$/;function refusal(reason,action){return{refusal:{reason,action}}}function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function parseSelector(value){if(value===void 0||value==="all")return{selector:{kind:"all"}};let action='send sessions: "all", { ids: [<sessionId | durableSessionId | sessionPath>] } or { childrenOf: <durableSessionId> }';if(!isRecord(value))return refusal('sessions must be "all" or an object',action);if("ids"in value&&!("childrenOf"in value)){let ids=value.ids;return!Array.isArray(ids)||ids.length===0||ids.length>1024?refusal("sessions.ids must be a non-empty array of at most 1024 strings",action):ids.every(id=>typeof id=="string"&&id.length>0)?{selector:{kind:"ids",ids:new Set(ids)}}:refusal("every sessions.ids entry must be a non-empty string",action)}if("childrenOf"in value&&!("ids"in value)){let parent=value.childrenOf;return typeof parent!="string"||parent.length===0?refusal("sessions.childrenOf must be a non-empty durable session id",action):{selector:{kind:"childrenOf",parent}}}return refusal("sessions must name exactly one of ids or childrenOf",action)}function parseMirror(value){if(value===void 0||value==="none")return{mirror:{kind:"none"}};if(value==="all")return{mirror:{kind:"all"}};let action='send mirror: "none", "all" or an array of record types such as ["message_update"]';return!Array.isArray(value)||value.length===0||value.length>64?refusal('mirror must be "none", "all" or a non-empty array of at most 64 record types',action):value.every(type=>typeof type=="string"&&RECORD_TYPE_PATTERN.test(type))?{mirror:{kind:"types",types:new Set(value)}}:refusal(`every mirror entry must be a record type matching ${RECORD_TYPE_PATTERN.source}`,action)}function optionalBoolean(value,field){return value===void 0||typeof value=="boolean"?value:{reason:`${field} must be a boolean`,action:`send ${field}: true or false, or omit it`}}function parseSubscription(request){let selector=parseSelector(request.sessions);if("refusal"in selector)return selector;let mirror=parseMirror(request.mirror);if("refusal"in mirror)return mirror;let includeWorkers=optionalBoolean(request.include_workers,"include_workers");if(typeof includeWorkers=="object")return{refusal:includeWorkers};let includeContext=optionalBoolean(request.include_context,"include_context");if(typeof includeContext=="object")return{refusal:includeContext};let effects=optionalBoolean(request.effects,"effects");return typeof effects=="object"?{refusal:effects}:{options:{selector:selector.selector,includeWorkers:includeWorkers??selector.selector.kind==="childrenOf",includeContext:includeContext??!1,mirror:mirror.mirror,effects:effects??selector.selector.kind==="childrenOf"}}}function subscriptionMatches(options,sessionId,entry){if(entry.kind==="worker"&&!options.includeWorkers)return!1;let selector=options.selector;switch(selector.kind){case"all":return!0;case"ids":return selector.ids.has(sessionId)||entry.durableSessionId!==void 0&&selector.ids.has(entry.durableSessionId)||entry.sessionPath!==void 0&&selector.ids.has(entry.sessionPath);case"childrenOf":{let lineage=entry.openContext??entry.context;return(lineage.parent_session_id??lineage.root_session_id)===selector.parent}default:{let exhaustive=selector;throw new Error(`unexpected selector ${String(exhaustive)}`)}}}function mirrorsRecord(mirror,recordType){switch(mirror.kind){case"none":return!1;case"all":return!0;case"types":return mirror.types.has(recordType);default:{let exhaustive=mirror;throw new Error(`unexpected mirror ${String(exhaustive)}`)}}}var MAX_SUBSCRIPTIONS_PER_CONNECTION=64,SessionStatusStream=class{constructor(delivery){this.subscriptions=new Map,this.pendings=new Map,this.terminals=new Map,this.lastViews=new Map,this.lastChanges=new Map,this.nextSubscription=0,this.delivery=delivery}setSource(source){this.source=source}setLastEffectProvider(provider){this.lastEffect=provider}get size(){return this.subscriptions.size}connectionSize(connectionId){let count=0;for(let pending of this.pendings.values())pending.connectionId===connectionId&&count++;for(let subscription of this.subscriptions.values())subscription.connectionId===connectionId&&count++;return count}subscribe(connectionId,options){let pending={id:`sub-${++this.nextSubscription}`,connectionId,options,seq:0,lines:[]};this.pendings.set(pending.id,pending);let snapshot=[];for(let sessionId of this.source?.sessionIds()??[]){let view=this.currentView(sessionId);if(view===void 0||!subscriptionMatches(options,sessionId,view.entry))continue;let derived=this.derive(sessionId,view);snapshot.push(this.recordFor(pending,sessionId,derived,this.lastChangeOf(sessionId)))}return{subscriptionId:pending.id,snapshot,seq:pending.seq}}activate(subscriptionId){let pending=this.pendings.get(subscriptionId);if(pending===void 0)return;this.pendings.delete(subscriptionId);let subscription={id:pending.id,connectionId:pending.connectionId,options:pending.options,seq:pending.seq};this.subscriptions.set(subscription.id,subscription);for(let line of pending.lines)this.delivery.enqueue(subscription.connectionId,line);pending.lines.length=0}unsubscribe(connectionId,subscriptionId){let pending=this.pendings.get(subscriptionId);return pending!==void 0?pending.connectionId!==connectionId?!1:(this.pendings.delete(subscriptionId),!0):this.subscriptions.get(subscriptionId)?.connectionId!==connectionId?!1:(this.subscriptions.delete(subscriptionId),!0)}dropConnection(connectionId){for(let[id,pending]of this.pendings)pending.connectionId===connectionId&&this.pendings.delete(id);for(let[id,subscription]of this.subscriptions)subscription.connectionId===connectionId&&this.subscriptions.delete(id)}publish(sessionId,cause,by,contextOnly=!1){let change={at:(this.delivery.now?.()??new Date).toISOString(),cause,by};if(this.lastChanges.set(sessionId,change),this.subscriptions.size===0&&this.pendings.size===0)return;let view=this.currentView(sessionId);if(view===void 0)return;let derived=this.derive(sessionId,view,cause);for(let pending of this.pendings.values())contextOnly&&!pending.options.includeContext||subscriptionMatches(pending.options,sessionId,view.entry)&&(pending.seq+=1,pending.lines.push(serializeJsonLine(this.recordFor(pending,sessionId,derived,change))));for(let subscription of this.subscriptions.values()){if(contextOnly&&!subscription.options.includeContext||!subscriptionMatches(subscription.options,sessionId,view.entry))continue;subscription.seq+=1;let record=this.recordFor(subscription,sessionId,derived,change);this.delivery.enqueue(subscription.connectionId,serializeJsonLine(record))||this.subscriptions.delete(subscription.id)}}publishEffect(sessionId,status){if(this.subscriptions.size===0&&this.pendings.size===0)return;let view=this.currentView(sessionId);if(view===void 0)return;let at=status.updatedAt??status.acceptedAt??(this.delivery.now?.()??new Date).toISOString();for(let pending of this.pendings.values())!pending.options.effects||!subscriptionMatches(pending.options,sessionId,view.entry)||(pending.seq+=1,pending.lines.push(serializeJsonLine(effectStatusRecord(pending,sessionId,status,at))));for(let subscription of this.subscriptions.values()){if(!subscription.options.effects||!subscriptionMatches(subscription.options,sessionId,view.entry))continue;subscription.seq+=1;let line=serializeJsonLine(effectStatusRecord(subscription,sessionId,status,at));this.delivery.enqueue(subscription.connectionId,line)||this.subscriptions.delete(subscription.id)}}seal(sessionId,terminal,cause,by){this.terminals.set(sessionId,terminal),this.publish(sessionId,cause,by)}forget(sessionId){this.terminals.delete(sessionId),this.lastViews.delete(sessionId),this.lastChanges.delete(sessionId)}mirrorTargets(sessionId,recordType){let subscriptions=[...this.pendings.values(),...this.subscriptions.values()].filter(subscription=>mirrorsRecord(subscription.options.mirror,recordType));if(subscriptions.length===0)return[];let entry=this.source?.entryView(sessionId)??this.lastViews.get(sessionId)?.entry;if(entry===void 0)return[];let targets=new Set;for(let subscription of subscriptions)subscriptionMatches(subscription.options,sessionId,entry)&&targets.add(subscription.connectionId);return[...targets]}matchingConnections(sessionId){let view=this.currentView(sessionId),matches=new Map;if(view===void 0)return matches;for(let subscription of this.subscriptions.values())subscriptionMatches(subscription.options,sessionId,view.entry)&&matches.set(subscription.connectionId,(matches.get(subscription.connectionId)??!1)||subscription.options.includeContext);return matches}currentView(sessionId){let live=this.source?.view(sessionId);return live!==void 0&&this.lastViews.set(sessionId,live),live??this.lastViews.get(sessionId)}derive(sessionId,view,cause){return deriveSessionStatus(view.entry,view.runtime,{pendingQuestions:this.delivery.pendingQuestions(sessionId)},this.terminals.get(sessionId),cause)}lastChangeOf(sessionId){return this.lastChanges.get(sessionId)??{at:(this.delivery.now?.()??new Date).toISOString(),cause:"open",by:"host"}}recordFor(subscription,sessionId,derived,lastChange){let{context,...rest}=derived,lastEffect=this.lastEffect?.(sessionId);return{type:"session_status",subscriptionId:subscription.id,seq:subscription.seq,sessionId,...rest,...subscription.options.includeContext?{context}:{},...lastEffect!==void 0?{lastEffect}:{},lastChange}}};var SurfaceDigestDedupe=class{constructor(){this.connections=new Map}announce(connectionId,sessionId,previous,digest){let sessions=this.connections.get(connectionId);if(sessions===void 0&&(sessions=new Map,this.connections.set(connectionId,sessions)),previous===digest)return!1;let tell=!0;for(let[other,announced]of sessions)if(!(other===sessionId||announced.last!==digest)&&announced.previous===previous){tell=!1;break}return sessions.set(sessionId,{last:digest,previous}),tell}dropConnection(connectionId){this.connections.delete(connectionId)}forgetSession(sessionId){for(let sessions of this.connections.values())sessions.delete(sessionId)}};var settleActors=actors=>Promise.all(actors.map(actor=>actor.flush().catch(()=>{}))).then(()=>{}),acceptActors=actors=>Promise.all(actors.map(actor=>actor.waitForAcceptance())).then(()=>{}),MAX_SHARED_STDIO_QUEUE_BYTES=64*1024*1024,MAX_SHARED_STDIO_QUEUE_RECORDS=4096,MESSAGE_KEY="message",COMPACT_DELTA_TYPES=new Set(["text_delta","thinking_delta","toolcall_delta"]);function compactDelta(value){if(value.type!=="message_update"||!Object.hasOwn(value,"message"))return;let event=value.assistantMessageEvent;if(typeof event!="object"||event===null)return;let typedEvent=event;if(!(typeof typedEvent.type!="string"||!COMPACT_DELTA_TYPES.has(typedEvent.type)||typeof typedEvent.contentIndex!="number"||typeof typedEvent.delta!="string"))return{type:typedEvent.type,contentIndex:typedEvent.contentIndex,delta:typedEvent.delta}}function demoteToDeltaOnly(value){let event=value.assistantMessageEvent;return{...value,message:null,assistantMessageEvent:{...event,partial:null}}}function toolUpdateKey(value){return value.type==="tool_execution_update"&&typeof value.toolCallId=="string"?`tool:${value.toolCallId}`:void 0}var SessionEventWriter=class{get pendingCloseRecordCount(){return this.reservedCloseRecords}get pendingCloseByteLength(){return this.reservedCloseBytes}constructor(writeRaw,waitOrSchedule,scheduleFlush){this.records=new SessionRecordAccounting,this.queues=new Map,this.fanout=new SessionEventFanout,this.connectionContext=new AsyncLocalStorage,this.securityPresentations=new RpcSecurityPresentations(this.fanout,(id,task)=>this.withConnection(id,task),()=>this.requestFlush()),this.workerSecurityBridge=!1,this.controlQueue={latestByKey:new Map,ready:!1},this.readyQueues=[],this.sealedSessions=new Set,this.workerSessions=new Set,this.openTurns=new SessionOpenTurns,this.status=new SessionStatusStream({enqueue:(connectionId,line)=>{let actor=this.fanout.get(connectionId)?.actor;return actor===void 0?!1:(actor.enqueue(line),this.requestFlush(),!0)},pendingQuestions:sessionId=>this.fanout.pendingQuestionCount(sessionId)}),this.surfaceDedupe=new SurfaceDigestDedupe,this.mediaPersisters=new Map,this.flushScheduled=!1,this.controlOverflowReported=!1,this.closeOverflowReported=!1,this.closeOverflowActors=new WeakSet,this.reservedCloseRecords=0,this.reservedCloseBytes=0,this.fanout.onConnectionClosed=id=>this.securityPresentations.dropConnection(id),this.writeRaw=writeRaw,waitOrSchedule&&scheduleFlush===void 0&&waitOrSchedule.length>0?this.scheduleFlush=waitOrSchedule:(this.waitForBackpressure=waitOrSchedule,this.scheduleFlush=scheduleFlush??(flush=>queueMicrotask(()=>{flush()})))}get bufferedRecordCount(){return this.records.count+(this.inFlight?1:0)}get bufferedByteLength(){return this.records.bytes+(this.inFlight?.node.byteLength??0)}isSessionSealed(sessionId){return this.sealedSessions.has(sessionId)}registerConnection(id,connection,options={}){this.fanout.registerConnection(id,connection,options)}unregisterConnection(id){this.securityPresentations.dropConnection(id),this.fanout.unregisterConnection(id),this.status.dropConnection(id),this.surfaceDedupe.dropConnection(id)}attachConnectionToSession(id,sessionId){this.fanout.attachConnectionToSession(id,sessionId),this.hasConnection(id)&&this.securityPresentations.attach(id,sessionId)}detachConnectionFromSession(id,sessionId){this.fanout.detachConnectionFromSession(id,sessionId),this.securityPresentations.detach(id,sessionId)}hasConnection(id){return this.fanout.get(id)!==void 0}setConnectionCapabilities(id,capabilities){this.fanout.setConnectionCapabilities(id,capabilities),this.securityPresentations.capabilitiesChanged(id)}clearConnectionCapabilities(id){this.fanout.clearConnectionCapabilities(id),this.securityPresentations.capabilitiesChanged(id)}bindSecurityPresentations(sessionId,durableId,notices,ui){if(!this.fanout.isEmpty())return this.securityPresentations.bind(sessionId,durableId,notices,ui.select)}acceptWorkerSecuritySnapshot(sessionId,snapshot){this.securityPresentations.acceptSnapshot(sessionId,snapshot)}routeWorkerGrant(sessionId,requestId,preferred,capabilities,cancel){return this.securityPresentations.routeWorkerGrant(sessionId,requestId,preferred,capabilities,cancel)}acceptWorkerGrantResponse(sessionId,id,connection){return this.securityPresentations.acceptWorkerResponse(sessionId,id,connection)}settleGrantDialog(sessionId,id){this.securityPresentations.settleGrant(sessionId,id)}hasRegisteredConnectionCapabilities(id){return this.fanout.hasRegisteredConnectionCapabilities(id)}getConnectionCapabilities(id){return this.fanout.getConnectionCapabilities(id)}setSessionMedia(sessionId,persister){persister===void 0?this.mediaPersisters.delete(sessionId):this.mediaPersisters.set(sessionId,persister)}setSessionKind(sessionId,kind){kind==="worker"?this.workerSessions.add(sessionId):this.workerSessions.delete(sessionId)}async flushConnection(id){let target=this.fanout.get(id);target&&await settleActors([target.actor])}withConnection(id,task){return this.connectionContext.run(id,task)}currentConnection(){return this.connectionContext.getStore()}enqueue(sessionId,value){if(this.sealedSessions.has(sessionId))return!1;let targetId=this.connectionContext.getStore(),record=value,isTargeted=record.type==="response"||record.type==="bash_execution_update"||record.type==="host_request"||record.type==="relocation_due"||record.type==="extension_ui_request"&&["select","confirm","input","editor"].includes(String(record.method)),tagged={...value,sessionId},line=serializeJsonLine(tagged);if(this.fanout.isEmpty()&&this.exceedsStdioCapacity(line))return this.closeSession(sessionId,{type:"response",command:"close_session",success:!1,error:"session_output_overflow, resync required"},"error"),!1;this.openTurns.note(sessionId,record.type);let attached=this.fanout.targets(sessionId,targetId,isTargeted,record.type),mirrors=isTargeted?[]:this.status.mirrorTargets(sessionId,String(record.type)),targets=mirrors.length===0?attached:[...new Set([...attached,...mirrors])],redacted=targets.some(target=>this.fanout.connectionHas(target,MEDIA_PLACEHOLDERS_CAPABILITY))?omitInlineMedia(tagged,this.mediaPersisters.get(sessionId)):tagged,placeholderLine=redacted===tagged?void 0:serializeJsonLine(redacted);isTargeted||this.fanout.rememberSnapshot(sessionId,tagged,line,placeholderLine,tagged);for(let target of targets){if(target!==void 0&&!this.fanout.get(target))continue;let registered=target===void 0?void 0:this.fanout.get(target),wants=placeholderLine!==void 0&&this.fanout.connectionHas(target,MEDIA_PLACEHOLDERS_CAPABILITY);if(registered){let keyed=compactDelta(tagged)!==void 0&&tagged.message!==null;registered.actor.enqueue(wants?placeholderLine:line,keyed?MESSAGE_KEY:void 0,void 0,keyed?serializeJsonLine(demoteToDeltaOnly(tagged)):void 0,isCorrelatedReply(tagged))}else this.appendSessionRecord(sessionId,wants?redacted:tagged,target)}let cause=isTargeted?void 0:causeOfRecord(record);return cause!==void 0&&this.status.publish(sessionId,cause,this.changeActor("agent")),this.afterRecord?.(sessionId),this.requestFlush(),!0}changeActor(fallback){return this.connectionContext.getStore()===void 0?fallback:"client"}publishSessionOpened(record){if(this.fanout.isEmpty()){let{context:_context,...wire}=record;this.appendSessionRecord(record.sessionId,{...wire}),this.requestFlush();return}let lines=sessionOpenedLines(record),subscribers=this.status.matchingConnections(record.sessionId),recipients=record.kind==="worker"?new Set([...this.fanout.attachedConnections(record.sessionId),...subscribers.keys()]):new Set(this.fanout.connectionIds());for(let id of recipients)this.fanout.get(id)?.actor.enqueue(subscribers.get(id)===!0?lines.withContext:lines.plain);this.requestFlush()}publishLoadedSurfacesChanged(sessionId,previous,digest){if(this.fanout.isEmpty()){this.enqueue(sessionId,{type:"loaded_surfaces_changed"});return}if(this.sealedSessions.has(sessionId))return;let line=serializeJsonLine({type:"loaded_surfaces_changed",sessionId});for(let id of this.fanout.attachedConnections(sessionId))this.surfaceDedupe.announce(id,sessionId,previous,digest)&&this.fanout.get(id)?.actor.enqueue(line);this.requestFlush()}waitForSessionBackpressure(sessionId){return this.fanout.isEmpty()?this.flush():acceptActors(this.sessionActors(sessionId))}sessionActors(sessionId){return[...new Set([...this.fanout.targets(sessionId,this.currentConnection(),!1,void 0),this.currentConnection()])].flatMap(target=>{let registered=target===void 0?void 0:this.fanout.get(target);return registered?[registered.actor]:[]})}enqueueControl(value,targetOverride){if(this.failure!==void 0)return Promise.reject(this.failure);let targetId=targetOverride??this.connectionContext.getStore(),registered=targetId===void 0?void 0:this.fanout.get(targetId);if(registered)return registered.actor.enqueue(serializeJsonLine(value),void 0,void 0,void 0,isCorrelatedReply(value)),this.requestFlush(),Promise.resolve();if(this.exceedsStdioCapacity(serializeJsonLine(value)))return this.controlOverflowReported||(this.controlOverflowReported=!0,this.records.append(this.controlQueue,{type:"overflow",error:"rpc_control_output_overflow, resync required"}),this.markReady(this.controlQueue),this.requestFlush()),Promise.reject(new Error("rpc_control_output_overflow, resync required"));let queue=targetId===void 0?this.controlQueue:this.connectionQueue(targetId),completion=new Promise((resolve,reject)=>{this.records.append(queue,{...value},void 0,resolve,reject)});return this.markReady(queue),this.requestFlush(),completion}sendOpenQueued(connection,record){let wire={type:"queued",for_request:record.for_request,position:record.position,in_flight:record.in_flight},target=this.fanout.get(connection);target!==void 0&&(target.actor.enqueue(serializeJsonLine(wire)),this.requestFlush())}broadcastHostRecord(record){let wire;switch(record.type){case"host_superseded":wire={type:"host_superseded",instanceId:record.instanceId,generation:record.generation,successor:record.successor};break;case"host_stalled":wire={type:"host_stalled",driftMs:record.driftMs,...record.sessionId!==void 0?{sessionId:record.sessionId}:{},...record.tool!==void 0?{tool:record.tool}:{}};break;case"host_memory_pressure":wire={type:"host_memory_pressure",rssMb:record.rssMb,...record.footprintMb!==void 0?{footprintMb:record.footprintMb}:{},...record.measure!==void 0?{measure:record.measure}:{},sessions:record.sessions,...record.main!==void 0?{main:record.main}:{},...record.kernels!==void 0?{kernels:record.kernels}:{}};break;case"host_trimmed":wire={type:"host_trimmed",footprintBeforeMb:record.footprintBeforeMb,footprintAfterMb:record.footprintAfterMb,measure:record.measure,collected:record.collected,sessions:record.sessions,pauseMs:record.pauseMs};break;case"subscription_limits":wire={type:"subscription_limits",provider:record.provider,account:record.account,windows:record.windows,reportedAt:record.reportedAt,...record.plan===void 0?{}:{plan:record.plan},...record.error===void 0?{}:{error:record.error}};break;default:{let exhaustive=record;throw new Error(`unexpected host record ${exhaustive}`)}}this.fanout.isEmpty()?(this.records.append(this.controlQueue,wire),this.markReady(this.controlQueue)):this.fanout.broadcast(serializeJsonLine(wire)),this.requestFlush()}closeSession(sessionId,response,reason,sessionPath){if(!this.settleBeforeSeal(sessionId))return;this.sealedSessions.add(sessionId),this.securityPresentations.dropSession(sessionId),this.fanout.forgetSession(sessionId),this.mediaPersisters.delete(sessionId);let targetId=this.connectionContext.getStore(),lifecycle={type:"session_closed",sessionId,...reason&&{reason},...sessionPath&&{sessionPath}};this.fanout.isEmpty()?this.appendSessionRecord(sessionId,lifecycle):this.workerSessions.has(sessionId)?this.fanout.deliverToSession(sessionId,serializeJsonLine(lifecycle)):this.fanout.broadcast(serializeJsonLine(lifecycle)),this.sealStatus(sessionId,lifecycle);let taggedResponse={...response,sessionId},registered=targetId===void 0?void 0:this.fanout.get(targetId);registered?registered.actor.enqueue(serializeJsonLine(taggedResponse),void 0,void 0,void 0,isCorrelatedReply(response)):this.appendSessionRecord(sessionId,taggedResponse,targetId),this.requestFlush()}parkSession(sessionId,sessionPath,reason){this.sealLifecycle(sessionId,{type:"session_parked",sessionId,sessionPath,reason})}releaseSession(sessionId,sessionPath){this.sealLifecycle(sessionId,{type:"session_closed",sessionId,reason:"released",sessionPath})}sealRolledBackOpen(sessionId,sessionPath){this.sealLifecycle(sessionId,{type:"session_closed",sessionId,reason:"error",...sessionPath!==void 0&&{sessionPath}})}sealLifecycle(sessionId,lifecycle,attachedOnly=!1){this.settleBeforeSeal(sessionId)&&(this.sealedSessions.add(sessionId),this.securityPresentations.dropSession(sessionId),this.fanout.forgetSession(sessionId),this.mediaPersisters.delete(sessionId),this.fanout.isEmpty()?this.appendSessionRecord(sessionId,lifecycle):attachedOnly||this.workerSessions.has(sessionId)?this.fanout.deliverToSession(sessionId,serializeJsonLine(lifecycle)):this.fanout.broadcast(serializeJsonLine(lifecycle)),this.sealStatus(sessionId,lifecycle),this.requestFlush())}sealStatus(sessionId,lifecycle){let terminal=terminalOfLifecycle(lifecycle),cause=causeOfRecord(lifecycle);terminal!==void 0&&cause!==void 0&&this.status.seal(sessionId,terminal,cause,this.changeActor("host"))}reserveCloseResponse(sessionId,response,records=2){let bytes=Buffer.byteLength(serializeJsonLine({...response,sessionId}))+(records===2?Buffer.byteLength(serializeJsonLine({type:"session_closed",sessionId,reason:"client_close"})):0);if(this.bufferedRecordCount+this.reservedCloseRecords+records>MAX_SHARED_STDIO_QUEUE_RECORDS||this.bufferedByteLength+this.reservedCloseBytes+bytes>MAX_SHARED_STDIO_QUEUE_BYTES){let overflow={type:"overflow",command:"close_session",error:"rpc_close_output_overflow, resync required"},targetId=this.currentConnection();if(targetId!==void 0){let actor=this.fanout.get(targetId)?.actor;actor&&!this.closeOverflowActors.has(actor)&&(this.closeOverflowActors.add(actor),actor.enqueue(serializeJsonLine(overflow),void 0,()=>this.closeOverflowActors.delete(actor)))}else this.closeOverflowReported||(this.closeOverflowReported=!0,this.records.append(this.controlQueue,overflow),this.markReady(this.controlQueue),this.requestFlush());return}this.reservedCloseRecords+=records,this.reservedCloseBytes+=bytes;let active=!0,release=()=>{active&&(active=!1,this.reservedCloseRecords-=records,this.reservedCloseBytes-=bytes)};return{release,complete:(terminal,reason)=>{active&&(release(),terminal&&!this.sealedSessions.has(sessionId)?this.closeSession(sessionId,response,reason):this.appendClosedResponse(sessionId,response))}}}enqueueClosedResponse(sessionId,response){this.reserveCloseResponse(sessionId,response,1)?.complete(!1)}appendClosedResponse(sessionId,response){let targetId=this.connectionContext.getStore(),taggedResponse={...response,sessionId},registered=targetId===void 0?void 0:this.fanout.get(targetId);registered?registered.actor.enqueue(serializeJsonLine(taggedResponse),void 0,void 0,void 0,isCorrelatedReply(response)):this.appendSessionRecord(sessionId,taggedResponse,targetId),this.requestFlush()}settleBeforeSeal(sessionId){if(this.sealedSessions.has(sessionId))return!1;for(let owed=this.openTurns.take(sessionId);owed>0;owed-=1)this.enqueue(sessionId,{type:"agent_settled",reason:"session_closed"});return!this.sealedSessions.has(sessionId)}forgetSession(sessionId){this.sealedSessions.delete(sessionId),this.openTurns.take(sessionId),this.workerSessions.delete(sessionId),this.fanout.forgetSession(sessionId),this.mediaPersisters.delete(sessionId),this.status.forget(sessionId),this.surfaceDedupe.forgetSession(sessionId)}flush(){if(this.failure!==void 0)return Promise.reject(this.failure);if(this.flushScheduled=!1,this.drainPromise)return this.drainPromise;if(this.readyQueues.length===0)return settleActors([...this.fanout.values()].map(({actor})=>actor));let resolveDrain,rejectDrain,drain=new Promise((resolve,reject)=>{resolveDrain=resolve,rejectDrain=reject});return this.drainPromise=drain,this.drainUntilEmpty().then(resolveDrain,rejectDrain),drain.then(()=>{this.drainPromise===drain&&(this.drainPromise=void 0),this.readyQueues.length>0&&this.requestFlush()},cause=>{this.drainPromise===drain&&(this.drainPromise=void 0),this.fail(cause)}),drain}async drainUntilEmpty(){do await this.drainReadyQueues();while(this.readyQueues.length>0);await settleActors([...this.fanout.values()].map(({actor})=>actor)),this.controlOverflowReported=!1,this.reservedCloseRecords===0&&(this.closeOverflowReported=!1)}async drainReadyQueues(){for(;this.readyQueues.length>0;){let queue=this.readyQueues.shift();queue.ready=!1;let node=queue.head;if(node){this.records.unlink(queue,node),this.inFlight={queue,node};try{let connection=queue.targetId?this.fanout.get(queue.targetId)?.connection:void 0,writeRaw=connection?.writeRaw??this.writeRaw,waitForBackpressure=connection?.waitForBackpressure??this.waitForBackpressure;!connection&&queue.targetId||(writeRaw(node.line),waitForBackpressure&&await waitForBackpressure()),node.resolve?.()}catch(cause){throw node.reject?.(cause),cause}finally{this.inFlight=void 0}if(queue.head)this.markReady(queue);else if(queue.sessionId||queue.targetId)for(let[key,candidate]of this.queues)candidate===queue&&this.queues.delete(key)}}}exceedsStdioCapacity(line){return this.bufferedRecordCount+this.reservedCloseRecords>=MAX_SHARED_STDIO_QUEUE_RECORDS||this.bufferedByteLength+this.reservedCloseBytes+Buffer.byteLength(line)>MAX_SHARED_STDIO_QUEUE_BYTES}connectionQueue(targetId){let key=`connection:${targetId}`,queue=this.queues.get(key);return queue||(queue={targetId,latestByKey:new Map,ready:!1},this.queues.set(key,queue)),queue}appendSessionRecord(sessionId,value,targetId){let key=`${targetId??"default"}:${sessionId}`,queue=this.queues.get(key);if(queue||(queue={sessionId,targetId,latestByKey:new Map,ready:!1},this.queues.set(key,queue)),compactDelta(value)){let previousFull=queue.latestByKey.get(MESSAGE_KEY);previousFull&&this.demoteAndMerge(queue,previousFull);let node=this.records.append(queue,value,MESSAGE_KEY);queue.latestByKey.set(MESSAGE_KEY,node),this.markReady(queue);return}let toolKey=toolUpdateKey(value);if(toolKey){let previous=queue.latestByKey.get(toolKey);previous&&this.records.unlink(queue,previous);let node=this.records.append(queue,value,toolKey);queue.latestByKey.set(toolKey,node),this.markReady(queue);return}queue.latestByKey.clear(),this.records.append(queue,value),this.markReady(queue)}demoteAndMerge(queue,node){this.records.replace(node,demoteToDeltaOnly(node.value));let current=compactDelta(node.value),preceding=node.previous,previous=preceding?compactDelta(preceding.value):void 0;if(preceding&&previous&¤t&&preceding.value.message===null&&previous.type===current.type&&previous.contentIndex===current.contentIndex){let precedingEvent=preceding.value.assistantMessageEvent;this.records.replace(preceding,{...preceding.value,assistantMessageEvent:{...precedingEvent,delta:previous.delta+current.delta}}),this.records.unlink(queue,node)}}markReady(queue){queue.ready||this.inFlight?.queue===queue||!queue.head||(queue.ready=!0,this.readyQueues.push(queue))}requestFlush(){this.failure!==void 0||this.flushScheduled||this.drainPromise||(this.flushScheduled=!0,this.scheduleFlush(()=>this.flush()))}fail(cause){if(this.failure===void 0){this.failure=cause;for(let queue of this.allQueues())for(let node=queue.head;node;node=node.next)node.reject?.(cause)}}*allQueues(){yield*this.queues.values(),yield this.controlQueue}};function socketSink(socket){let needsDrain=!1,cut=!1;return{writeRaw(chunk){!cut&&!socket.destroyed&&(needsDrain=!socket.write(chunk))},close(){if(cut||(cut=!0,socket.destroyed))return;socket.end();let grace=setTimeout(()=>socket.destroy(),5e3);grace.unref(),socket.once("close",()=>clearTimeout(grace))},waitForBackpressure(){return socket.destroyed||!needsDrain?Promise.resolve():(needsDrain=!1,new Promise(resolve=>{let done=()=>{socket.off("drain",done),socket.off("close",done),resolve()};socket.once("drain",done),socket.once("close",done)}))}}}var SESSION_CONTEXT_PATCH_LIMITS={keyPattern:/^[a-z][a-z0-9_]{0,63}$/,valueChars:256,overlayBytes:4096},RESERVED_SESSION_CONTEXT_KEYS=new Set(["host_socket","host_instance","shard_key","tree_key","parent_session_id","root_session_id"]);function refuse(reason,action){return{refusal:{reason,action}}}function applyContextPatch(overlay,patch){let shape='send patch as an object of lowercase keys to string, number, boolean or null values, e.g. { "task_status": "running" }';if(typeof patch!="object"||patch===null||Array.isArray(patch))return refuse("patch must be an object",shape);let entries=Object.entries(patch);if(entries.length===0)return refuse("patch has no keys",shape);let merged={...overlay};for(let[key,value]of entries){if(!SESSION_CONTEXT_PATCH_LIMITS.keyPattern.test(key))return refuse(`key "${key}" must match ${SESSION_CONTEXT_PATCH_LIMITS.keyPattern.source}`,"rename the key to lowercase letters, digits and underscores");if(RESERVED_SESSION_CONTEXT_KEYS.has(key))return refuse(`key "${key}" is set by the host and cannot be patched`,"drop that key from the patch; the host keeps it current");if(typeof value=="string"&&value.length>SESSION_CONTEXT_PATCH_LIMITS.valueChars)return refuse(`value for "${key}" is ${value.length} characters, at most ${SESSION_CONTEXT_PATCH_LIMITS.valueChars} are accepted`,"shorten the value or store it elsewhere and publish a reference");if(typeof value=="number"&&!Number.isFinite(value))return refuse(`value for "${key}" must be a finite number`,shape);if(value!==null&&typeof value!="string"&&typeof value!="number"&&typeof value!="boolean")return refuse(`value for "${key}" must be a string, number, boolean or null`,shape);merged[key]=value}let bytes=Buffer.byteLength(JSON.stringify(merged));return bytes>SESSION_CONTEXT_PATCH_LIMITS.overlayBytes?refuse(`patched context would be ${bytes} bytes, at most ${SESSION_CONTEXT_PATCH_LIMITS.overlayBytes} are accepted`,"remove keys with null or shorten values before adding more"):{overlay:merged}}function publishedSessionContext(base,overlay){if(overlay===void 0)return base;let published={...base};for(let[key,value]of Object.entries(overlay))value===null?delete published[key]:published[key]=value;return published}function modelOf(model){return model===void 0?void 0:{provider:model.provider,id:model.id}}function sessionRuntimeView(session,pendingQuestions){let model=modelOf(session.model);return{isStreaming:session.isStreaming===!0,isCompacting:session.isCompacting===!0,...model!==void 0?{model}:{},...session.thinkingLevel!==void 0?{thinkingLevel:session.thinkingLevel}:{},steering:typeof session.getSteeringMessages=="function"?session.getSteeringMessages().length:0,followUp:typeof session.getFollowUpMessages=="function"?session.getFollowUpMessages().length:0,pendingQuestions:pendingQuestions??sessionQuestionBridges.get(session)?.pendingQuestions()?.length??0}}function runtimeView(entry){let session=entry.runtime?.session;if(session!==void 0)return sessionRuntimeView(session);let state=entry.worker?.snapshot?.state;if(state===void 0)return;let model=modelOf(state.model??void 0);return{isStreaming:state.isStreaming,isCompacting:state.isCompacting,...model!==void 0?{model}:{},...state.thinkingLevel!==void 0?{thinkingLevel:state.thinkingLevel}:{},steering:state.steering.length,followUp:state.followUp.length,pendingQuestions:state.pendingQuestions?.length??0}}function entryView(entry){let session=entry.runtime?.session,state=entry.worker?.snapshot?.state,durableSessionId=session?.sessionId??state?.sessionId??entry.durableSessionId,sessionPath=session?.sessionFile??state?.sessionFile??entry.sessionPath,name=session?.sessionManager.getSessionName()??state?.sessionName;return{state:entry.state,kind:entry.kind,cwd:entry.cwd,attachments:entry.attachments,...durableSessionId!==void 0?{durableSessionId}:{},...sessionPath!==void 0?{sessionPath}:{},...name!==void 0?{name}:{},context:publishedSessionContext(entry.context,entry.contextOverlay),openContext:entry.context}}function registryStatusSource(registry){return{sessionIds:()=>registry.list().map(row=>row.sessionId),entryView:sessionId=>{let entry=registry.peek(sessionId);return entry===void 0?void 0:entryView(entry)},view:sessionId=>{let entry=registry.peek(sessionId);if(entry===void 0)return;let runtime=runtimeView(entry);return runtime===void 0?{entry:entryView(entry)}:{entry:entryView(entry),runtime}}}}function refusal2(id,command,code,detail){return{id,type:"response",command,success:!1,error:`${code}: ${detail.reason}`,errorCode:code,errorData:{reason:detail.reason,action:detail.action}}}function answerSubscribeSessions(command,connectionId,stream){if(connectionId===void 0)return{answer:refusal2(command.id,command.type,RPC_ERROR_UNSUPPORTED_TRANSPORT,{reason:"stdio has no per-connection identity to deliver a subscription to",action:"connect to the host socket (senpi --mode rpc --listen) and subscribe there"})};if(stream.connectionSize(connectionId)>=MAX_SUBSCRIPTIONS_PER_CONNECTION)return{answer:refusal2(command.id,command.type,RPC_ERROR_SUBSCRIPTION_LIMIT,{reason:`this connection already holds ${MAX_SUBSCRIPTIONS_PER_CONNECTION} subscriptions`,action:"unsubscribe_sessions the ones you no longer read, then subscribe again"})};let parsed=parseSubscription(command);if("refusal"in parsed)return{answer:refusal2(command.id,command.type,RPC_ERROR_INVALID_SUBSCRIPTION,parsed.refusal)};let subscribed=stream.subscribe(connectionId,parsed.options);return{answer:{id:command.id,type:"response",command:"subscribe_sessions",success:!0,data:{subscriptionId:subscribed.subscriptionId,snapshot:subscribed.snapshot,seq:subscribed.seq,hostGeneration:String(hostGeneration(process.env)),hostInstanceId:hostInstanceId()}},activate:subscribed.subscriptionId}}function answerUnsubscribeSessions(command,connectionId,stream){return connectionId===void 0?refusal2(command.id,command.type,RPC_ERROR_UNSUPPORTED_TRANSPORT,{reason:"stdio has no per-connection identity holding any subscription",action:"connect to the host socket (senpi --mode rpc --listen) and manage subscriptions there"}):typeof command.subscriptionId=="string"&&stream.unsubscribe(connectionId,command.subscriptionId)?{id:command.id,type:"response",command:"unsubscribe_sessions",success:!0}:refusal2(command.id,command.type,RPC_ERROR_UNKNOWN_SUBSCRIPTION,{reason:"this connection holds no subscription with that id",action:"send the subscriptionId your subscribe_sessions answer returned, on the same connection"})}function notAttachedRefusal(command){return refusal2(command.id,command.type,RPC_ERROR_SESSION_NOT_ATTACHED,{reason:"this connection is not attached to that session",action:"attach first with open_session { sessionPath }, then patch its context"})}function answerSetSessionContext(command,entry,stream){let applied=applyContextPatch(entry.contextOverlay,command.patch);return"refusal"in applied?refusal2(command.id,command.type,RPC_ERROR_INVALID_SESSION_CONTEXT,applied.refusal):(entry.contextOverlay=applied.overlay,stream.publish(command.sessionId,"context","client",!0),{id:command.id,type:"response",command:"set_session_context",success:!0,data:{context:publishedSessionContext(entry.context,entry.contextOverlay)}})}export{applyContextPatch,publishedSessionContext,recordLoopBlockedMs,sessionRuntimeView,registryStatusSource,refusal2 as refusal,answerSubscribeSessions,answerUnsubscribeSessions,notAttachedRefusal,answerSetSessionContext,SessionEventWriter,socketSink};
|