@code-yeongyu/senpi 2026.10.26-1 → 2026.10.27-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/bundle/chunks/{anthropic-messages-M235FJQV.js → anthropic-messages-ODSFJCXR.js} +1 -1
- package/dist/bundle/chunks/{args-FBNJOJSR.js → args-SWKJAYQD.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-YQRTQ7H6.js → azure-openai-responses-REGXS4XO.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/{chunk-DMFAVE5N.js → chunk-4QVORFQ2.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZXHLH4MW.js → chunk-5W3DTM2J.js} +1 -1
- package/dist/bundle/chunks/{chunk-QYFOHFPS.js → chunk-6CJC6JY4.js} +1 -1
- package/dist/bundle/chunks/{chunk-QREKAXKX.js → chunk-6GAMRAVM.js} +5 -5
- package/dist/bundle/chunks/{chunk-MSHRIZGT.js → chunk-7IZH2KQX.js} +3 -3
- package/dist/bundle/chunks/{chunk-PH3JDRB2.js → chunk-ARSENVDY.js} +1 -1
- package/dist/bundle/chunks/{chunk-5KZH6EYQ.js → chunk-CNDPCYNT.js} +1 -1
- package/dist/bundle/chunks/{chunk-DYWIFF2Q.js → chunk-CYOHFREV.js} +1 -1
- package/dist/bundle/chunks/{chunk-3LPWILUZ.js → chunk-DNDZPKIQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZLIVZQDY.js → chunk-EQNUL2WB.js} +1 -1
- package/dist/bundle/chunks/{chunk-LO5MYLGF.js → chunk-FQDARFHT.js} +1 -1
- package/dist/bundle/chunks/{chunk-7KOUQSBX.js → chunk-GPPOPK76.js} +3 -3
- package/dist/bundle/chunks/{chunk-I7QWTWVZ.js → chunk-IHKHCWWG.js} +1 -1
- package/dist/bundle/chunks/{chunk-MY4ZD3EW.js → chunk-IY4G44MN.js} +1 -1
- package/dist/bundle/chunks/{chunk-OQ3WDMB3.js → chunk-JH7MVRNJ.js} +1 -1
- package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
- package/dist/bundle/chunks/{chunk-UW6M3EXY.js → chunk-KRO63T4Q.js} +1 -1
- package/dist/bundle/chunks/{chunk-J5FCH7TI.js → chunk-KTDIIF2F.js} +1 -1
- package/dist/bundle/chunks/{chunk-3WP5RFQR.js → chunk-NDNIMC5W.js} +1 -1
- package/dist/bundle/chunks/chunk-NXQFBAUI.js +2 -0
- package/dist/bundle/chunks/{chunk-CDQPA4FP.js → chunk-ODRGPC5S.js} +1 -1
- package/dist/bundle/chunks/{chunk-5FQMNHXV.js → chunk-P5E4EVQF.js} +1 -1
- package/dist/bundle/chunks/{chunk-3A3XCLNH.js → chunk-PDG4XWO2.js} +1 -1
- package/dist/bundle/chunks/{chunk-5LWUUXMH.js → chunk-PY4ZQHTN.js} +1 -1
- package/dist/bundle/chunks/chunk-Q2A2V6NT.js +5 -0
- package/dist/bundle/chunks/{chunk-SOXQ42VS.js → chunk-RQ5JTXUC.js} +1 -1
- package/dist/bundle/chunks/{chunk-JPGT64RV.js → chunk-TV3UI63P.js} +1 -1
- package/dist/bundle/chunks/{chunk-JFQUWEN7.js → chunk-TWHF52G6.js} +4 -4
- package/dist/bundle/chunks/{chunk-6U3TBFBA.js → chunk-UJPG4ROT.js} +1 -1
- package/dist/bundle/chunks/{chunk-UDM7VO6O.js → chunk-XM36IFP3.js} +1 -1
- package/dist/bundle/chunks/{chunk-XQ45JMGN.js → chunk-Z6BREXV3.js} +1 -1
- package/dist/bundle/chunks/chunk-ZAS4NBVK.js +410 -0
- package/dist/bundle/chunks/{chunk-ADQAPDPV.js → chunk-ZP6JJDYV.js} +1 -1
- package/dist/bundle/chunks/{chunk-EZP6AYW2.js → chunk-ZXIAVLJV.js} +88 -88
- package/dist/bundle/chunks/{cli-main-55EQA5PS.js → cli-main-36FRZ7ZB.js} +1 -1
- package/dist/bundle/chunks/client-CBDPMWZJ.js +2 -0
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-ZJQGIIRC.js → cloudflare-workers-ai-system-one-WDHT7BBV.js} +1 -1
- package/dist/bundle/chunks/cursor-agent.js +1 -1
- package/dist/bundle/chunks/devin-agent.js +1 -1
- package/dist/bundle/chunks/{dispatch-C2IBP5VV.js → dispatch-W5PENPZM.js} +3 -3
- package/dist/bundle/chunks/{google-generative-ai-5G4RIQA6.js → google-generative-ai-FHYFXKLA.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-ALD4P27O.js → google-vertex-5DWE5U5K.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-PEIKGCKZ.js → help-fast-path-FETLIF3W.js} +1 -1
- package/dist/bundle/chunks/{host-command-657VCQ34.js → host-command-XEB7SHT3.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-OV5XDOJK.js → host-session-command-FFN52LXJ.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-MLXSYK2H.js → interactive-mode-5S7NNV2Z.js} +1 -1
- package/dist/bundle/chunks/{mistral-conversations-RAE4K2XY.js → mistral-conversations-I2PI7DDF.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-774MACJU.js → multi-session-host-E3OPE6HJ.js} +3 -3
- package/dist/bundle/chunks/{openai-codex-responses-3CW4R7TG.js → openai-codex-responses-WSEX6SH7.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-5UIJXQUF.js → openai-completions-YCBHTBSS.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-2BO44MRW.js → openai-responses-GXNSQF4A.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-MZHVCX2S.js → package-manager-cli-3XO456VE.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-RF6GER4R.js → pi-messages-O6GISIRW.js} +1 -1
- package/dist/bundle/chunks/protocol-GLNWYI5G.js +2 -0
- package/dist/bundle/chunks/{role-ETSFSDTS.js → role-SHIZQKK4.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-ZMQB4GPO.js → rotation-stream-2PCZWNKF.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-UJXOIZCW.js → rpc-mode-GLWKLTMA.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-KA7LFFNK.js → schedule-command-B7RRSANU.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-MFWXUR7Z.js → session-control-endpoint-KLE2X4RR.js} +1 -1
- package/dist/bundle/chunks/{session-picker-BP33NRSL.js → session-picker-DTGBOXDR.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +131 -131
- package/dist/bundle/chunks/{typesafe-system-one-GNNZ5O5U.js → typesafe-system-one-XGYJJVSE.js} +1 -1
- package/dist/bundle/cli.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/core/agent-session.d.ts +11 -1
- package/dist/core/agent-session.js +19 -2
- package/dist/core/extensions/builtin/anthropic-subscription/account-context.d.ts +15 -0
- package/dist/core/extensions/builtin/anthropic-subscription/account-context.js +53 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.d.ts +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +26 -9
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane-boundary.d.ts +16 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane-boundary.js +27 -0
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.d.ts +2 -13
- package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -26
- package/dist/core/extensions/builtin/anthropic-subscription/errors.js +3 -2
- package/dist/core/extensions/builtin/anthropic-subscription/guidance.d.ts +6 -1
- package/dist/core/extensions/builtin/anthropic-subscription/guidance.js +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/stream-guidance.js +13 -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/modern/consent-queue.js +1 -0
- 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.js +6 -2
- 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/sdk.lazy.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/sdk.lazy.js +2 -0
- package/dist/core/extensions/builtin/mcp/transport-sdk.js +12 -1
- 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/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.js +2 -1
- package/dist/modes/json-event.d.ts +79 -1
- package/dist/modes/json-event.js +79 -1
- package/dist/modes/rpc/compact-commit-dependencies.d.ts +13 -0
- package/dist/modes/rpc/compact-commit-dependencies.js +48 -0
- package/dist/modes/rpc/compact-content-index.d.ts +84 -0
- package/dist/modes/rpc/compact-content-index.js +243 -0
- package/dist/modes/rpc/compact-content-store.d.ts +10 -43
- package/dist/modes/rpc/compact-content-store.js +61 -163
- 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-read-lease.d.ts +15 -0
- package/dist/modes/rpc/compact-read-lease.js +40 -0
- package/dist/modes/rpc/compact-session-state.d.ts +14 -63
- package/dist/modes/rpc/compact-session-state.js +69 -51
- package/dist/modes/rpc/compact-spool-dir.d.ts +4 -1
- package/dist/modes/rpc/compact-spool-dir.js +41 -8
- package/dist/modes/rpc/compact-spool-storage.d.ts +58 -0
- package/dist/modes/rpc/compact-spool-storage.js +195 -0
- package/dist/modes/rpc/compact-storage-io.d.ts +2 -1
- package/dist/modes/rpc/compact-storage-io.js +7 -1
- package/dist/modes/rpc/compact-text-roots.d.ts +22 -0
- package/dist/modes/rpc/compact-text-roots.js +138 -0
- package/dist/modes/rpc/compact-text-view.d.ts +22 -0
- package/dist/modes/rpc/compact-text-view.js +16 -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 +2 -0
- package/dist/modes/rpc/connection-handler.js +12 -6
- package/dist/modes/rpc/host-ensure-start.js +5 -3
- package/dist/modes/rpc/security-presentations.js +2 -1
- package/dist/modes/rpc/session-binding.js +10 -0
- package/dist/modes/rpc/session-event-writer.d.ts +2 -0
- package/dist/modes/rpc/session-event-writer.js +2 -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-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/structured-session-records.d.ts +12 -0
- package/dist/modes/rpc/structured-session-records.js +88 -0
- package/docs/extensions.md +7 -1
- package/docs/rpc.md +17 -2
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-GQJYNFMO.js +0 -2
- package/dist/bundle/chunks/chunk-VICDRG3J.js +0 -410
- package/dist/bundle/chunks/chunk-VL7O5PO4.js +0 -5
- package/dist/bundle/chunks/client-LOS6GSEW.js +0 -2
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { CompactReadError } from "./compact-read-lease.js";
|
|
3
|
+
import { createSpoolDir, removeSpoolDir } from "./compact-spool-dir.js";
|
|
4
|
+
import { CompactStorageReadError, describeCause, } from "./compact-storage-io.js";
|
|
5
|
+
/**
|
|
6
|
+
* The spool file path of compact current-state storage (senpi#4272, R3.C6.4): one serial
|
|
7
|
+
* asynchronous job chain, the private epoch directory, spool file writes, reads and reclamation,
|
|
8
|
+
* and the failure ledger. `CompactContentStore` adds leaf ownership and spilling on top.
|
|
9
|
+
*/
|
|
10
|
+
export const RECLAIM_WORK_ALLOWANCE_BYTES = 1024 * 1024;
|
|
11
|
+
const RECLAIM_RETRY_DELAYS_MS = [100, 400, 1_600, 6_400, 25_600];
|
|
12
|
+
const FAILURE_HISTORY = 32;
|
|
13
|
+
export class CompactSpoolStorage {
|
|
14
|
+
assertReadable() {
|
|
15
|
+
if (this.retired)
|
|
16
|
+
throw new CompactReadError("retired");
|
|
17
|
+
}
|
|
18
|
+
beginRead() {
|
|
19
|
+
this.assertReadable();
|
|
20
|
+
this.reads++;
|
|
21
|
+
this.reading ??= Promise.withResolvers();
|
|
22
|
+
let done = false;
|
|
23
|
+
return () => {
|
|
24
|
+
if (done)
|
|
25
|
+
return;
|
|
26
|
+
done = true;
|
|
27
|
+
if (--this.reads === 0) {
|
|
28
|
+
this.reading?.resolve();
|
|
29
|
+
this.reading = undefined;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
constructor(dir, io) {
|
|
34
|
+
this.failures = [];
|
|
35
|
+
this.nextFile = 1;
|
|
36
|
+
this.chain = Promise.resolve();
|
|
37
|
+
this.queued = 0;
|
|
38
|
+
this.spillQueued = false;
|
|
39
|
+
this.halted = false;
|
|
40
|
+
this.retired = false;
|
|
41
|
+
this.pendingIoCopyBytes = 0;
|
|
42
|
+
this.spillBytesWritten = 0;
|
|
43
|
+
this.reclaimDebtBytes = 0;
|
|
44
|
+
this.reads = 0;
|
|
45
|
+
this.dir = dir;
|
|
46
|
+
this.io = io;
|
|
47
|
+
}
|
|
48
|
+
/** Resolves once every queued storage job, including the ones they queued, has completed. */
|
|
49
|
+
async idle() {
|
|
50
|
+
while (this.queued > 0)
|
|
51
|
+
await this.chain;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Invalidates spilling, joins owned I/O, then removes the epoch directory only when this store
|
|
55
|
+
* created it and it still passes the ownership checks; otherwise rejects with `CompactSpoolRefusedError`.
|
|
56
|
+
*/
|
|
57
|
+
async retire() {
|
|
58
|
+
this.retired = true;
|
|
59
|
+
await this.reading?.promise;
|
|
60
|
+
await this.idle();
|
|
61
|
+
const token = this.spoolToken;
|
|
62
|
+
this.spoolToken = undefined;
|
|
63
|
+
if (token !== undefined)
|
|
64
|
+
await removeSpoolDir(this.dir, token);
|
|
65
|
+
}
|
|
66
|
+
/** Reads a committed file and rejects a missing, unreadable or short one. */
|
|
67
|
+
async readFile(path, bytes, operation) {
|
|
68
|
+
const done = this.beginRead();
|
|
69
|
+
try {
|
|
70
|
+
const result = await this.readCommittedFile(path, bytes, operation);
|
|
71
|
+
this.assertReadable();
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
done();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async readCommittedFile(path, bytes, operation) {
|
|
79
|
+
let data;
|
|
80
|
+
try {
|
|
81
|
+
data = await this.io.readFile(path);
|
|
82
|
+
}
|
|
83
|
+
catch (cause) {
|
|
84
|
+
throw this.readFailure(operation, path, cause);
|
|
85
|
+
}
|
|
86
|
+
if (data.byteLength !== bytes) {
|
|
87
|
+
throw this.readFailure(operation, path, new Error(`expected ${bytes} bytes, found ${data.byteLength}`));
|
|
88
|
+
}
|
|
89
|
+
return data;
|
|
90
|
+
}
|
|
91
|
+
/** Writes one new spool file; undefined means the write failed and was reported (a spill write also halts spilling). */
|
|
92
|
+
async writeFile(data, operation, retainedInMemoryBytes) {
|
|
93
|
+
const path = await this.ensureDir(retainedInMemoryBytes);
|
|
94
|
+
if (path === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const file = join(path, `${operation === "spill_write" ? "leaf" : "page"}-${this.nextFile++}.bin`);
|
|
97
|
+
this.pendingIoCopyBytes += data.byteLength;
|
|
98
|
+
try {
|
|
99
|
+
await this.io.writeFile(file, data);
|
|
100
|
+
this.spillBytesWritten += data.byteLength;
|
|
101
|
+
return file;
|
|
102
|
+
}
|
|
103
|
+
catch (cause) {
|
|
104
|
+
try {
|
|
105
|
+
this.fail(operation, file, cause, retainedInMemoryBytes);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
this.discard(file, data.byteLength);
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
this.pendingIoCopyBytes -= data.byteLength;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* An unreferenced (or partially written) spool file: reclamation debt until its unlink completes or
|
|
118
|
+
* finds it gone. Files dropped after retirement started belong to the directory retirement removes.
|
|
119
|
+
*/
|
|
120
|
+
discard(path, bytes) {
|
|
121
|
+
if (this.retired)
|
|
122
|
+
return;
|
|
123
|
+
this.reclaimDebtBytes += bytes;
|
|
124
|
+
this.enqueue("reclaim", () => this.reclaim(path, bytes, 0));
|
|
125
|
+
}
|
|
126
|
+
/** A failed unlink keeps its debt and is retried with bounded backoff outside the job chain. */
|
|
127
|
+
async reclaim(path, bytes, attempt) {
|
|
128
|
+
try {
|
|
129
|
+
await this.io.unlink(path);
|
|
130
|
+
}
|
|
131
|
+
catch (cause) {
|
|
132
|
+
if (cause.code !== "ENOENT") {
|
|
133
|
+
this.fail("reclaim", path, cause, 0);
|
|
134
|
+
const delay = RECLAIM_RETRY_DELAYS_MS[attempt];
|
|
135
|
+
if (delay !== undefined) {
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
if (this.retired)
|
|
138
|
+
this.reclaimDebtBytes -= bytes;
|
|
139
|
+
else
|
|
140
|
+
this.enqueue("reclaim", () => this.reclaim(path, bytes, attempt + 1));
|
|
141
|
+
}, delay).unref?.();
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.reclaimDebtBytes -= bytes;
|
|
147
|
+
this.scheduleSpill();
|
|
148
|
+
}
|
|
149
|
+
get spillAllowed() {
|
|
150
|
+
return !this.halted && !this.retired;
|
|
151
|
+
}
|
|
152
|
+
/** True while a storage failure or unreclaimed debt above the work allowance holds spilling back. */
|
|
153
|
+
get spillStopped() {
|
|
154
|
+
return this.halted || this.reclaimDebtBytes > RECLAIM_WORK_ALLOWANCE_BYTES;
|
|
155
|
+
}
|
|
156
|
+
enqueue(operation, job) {
|
|
157
|
+
this.queued++;
|
|
158
|
+
this.chain = this.chain
|
|
159
|
+
.then(job)
|
|
160
|
+
.catch((cause) => {
|
|
161
|
+
if (operation === "spill_write")
|
|
162
|
+
this.spillQueued = false;
|
|
163
|
+
this.fail(operation, this.dir, cause, 0);
|
|
164
|
+
})
|
|
165
|
+
.finally(() => {
|
|
166
|
+
this.queued--;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/** Creates the private epoch directory once; a refusal or failure halts spilling and is reported. */
|
|
170
|
+
ensureDir(retained) {
|
|
171
|
+
this.dirReady ??= createSpoolDir(this.dir).then((token) => {
|
|
172
|
+
this.spoolToken = token;
|
|
173
|
+
return this.dir;
|
|
174
|
+
}, (cause) => {
|
|
175
|
+
this.fail("spool_create", this.dir, cause, retained);
|
|
176
|
+
return undefined;
|
|
177
|
+
});
|
|
178
|
+
return this.dirReady;
|
|
179
|
+
}
|
|
180
|
+
readFailure(operation, path, cause) {
|
|
181
|
+
this.fail(operation, path, cause, 0);
|
|
182
|
+
return new CompactStorageReadError(operation, path, cause);
|
|
183
|
+
}
|
|
184
|
+
fail(operation, path, cause, retained) {
|
|
185
|
+
if (operation === "spill_write" || operation === "spool_create")
|
|
186
|
+
this.halted = true;
|
|
187
|
+
this.record({ operation, path, cause: describeCause(cause), retainedInMemoryBytes: retained });
|
|
188
|
+
}
|
|
189
|
+
record(failure) {
|
|
190
|
+
this.failures.push(failure);
|
|
191
|
+
if (this.failures.length > FAILURE_HISTORY)
|
|
192
|
+
this.failures.shift();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=compact-spool-storage.js.map
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The asynchronous file seam of compact current-state storage (senpi#4272) and the failures it
|
|
3
3
|
* reports. Every spool access goes through `CompactStorageIo`; nothing here blocks the host loop.
|
|
4
4
|
*/
|
|
5
|
-
export type CompactStorageOperation = "spool_create" | "spill_write" | "leaf_read" | "reclaim";
|
|
5
|
+
export type CompactStorageOperation = "spool_create" | "spill_write" | "page_write" | "leaf_read" | "page_read" | "reclaim";
|
|
6
6
|
export interface CompactStorageFailure {
|
|
7
7
|
readonly operation: CompactStorageOperation;
|
|
8
8
|
readonly path: string;
|
|
@@ -17,6 +17,7 @@ export interface CompactStorageIo {
|
|
|
17
17
|
unlink(path: string): Promise<void>;
|
|
18
18
|
}
|
|
19
19
|
export declare const nodeCompactStorageIo: CompactStorageIo;
|
|
20
|
+
/** Never throws: a host-supplied cause that cannot be described must not abort the failure report. */
|
|
20
21
|
export declare function describeCause(cause: unknown): string;
|
|
21
22
|
/** Committed storage that can no longer be read: a real error, never empty content. */
|
|
22
23
|
export declare class CompactStorageReadError extends Error {
|
|
@@ -4,8 +4,14 @@ export const nodeCompactStorageIo = {
|
|
|
4
4
|
readFile: (path) => readFile(path),
|
|
5
5
|
unlink: (path) => unlink(path),
|
|
6
6
|
};
|
|
7
|
+
/** Never throws: a host-supplied cause that cannot be described must not abort the failure report. */
|
|
7
8
|
export function describeCause(cause) {
|
|
8
|
-
|
|
9
|
+
try {
|
|
10
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return "unprintable failure cause";
|
|
14
|
+
}
|
|
9
15
|
}
|
|
10
16
|
/** Committed storage that can no longer be read: a real error, never empty content. */
|
|
11
17
|
export class CompactStorageReadError extends Error {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CompactContentStore, type LeafId } from "./compact-content-store.ts";
|
|
2
|
+
/** Persistent append tree: positive refs are leaves, negative refs are nodes, zero is empty. */
|
|
3
|
+
export declare class CompactTextRoots {
|
|
4
|
+
private readonly nodes;
|
|
5
|
+
private readonly pending;
|
|
6
|
+
private readonly store;
|
|
7
|
+
private next;
|
|
8
|
+
constructor(store: CompactContentStore);
|
|
9
|
+
get residentNodes(): number;
|
|
10
|
+
units(id: LeafId): number;
|
|
11
|
+
isCommitted(id: LeafId): boolean;
|
|
12
|
+
/** Transfers one live root and one newly owned bounded leaf into the new root. */
|
|
13
|
+
append(root: LeafId, text: string): LeafId;
|
|
14
|
+
adjust(id: LeafId, live: number, pins: number): void;
|
|
15
|
+
/** Bounded UTF-16 result and bounded leaf/tail count, checked before any I/O. */
|
|
16
|
+
read(root: LeafId, tail: string, offset: number, units: number, maxLeaves?: number, valid?: () => void): Promise<string>;
|
|
17
|
+
private node;
|
|
18
|
+
private appendLeaf;
|
|
19
|
+
private height;
|
|
20
|
+
private join;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=compact-text-roots.d.ts.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { CompactCommitDependencies } from "./compact-commit-dependencies.js";
|
|
2
|
+
import { COMPACT_LEAF_CODE_UNITS } from "./compact-content-store.js";
|
|
3
|
+
import { CompactReadError } from "./compact-read-lease.js";
|
|
4
|
+
/** Persistent append tree: positive refs are leaves, negative refs are nodes, zero is empty. */
|
|
5
|
+
export class CompactTextRoots {
|
|
6
|
+
constructor(store) {
|
|
7
|
+
this.nodes = new Map();
|
|
8
|
+
this.next = -1;
|
|
9
|
+
this.store = store;
|
|
10
|
+
this.pending = new CompactCommitDependencies(store, (id) => store.committed(id));
|
|
11
|
+
}
|
|
12
|
+
get residentNodes() {
|
|
13
|
+
return this.nodes.size;
|
|
14
|
+
}
|
|
15
|
+
units(id) {
|
|
16
|
+
return id === 0 ? 0 : id > 0 ? this.store.leafUnits(id) : this.node(id).units;
|
|
17
|
+
}
|
|
18
|
+
isCommitted(id) {
|
|
19
|
+
return this.nodes.has(id) && !this.pending.has(id);
|
|
20
|
+
}
|
|
21
|
+
/** Transfers one live root and one newly owned bounded leaf into the new root. */
|
|
22
|
+
append(root, text) {
|
|
23
|
+
this.units(root);
|
|
24
|
+
const leaf = this.store.createLeaf(text);
|
|
25
|
+
if (root === 0)
|
|
26
|
+
return leaf;
|
|
27
|
+
const result = this.appendLeaf(root, leaf);
|
|
28
|
+
this.store.adjust(root, -1, 0);
|
|
29
|
+
this.store.adjust(leaf, -1, 0);
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
adjust(id, live, pins) {
|
|
33
|
+
const node = this.node(id);
|
|
34
|
+
const beforeLive = Number(node.live > 0);
|
|
35
|
+
const beforePin = Number(node.live === 0 && node.pins > 0);
|
|
36
|
+
if (node.live + live < 0 || node.pins + pins < 0)
|
|
37
|
+
throw new CompactReadError("ownership");
|
|
38
|
+
node.live += live;
|
|
39
|
+
node.pins += pins;
|
|
40
|
+
const liveChange = Number(node.live > 0) - beforeLive;
|
|
41
|
+
const pinChange = Number(node.live === 0 && node.pins > 0) - beforePin;
|
|
42
|
+
// Extra cuts on a live subtree do not visit children. Only category transitions propagate.
|
|
43
|
+
if (liveChange || pinChange) {
|
|
44
|
+
this.store.adjust(node.left, liveChange, pinChange);
|
|
45
|
+
this.store.adjust(node.right, liveChange, pinChange);
|
|
46
|
+
}
|
|
47
|
+
if (node.live === 0 && node.pins === 0) {
|
|
48
|
+
this.pending.delete(id);
|
|
49
|
+
this.nodes.delete(id);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Bounded UTF-16 result and bounded leaf/tail count, checked before any I/O. */
|
|
53
|
+
async read(root, tail, offset, units, maxLeaves = 2, valid = () => this.store.assertReadable()) {
|
|
54
|
+
this.store.assertReadable();
|
|
55
|
+
valid();
|
|
56
|
+
const length = this.units(root);
|
|
57
|
+
if (!Number.isSafeInteger(offset) ||
|
|
58
|
+
!Number.isSafeInteger(units) ||
|
|
59
|
+
offset < 0 ||
|
|
60
|
+
units < 0 ||
|
|
61
|
+
offset + units > length + tail.length)
|
|
62
|
+
throw new CompactReadError("range");
|
|
63
|
+
if (units > COMPACT_LEAF_CODE_UNITS)
|
|
64
|
+
throw new CompactReadError("size_limit");
|
|
65
|
+
if (!Number.isSafeInteger(maxLeaves) || maxLeaves < 1 || maxLeaves > 2)
|
|
66
|
+
throw new CompactReadError("count_limit");
|
|
67
|
+
const selected = [];
|
|
68
|
+
const visit = (id, start, end) => {
|
|
69
|
+
if (start >= end || id === 0)
|
|
70
|
+
return;
|
|
71
|
+
if (id > 0) {
|
|
72
|
+
if (selected.length === maxLeaves)
|
|
73
|
+
throw new CompactReadError("count_limit");
|
|
74
|
+
selected.push({ id, offset: start, units: end - start });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const node = this.node(id);
|
|
78
|
+
const leftUnits = this.units(node.left);
|
|
79
|
+
visit(node.left, start, Math.min(end, leftUnits));
|
|
80
|
+
visit(node.right, Math.max(0, start - leftUnits), end - leftUnits);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
visit(root, offset, Math.min(length, offset + units));
|
|
84
|
+
const tailUnits = units === 0 ? 0 : Math.max(0, offset + units - Math.max(offset, length));
|
|
85
|
+
if (selected.length + Number(tailUnits > 0) > maxLeaves)
|
|
86
|
+
throw new CompactReadError("count_limit");
|
|
87
|
+
this.store.adjust(root, 0, 1);
|
|
88
|
+
const done = this.store.beginRead();
|
|
89
|
+
try {
|
|
90
|
+
let result = "";
|
|
91
|
+
for (const part of selected) {
|
|
92
|
+
const text = await this.store.readLeaf(part.id);
|
|
93
|
+
valid();
|
|
94
|
+
result += text.slice(part.offset, part.offset + part.units);
|
|
95
|
+
}
|
|
96
|
+
valid();
|
|
97
|
+
return result + tail.slice(Math.max(0, offset - length), Math.max(0, offset - length) + tailUnits);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
this.store.adjust(root, 0, -1);
|
|
101
|
+
done();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
node(id) {
|
|
105
|
+
const node = this.nodes.get(id);
|
|
106
|
+
if (!node)
|
|
107
|
+
throw new CompactReadError("ownership");
|
|
108
|
+
return node;
|
|
109
|
+
}
|
|
110
|
+
appendLeaf(root, leaf) {
|
|
111
|
+
const node = this.nodes.get(root);
|
|
112
|
+
if (!node || this.height(node.left) === this.height(node.right))
|
|
113
|
+
return this.join(root, leaf);
|
|
114
|
+
const right = this.appendLeaf(node.right, leaf);
|
|
115
|
+
const result = this.join(node.left, right);
|
|
116
|
+
this.store.adjust(right, -1, 0);
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
height(id) {
|
|
120
|
+
return id > 0 ? 0 : this.node(id).height;
|
|
121
|
+
}
|
|
122
|
+
join(left, right) {
|
|
123
|
+
const id = this.next--;
|
|
124
|
+
this.nodes.set(id, {
|
|
125
|
+
left,
|
|
126
|
+
right,
|
|
127
|
+
units: this.units(left) + this.units(right),
|
|
128
|
+
height: 1 + Math.max(this.height(left), this.height(right)),
|
|
129
|
+
live: 1,
|
|
130
|
+
pins: 0,
|
|
131
|
+
});
|
|
132
|
+
this.store.adjust(left, 1, 0);
|
|
133
|
+
this.store.adjust(right, 1, 0);
|
|
134
|
+
this.pending.set(id, [left, right]);
|
|
135
|
+
return id;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=compact-text-roots.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type LeafId } from "./compact-content-store.ts";
|
|
2
|
+
import type { CompactTextRoots } from "./compact-text-roots.ts";
|
|
3
|
+
import type { CompactBlockKind, CompactPersistence } from "./compact-transition.ts";
|
|
4
|
+
export interface FrozenBlock {
|
|
5
|
+
readonly contentIndex: number;
|
|
6
|
+
readonly kind: CompactBlockKind;
|
|
7
|
+
readonly root: LeafId;
|
|
8
|
+
/** A cut shares the immutable tail (under one leaf), not a new leaf for each freeze. */
|
|
9
|
+
readonly tail: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CompactMessageView {
|
|
12
|
+
readonly messageId: string;
|
|
13
|
+
readonly persistence: CompactPersistence | undefined;
|
|
14
|
+
readonly blocks: readonly {
|
|
15
|
+
readonly contentIndex: number;
|
|
16
|
+
readonly kind: CompactBlockKind;
|
|
17
|
+
readonly text: string;
|
|
18
|
+
}[];
|
|
19
|
+
}
|
|
20
|
+
/** Compatibility assembly only; each underlying range is bounded, the resulting legacy view is not. */
|
|
21
|
+
export declare function readCompactBlocks(texts: CompactTextRoots, blocks: readonly FrozenBlock[], valid: () => void): Promise<CompactMessageView["blocks"]>;
|
|
22
|
+
//# sourceMappingURL=compact-text-view.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { COMPACT_LEAF_CODE_UNITS } from "./compact-content-store.js";
|
|
2
|
+
/** Compatibility assembly only; each underlying range is bounded, the resulting legacy view is not. */
|
|
3
|
+
export async function readCompactBlocks(texts, blocks, valid) {
|
|
4
|
+
const result = [];
|
|
5
|
+
for (const { contentIndex, kind, root, tail } of blocks) {
|
|
6
|
+
valid();
|
|
7
|
+
const length = texts.units(root) + tail.length;
|
|
8
|
+
let text = "";
|
|
9
|
+
for (let offset = 0; offset < length; offset += COMPACT_LEAF_CODE_UNITS) {
|
|
10
|
+
text += await texts.read(root, tail, offset, Math.min(COMPACT_LEAF_CODE_UNITS, length - offset), 2, valid);
|
|
11
|
+
}
|
|
12
|
+
result.push({ contentIndex, kind, text });
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=compact-text-view.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed transitions compact current state accepts (senpi#4272, R3.C6.4) and the typed
|
|
3
|
+
* rejection `CompactSessionState.accept` throws when one fails validation.
|
|
4
|
+
*/
|
|
5
|
+
export type CompactBlockKind = "text" | "thinking" | "tool_arguments";
|
|
6
|
+
export type CompactPersistence = "pending" | "appended" | "failed";
|
|
7
|
+
export interface BlockAddress {
|
|
8
|
+
readonly messageId: string;
|
|
9
|
+
readonly contentIndex: number;
|
|
10
|
+
}
|
|
11
|
+
export type CompactTransition = (BlockAddress & {
|
|
12
|
+
readonly type: "block_start";
|
|
13
|
+
readonly kind: CompactBlockKind;
|
|
14
|
+
readonly seed: string;
|
|
15
|
+
/**
|
|
16
|
+
* The whole seed when later deltas replay it from offset 0, or 0 when they continue after it. Partial
|
|
17
|
+
* coverage is refused: the uncovered seed tail would make every later delta overlap or leave a gap.
|
|
18
|
+
*/
|
|
19
|
+
readonly coveredCodeUnits: number;
|
|
20
|
+
}) | (BlockAddress & {
|
|
21
|
+
readonly type: "block_delta";
|
|
22
|
+
readonly offset: number;
|
|
23
|
+
readonly text: string;
|
|
24
|
+
}) | (BlockAddress & {
|
|
25
|
+
readonly type: "block_replace";
|
|
26
|
+
readonly text: string;
|
|
27
|
+
}) | {
|
|
28
|
+
readonly type: "message_status";
|
|
29
|
+
readonly messageId: string;
|
|
30
|
+
readonly persistence: CompactPersistence;
|
|
31
|
+
} | {
|
|
32
|
+
readonly type: "message_remove";
|
|
33
|
+
readonly messageId: string;
|
|
34
|
+
};
|
|
35
|
+
type CompactRejection = "unknown_block" | "duplicate_block" | "invalid_coverage" | "non_contiguous" | "conflicting_overlap" | "unknown_message";
|
|
36
|
+
export declare class CompactTransitionError extends Error {
|
|
37
|
+
readonly reason: CompactRejection;
|
|
38
|
+
constructor(reason: CompactRejection, detail: string);
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=compact-transition.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed transitions compact current state accepts (senpi#4272, R3.C6.4) and the typed
|
|
3
|
+
* rejection `CompactSessionState.accept` throws when one fails validation.
|
|
4
|
+
*/
|
|
5
|
+
export class CompactTransitionError extends Error {
|
|
6
|
+
constructor(reason, detail) {
|
|
7
|
+
super(`compact transition rejected (${reason}): ${detail}`);
|
|
8
|
+
this.name = "CompactTransitionError";
|
|
9
|
+
this.reason = reason;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=compact-transition.js.map
|
|
@@ -50,6 +50,8 @@ export interface RpcConnectionSink {
|
|
|
50
50
|
writeRaw(chunk: string): void;
|
|
51
51
|
/** Shared bindings accept correlated records synchronously, before JSONL buffering. */
|
|
52
52
|
writeRecord?(record: object): void;
|
|
53
|
+
/** The internal structured lane (senpi#4272): seeded records and block controls, never a legacy destination. */
|
|
54
|
+
writeStructured?(record: object): void;
|
|
53
55
|
waitForBackpressure(): Promise<void>;
|
|
54
56
|
/** Tears the transport down once its event queue can no longer deliver (overflow, write failure). */
|
|
55
57
|
close?(): void;
|
|
@@ -47,7 +47,7 @@ import { runAbort, runGetEffectStatus, runQueuedInputChange, runSetModel, runSet
|
|
|
47
47
|
import { EffectLedger } from "./effect-ledger.js";
|
|
48
48
|
import { EFFECT_LEDGER_CAPABILITY } from "./effect-record.js";
|
|
49
49
|
import { createRpcEventOutputBuffer } from "./event-output-buffer.js";
|
|
50
|
-
import { settleExtensionUiResponse } from "./extension-ui-response.js";
|
|
50
|
+
import { answeredUiRequestId, settleExtensionUiResponse } from "./extension-ui-response.js";
|
|
51
51
|
import { answerGetEntries } from "./get-entries-command.js";
|
|
52
52
|
import { runHoldInput } from "./held-input-commands.js";
|
|
53
53
|
import { HostSessionControl } from "./host-session-control.js";
|
|
@@ -64,6 +64,7 @@ import { runPauseForRun, runResumeForRun } from "./run-hold-commands.js";
|
|
|
64
64
|
import { availableModelsData, interruptRunningTurn } from "./session-control-actions-data.js";
|
|
65
65
|
import { SessionExtensionUiRequests, sessionDialogRequests } from "./session-extension-ui-requests.js";
|
|
66
66
|
import { subscribeSessionRecords } from "./session-record-stream.js";
|
|
67
|
+
import { subscribeStructuredSessionRecords } from "./structured-session-records.js";
|
|
67
68
|
import { RpcSessionRelocation } from "./session-relocation.js";
|
|
68
69
|
import { answerHostResponse, isHostResponse, relocationChannel, } from "./session-relocation-channel.js";
|
|
69
70
|
export { buildRpcSessionState } from "./rpc-session-state.js";
|
|
@@ -190,6 +191,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
190
191
|
// Set by the first command that asks this session for a turn; `set_retry_fallback` is refused after it.
|
|
191
192
|
let turnRequested = false;
|
|
192
193
|
let unsubscribe;
|
|
194
|
+
let unsubscribeStructured;
|
|
193
195
|
let unsubscribeBackpressure;
|
|
194
196
|
let unsubscribeLoadedSurfaces;
|
|
195
197
|
let unsubscribeExtensionEvents;
|
|
@@ -762,6 +764,10 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
762
764
|
unsubscribe = subscribeSessionRecords(session, outputEvent, (abortSource) => {
|
|
763
765
|
lastAbortSource = abortSource;
|
|
764
766
|
});
|
|
767
|
+
unsubscribeStructured?.();
|
|
768
|
+
unsubscribeStructured = sink.writeStructured
|
|
769
|
+
? subscribeStructuredSessionRecords(session, (record) => sink.writeStructured?.(tagSessionRecord(record)))
|
|
770
|
+
: undefined;
|
|
765
771
|
unsubscribeBackpressure = session.agent.subscribe(async () => {
|
|
766
772
|
await waitForRpcBackpressure();
|
|
767
773
|
});
|
|
@@ -1704,15 +1710,13 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1704
1710
|
parsed !== null &&
|
|
1705
1711
|
"type" in parsed &&
|
|
1706
1712
|
parsed.type === "extension_ui_response") {
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
typeof parsed.id === "string" &&
|
|
1710
|
-
grantRequests.has(parsed.id)) {
|
|
1713
|
+
const requestId = answeredUiRequestId(parsed);
|
|
1714
|
+
if (!options.workerSecurityBridge && requestId !== undefined && grantRequests.has(requestId)) {
|
|
1711
1715
|
const sender = options.clientInfo?.connectionId();
|
|
1712
1716
|
const capable = sender === undefined
|
|
1713
1717
|
? clientCapabilities?.includes("grant_select") === true
|
|
1714
1718
|
: options.clientInfo?.hasCapability?.(sender, "grant_select") === true;
|
|
1715
|
-
if (grantRequests.get(
|
|
1719
|
+
if (grantRequests.get(requestId) !== sender || !capable)
|
|
1716
1720
|
return;
|
|
1717
1721
|
}
|
|
1718
1722
|
const reply = settleExtensionUiResponse(parsed, {
|
|
@@ -1767,10 +1771,12 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1767
1771
|
pendingExtensionRequests.close();
|
|
1768
1772
|
unsubscribeProviderAccountEvents();
|
|
1769
1773
|
unsubscribe?.();
|
|
1774
|
+
unsubscribeStructured?.();
|
|
1770
1775
|
unsubscribeBackpressure?.();
|
|
1771
1776
|
unsubscribeLoadedSurfaces?.();
|
|
1772
1777
|
unsubscribeExtensionEvents?.();
|
|
1773
1778
|
unsubscribe = undefined;
|
|
1779
|
+
unsubscribeStructured = undefined;
|
|
1774
1780
|
unsubscribeBackpressure = undefined;
|
|
1775
1781
|
unsubscribeLoadedSurfaces = undefined;
|
|
1776
1782
|
unsubscribeExtensionEvents = undefined;
|
|
@@ -16,7 +16,7 @@ import { keepOwnerPipe, OWNER_WATCH_FD } from "./host-lifecycle-launch.js";
|
|
|
16
16
|
import { pollProtocolInfo } from "./host-readiness.js";
|
|
17
17
|
import { initialHostEnvironment } from "./host-spawn-environment.js";
|
|
18
18
|
import { hostLaunchProfile } from "./protocol-identity.js";
|
|
19
|
-
import {
|
|
19
|
+
import { ensureSocketSecret, socketSecretPath } from "./socket-transport.js";
|
|
20
20
|
import { relayPresenterBootstrap } from "./mcp-presenter-bootstrap.js";
|
|
21
21
|
export const DEFAULT_READINESS_TIMEOUT_MS = 10_000;
|
|
22
22
|
export { DEFAULT_STOP_TIMEOUT_MS };
|
|
@@ -38,9 +38,11 @@ export async function startHost(paths, socket, options, generation = 0) {
|
|
|
38
38
|
reason,
|
|
39
39
|
});
|
|
40
40
|
// The settings file must exist before the supervisor reads it at boot, so it
|
|
41
|
-
// records the policy before the spawn instead of beside the pidfile.
|
|
41
|
+
// records the policy before the spawn instead of beside the pidfile. A valid public
|
|
42
|
+
// secret belongs to the endpoint, not to this start: clients that already read it
|
|
43
|
+
// derive the pipe name from it, so a start reuses it and never rotates it.
|
|
42
44
|
if (process.platform === "win32")
|
|
43
|
-
await
|
|
45
|
+
await ensureSocketSecret(socketSecretPath(socket));
|
|
44
46
|
await writeHostSettings(paths, {
|
|
45
47
|
socket,
|
|
46
48
|
capabilities: PINNED_HOST_CLIENT_CAPABILITIES,
|
|
@@ -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.
|
|
@@ -54,8 +55,17 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
|
|
|
54
55
|
return dropAfterClose();
|
|
55
56
|
writeInScope(record);
|
|
56
57
|
};
|
|
58
|
+
const writeStructuredInScope = bindToProviderScope((record) => writer.structuredLanes.accept(sessionId, record));
|
|
57
59
|
const taggedSink = {
|
|
58
60
|
writeRecord,
|
|
61
|
+
...(structuredLaneEnabled()
|
|
62
|
+
? {
|
|
63
|
+
writeStructured: (record) => {
|
|
64
|
+
if (entry.scope.state !== "closed")
|
|
65
|
+
writeStructuredInScope(record);
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
: {}),
|
|
59
69
|
// A late raw reply after the scope closed is dropped like any record (senpi#2871); while the scope is
|
|
60
70
|
// open, records arrive only through writeRecord, so a raw write is a wiring bug.
|
|
61
71
|
writeRaw: () => {
|
|
@@ -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;
|
|
@@ -3,6 +3,7 @@ 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
5
|
import { normalizeJsonValue } from "./json-normal-value.js";
|
|
6
|
+
import { SessionStructuredLanes } from "./session-structured-lanes.js";
|
|
6
7
|
import { serializeJsonLine } from "./jsonl.js";
|
|
7
8
|
import { omitNormalInlineMedia } from "./media-placeholders.js";
|
|
8
9
|
import { RpcSecurityPresentations } from "./security-presentations.js";
|
|
@@ -141,6 +142,7 @@ export class SessionEventWriter {
|
|
|
141
142
|
return this.reservedCloseBytes;
|
|
142
143
|
}
|
|
143
144
|
constructor(writeRaw, waitOrSchedule, scheduleFlush) {
|
|
145
|
+
this.structuredLanes = new SessionStructuredLanes();
|
|
144
146
|
this.presenterConnections = new Map();
|
|
145
147
|
this.presenterBindings = new Map();
|
|
146
148
|
this.records = new SessionRecordAccounting();
|