@code-yeongyu/senpi 2026.10.25-4 → 2026.10.26-2
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 +47 -0
- package/dist/bundle/chunks/{anthropic-messages-WBASBTIW.js → anthropic-messages-ODSFJCXR.js} +3 -3
- package/dist/bundle/chunks/{args-B444OQBK.js → args-SWKJAYQD.js} +1 -1
- package/dist/bundle/chunks/azure-openai-responses-REGXS4XO.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +3 -3
- package/dist/bundle/chunks/chunk-2XF6W6F6.js +410 -0
- package/dist/bundle/chunks/{chunk-WDPQ6CZJ.js → chunk-4QVORFQ2.js} +1 -1
- package/dist/bundle/chunks/{chunk-6AOVUTUR.js → chunk-5W3DTM2J.js} +2 -2
- package/dist/bundle/chunks/{chunk-E6WYH3ZL.js → chunk-6GAMRAVM.js} +9 -7
- package/dist/bundle/chunks/{chunk-N7IN46Q5.js → chunk-7IZH2KQX.js} +9 -7
- package/dist/bundle/chunks/{chunk-7GZUFOM3.js → chunk-ARSENVDY.js} +1 -1
- package/dist/bundle/chunks/{chunk-STMXIBXF.js → chunk-CNDPCYNT.js} +1 -1
- package/dist/bundle/chunks/{chunk-XF3LJC3O.js → chunk-CYOHFREV.js} +1 -1
- package/dist/bundle/chunks/{chunk-E6D4RIT7.js → chunk-DNDZPKIQ.js} +2 -2
- package/dist/bundle/chunks/{chunk-E4LBKOZG.js → chunk-EQNUL2WB.js} +4 -4
- package/dist/bundle/chunks/{chunk-2RB5EL4X.js → chunk-ESNSRUW4.js} +1 -1
- package/dist/bundle/chunks/{chunk-4AOMNOGY.js → chunk-FQDARFHT.js} +1 -1
- package/dist/bundle/chunks/{chunk-BWA4FVFW.js → chunk-GPPOPK76.js} +3 -3
- package/dist/bundle/chunks/{chunk-SBGIXMX3.js → chunk-GWGNVYPB.js} +1 -1
- package/dist/bundle/chunks/{chunk-KKD4CYCI.js → chunk-H4WJ4LY3.js} +1 -1
- package/dist/bundle/chunks/{chunk-V2UYXTRR.js → chunk-HWTD4QF3.js} +1 -1
- package/dist/bundle/chunks/chunk-I2WFLKZ2.js +5 -0
- package/dist/bundle/chunks/{chunk-WD6WBYC5.js → chunk-IHKHCWWG.js} +1 -1
- package/dist/bundle/chunks/{chunk-T36LJ6MW.js → chunk-IY4G44MN.js} +1 -1
- package/dist/bundle/chunks/{chunk-BZ7726QO.js → chunk-J2YAYTB2.js} +1 -1
- package/dist/bundle/chunks/{chunk-UFJ2K7Q7.js → chunk-JH7MVRNJ.js} +1 -1
- package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
- package/dist/bundle/chunks/{chunk-OLCDIITS.js → chunk-KRO63T4Q.js} +1 -1
- package/dist/bundle/chunks/{chunk-TTU2RNVX.js → chunk-KTDIIF2F.js} +1 -1
- package/dist/bundle/chunks/{chunk-KNVNKDER.js → chunk-L2O6K3SJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-3BTN337S.js → chunk-NDNIMC5W.js} +1 -1
- package/dist/bundle/chunks/{chunk-ITGYN22E.js → chunk-ODRGPC5S.js} +1 -1
- package/dist/bundle/chunks/{chunk-4MOZXOJX.js → chunk-PDG4XWO2.js} +1 -1
- package/dist/bundle/chunks/{chunk-3QCYBOKN.js → chunk-PPECSSWX.js} +1 -1
- package/dist/bundle/chunks/{chunk-4P7WARQZ.js → chunk-PY4ZQHTN.js} +1 -1
- package/dist/bundle/chunks/{chunk-C2MQ4QXN.js → chunk-RPW3XQW3.js} +48 -42
- package/dist/bundle/chunks/{chunk-JMOUU75H.js → chunk-RQ5JTXUC.js} +1 -1
- package/dist/bundle/chunks/chunk-TQ67PB7U.js +13 -0
- package/dist/bundle/chunks/{chunk-5JJDR6XI.js → chunk-UJPG4ROT.js} +1 -1
- package/dist/bundle/chunks/{chunk-FHXR4ZAC.js → chunk-XM36IFP3.js} +1 -1
- package/dist/bundle/chunks/{chunk-PT6JJUN2.js → chunk-XMNPK472.js} +1 -1
- package/dist/bundle/chunks/{client-WTYEICLH.js → chunk-Y3TMJV3J.js} +3 -3
- package/dist/bundle/chunks/{chunk-GSSD3R5X.js → chunk-Z6BREXV3.js} +1 -1
- package/dist/bundle/chunks/{chunk-KXB5WIS5.js → chunk-Z6SAQ5O3.js} +1 -1
- package/dist/bundle/chunks/{chunk-LOIF6PHI.js → chunk-ZAUVY6ZC.js} +3 -3
- package/dist/bundle/chunks/{chunk-7CNOCJCN.js → chunk-ZP6JJDYV.js} +1 -1
- package/dist/bundle/chunks/cli-main-Q2GA53XM.js +3 -0
- package/dist/bundle/chunks/client-LOS6GSEW.js +2 -0
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-K5H6BJP3.js → cloudflare-workers-ai-system-one-WDHT7BBV.js} +1 -1
- package/dist/bundle/chunks/cursor-agent.js +3 -3
- package/dist/bundle/chunks/devin-agent.js +3 -3
- package/dist/bundle/chunks/dispatch-5R3TYAYB.js +9 -0
- package/dist/bundle/chunks/google-generative-ai-FHYFXKLA.js +2 -0
- package/dist/bundle/chunks/google-vertex-5DWE5U5K.js +2 -0
- package/dist/bundle/chunks/{help-fast-path-3Z26DQSV.js → help-fast-path-FETLIF3W.js} +1 -1
- package/dist/bundle/chunks/host-command-XEB7SHT3.js +2 -0
- package/dist/bundle/chunks/{host-lifecycle-3VHHIGDL.js → host-lifecycle-HX3EGRRW.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +2 -2
- package/dist/bundle/chunks/{host-session-command-56XF6ACS.js → host-session-command-FFN52LXJ.js} +1 -1
- package/dist/bundle/chunks/{host-socket-request-QL2WEZTN.js → host-socket-request-K4LCUOUD.js} +1 -1
- package/dist/bundle/chunks/interactive-mode-5DEKY3YW.js +2 -0
- package/dist/bundle/chunks/{mcp-presenter-bootstrap-XUSCQVRC.js → mcp-presenter-bootstrap-FOMKF4TU.js} +1 -1
- package/dist/bundle/chunks/{mistral-conversations-DH44NOYV.js → mistral-conversations-I2PI7DDF.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-Z2CW4SDM.js → multi-session-host-OPR5VBFO.js} +6 -7
- package/dist/bundle/chunks/{openai-codex-responses-SERAFKAS.js → openai-codex-responses-WSEX6SH7.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-BZB5O45A.js → openai-completions-YCBHTBSS.js} +1 -1
- package/dist/bundle/chunks/openai-responses-GXNSQF4A.js +3 -0
- package/dist/bundle/chunks/{package-manager-cli-N55U6PEO.js → package-manager-cli-W55E3LHP.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-VXATX45B.js → pi-messages-O6GISIRW.js} +2 -2
- package/dist/bundle/chunks/{role-JVSBNKGE.js → role-SHIZQKK4.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-CBGCBYHV.js → rotation-stream-2PCZWNKF.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-IDUJTSYS.js +2 -0
- package/dist/bundle/chunks/{rpc-monitor-source-AE3C6EQV.js → rpc-monitor-source-DRZXJHTL.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-VS7CZC2J.js → schedule-command-B7RRSANU.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-LMZH5JLH.js +3 -0
- package/dist/bundle/chunks/{session-picker-E4KUJDLO.js → session-picker-DTGBOXDR.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +214 -205
- package/dist/bundle/chunks/{typesafe-system-one-P75WJ767.js → typesafe-system-one-XGYJJVSE.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +2 -2
- 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/host-command-args.d.ts +1 -0
- package/dist/cli/host-command-args.js +5 -0
- package/dist/cli/host-command.js +7 -1
- package/dist/cli.js +21 -3
- package/dist/core/agent-session.d.ts +11 -1
- package/dist/core/agent-session.js +19 -2
- package/dist/core/extensions/builtin/compaction/index.js +7 -1
- package/dist/core/extensions/builtin/herdr/index.js +9 -5
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +6 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +25 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +3 -3
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +50 -19
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +4 -3
- package/dist/core/extensions/builtin/mcp/auth/oauth.js +19 -12
- package/dist/core/extensions/builtin/mcp/catalog-cache.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/catalog-cache.js +19 -6
- package/dist/core/extensions/builtin/mcp/catalog.d.ts +3 -2
- package/dist/core/extensions/builtin/mcp/catalog.js +20 -3
- package/dist/core/extensions/builtin/mcp/expose/call.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/expose/call.js +2 -1
- package/dist/core/extensions/builtin/mcp/expose/register.js +1 -1
- package/dist/core/extensions/builtin/mcp/expose/schema-compat.d.ts +2 -2
- package/dist/core/extensions/builtin/mcp/expose/schema-compat.js +11 -1
- package/dist/core/extensions/builtin/mcp/invocation.js +16 -2
- package/dist/core/extensions/builtin/mcp/modern/dispatch.js +1 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +33 -35
- package/dist/core/extensions/builtin/mcp/modern/request.d.ts +1 -1
- package/dist/core/extensions/builtin/mcp/modern/request.js +4 -1
- package/dist/core/extensions/builtin/mcp/modern/sdk-bridge.js +18 -3
- package/dist/core/extensions/builtin/mcp/modern/stdio-probe.js +23 -0
- package/dist/core/extensions/builtin/mcp/modern/subscriptions.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/modern/subscriptions.js +226 -0
- package/dist/core/extensions/builtin/mcp/modern/tool-schema.d.ts +13 -0
- package/dist/core/extensions/builtin/mcp/modern/tool-schema.js +75 -0
- package/dist/core/extensions/builtin/mcp/modern/tools.d.ts +107 -0
- package/dist/core/extensions/builtin/mcp/modern/tools.js +53 -0
- package/dist/core/extensions/builtin/mcp/modern/transport.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/modern/transport.js +4 -2
- package/dist/core/extensions/builtin/mcp/resources.js +4 -0
- package/dist/core/extensions/builtin/mcp/tool-client.d.ts +13 -0
- package/dist/core/extensions/builtin/mcp/tool-client.js +17 -0
- package/dist/core/extensions/builtin/mcp/tool-contracts.d.ts +19 -0
- package/dist/core/extensions/builtin/mcp/tool-contracts.js +2 -0
- package/dist/core/extensions/builtin/mcp/wrap.js +2 -2
- package/dist/core/extensions/builtin/permission-system/bash-command-segments.d.ts +37 -0
- package/dist/core/extensions/builtin/permission-system/bash-command-segments.js +899 -0
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts +2 -0
- package/dist/core/extensions/builtin/permission-system/parsers.js +44 -10
- package/dist/core/extensions/builtin/terminal/extension-state.js +2 -1
- package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +9 -0
- package/dist/core/extensions/builtin/vertex-model-garden/auth.js +17 -1
- package/dist/core/extensions/builtin/websearch/websearch/providers/google.d.ts +6 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/google.js +6 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/lifecycle-children.d.ts +3 -1
- package/dist/core/lifecycle-children.js +79 -15
- package/dist/core/lifecycle-record.js +1 -24
- package/dist/core/lifecycle-session-binding.js +2 -1
- package/dist/core/lifecycle-wait-items.d.ts +17 -0
- package/dist/core/lifecycle-wait-items.js +72 -0
- package/dist/core/model-runtime.js +6 -1
- package/dist/core/provider-api-key-auth.d.ts +6 -1
- package/dist/core/provider-api-key-auth.js +11 -4
- package/dist/core/provider-composer.d.ts +8 -1
- package/dist/core/provider-composer.js +7 -3
- package/dist/core/sdk.js +4 -1
- package/dist/core/session-activity.js +2 -0
- package/dist/core/session-lifecycle-types.d.ts +20 -4
- package/dist/core/session-lifecycle.js +35 -4
- package/dist/core/structured-session-lane.d.ts +33 -0
- package/dist/core/structured-session-lane.js +62 -0
- package/dist/core/work-custody-record.d.ts +2 -1
- package/dist/core/work-custody-record.js +2 -0
- package/dist/core/work-obligations.d.ts +1 -1
- package/dist/core/work-obligations.js +8 -2
- package/dist/modes/interactive/session-control-commands.js +21 -4
- package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
- package/dist/modes/interactive/session-control-rpc-commands.js +19 -0
- package/dist/modes/json-event.d.ts +79 -1
- package/dist/modes/json-event.js +79 -1
- package/dist/modes/rpc/compact-content-index.d.ts +85 -0
- package/dist/modes/rpc/compact-content-index.js +254 -0
- package/dist/modes/rpc/compact-content-store.d.ts +59 -0
- package/dist/modes/rpc/compact-content-store.js +134 -0
- package/dist/modes/rpc/compact-index-pages.d.ts +12 -0
- package/dist/modes/rpc/compact-index-pages.js +31 -0
- package/dist/modes/rpc/compact-session-state.d.ts +83 -0
- package/dist/modes/rpc/compact-session-state.js +197 -0
- package/dist/modes/rpc/compact-spool-dir.d.ts +8 -0
- package/dist/modes/rpc/compact-spool-dir.js +94 -0
- package/dist/modes/rpc/compact-spool-storage.d.ts +53 -0
- package/dist/modes/rpc/compact-spool-storage.js +162 -0
- package/dist/modes/rpc/compact-storage-io.d.ts +28 -0
- package/dist/modes/rpc/compact-storage-io.js +25 -0
- package/dist/modes/rpc/compact-transition.d.ts +41 -0
- package/dist/modes/rpc/compact-transition.js +12 -0
- package/dist/modes/rpc/connection-handler.d.ts +4 -0
- package/dist/modes/rpc/connection-handler.js +51 -13
- package/dist/modes/rpc/connection-question-bridge.d.ts +2 -1
- package/dist/modes/rpc/connection-question-bridge.js +6 -2
- package/dist/modes/rpc/extension-ui-response.d.ts +7 -2
- package/dist/modes/rpc/extension-ui-response.js +27 -7
- package/dist/modes/rpc/get-snapshot.d.ts +18 -0
- package/dist/modes/rpc/get-snapshot.js +43 -0
- package/dist/modes/rpc/host-capabilities.js +3 -0
- package/dist/modes/rpc/host-ensure-start.js +6 -3
- 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-request.d.ts +1 -1
- package/dist/modes/rpc/host-handover-request.js +4 -3
- package/dist/modes/rpc/host-handover-wire.js +6 -0
- package/dist/modes/rpc/host-idle-handover.d.ts +2 -0
- package/dist/modes/rpc/host-idle-handover.js +3 -1
- package/dist/modes/rpc/host-observe-request.js +1 -0
- package/dist/modes/rpc/host-runner.d.ts +1 -0
- package/dist/modes/rpc/host-runner.js +9 -7
- package/dist/modes/rpc/host-successor.js +4 -0
- package/dist/modes/rpc/json-normal-value.d.ts +6 -0
- package/dist/modes/rpc/json-normal-value.js +100 -0
- package/dist/modes/rpc/mcp-presenter-bootstrap.js +5 -0
- package/dist/modes/rpc/media-placeholders.d.ts +9 -3
- package/dist/modes/rpc/media-placeholders.js +12 -4
- package/dist/modes/rpc/rpc-host-command-types.d.ts +2 -0
- package/dist/modes/rpc/rpc-question-types.d.ts +1 -0
- package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
- package/dist/modes/rpc/rpc-session-command-types.d.ts +6 -0
- package/dist/modes/rpc/rpc-session-state-types.d.ts +3 -0
- package/dist/modes/rpc/rpc-session-state.js +2 -0
- package/dist/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/modes/rpc/security-presentations.js +2 -1
- package/dist/modes/rpc/session-attachment-gate.js +1 -0
- package/dist/modes/rpc/session-binding.js +30 -17
- package/dist/modes/rpc/session-command-activity.js +1 -0
- package/dist/modes/rpc/session-event-fanout.d.ts +1 -1
- package/dist/modes/rpc/session-event-fanout.js +17 -9
- package/dist/modes/rpc/session-event-writer.d.ts +2 -0
- package/dist/modes/rpc/session-event-writer.js +24 -5
- package/dist/modes/rpc/session-extension-ui-requests.d.ts +2 -1
- package/dist/modes/rpc/session-extension-ui-requests.js +15 -10
- package/dist/modes/rpc/session-held.js +1 -0
- package/dist/modes/rpc/session-record-stream.d.ts +3 -3
- package/dist/modes/rpc/session-record-stream.js +3 -3
- package/dist/modes/rpc/session-status-stream.d.ts +2 -1
- package/dist/modes/rpc/session-status-stream.js +8 -2
- package/dist/modes/rpc/session-status-subscriptions.d.ts +2 -1
- package/dist/modes/rpc/session-status-subscriptions.js +4 -0
- package/dist/modes/rpc/session-structured-lanes.d.ts +12 -0
- package/dist/modes/rpc/session-structured-lanes.js +24 -0
- package/dist/modes/rpc/session-worker-client.js +6 -5
- package/dist/modes/rpc/session-worker.js +5 -2
- package/dist/modes/rpc/snapshot-types.d.ts +29 -0
- package/dist/modes/rpc/snapshot-types.js +2 -0
- package/dist/modes/rpc/structured-session-records.d.ts +12 -0
- package/dist/modes/rpc/structured-session-records.js +88 -0
- package/dist/modes/rpc/ui-answer-ledger.d.ts +13 -0
- package/dist/modes/rpc/ui-answer-ledger.js +34 -0
- package/dist/senpi +21 -3
- package/docs/extensions.md +7 -1
- package/docs/mcp.md +20 -0
- package/docs/rpc.md +61 -2
- package/package.json +7 -6
- package/dist/bundle/chunks/azure-openai-responses-7I45DI72.js +0 -2
- package/dist/bundle/chunks/chunk-DMI55M2N.js +0 -2
- package/dist/bundle/chunks/chunk-DODH3N6U.js +0 -410
- package/dist/bundle/chunks/chunk-J37GRED2.js +0 -5
- package/dist/bundle/chunks/chunk-TBU6VYHN.js +0 -13
- package/dist/bundle/chunks/cli-main-YYH3IBC6.js +0 -3
- package/dist/bundle/chunks/dispatch-MXG5YWLV.js +0 -9
- package/dist/bundle/chunks/google-generative-ai-UM5Y7T6B.js +0 -2
- package/dist/bundle/chunks/google-vertex-OKU2VQHL.js +0 -2
- package/dist/bundle/chunks/host-command-TDLLHXVI.js +0 -2
- package/dist/bundle/chunks/interactive-mode-OEPULLZK.js +0 -2
- package/dist/bundle/chunks/openai-responses-TNHQ6SJS.js +0 -3
- package/dist/bundle/chunks/rpc-mode-ETXQWEB2.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-4LEIDIVI.js +0 -3
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { isBigIntObject, isBooleanObject, isNumberObject, isStringObject } from "node:util/types";
|
|
2
|
+
/** JSON.stringify's value semantics, without serializing or parsing a record. */
|
|
3
|
+
export function normalizeJsonValue(input) {
|
|
4
|
+
const ancestors = new Set();
|
|
5
|
+
let changed = false;
|
|
6
|
+
const visit = (input, key) => {
|
|
7
|
+
let value = input;
|
|
8
|
+
if ((typeof value === "object" && value !== null) || typeof value === "function" || typeof value === "bigint") {
|
|
9
|
+
const toJSON = value.toJSON;
|
|
10
|
+
if (typeof toJSON === "function") {
|
|
11
|
+
value = toJSON.call(value, key);
|
|
12
|
+
changed = true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === "object" && value !== null) {
|
|
16
|
+
if (isNumberObject(value)) {
|
|
17
|
+
value = +value;
|
|
18
|
+
changed = true;
|
|
19
|
+
}
|
|
20
|
+
else if (isStringObject(value)) {
|
|
21
|
+
value = String(value);
|
|
22
|
+
changed = true;
|
|
23
|
+
}
|
|
24
|
+
else if (isBooleanObject(value)) {
|
|
25
|
+
value = Boolean.prototype.valueOf.call(value);
|
|
26
|
+
changed = true;
|
|
27
|
+
}
|
|
28
|
+
else if (isBigIntObject(value)) {
|
|
29
|
+
throw new TypeError("Do not know how to serialize a BigInt");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (typeof value === "bigint")
|
|
33
|
+
throw new TypeError("Do not know how to serialize a BigInt");
|
|
34
|
+
if (typeof value === "number") {
|
|
35
|
+
const normalized = Number.isFinite(value) ? (Object.is(value, -0) ? 0 : value) : null;
|
|
36
|
+
if (!Object.is(normalized, value))
|
|
37
|
+
changed = true;
|
|
38
|
+
return normalized;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") {
|
|
41
|
+
changed = true;
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value !== "object" || value === null)
|
|
45
|
+
return value;
|
|
46
|
+
if (ancestors.has(value))
|
|
47
|
+
throw new TypeError("Converting circular structure to JSON");
|
|
48
|
+
ancestors.add(value);
|
|
49
|
+
try {
|
|
50
|
+
// A non-callable toJSON getter must not be read again by later serialization.
|
|
51
|
+
if ("toJSON" in value)
|
|
52
|
+
changed = true;
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
const array = [];
|
|
55
|
+
const length = value.length;
|
|
56
|
+
for (let index = 0; index < length; index++) {
|
|
57
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
58
|
+
if (!descriptor || descriptor.get)
|
|
59
|
+
changed = true;
|
|
60
|
+
array.push(visit(value[index], String(index)) ?? null);
|
|
61
|
+
}
|
|
62
|
+
return array;
|
|
63
|
+
}
|
|
64
|
+
const result = {};
|
|
65
|
+
const names = Object.keys(value);
|
|
66
|
+
if (Object.getPrototypeOf(value) !== Object.prototype || Reflect.ownKeys(value).length !== names.length)
|
|
67
|
+
changed = true;
|
|
68
|
+
for (const name of names) {
|
|
69
|
+
if (Object.getOwnPropertyDescriptor(value, name)?.get)
|
|
70
|
+
changed = true;
|
|
71
|
+
const child = visit(value[name], name);
|
|
72
|
+
if (child !== undefined)
|
|
73
|
+
Object.defineProperty(result, name, {
|
|
74
|
+
value: child,
|
|
75
|
+
enumerable: true,
|
|
76
|
+
configurable: true,
|
|
77
|
+
writable: true,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
ancestors.delete(value);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
let value;
|
|
87
|
+
try {
|
|
88
|
+
value = visit(input, "");
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// The recursive walk overflows the stack before native JSON does; let native JSON decide.
|
|
92
|
+
if (!(error instanceof RangeError))
|
|
93
|
+
throw error;
|
|
94
|
+
return { value: JSON.parse(JSON.stringify(input)), changed: true };
|
|
95
|
+
}
|
|
96
|
+
if (value === undefined)
|
|
97
|
+
throw new SyntaxError("JSON input has no representation");
|
|
98
|
+
return { value, changed };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=json-normal-value.js.map
|
|
@@ -11,6 +11,11 @@ export function presenterBootstrapStream(fd) {
|
|
|
11
11
|
const stat = fstatSync(fd);
|
|
12
12
|
if (!stat.isSocket() && !stat.isFIFO())
|
|
13
13
|
return undefined;
|
|
14
|
+
// Node's fs read blocks a worker until peer EOF, even after destroy(), and process.exit()
|
|
15
|
+
// joins that worker. A socket handle can cancel the pending read after enrollment.
|
|
16
|
+
// Bun does not support inherited net.Socket fds; it retains the file-stream adapter below.
|
|
17
|
+
if (stat.isSocket() && !process.versions.bun)
|
|
18
|
+
return new Socket({ fd, readable: true, writable: true });
|
|
14
19
|
}
|
|
15
20
|
catch {
|
|
16
21
|
// Bootstrap is optional authority, never a reason to terminate the shared host.
|
|
@@ -11,17 +11,18 @@
|
|
|
11
11
|
* `SessionEventWriter.enqueue`.
|
|
12
12
|
*
|
|
13
13
|
* Two hard rules:
|
|
14
|
-
* - Type-gate
|
|
15
|
-
* never
|
|
14
|
+
* - Type-gate the media walk. JSON-value normalization is shared with writer
|
|
15
|
+
* acceptance; media projection itself never walks `message_update` partials.
|
|
16
16
|
* - Copy-on-write. Responses hand out live agent state by reference
|
|
17
17
|
* (`success(id, "get_messages", { messages: session.messages })`), so the input graph
|
|
18
|
-
* is never mutated and unchanged sub-trees keep their identity. When nothing changed
|
|
18
|
+
* is never mutated and unchanged JSON sub-trees keep their identity. When nothing changed
|
|
19
19
|
* the SAME record reference comes back, which lets the caller reuse the line it
|
|
20
20
|
* already serialized.
|
|
21
21
|
*
|
|
22
22
|
* User-authored images (`prompt.images`) stay intact: they are bounded by the client
|
|
23
23
|
* that sent them; the fan-out hazard is tool output.
|
|
24
24
|
*/
|
|
25
|
+
type RpcRecord = Record<string, unknown>;
|
|
25
26
|
/** Why a host could not keep a tool image on disk for the client to read. */
|
|
26
27
|
export type MediaUnavailableReason = "image_too_large" | "session_limit" | "storage_error";
|
|
27
28
|
/** Where a persisted image lives (absolute), or why there is no file. */
|
|
@@ -61,6 +62,8 @@ export interface ImageRefBlock {
|
|
|
61
62
|
* omitting it.
|
|
62
63
|
*/
|
|
63
64
|
export declare function base64ByteLength(data: string): number;
|
|
65
|
+
/** True when this record type can carry a tool result at all (cheap gate). */
|
|
66
|
+
export declare function carriesMedia(record: RpcRecord): boolean;
|
|
64
67
|
/**
|
|
65
68
|
* Replace inline tool-result images with `image_ref` placeholders.
|
|
66
69
|
*
|
|
@@ -68,4 +71,7 @@ export declare function base64ByteLength(data: string): number;
|
|
|
68
71
|
* serialization entirely.
|
|
69
72
|
*/
|
|
70
73
|
export declare function omitInlineMedia<T extends object>(record: T, persist?: MediaPersister): T;
|
|
74
|
+
/** `omitInlineMedia` for a value that is already JSON-normal: type gate first, no normalization walk. */
|
|
75
|
+
export declare function omitNormalInlineMedia<T extends object>(record: T, persist?: MediaPersister): T;
|
|
76
|
+
export {};
|
|
71
77
|
//# sourceMappingURL=media-placeholders.d.ts.map
|
|
@@ -11,17 +11,18 @@
|
|
|
11
11
|
* `SessionEventWriter.enqueue`.
|
|
12
12
|
*
|
|
13
13
|
* Two hard rules:
|
|
14
|
-
* - Type-gate
|
|
15
|
-
* never
|
|
14
|
+
* - Type-gate the media walk. JSON-value normalization is shared with writer
|
|
15
|
+
* acceptance; media projection itself never walks `message_update` partials.
|
|
16
16
|
* - Copy-on-write. Responses hand out live agent state by reference
|
|
17
17
|
* (`success(id, "get_messages", { messages: session.messages })`), so the input graph
|
|
18
|
-
* is never mutated and unchanged sub-trees keep their identity. When nothing changed
|
|
18
|
+
* is never mutated and unchanged JSON sub-trees keep their identity. When nothing changed
|
|
19
19
|
* the SAME record reference comes back, which lets the caller reuse the line it
|
|
20
20
|
* already serialized.
|
|
21
21
|
*
|
|
22
22
|
* User-authored images (`prompt.images`) stay intact: they are bounded by the client
|
|
23
23
|
* that sent them; the fan-out hazard is tool output.
|
|
24
24
|
*/
|
|
25
|
+
import { normalizeJsonValue } from "./json-normal-value.js";
|
|
25
26
|
/** Record types that can carry a tool result somewhere in their payload. */
|
|
26
27
|
const MEDIA_BEARING_RECORD_TYPES = new Set([
|
|
27
28
|
"tool_execution_end",
|
|
@@ -128,7 +129,7 @@ function omitToolExecutionEndImages(record, persist) {
|
|
|
128
129
|
return omitted === result.content ? record : { ...record, result: { ...result, content: omitted } };
|
|
129
130
|
}
|
|
130
131
|
/** True when this record type can carry a tool result at all (cheap gate). */
|
|
131
|
-
function carriesMedia(record) {
|
|
132
|
+
export function carriesMedia(record) {
|
|
132
133
|
if (typeof record.type !== "string")
|
|
133
134
|
return false;
|
|
134
135
|
if (MEDIA_BEARING_RECORD_TYPES.has(record.type))
|
|
@@ -144,6 +145,13 @@ function carriesMedia(record) {
|
|
|
144
145
|
* serialization entirely.
|
|
145
146
|
*/
|
|
146
147
|
export function omitInlineMedia(record, persist) {
|
|
148
|
+
const { value, changed } = normalizeJsonValue(record);
|
|
149
|
+
if (!changed)
|
|
150
|
+
return omitNormalInlineMedia(record, persist);
|
|
151
|
+
return omitNormalInlineMedia(value, persist);
|
|
152
|
+
}
|
|
153
|
+
/** `omitInlineMedia` for a value that is already JSON-normal: type gate first, no normalization walk. */
|
|
154
|
+
export function omitNormalInlineMedia(record, persist) {
|
|
147
155
|
const typed = record;
|
|
148
156
|
if (!carriesMedia(typed))
|
|
149
157
|
return record;
|
|
@@ -192,6 +192,8 @@ export type RpcCommand = RpcAttachmentCommand | (RpcSessionCommand & {
|
|
|
192
192
|
* caused the transition (default: true for `childrenOf`, false otherwise).
|
|
193
193
|
*/
|
|
194
194
|
effects?: boolean;
|
|
195
|
+
/** Observe dialog registration and settlement without receiving command replies. */
|
|
196
|
+
dialogs?: boolean;
|
|
195
197
|
/** Also deliver these content record types of matched sessions (default `"none"`); opt-in, never an attachment. */
|
|
196
198
|
mirror?: "none" | "all" | string[];
|
|
197
199
|
/** Mark the request activity-neutral for the supervisor's idle exit, as on `list_sessions`. */
|
|
@@ -10,7 +10,14 @@ import type { EffectStatus } from "./effect-record.ts";
|
|
|
10
10
|
import type { RpcSlashCommand } from "./rpc-command-surface.ts";
|
|
11
11
|
import type { RpcEvalCell, RpcEvalCellSnapshot } from "./rpc-result-types.ts";
|
|
12
12
|
import type { RpcAuthProvider, RpcLoadedExtension, RpcLoadedMcpServer, RpcProviderAccount, RpcSessionState } from "./rpc-session-state-types.ts";
|
|
13
|
+
import type { RpcSnapshot } from "./snapshot-types.ts";
|
|
13
14
|
export type RpcReadResponse = {
|
|
15
|
+
id?: string;
|
|
16
|
+
type: "response";
|
|
17
|
+
command: "get_snapshot";
|
|
18
|
+
success: true;
|
|
19
|
+
data: RpcSnapshot;
|
|
20
|
+
} | {
|
|
14
21
|
id?: string;
|
|
15
22
|
type: "response";
|
|
16
23
|
command: "get_state";
|
|
@@ -182,6 +182,12 @@ export type RpcSessionCommand = {
|
|
|
182
182
|
} | {
|
|
183
183
|
id?: string;
|
|
184
184
|
type: "get_state";
|
|
185
|
+
} | {
|
|
186
|
+
id?: string;
|
|
187
|
+
type: "get_snapshot";
|
|
188
|
+
tail?: number;
|
|
189
|
+
maxBytes?: number;
|
|
190
|
+
observe?: boolean;
|
|
185
191
|
} | {
|
|
186
192
|
id?: string;
|
|
187
193
|
type: "request_self_settle";
|
|
@@ -9,6 +9,7 @@ import type { SessionEntry, UsageTotals } from "../../core/session-manager.ts";
|
|
|
9
9
|
import type { ThinkingLevelSource } from "../../core/session-thinking-memory.ts";
|
|
10
10
|
import type { SourceInfo } from "../../core/source-info.ts";
|
|
11
11
|
import type { RpcExtensionUIRequest, RpcQuestionUiRequest } from "./rpc-question-types.ts";
|
|
12
|
+
import type { UiAnswerReceipt } from "./ui-answer-ledger.ts";
|
|
12
13
|
/** One provider row for the /login and /logout selectors. */
|
|
13
14
|
export interface RpcAuthProvider {
|
|
14
15
|
/** Provider id (e.g. "anthropic", "openai"). */
|
|
@@ -61,6 +62,8 @@ export interface RpcSessionModelEntry {
|
|
|
61
62
|
serviceTier?: ServiceTier;
|
|
62
63
|
}
|
|
63
64
|
export interface RpcSessionState {
|
|
65
|
+
/** Latest 64 UI-answer receipts in this session generation, oldest first. */
|
|
66
|
+
answeredUiRequests?: UiAnswerReceipt[];
|
|
64
67
|
paused: boolean;
|
|
65
68
|
pausedNoticeCount: number;
|
|
66
69
|
holds: {
|
|
@@ -7,6 +7,7 @@ import { sanitizeProviderDiagnostic } from "@earendil-works/pi-ai";
|
|
|
7
7
|
import { ProjectTrustStore } from "../../core/trust-manager.js";
|
|
8
8
|
import { clientFacingIdentity } from "./client-admission-record.js";
|
|
9
9
|
import { sessionQuestionBridges } from "./connection-question-bridge.js";
|
|
10
|
+
import { sessionUiAnswerLedger } from "./ui-answer-ledger.js";
|
|
10
11
|
import { sessionDialogRequests } from "./session-extension-ui-requests.js";
|
|
11
12
|
/**
|
|
12
13
|
* Project one session into the wire state shape.
|
|
@@ -29,6 +30,7 @@ export function buildRpcSessionState(session, lastAbortSource) {
|
|
|
29
30
|
const projectTrusted = new ProjectTrustStore(session.agentDir).get(cwd) === true;
|
|
30
31
|
const lastProviderDiagnostic = sanitizeProviderDiagnostic(session.agent.state.providerDiagnostic);
|
|
31
32
|
return {
|
|
33
|
+
answeredUiRequests: sessionUiAnswerLedger(session).snapshot(),
|
|
32
34
|
paused: session.paused ?? false,
|
|
33
35
|
pausedNoticeCount: session.pausedNoticeCount ?? 0,
|
|
34
36
|
holds: session.holds ?? [],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Public RPC protocol surface. Definitions are grouped by responsibility. */
|
|
2
|
+
export type { RpcSnapshot, RpcSnapshotMarker, RpcUiRequestEvent } from "./snapshot-types.ts";
|
|
2
3
|
export type { SessionContext, SessionKind } from "../../core/extensions/types.ts";
|
|
3
4
|
export type { RpcCommandInvocationEvent } from "./rpc-command-invocation.ts";
|
|
4
5
|
export type { RpcCommandsChangedEvent, RpcSlashCommand } from "./rpc-command-surface.ts";
|
|
@@ -109,7 +109,8 @@ export class RpcSecurityPresentations {
|
|
|
109
109
|
if (pending.connection !== connection ||
|
|
110
110
|
(connection !== undefined && !this.fanout.connectionHas(connection, "grant_select")))
|
|
111
111
|
return false;
|
|
112
|
-
|
|
112
|
+
// The entry stays until the worker reports the grant settled (settleGrant): a frame the worker
|
|
113
|
+
// refuses leaves the grant pending, and it must stay answerable only by its recipient.
|
|
113
114
|
return true;
|
|
114
115
|
}
|
|
115
116
|
settleGrant(sessionId, requestId) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { bindToProviderScope, runWithProviderScope } from "@earendil-works/pi-ai/node/provider-scope";
|
|
2
2
|
import { createRpcConnectionHandler, } from "./connection-handler.js";
|
|
3
3
|
import { createToolAttributionSpans } from "./session-attribution.js";
|
|
4
|
+
import { structuredLaneEnabled } from "./structured-session-records.js";
|
|
4
5
|
/**
|
|
5
6
|
* Creates the extension UI bridge and subscriptions within the entry's provider
|
|
6
7
|
* scope. The classic handler remains the single source of command semantics.
|
|
@@ -14,14 +15,9 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
|
|
|
14
15
|
// session is inside is what the loop-lag watchdog blames for a stall. The record
|
|
15
16
|
// stream already carries that transition; no extra subscription is needed.
|
|
16
17
|
const toolSpans = createToolAttributionSpans(sessionId);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
continue;
|
|
21
|
-
const record = JSON.parse(line);
|
|
22
|
-
toolSpans.observe(record);
|
|
23
|
-
writer.enqueue(sessionId, record);
|
|
24
|
-
}
|
|
18
|
+
const enqueueRecord = (record) => {
|
|
19
|
+
toolSpans.observe(record);
|
|
20
|
+
writer.enqueue(sessionId, record);
|
|
25
21
|
};
|
|
26
22
|
// Attachments share one entry, so resolve the host from the live runtime. In
|
|
27
23
|
// particular, switch_session must use the entry's replacement-aware method
|
|
@@ -48,16 +44,34 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
|
|
|
48
44
|
// an unawaited prompt (senpi#2871).
|
|
49
45
|
let droppedAfterClose = false;
|
|
50
46
|
const handler = await runWithProviderScope(entry.scope, async () => {
|
|
51
|
-
const writeInScope = bindToProviderScope(
|
|
47
|
+
const writeInScope = bindToProviderScope(enqueueRecord);
|
|
48
|
+
const dropAfterClose = () => {
|
|
49
|
+
if (!droppedAfterClose)
|
|
50
|
+
process.stderr.write(`senpi rpc host: dropped a record for closed session ${sessionId} at ${new Date().toISOString()}\n`);
|
|
51
|
+
droppedAfterClose = true;
|
|
52
|
+
};
|
|
53
|
+
const writeRecord = (record) => {
|
|
54
|
+
if (entry.scope.state === "closed")
|
|
55
|
+
return dropAfterClose();
|
|
56
|
+
writeInScope(record);
|
|
57
|
+
};
|
|
58
|
+
const writeStructuredInScope = bindToProviderScope((record) => writer.structuredLanes.accept(sessionId, record));
|
|
52
59
|
const taggedSink = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
writeRecord,
|
|
61
|
+
...(structuredLaneEnabled()
|
|
62
|
+
? {
|
|
63
|
+
writeStructured: (record) => {
|
|
64
|
+
if (entry.scope.state !== "closed")
|
|
65
|
+
writeStructuredInScope(record);
|
|
66
|
+
},
|
|
59
67
|
}
|
|
60
|
-
|
|
68
|
+
: {}),
|
|
69
|
+
// A late raw reply after the scope closed is dropped like any record (senpi#2871); while the scope is
|
|
70
|
+
// open, records arrive only through writeRecord, so a raw write is a wiring bug.
|
|
71
|
+
writeRaw: () => {
|
|
72
|
+
if (entry.scope.state === "closed")
|
|
73
|
+
return dropAfterClose();
|
|
74
|
+
throw new Error("Shared session bindings deliver records through writeRecord, never writeRaw");
|
|
61
75
|
},
|
|
62
76
|
waitForBackpressure: bindToProviderScope(async () => { }),
|
|
63
77
|
};
|
|
@@ -65,7 +79,6 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
|
|
|
65
79
|
sessionId,
|
|
66
80
|
shutdownHandler: bindToProviderScope(requestClose),
|
|
67
81
|
disposeRuntime: false,
|
|
68
|
-
eventFlushScheduler: (flush) => flush(),
|
|
69
82
|
publishLoadedSurfacesChanged: (previous, digest) => {
|
|
70
83
|
if (entry.scope.state !== "closed")
|
|
71
84
|
writer.publishLoadedSurfacesChanged(sessionId, previous, digest);
|
|
@@ -50,7 +50,7 @@ export declare class SessionEventFanout {
|
|
|
50
50
|
pendingQuestionCount(sessionId: string): number;
|
|
51
51
|
/** Deliver one line to the connections attached to a session, never to the rest of the fanout. */
|
|
52
52
|
deliverToSession(sessionId: string, line: string): void;
|
|
53
|
-
rememberSnapshot(sessionId: string, value: Record<string, unknown>, line: string, placeholderLine?: string
|
|
53
|
+
rememberSnapshot(sessionId: string, value: Record<string, unknown>, line: string, placeholderLine?: string): void;
|
|
54
54
|
forgetSession(sessionId: string): void;
|
|
55
55
|
private replaySnapshot;
|
|
56
56
|
private resyncSnapshot;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MEDIA_PLACEHOLDERS_CAPABILITY } from "./custom-capability.js";
|
|
2
2
|
import { serializeJsonLine } from "./jsonl.js";
|
|
3
|
-
import {
|
|
3
|
+
import { carriesMedia, omitNormalInlineMedia } from "./media-placeholders.js";
|
|
4
4
|
import { SocketEventSinkActor } from "./socket-event-fanout.js";
|
|
5
5
|
import { EVENT_RESYNC_CAPABILITY } from "./socket-peer-recovery.js";
|
|
6
6
|
const BROADCAST_LIFECYCLE_RECORDS = new Set([
|
|
@@ -15,9 +15,11 @@ const BROADCAST_LIFECYCLE_RECORDS = new Set([
|
|
|
15
15
|
function snapshotPlaceholderLine(record) {
|
|
16
16
|
if (record.placeholderLine !== undefined)
|
|
17
17
|
return record.placeholderLine;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (!record.mayCarryMedia)
|
|
19
|
+
return record.line;
|
|
20
|
+
const source = JSON.parse(record.line);
|
|
21
|
+
const redacted = omitNormalInlineMedia(source);
|
|
22
|
+
record.placeholderLine = redacted === source ? record.line : serializeJsonLine(redacted);
|
|
21
23
|
return record.placeholderLine;
|
|
22
24
|
}
|
|
23
25
|
export class SessionEventFanout {
|
|
@@ -147,13 +149,20 @@ export class SessionEventFanout {
|
|
|
147
149
|
if (this.connectionSessions.get(id)?.has(sessionId))
|
|
148
150
|
actor.enqueue(line);
|
|
149
151
|
}
|
|
150
|
-
rememberSnapshot(sessionId, value, line, placeholderLine
|
|
152
|
+
rememberSnapshot(sessionId, value, line, placeholderLine) {
|
|
153
|
+
const record = {
|
|
154
|
+
line,
|
|
155
|
+
placeholderLine,
|
|
156
|
+
hasMessage: value.message != null,
|
|
157
|
+
mayCarryMedia: carriesMedia(value),
|
|
158
|
+
};
|
|
151
159
|
if (value.type === "session_lifecycle") {
|
|
152
|
-
this.lifecycleSnapshots.set(sessionId,
|
|
160
|
+
this.lifecycleSnapshots.set(sessionId, record);
|
|
153
161
|
return;
|
|
154
162
|
}
|
|
155
163
|
if (value.type === "extension_ui_request" && value.method === "question" && typeof value.id === "string") {
|
|
156
164
|
const pending = this.pendingQuestions.get(sessionId) ?? new Map();
|
|
165
|
+
// Writer acceptance already detached and JSON-normalized the frame.
|
|
157
166
|
pending.set(value.id, { ...value });
|
|
158
167
|
this.pendingQuestions.set(sessionId, pending);
|
|
159
168
|
return;
|
|
@@ -169,7 +178,6 @@ export class SessionEventFanout {
|
|
|
169
178
|
return;
|
|
170
179
|
}
|
|
171
180
|
const event = value.assistantMessageEvent;
|
|
172
|
-
const record = { line, placeholderLine, source };
|
|
173
181
|
if (value.type === "message_start" || (value.type === "message_update" && event?.type === "text_start")) {
|
|
174
182
|
this.sessionSnapshots.set(sessionId, [record]);
|
|
175
183
|
}
|
|
@@ -200,7 +208,7 @@ export class SessionEventFanout {
|
|
|
200
208
|
const current = this.connectionSessions.get(id) ?? new Set();
|
|
201
209
|
return [...new Set([...attached, ...current])].map((sessionId) => {
|
|
202
210
|
const records = this.sessionSnapshots.get(sessionId) ?? [];
|
|
203
|
-
const latest = records.findLast((record) => record.
|
|
211
|
+
const latest = records.findLast((record) => record.hasMessage);
|
|
204
212
|
// The producer may mutate its partial object after emission. The serialized
|
|
205
213
|
// record, not that object, fixes the recovery cut before later deltas enqueue.
|
|
206
214
|
const frozen = latest === undefined ? undefined : JSON.parse(latest.line);
|
|
@@ -211,7 +219,7 @@ export class SessionEventFanout {
|
|
|
211
219
|
message: typeof frozen === "object" && frozen !== null && "message" in frozen ? frozen.message : null,
|
|
212
220
|
...(!current.has(sessionId) ? { closed: true } : {}),
|
|
213
221
|
};
|
|
214
|
-
return placeholders ?
|
|
222
|
+
return placeholders ? omitNormalInlineMedia(record) : record;
|
|
215
223
|
});
|
|
216
224
|
}
|
|
217
225
|
}
|
|
@@ -2,6 +2,7 @@ import type { ExtensionUIContext, SessionKind } from "../../core/extensions/type
|
|
|
2
2
|
import { type McpFormBinding } from "../../core/mcp-form-types.ts";
|
|
3
3
|
import { McpPresenterAuthority, type McpPresenterConnection } from "./mcp-presenter-authority.ts";
|
|
4
4
|
import type { SecurityNoticeSnapshot, SecurityNoticeState } from "../../core/security-notices.ts";
|
|
5
|
+
import { SessionStructuredLanes } from "./session-structured-lanes.ts";
|
|
5
6
|
import { type MediaPersister } from "./media-placeholders.ts";
|
|
6
7
|
import type { RpcHostLifecycleEvent, RpcOpenQueuedEvent, RpcSessionClosedEvent, RpcSessionClosedReason, RpcSessionParkedEvent } from "./rpc-types.ts";
|
|
7
8
|
import { type SessionEventWriterConnection } from "./session-event-fanout.ts";
|
|
@@ -23,6 +24,7 @@ export declare const MAX_SHARED_STDIO_QUEUE_RECORDS = 4096;
|
|
|
23
24
|
* scheduling boundary and violate D9.
|
|
24
25
|
*/
|
|
25
26
|
export declare class SessionEventWriter {
|
|
27
|
+
readonly structuredLanes: SessionStructuredLanes;
|
|
26
28
|
private presenterAuthority?;
|
|
27
29
|
private readonly presenterConnections;
|
|
28
30
|
private readonly presenterBindings;
|
|
@@ -2,8 +2,10 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import { bindPrivateMcpForms } from "../../core/mcp-form-types.js";
|
|
3
3
|
import { McpPresenterAuthority } from "./mcp-presenter-authority.js";
|
|
4
4
|
import { MEDIA_PLACEHOLDERS_CAPABILITY } from "./custom-capability.js";
|
|
5
|
+
import { normalizeJsonValue } from "./json-normal-value.js";
|
|
6
|
+
import { SessionStructuredLanes } from "./session-structured-lanes.js";
|
|
5
7
|
import { serializeJsonLine } from "./jsonl.js";
|
|
6
|
-
import {
|
|
8
|
+
import { omitNormalInlineMedia } from "./media-placeholders.js";
|
|
7
9
|
import { RpcSecurityPresentations } from "./security-presentations.js";
|
|
8
10
|
import { SessionEventFanout } from "./session-event-fanout.js";
|
|
9
11
|
import { SessionOpenTurns } from "./session-open-turns.js";
|
|
@@ -140,6 +142,7 @@ export class SessionEventWriter {
|
|
|
140
142
|
return this.reservedCloseBytes;
|
|
141
143
|
}
|
|
142
144
|
constructor(writeRaw, waitOrSchedule, scheduleFlush) {
|
|
145
|
+
this.structuredLanes = new SessionStructuredLanes();
|
|
143
146
|
this.presenterConnections = new Map();
|
|
144
147
|
this.presenterBindings = new Map();
|
|
145
148
|
this.records = new SessionRecordAccounting();
|
|
@@ -330,7 +333,7 @@ export class SessionEventWriter {
|
|
|
330
333
|
if (this.sealedSessions.has(sessionId))
|
|
331
334
|
return false;
|
|
332
335
|
const targetId = this.connectionContext.getStore();
|
|
333
|
-
const record = value;
|
|
336
|
+
const record = normalizeJsonValue(value).value;
|
|
334
337
|
const isTargeted = record.type === "response" ||
|
|
335
338
|
record.type === "bash_execution_update" ||
|
|
336
339
|
// Relocation records belong to the one connection that offers it (session-relocation-channel.ts).
|
|
@@ -338,8 +341,22 @@ export class SessionEventWriter {
|
|
|
338
341
|
record.type === "relocation_due" ||
|
|
339
342
|
(record.type === "extension_ui_request" &&
|
|
340
343
|
["select", "confirm", "input", "editor"].includes(String(record.method)));
|
|
341
|
-
const tagged = { ...
|
|
344
|
+
const tagged = { ...record, sessionId };
|
|
342
345
|
const line = serializeJsonLine(tagged);
|
|
346
|
+
// Observation records are per-subscription opt-ins on the subscriber's own actor FIFO: never attachments,
|
|
347
|
+
// the attach replay, response broadcast, or the stdio lane, which cannot subscribe.
|
|
348
|
+
if (record.type === "snapshot_marker" ||
|
|
349
|
+
record.type === "ui_request_pending" ||
|
|
350
|
+
record.type === "ui_request_settled") {
|
|
351
|
+
const targets = record.type === "snapshot_marker"
|
|
352
|
+
? this.status.mirrorTargets(sessionId, "snapshot_marker").filter((id) => id === targetId)
|
|
353
|
+
: this.status.dialogTargets(sessionId);
|
|
354
|
+
for (const target of targets)
|
|
355
|
+
this.fanout.get(target)?.actor.enqueue(line);
|
|
356
|
+
if (targets.length > 0)
|
|
357
|
+
this.requestFlush();
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
343
360
|
if (this.fanout.isEmpty() && this.exceedsStdioCapacity(line)) {
|
|
344
361
|
this.closeSession(sessionId, {
|
|
345
362
|
type: "response",
|
|
@@ -357,10 +374,12 @@ export class SessionEventWriter {
|
|
|
357
374
|
// A record is only walked and re-serialized when a target asked for placeholders;
|
|
358
375
|
// otherwise this is byte-for-byte today's path, with serializeJsonLine called once.
|
|
359
376
|
const hasPlaceholderTarget = targets.some((target) => this.fanout.connectionHas(target, MEDIA_PLACEHOLDERS_CAPABILITY));
|
|
360
|
-
const redacted = hasPlaceholderTarget
|
|
377
|
+
const redacted = hasPlaceholderTarget
|
|
378
|
+
? omitNormalInlineMedia(tagged, this.mediaPersisters.get(sessionId))
|
|
379
|
+
: tagged;
|
|
361
380
|
const placeholderLine = redacted === tagged ? undefined : serializeJsonLine(redacted);
|
|
362
381
|
if (!isTargeted)
|
|
363
|
-
this.fanout.rememberSnapshot(sessionId, tagged, line, placeholderLine
|
|
382
|
+
this.fanout.rememberSnapshot(sessionId, tagged, line, placeholderLine);
|
|
364
383
|
for (const target of targets) {
|
|
365
384
|
if (target !== undefined && !this.fanout.get(target))
|
|
366
385
|
continue;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UiRequestChange } from "./snapshot-types.ts";
|
|
1
2
|
import type { RpcExtensionUIRequest, RpcExtensionUIResponse } from "./rpc-types.ts";
|
|
2
3
|
type PendingRequest = {
|
|
3
4
|
resolve: (value: RpcExtensionUIResponse) => void;
|
|
@@ -9,7 +10,7 @@ export declare const sessionDialogRequests: WeakMap<object, SessionExtensionUiRe
|
|
|
9
10
|
export declare class SessionExtensionUiRequests {
|
|
10
11
|
private readonly pending;
|
|
11
12
|
private readonly changed;
|
|
12
|
-
constructor(changed?: () => void);
|
|
13
|
+
constructor(changed?: (change?: UiRequestChange) => void);
|
|
13
14
|
set(id: string, request: PendingRequest): void;
|
|
14
15
|
delete(id: string): void;
|
|
15
16
|
snapshot(): RpcExtensionUIRequest[];
|
|
@@ -7,11 +7,13 @@ export class SessionExtensionUiRequests {
|
|
|
7
7
|
}
|
|
8
8
|
set(id, request) {
|
|
9
9
|
this.pending.set(id, request);
|
|
10
|
-
this.changed?.(
|
|
10
|
+
this.changed?.(request.frame
|
|
11
|
+
? { type: "ui_request_pending", uiRequestId: id, method: request.frame.method, request: request.frame }
|
|
12
|
+
: undefined);
|
|
11
13
|
}
|
|
12
14
|
delete(id) {
|
|
13
|
-
this.pending.delete(id)
|
|
14
|
-
|
|
15
|
+
if (this.pending.delete(id))
|
|
16
|
+
this.changed?.({ type: "ui_request_settled", uiRequestId: id, disposition: "cancelled" });
|
|
15
17
|
}
|
|
16
18
|
snapshot() {
|
|
17
19
|
return [...this.pending.values()].flatMap((request) => (request.frame ? [request.frame] : []));
|
|
@@ -22,22 +24,25 @@ export class SessionExtensionUiRequests {
|
|
|
22
24
|
return false;
|
|
23
25
|
beforeAnswer?.();
|
|
24
26
|
this.pending.delete(response.id);
|
|
25
|
-
this.changed?.(
|
|
27
|
+
this.changed?.({
|
|
28
|
+
type: "ui_request_settled",
|
|
29
|
+
uiRequestId: response.id,
|
|
30
|
+
disposition: "cancelled" in response ? "cancelled" : "answered",
|
|
31
|
+
});
|
|
26
32
|
request.resolve(response);
|
|
27
33
|
return true;
|
|
28
34
|
}
|
|
29
35
|
cancelAll() {
|
|
30
|
-
for (const [id, request] of this.pending)
|
|
36
|
+
for (const [id, request] of this.pending) {
|
|
37
|
+
this.delete(id);
|
|
31
38
|
request.resolve({ type: "extension_ui_response", id, cancelled: true });
|
|
32
|
-
|
|
33
|
-
this.changed?.();
|
|
39
|
+
}
|
|
34
40
|
}
|
|
35
41
|
close() {
|
|
36
|
-
for (const request of this.pending
|
|
42
|
+
for (const [id, request] of this.pending) {
|
|
43
|
+
this.delete(id);
|
|
37
44
|
request.reject(new Error("Extension UI request cancelled: session closed"));
|
|
38
45
|
}
|
|
39
|
-
this.pending.clear();
|
|
40
|
-
this.changed?.();
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
//# sourceMappingURL=session-extension-ui-requests.js.map
|
|
@@ -9,6 +9,7 @@ import { RpcSessionRegistryError } from "./session-registry-types.js";
|
|
|
9
9
|
// a read or a control needed to finish/cancel an already-admitted turn.
|
|
10
10
|
const READ_ONLY_OR_CONTROL = new Set([
|
|
11
11
|
"get_state",
|
|
12
|
+
"get_snapshot",
|
|
12
13
|
"get_effect_status",
|
|
13
14
|
"get_pending_inputs",
|
|
14
15
|
"get_steering_messages",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* reads the same records from either.
|
|
8
8
|
*/
|
|
9
9
|
import type { AgentAbortSource } from "../../core/agent-abort-provenance.ts";
|
|
10
|
-
import type { AgentSession } from "../../core/agent-session.ts";
|
|
11
|
-
/** Subscribes `output` to the session's records; returns the unsubscribe. */
|
|
12
|
-
export declare function subscribeSessionRecords(session: AgentSession, output: (record: object) => void, onAbortSource?: (source: AgentAbortSource) => void): () => void;
|
|
10
|
+
import type { AgentSession, AgentSessionEventListener } from "../../core/agent-session.ts";
|
|
11
|
+
/** Subscribes `output` to the session's records (from `source`, by default `session.subscribe`); returns the unsubscribe. */
|
|
12
|
+
export declare function subscribeSessionRecords(session: AgentSession, output: (record: object) => void, onAbortSource?: (source: AgentAbortSource) => void, source?: (listener: AgentSessionEventListener) => () => void): () => void;
|
|
13
13
|
//# sourceMappingURL=session-record-stream.d.ts.map
|