@code-yeongyu/senpi 2026.11.6-3 → 2026.11.9
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 +22 -0
- package/dist/bundle/chunks/{chunk-JMQOLOC2.js → chunk-3CRPNPTR.js} +1 -1
- package/dist/bundle/chunks/{chunk-5LKB732P.js → chunk-3TZQXZKH.js} +1 -1
- package/dist/bundle/chunks/{chunk-X53PD4ZC.js → chunk-45MVD7BU.js} +1 -1
- package/dist/bundle/chunks/{chunk-L7ZIS3MC.js → chunk-4JIBEZZE.js} +3 -3
- package/dist/bundle/chunks/{chunk-CF5OGBC4.js → chunk-4NYNMAYF.js} +1 -1
- package/dist/bundle/chunks/{chunk-6CEDL5S2.js → chunk-4UR6UFBH.js} +1 -1
- package/dist/bundle/chunks/{chunk-F5JLDCYD.js → chunk-527Q2UYM.js} +5 -5
- package/dist/bundle/chunks/{chunk-7DRWPZ3N.js → chunk-622VYTRI.js} +1 -1
- package/dist/bundle/chunks/{chunk-GKAYPJ4E.js → chunk-7PROV6BB.js} +1 -1
- package/dist/bundle/chunks/{chunk-4E7GDG4H.js → chunk-7VMEE62L.js} +1 -1
- package/dist/bundle/chunks/{chunk-CFEHJ4VU.js → chunk-HXNXISJD.js} +1 -1
- package/dist/bundle/chunks/{chunk-4BATXPB7.js → chunk-K3UHGDUQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-SB6MXNO5.js → chunk-M7GF73FO.js} +2 -2
- package/dist/bundle/chunks/{chunk-V2QAYGOK.js → chunk-OCMGRBJ5.js} +22 -22
- package/dist/bundle/chunks/{chunk-EO2VPZ2K.js → chunk-SGAFIMAU.js} +1 -1
- package/dist/bundle/chunks/{chunk-BASS47UD.js → chunk-STGOYIXC.js} +1 -1
- package/dist/bundle/chunks/{chunk-DSWDJ7HT.js → chunk-WQ4RBPMS.js} +1 -1
- package/dist/bundle/chunks/{chunk-MJTCZUXS.js → chunk-WX2HB4DD.js} +1 -1
- package/dist/bundle/chunks/chunk-XZDCMDUH.js +2 -0
- package/dist/bundle/chunks/{chunk-LUZVGPQ7.js → chunk-Y5L4UFMI.js} +1 -1
- package/dist/bundle/chunks/{cli-main-RWHDZKTQ.js → cli-main-E4NDWAUD.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-G5LYF62Q.js → hand-entry-5X5Z5Y3D.js} +1 -1
- package/dist/bundle/chunks/{headless-host-ES74ZB6M.js → headless-host-YXUGONR3.js} +1 -1
- package/dist/bundle/chunks/{host-command-I2JZ5JM7.js → host-command-VHX2ILLM.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-Y52NFPP2.js → host-session-command-P3MRW63Z.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-PA7DGVS6.js → interactive-mode-XUSBLLWI.js} +1 -1
- package/dist/bundle/chunks/{main-OBL6375C.js → main-3TWVKTJN.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-NM6I5BWZ.js → multi-session-host-HRGM66PY.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-GNRQ5MVR.js → package-manager-cli-WSH7U4YE.js} +1 -1
- package/dist/bundle/chunks/{role-F56BS2ZQ.js → role-4BUU46VW.js} +3 -3
- package/dist/bundle/chunks/{rotation-stream-BN2YK2MC.js → rotation-stream-TODBIHXJ.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-G4KIP7XS.js → rpc-mode-OAPIV46G.js} +1 -1
- package/dist/bundle/chunks/{run-attached-tui-XTGPAGYM.js → run-attached-tui-MXCC5IZR.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-L4M3TSQ3.js → schedule-command-MX56D6UE.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-V46WMCDF.js → session-control-endpoint-IL6ERD4U.js} +1 -1
- package/dist/bundle/chunks/{session-picker-AG6KSR5A.js → session-picker-6VRC25MJ.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +44 -44
- 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-services.d.ts +2 -0
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session.d.ts +8 -0
- package/dist/core/agent-session.js +51 -4
- package/dist/core/egress/classify.d.ts +8 -3
- package/dist/core/egress/classify.js +177 -158
- package/dist/core/egress/console-redactor.d.ts +7 -0
- package/dist/core/egress/console-redactor.js +74 -0
- package/dist/core/egress/credentials.d.ts +73 -0
- package/dist/core/egress/credentials.js +20 -0
- package/dist/core/egress/device-custody.d.ts +1 -1
- package/dist/core/egress/device-custody.js +18 -15
- package/dist/core/egress/device-proxy-call.d.ts +59 -0
- package/dist/core/egress/device-proxy-call.js +148 -0
- package/dist/core/egress/device-proxy-lost-calls.d.ts +27 -0
- package/dist/core/egress/device-proxy-lost-calls.js +28 -0
- package/dist/core/egress/device-proxy-ports.d.ts +49 -0
- package/dist/core/egress/device-proxy-ports.js +2 -0
- package/dist/core/egress/device-proxy-request.d.ts +34 -0
- package/dist/core/egress/device-proxy-request.js +69 -0
- package/dist/core/egress/device-proxy-waits.d.ts +40 -0
- package/dist/core/egress/device-proxy-waits.js +67 -0
- package/dist/core/egress/device-proxy.d.ts +3 -0
- package/dist/core/egress/device-proxy.js +214 -0
- package/dist/core/egress/endpoint.js +2 -1
- package/dist/core/egress/entry-verify.d.ts +13 -0
- package/dist/core/egress/entry-verify.js +33 -0
- package/dist/core/egress/hpke.js +13 -9
- package/dist/core/egress/key-policy-template.d.ts +7 -1
- package/dist/core/egress/ledger-schema.d.ts +80 -0
- package/dist/core/egress/ledger-schema.js +117 -0
- package/dist/core/egress/ledger.d.ts +55 -0
- package/dist/core/egress/ledger.js +159 -0
- package/dist/core/egress/pinned-infra.d.ts +23 -2
- package/dist/core/egress/pinned-infra.js +9 -2
- package/dist/core/egress/redact.d.ts +10 -0
- package/dist/core/egress/redact.js +134 -0
- package/dist/core/egress/sealed-source.d.ts +35 -0
- package/dist/core/egress/sealed-source.js +140 -0
- package/dist/core/egress/verified-keys.js +1 -1
- package/dist/core/egress/wire.d.ts +5 -3
- package/dist/core/egress/wire.js +5 -3
- package/dist/core/extensions/builtin/monitor-state-event.d.ts +26 -0
- package/dist/core/extensions/builtin/monitor-state-event.js +2 -0
- package/dist/core/extensions/builtin/terminal/background-age.d.ts +25 -0
- package/dist/core/extensions/builtin/terminal/background-age.js +91 -0
- package/dist/core/extensions/builtin/terminal/extension-state.d.ts +1 -1
- package/dist/core/extensions/builtin/terminal/extension-state.js +21 -3
- package/dist/core/extensions/builtin/terminal/notify.d.ts +6 -0
- package/dist/core/extensions/builtin/terminal/notify.js +39 -5
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts +9 -2
- package/dist/core/extensions/builtin/terminal/session-bundle.js +36 -1
- package/dist/core/extensions/builtin/terminal/settings.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/settings.js +8 -0
- package/dist/core/extensions/builtin/terminal/tools/background-auto-reap.d.ts +15 -0
- package/dist/core/extensions/builtin/terminal/tools/background-auto-reap.js +87 -0
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/tools/bash.js +6 -6
- package/dist/core/extensions/builtin/terminal/tools/child-placement.d.ts +8 -0
- package/dist/core/extensions/builtin/terminal/tools/child-placement.js +43 -6
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts +11 -2
- package/dist/core/extensions/session-control-types.d.ts +15 -0
- package/dist/core/extensions/session-control-types.js +11 -0
- package/dist/core/external-admission.d.ts +16 -2
- package/dist/core/external-admission.js +22 -4
- package/dist/core/retry-fallback/controller.d.ts +1 -0
- package/dist/core/retry-fallback/controller.js +3 -0
- package/dist/core/retry-fallback/unusable.d.ts +2 -0
- package/dist/core/retry-fallback/unusable.js +7 -1
- package/dist/core/retry-fallback/usage-limit.d.ts +6 -1
- package/dist/core/retry-fallback/usage-limit.js +9 -4
- package/dist/core/sdk.d.ts +6 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/terminal-settings.d.ts +1 -0
- package/dist/core/turn-origin.d.ts +4 -0
- package/dist/core/turn-origin.js +10 -0
- package/dist/modes/interactive/components/remote-delivery-message.d.ts +2 -1
- package/dist/modes/interactive/components/remote-delivery-message.js +13 -5
- package/dist/modes/rpc/admit-external-message-command.d.ts +37 -0
- package/dist/modes/rpc/admit-external-message-command.js +61 -0
- package/dist/modes/rpc/admit-external-message-wire.d.ts +42 -0
- package/dist/modes/rpc/admit-external-message-wire.js +55 -0
- package/dist/modes/rpc/connection-handler.js +14 -0
- package/dist/modes/rpc/effect-commands.d.ts +1 -1
- package/dist/modes/rpc/host-idle-handover.js +1 -0
- package/dist/modes/rpc/method-classes-schema-delivery.d.ts +32 -0
- package/dist/modes/rpc/method-classes-schema-delivery.js +22 -0
- package/dist/modes/rpc/method-classes-schema-session.d.ts +27 -0
- package/dist/modes/rpc/method-classes-schema-session.js +2 -0
- package/dist/modes/rpc/method-classes-schema.d.ts +27 -0
- package/dist/modes/rpc/method-classes.d.ts +1 -0
- package/dist/modes/rpc/method-classes.js +2 -0
- package/dist/modes/rpc/rpc-input-validation.js +4 -3
- package/dist/modes/rpc/rpc-response-types.d.ts +2 -1
- package/dist/modes/rpc/rpc-session-command-types.d.ts +2 -1
- package/dist/modes/rpc/tool-child/terminal-jobs.d.ts +1 -0
- package/dist/modes/rpc/tool-child/terminal-jobs.js +17 -1
- package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
- package/docs/engine-socket/fixtures/rpc-inbound-samples.json +19 -2
- package/docs/rpc.md +57 -0
- package/docs/terminal-tools.md +25 -1
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-UODSYQ2D.js +0 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { callKey, cancelledKey, decodeCallRow, decodeVariant, encodeCallRow, encodeVariant, isStoredMessage, requestChunkKey, resultChunkKey, splitUtf8, stepIndexKey, stepIndexPrefix, variantKey, variantPrefix, } from "./ledger-schema.js";
|
|
2
|
+
const putRow = (row) => ({ op: "put", key: callKey(row.callId), data: encodeCallRow(row) });
|
|
3
|
+
const deletes = (count, key) => Array.from({ length: count }, (_, index) => ({ op: "delete", key: key(index) }));
|
|
4
|
+
/** Every stored chunk of `row`: its request and, once `ended` with a message, the assembled message. */
|
|
5
|
+
const chunkDeletes = (row) => [
|
|
6
|
+
...deletes(row.requestChunks, (index) => requestChunkKey(row.callId, index)),
|
|
7
|
+
...deletes(row.result?.kind === "message" ? row.result.chunks : 0, (index) => resultChunkKey(row.callId, index)),
|
|
8
|
+
];
|
|
9
|
+
/** A cancelled row keeps its identity and an error result, never pointers to chunks W14 deleted. */
|
|
10
|
+
function cancelledRow(row) {
|
|
11
|
+
const { result, ...fields } = row;
|
|
12
|
+
return { ...fields, state: "cancelled", requestChunks: 0, ...(result?.kind === "error" ? { result } : {}) };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* W14 on the step's live row. A dispatched or ended row becomes `cancelled`; a superseded row (the abort came while its
|
|
16
|
+
* reissue waited: the pacer backoff or the user's choice) stays superseded. Either way its chunks are deleted, because a
|
|
17
|
+
* cancelled step is never dialed again, so no later W4 retires them.
|
|
18
|
+
*/
|
|
19
|
+
function cancelWrites(row) {
|
|
20
|
+
if (row.state === "closed" || row.state === "cancelled")
|
|
21
|
+
return [];
|
|
22
|
+
const kept = row.state === "superseded" ? { ...row, requestChunks: 0 } : cancelledRow(row);
|
|
23
|
+
return [putRow(kept), ...chunkDeletes(row)];
|
|
24
|
+
}
|
|
25
|
+
/** The W9 part the ledger owns: the step's row becomes `closed` and its request chunks are deleted (4.5 retention). */
|
|
26
|
+
export function closeCallWrites(row) {
|
|
27
|
+
return [putRow({ ...row, state: "closed" }), ...chunkDeletes(row)];
|
|
28
|
+
}
|
|
29
|
+
export function createEgressLedger(options) {
|
|
30
|
+
const { store, mutate } = options;
|
|
31
|
+
const withRequest = (row, requestJson) => {
|
|
32
|
+
const chunks = splitUtf8(requestJson);
|
|
33
|
+
const next = { ...row, requestChunks: chunks.length };
|
|
34
|
+
const writes = chunks.map((data, index) => ({
|
|
35
|
+
op: "put",
|
|
36
|
+
key: requestChunkKey(row.callId, index),
|
|
37
|
+
data,
|
|
38
|
+
}));
|
|
39
|
+
return { row: next, writes: [...writes, putRow(next)] };
|
|
40
|
+
};
|
|
41
|
+
const variantWrite = async (variant) => {
|
|
42
|
+
const key = variantKey(variant.providerId, variant.deviceId);
|
|
43
|
+
const prior = decodeVariant(await store.getDocument(key));
|
|
44
|
+
const sameRun = prior !== undefined && prior.headDigest === variant.headDigest && prior.stepId === variant.stepId;
|
|
45
|
+
const mismatches = sameRun ? prior.mismatches + 1 : 1;
|
|
46
|
+
const { headDigest, liveAtMismatch, stepId } = variant;
|
|
47
|
+
return { op: "put", key, data: encodeVariant({ headDigest, liveAtMismatch, mismatches, stepId }) };
|
|
48
|
+
};
|
|
49
|
+
const stepOf = (row) => {
|
|
50
|
+
if (row.call.kind !== "step")
|
|
51
|
+
throw new Error("the device-route ledger indexes loop steps only");
|
|
52
|
+
return row.call;
|
|
53
|
+
};
|
|
54
|
+
const readRow = async (callId) => decodeCallRow(await store.getDocument(callKey(callId)));
|
|
55
|
+
const readChunks = async (row, what, count, key) => {
|
|
56
|
+
const chunks = await Promise.all(Array.from({ length: count }, (_, index) => store.getDocument(key(index))));
|
|
57
|
+
if (!chunks.every((chunk) => typeof chunk === "string"))
|
|
58
|
+
throw new Error(`egress_call ${row.callId}: stored ${what} is incomplete`);
|
|
59
|
+
return chunks.join("");
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
async rowsForStep(stepId) {
|
|
63
|
+
const index = await store.scanDocuments(stepIndexPrefix(stepId));
|
|
64
|
+
const rows = await Promise.all(index.map((entry) => (typeof entry.data === "string" ? readRow(entry.data) : undefined)));
|
|
65
|
+
return rows.filter((row) => row !== undefined);
|
|
66
|
+
},
|
|
67
|
+
readRequest: (row) => readChunks(row, "request", row.requestChunks, (index) => requestChunkKey(row.callId, index)),
|
|
68
|
+
async readMessage(row) {
|
|
69
|
+
if (row.result?.kind !== "message")
|
|
70
|
+
throw new Error(`egress_call ${row.callId} has no stored message`);
|
|
71
|
+
const key = (index) => resultChunkKey(row.callId, index);
|
|
72
|
+
const parsed = JSON.parse(await readChunks(row, "message", row.result.chunks, key));
|
|
73
|
+
if (!isStoredMessage(parsed))
|
|
74
|
+
throw new Error(`egress_call ${row.callId}: stored message is malformed`);
|
|
75
|
+
return parsed;
|
|
76
|
+
},
|
|
77
|
+
async dispatch(next, requestJson, previous) {
|
|
78
|
+
const { stepId, attempt } = stepOf(next);
|
|
79
|
+
const created = withRequest({ ...next, state: "dispatched", requestChunks: 0 }, requestJson);
|
|
80
|
+
const retire = previous
|
|
81
|
+
? [
|
|
82
|
+
putRow({ ...previous, state: previous.state === "ended" ? "closed" : previous.state }),
|
|
83
|
+
...chunkDeletes(previous),
|
|
84
|
+
]
|
|
85
|
+
: [];
|
|
86
|
+
const index = { op: "put", key: stepIndexKey(stepId, attempt), data: next.callId };
|
|
87
|
+
await mutate(async () => [...retire, ...created.writes, index]);
|
|
88
|
+
return created.row;
|
|
89
|
+
},
|
|
90
|
+
async recordHooked(row, requestJson) {
|
|
91
|
+
const updated = withRequest({ ...row, hooksApplied: true }, requestJson);
|
|
92
|
+
await mutate(async () => [...chunkDeletes(row), ...updated.writes]);
|
|
93
|
+
return updated.row;
|
|
94
|
+
},
|
|
95
|
+
async end(row, result) {
|
|
96
|
+
if (result.kind === "error") {
|
|
97
|
+
const failed = { ...row, state: "ended", result };
|
|
98
|
+
await mutate(async () => [putRow(failed)]);
|
|
99
|
+
return failed;
|
|
100
|
+
}
|
|
101
|
+
const chunks = splitUtf8(JSON.stringify(result.message));
|
|
102
|
+
const { stopReason, usage } = result.message;
|
|
103
|
+
const ended = {
|
|
104
|
+
...row,
|
|
105
|
+
state: "ended",
|
|
106
|
+
result: { kind: "message", stopReason, usage, chunks: chunks.length },
|
|
107
|
+
};
|
|
108
|
+
const writes = chunks.map((data, index) => ({
|
|
109
|
+
op: "put",
|
|
110
|
+
key: resultChunkKey(row.callId, index),
|
|
111
|
+
data,
|
|
112
|
+
}));
|
|
113
|
+
await mutate(async () => [...writes, putRow(ended)]);
|
|
114
|
+
return ended;
|
|
115
|
+
},
|
|
116
|
+
async replaceRefused(row, callId) {
|
|
117
|
+
const { stepId, attempt } = stepOf(row);
|
|
118
|
+
const requestJson = await readChunks(row, "request", row.requestChunks, (i) => requestChunkKey(row.callId, i));
|
|
119
|
+
const created = withRequest({ ...row, callId, state: "dispatched", requestChunks: 0 }, requestJson);
|
|
120
|
+
const index = { op: "put", key: stepIndexKey(stepId, attempt), data: callId };
|
|
121
|
+
const dropped = { op: "delete", key: callKey(row.callId) };
|
|
122
|
+
await mutate(async () => [dropped, ...chunkDeletes(row), ...created.writes, index]);
|
|
123
|
+
return created.row;
|
|
124
|
+
},
|
|
125
|
+
async supersede(row, variant) {
|
|
126
|
+
const superseded = { ...row, state: "superseded" };
|
|
127
|
+
await mutate(async () => [putRow(superseded), ...(variant ? [await variantWrite(variant)] : [])]);
|
|
128
|
+
return superseded;
|
|
129
|
+
},
|
|
130
|
+
async cancelStep(stepId, row) {
|
|
131
|
+
const live = row ? cancelWrites(row) : [];
|
|
132
|
+
await mutate(async () => [{ op: "put", key: cancelledKey(stepId), data: stepId }, ...live]);
|
|
133
|
+
},
|
|
134
|
+
async isCancelled(stepId) {
|
|
135
|
+
return (await store.getDocument(cancelledKey(stepId))) !== undefined;
|
|
136
|
+
},
|
|
137
|
+
async recordVariant(variant) {
|
|
138
|
+
await mutate(async () => [await variantWrite(variant)]);
|
|
139
|
+
},
|
|
140
|
+
async variants(providerId) {
|
|
141
|
+
const prefix = variantPrefix(providerId);
|
|
142
|
+
const entries = await store.scanDocuments(prefix);
|
|
143
|
+
const variants = new Map();
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const stored = decodeVariant(entry.data);
|
|
146
|
+
if (!stored)
|
|
147
|
+
continue;
|
|
148
|
+
const { headDigest, liveAtMismatch, mismatches } = stored;
|
|
149
|
+
variants.set(decodeURIComponent(entry.key.slice(prefix.length)), {
|
|
150
|
+
headDigest,
|
|
151
|
+
liveAtMismatch,
|
|
152
|
+
mismatches,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return variants;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=ledger.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type KeyFactsAnswer } from "./key-facts-client.ts";
|
|
2
|
-
import { type CustodyRoleArns, type KeyPolicyTemplate } from "./key-policy-template.ts";
|
|
2
|
+
import { type CustodyRoleArns, type KeyPolicyKind, type KeyPolicyTemplate } from "./key-policy-template.ts";
|
|
3
3
|
import type { ServiceKeyEntry } from "./roster-entries.ts";
|
|
4
4
|
import type { VerifierKeyPin } from "./signed-statement.ts";
|
|
5
5
|
export type CustodyRole = keyof CustodyRoleArns;
|
|
@@ -14,7 +14,28 @@ export interface PinnedCustodyInfra {
|
|
|
14
14
|
/** The verifier signing keys this release trusts (three consecutive generations once minted, 15.15 M-2). */
|
|
15
15
|
readonly keyFactsVerifierKeys: readonly VerifierKeyPin[];
|
|
16
16
|
}
|
|
17
|
-
export declare const PINNED_CUSTODY_INFRA:
|
|
17
|
+
export declare const PINNED_CUSTODY_INFRA: {
|
|
18
|
+
readonly custodyAccountIds: readonly [];
|
|
19
|
+
readonly arnNamespace: "arn:aws:kms:us-east-1:";
|
|
20
|
+
readonly roleNames: {
|
|
21
|
+
readonly runtime: readonly ["custody-runtime"];
|
|
22
|
+
readonly registrySigner: readonly ["registry-signer"];
|
|
23
|
+
readonly provisioning: readonly ["custody-provisioning"];
|
|
24
|
+
readonly reader: readonly ["key-facts-reader"];
|
|
25
|
+
readonly breakglass: readonly ["custody-breakglass"];
|
|
26
|
+
};
|
|
27
|
+
readonly templates: {
|
|
28
|
+
readonly 1: {
|
|
29
|
+
readonly render: ({ keyKind, roles }: {
|
|
30
|
+
readonly accountId: string;
|
|
31
|
+
readonly keyKind: KeyPolicyKind;
|
|
32
|
+
readonly roles: CustodyRoleArns;
|
|
33
|
+
}) => string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
readonly keyFactsEndpoint: undefined;
|
|
37
|
+
readonly keyFactsVerifierKeys: readonly [];
|
|
38
|
+
};
|
|
18
39
|
export type ServiceKeyCheckReason = "ok" | "template-unknown" | "facts-unknown" | "facts-stale" | "shape" | "arn" | "arn-namespace" | "account-not-pinned" | "key-usage" | "key-property" | "spki" | "principal-id" | "role-outside-pins" | "policy";
|
|
19
40
|
export interface ServiceKeyCheck {
|
|
20
41
|
readonly result: "ok" | "mismatch" | "unknown";
|
|
@@ -29,7 +29,13 @@ const SERVICE_KEY_NOTICES = {
|
|
|
29
29
|
"principal-id": "the custody key's access policy no longer matches; cloud custody for this key is unavailable",
|
|
30
30
|
};
|
|
31
31
|
const KMS_KEY_ARN = /^(arn:[a-z0-9-]+:kms:[a-z0-9-]+:)(\d{12}):key\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
32
|
-
const ROLE_ORDER = [
|
|
32
|
+
const ROLE_ORDER = [
|
|
33
|
+
"runtime",
|
|
34
|
+
"registrySigner",
|
|
35
|
+
"provisioning",
|
|
36
|
+
"reader",
|
|
37
|
+
"breakglass",
|
|
38
|
+
];
|
|
33
39
|
export function roleArn(accountId, roleName) {
|
|
34
40
|
return `arn:aws:iam::${accountId}:role/${roleName}`;
|
|
35
41
|
}
|
|
@@ -117,7 +123,8 @@ function isFresh(facts, now) {
|
|
|
117
123
|
return now - facts.readAt <= MAX_FACTS_AGE_MS && facts.readAt - now <= MAX_FACTS_FUTURE_SKEW_MS;
|
|
118
124
|
}
|
|
119
125
|
function outcome(result, reason) {
|
|
120
|
-
const
|
|
126
|
+
const notices = SERVICE_KEY_NOTICES;
|
|
127
|
+
const notice = notices[reason];
|
|
121
128
|
return notice === undefined ? { result, reason } : { result, reason, notice };
|
|
122
129
|
}
|
|
123
130
|
/** `facts` maps each of `entry.kmsKeyArns` to the verifier's answer for exactly that ARN. */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const REDACTED = "<redacted>";
|
|
2
|
+
export interface RedactorOptions {
|
|
3
|
+
readonly secrets: readonly string[];
|
|
4
|
+
readonly publicPrefixes: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export interface Redactor {
|
|
7
|
+
redact(text: string): string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createRedactor(options: RedactorOptions): Redactor;
|
|
10
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export const REDACTED = "<redacted>";
|
|
2
|
+
const MIN_FRAGMENT = 8;
|
|
3
|
+
const NO_NODE = -1;
|
|
4
|
+
function newNode() {
|
|
5
|
+
return { next: new Map(), fail: 0, pattern: undefined, dictionary: NO_NODE };
|
|
6
|
+
}
|
|
7
|
+
function dedupe(patterns) {
|
|
8
|
+
const byText = new Map();
|
|
9
|
+
for (const pattern of patterns) {
|
|
10
|
+
const seen = byText.get(pattern.text);
|
|
11
|
+
if (seen === undefined || (seen.wholeToken && !pattern.wholeToken))
|
|
12
|
+
byText.set(pattern.text, pattern);
|
|
13
|
+
}
|
|
14
|
+
return [...byText.values()];
|
|
15
|
+
}
|
|
16
|
+
function isTokenChar(char) {
|
|
17
|
+
return char !== undefined && /[A-Za-z0-9]/.test(char);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Base64 characters that depend only on the secret's bytes when the secret starts `offset` bytes into a longer
|
|
21
|
+
* stream: character i covers bits [6i, 6i + 6), and it is kept only when that range lies inside the secret's bits.
|
|
22
|
+
*/
|
|
23
|
+
function alignedBase64Core(secret, offset, alphabet) {
|
|
24
|
+
const encoded = Buffer.concat([Buffer.alloc(offset), secret])
|
|
25
|
+
.toString(alphabet)
|
|
26
|
+
.replace(/=+$/, "");
|
|
27
|
+
const firstBit = offset * 8;
|
|
28
|
+
const lastBit = firstBit + secret.length * 8;
|
|
29
|
+
let core = "";
|
|
30
|
+
for (let i = 0; i < encoded.length; i++) {
|
|
31
|
+
if (i * 6 >= firstBit && i * 6 + 6 <= lastBit)
|
|
32
|
+
core += encoded[i];
|
|
33
|
+
}
|
|
34
|
+
return core;
|
|
35
|
+
}
|
|
36
|
+
function patternsFor(secret, publicPrefixes) {
|
|
37
|
+
const bytes = Buffer.from(secret, "utf8");
|
|
38
|
+
const patterns = [{ text: secret, wholeToken: true }];
|
|
39
|
+
const urlEncoded = encodeURIComponent(secret);
|
|
40
|
+
if (urlEncoded !== secret)
|
|
41
|
+
patterns.push({ text: urlEncoded, wholeToken: true });
|
|
42
|
+
for (const alphabet of ["base64", "base64url"]) {
|
|
43
|
+
patterns.push({ text: bytes.toString(alphabet), wholeToken: true });
|
|
44
|
+
for (const offset of [0, 1, 2]) {
|
|
45
|
+
const core = alignedBase64Core(bytes, offset, alphabet);
|
|
46
|
+
if (core.length >= MIN_FRAGMENT)
|
|
47
|
+
patterns.push({ text: core, wholeToken: false });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (let i = 0; i + MIN_FRAGMENT <= secret.length; i++) {
|
|
51
|
+
const gram = secret.slice(i, i + MIN_FRAGMENT);
|
|
52
|
+
if (!publicPrefixes.some((prefix) => prefix.includes(gram)))
|
|
53
|
+
patterns.push({ text: gram, wholeToken: false });
|
|
54
|
+
}
|
|
55
|
+
return patterns;
|
|
56
|
+
}
|
|
57
|
+
function buildAutomaton(patterns) {
|
|
58
|
+
const nodes = [newNode()];
|
|
59
|
+
patterns.forEach((pattern, index) => {
|
|
60
|
+
let state = 0;
|
|
61
|
+
for (const char of pattern.text) {
|
|
62
|
+
let target = nodes[state].next.get(char);
|
|
63
|
+
if (target === undefined) {
|
|
64
|
+
target = nodes.length;
|
|
65
|
+
nodes.push(newNode());
|
|
66
|
+
nodes[state].next.set(char, target);
|
|
67
|
+
}
|
|
68
|
+
state = target;
|
|
69
|
+
}
|
|
70
|
+
nodes[state].pattern = index;
|
|
71
|
+
});
|
|
72
|
+
const queue = [...nodes[0].next.values()];
|
|
73
|
+
for (let head = 0; head < queue.length; head++) {
|
|
74
|
+
const state = queue[head];
|
|
75
|
+
for (const [char, target] of nodes[state].next) {
|
|
76
|
+
let fallback = nodes[state].fail;
|
|
77
|
+
while (fallback !== 0 && !nodes[fallback].next.has(char))
|
|
78
|
+
fallback = nodes[fallback].fail;
|
|
79
|
+
const candidate = nodes[fallback].next.get(char);
|
|
80
|
+
nodes[target].fail = candidate !== undefined && candidate !== target ? candidate : 0;
|
|
81
|
+
const fail = nodes[nodes[target].fail];
|
|
82
|
+
nodes[target].dictionary = fail.pattern !== undefined ? nodes[target].fail : fail.dictionary;
|
|
83
|
+
queue.push(target);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return nodes;
|
|
87
|
+
}
|
|
88
|
+
export function createRedactor(options) {
|
|
89
|
+
const patterns = dedupe(options.secrets
|
|
90
|
+
.filter((secret) => secret.length > 0)
|
|
91
|
+
.flatMap((secret) => patternsFor(secret, options.publicPrefixes)));
|
|
92
|
+
if (patterns.length === 0)
|
|
93
|
+
return { redact: (text) => text };
|
|
94
|
+
const nodes = buildAutomaton(patterns);
|
|
95
|
+
const lengths = patterns.map((pattern) => [...pattern.text].length);
|
|
96
|
+
return {
|
|
97
|
+
redact(text) {
|
|
98
|
+
const chars = [...text];
|
|
99
|
+
const spans = [];
|
|
100
|
+
let state = 0;
|
|
101
|
+
for (let end = 0; end < chars.length; end++) {
|
|
102
|
+
const char = chars[end];
|
|
103
|
+
while (state !== 0 && !nodes[state].next.has(char))
|
|
104
|
+
state = nodes[state].fail;
|
|
105
|
+
state = nodes[state].next.get(char) ?? 0;
|
|
106
|
+
for (let match = nodes[state].pattern !== undefined ? state : nodes[state].dictionary; match !== NO_NODE; match = nodes[match].dictionary) {
|
|
107
|
+
const index = nodes[match].pattern;
|
|
108
|
+
const start = end - lengths[index] + 1;
|
|
109
|
+
if (patterns[index].wholeToken && (isTokenChar(chars[start - 1]) || isTokenChar(chars[end + 1])))
|
|
110
|
+
continue;
|
|
111
|
+
spans.push([start, end + 1]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (spans.length === 0)
|
|
115
|
+
return text;
|
|
116
|
+
spans.sort((a, b) => a[0] - b[0]);
|
|
117
|
+
let out = "";
|
|
118
|
+
let cursor = 0;
|
|
119
|
+
let current = spans[0];
|
|
120
|
+
for (const span of spans.slice(1)) {
|
|
121
|
+
if (span[0] <= current[1]) {
|
|
122
|
+
current = [current[0], Math.max(current[1], span[1])];
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
out += chars.slice(cursor, current[0]).join("") + REDACTED;
|
|
126
|
+
cursor = current[1];
|
|
127
|
+
current = span;
|
|
128
|
+
}
|
|
129
|
+
out += chars.slice(cursor, current[0]).join("") + REDACTED + chars.slice(current[1]).join("");
|
|
130
|
+
return out;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type CredentialSource, type EpochLeaseToken, type ReleaseScope, type SecretField, type SecretLease, type Unsealer } from "./credentials.ts";
|
|
2
|
+
import type { HpkeWrap, ProviderId } from "./sealed-format.ts";
|
|
3
|
+
export interface ReleasedItemPosition {
|
|
4
|
+
readonly accountId: string;
|
|
5
|
+
readonly providerId: ProviderId;
|
|
6
|
+
readonly entryDigest: string;
|
|
7
|
+
readonly field: SecretField;
|
|
8
|
+
/** The lease this release was made under: the opener rebuilds epoch and placementId from it (r11.2 C.5). */
|
|
9
|
+
readonly lease: EpochLeaseToken;
|
|
10
|
+
}
|
|
11
|
+
export interface SealedSourceDeps {
|
|
12
|
+
readonly accountId: string;
|
|
13
|
+
readonly unsealer: Unsealer;
|
|
14
|
+
readonly lease: () => Promise<EpochLeaseToken>;
|
|
15
|
+
readonly recipientPk: Uint8Array;
|
|
16
|
+
readonly reloadEntryDigest: (providerId: ProviderId) => Promise<string>;
|
|
17
|
+
readonly readHead: () => Promise<string>;
|
|
18
|
+
/** Rejects with the signal's reason once the step aborts. */
|
|
19
|
+
readonly sleep: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
20
|
+
/** The abort signal of the step this release serves; it bounds the churn wait (custody design L844, C.3). */
|
|
21
|
+
readonly stepSignal: (scope: ReleaseScope) => AbortSignal;
|
|
22
|
+
/** Called once per step when the step enters the config-churn wait (C.3), so the caller can show one notice. */
|
|
23
|
+
readonly onChurnNotice: (scope: ReleaseScope) => void;
|
|
24
|
+
/** Opens one released item with this instance's recipient key; its HPKE info/AAD binding is fixed by r11.1. */
|
|
25
|
+
readonly openItem: (item: HpkeWrap, position: ReleasedItemPosition) => Promise<Uint8Array>;
|
|
26
|
+
}
|
|
27
|
+
/** Per egress instance (custody design 3.4): its leases live on the instance, never in module scope. */
|
|
28
|
+
export declare class SealedCredentialSource implements CredentialSource {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(deps: SealedSourceDeps);
|
|
31
|
+
resolve(providerId: ProviderId, entryDigest: string, fields: readonly SecretField[], scope: ReleaseScope): Promise<SecretLease>;
|
|
32
|
+
/** Disposes every live lease, and voids any lease still being resolved when the bump lands. */
|
|
33
|
+
bumpGeneration(): void;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=sealed-source.d.ts.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { CredentialWait, SecretNotLeased, } from "./credentials.js";
|
|
2
|
+
const RELOADS_PER_STEP = 3;
|
|
3
|
+
const STABLE_HEAD_MS = 5000;
|
|
4
|
+
const REDACTED = "<redacted>";
|
|
5
|
+
const RETRYABLE = [
|
|
6
|
+
"stale_snapshot",
|
|
7
|
+
"epoch_mismatch",
|
|
8
|
+
"unresolved_field",
|
|
9
|
+
"wrap_missing",
|
|
10
|
+
"unavailable",
|
|
11
|
+
];
|
|
12
|
+
class SealedLease {
|
|
13
|
+
#values;
|
|
14
|
+
#onDispose;
|
|
15
|
+
constructor(values, onDispose) {
|
|
16
|
+
this.#values = values;
|
|
17
|
+
this.#onDispose = onDispose;
|
|
18
|
+
}
|
|
19
|
+
value(field) {
|
|
20
|
+
const bytes = this.#values?.get(field);
|
|
21
|
+
if (bytes === undefined)
|
|
22
|
+
throw new SecretNotLeased(field);
|
|
23
|
+
return new TextDecoder().decode(bytes);
|
|
24
|
+
}
|
|
25
|
+
[Symbol.dispose]() {
|
|
26
|
+
const values = this.#values;
|
|
27
|
+
if (values === undefined)
|
|
28
|
+
return;
|
|
29
|
+
this.#values = undefined;
|
|
30
|
+
for (const bytes of values.values())
|
|
31
|
+
bytes.fill(0);
|
|
32
|
+
this.#onDispose(this);
|
|
33
|
+
}
|
|
34
|
+
toJSON() {
|
|
35
|
+
return REDACTED;
|
|
36
|
+
}
|
|
37
|
+
toString() {
|
|
38
|
+
return REDACTED;
|
|
39
|
+
}
|
|
40
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
41
|
+
return REDACTED;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Per egress instance (custody design 3.4): its leases live on the instance, never in module scope. */
|
|
45
|
+
export class SealedCredentialSource {
|
|
46
|
+
#deps;
|
|
47
|
+
#live = new Set();
|
|
48
|
+
#generation = 0;
|
|
49
|
+
constructor(deps) {
|
|
50
|
+
this.#deps = deps;
|
|
51
|
+
}
|
|
52
|
+
async resolve(providerId, entryDigest, fields, scope) {
|
|
53
|
+
if (new Set(fields).size !== fields.length)
|
|
54
|
+
throw new TypeError("a sealed release names each field at most once");
|
|
55
|
+
const step = { digest: entryDigest, reloads: 0, noticed: false };
|
|
56
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
57
|
+
const generation = this.#generation;
|
|
58
|
+
const values = await this.#resolveValues(providerId, step, fields, scope);
|
|
59
|
+
if (generation === this.#generation) {
|
|
60
|
+
const lease = new SealedLease(values, (disposed) => this.#live.delete(disposed));
|
|
61
|
+
this.#live.add(lease);
|
|
62
|
+
return lease;
|
|
63
|
+
}
|
|
64
|
+
for (const bytes of values.values())
|
|
65
|
+
bytes.fill(0);
|
|
66
|
+
}
|
|
67
|
+
throw new CredentialWait("config-churn", true);
|
|
68
|
+
}
|
|
69
|
+
/** Disposes every live lease, and voids any lease still being resolved when the bump lands. */
|
|
70
|
+
bumpGeneration() {
|
|
71
|
+
this.#generation++;
|
|
72
|
+
for (const lease of [...this.#live])
|
|
73
|
+
lease[Symbol.dispose]();
|
|
74
|
+
}
|
|
75
|
+
/** One release attempt; `step` carries the reload count, notice latch and digest across bump retries (C.3). */
|
|
76
|
+
async #resolveValues(providerId, step, fields, scope) {
|
|
77
|
+
const signal = this.#deps.stepSignal(scope);
|
|
78
|
+
for (;;) {
|
|
79
|
+
signal.throwIfAborted();
|
|
80
|
+
const lease = await this.#deps.lease();
|
|
81
|
+
const result = await this.#release(providerId, step.digest, fields, scope, lease);
|
|
82
|
+
if (result.ok)
|
|
83
|
+
return this.#open(providerId, step.digest, fields, result.items, lease);
|
|
84
|
+
if (result.code !== "stale_snapshot")
|
|
85
|
+
throw new CredentialWait(result.code, RETRYABLE.some((code) => code === result.code));
|
|
86
|
+
if (step.reloads < RELOADS_PER_STEP) {
|
|
87
|
+
step.reloads++;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// config-churn (C.3): every further stale retry re-enters the stable-head wait, never the immediate reloads.
|
|
91
|
+
if (!step.noticed) {
|
|
92
|
+
step.noticed = true;
|
|
93
|
+
this.#deps.onChurnNotice(scope);
|
|
94
|
+
}
|
|
95
|
+
await this.#waitForStableHead(signal);
|
|
96
|
+
}
|
|
97
|
+
step.digest = await this.#deps.reloadEntryDigest(providerId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async #release(providerId, entryDigest, fields, scope, lease) {
|
|
101
|
+
return this.#deps.unsealer.release({
|
|
102
|
+
accountId: this.#deps.accountId,
|
|
103
|
+
providerId,
|
|
104
|
+
entryDigest,
|
|
105
|
+
fields,
|
|
106
|
+
lease,
|
|
107
|
+
recipientPk: this.#deps.recipientPk,
|
|
108
|
+
scope,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/** Returns once the head held still for STABLE_HEAD_MS; rejects when the step aborts first. */
|
|
112
|
+
async #waitForStableHead(signal) {
|
|
113
|
+
let previous = await this.#deps.readHead();
|
|
114
|
+
for (;;) {
|
|
115
|
+
await this.#deps.sleep(STABLE_HEAD_MS, signal);
|
|
116
|
+
const current = await this.#deps.readHead();
|
|
117
|
+
if (current === previous)
|
|
118
|
+
return;
|
|
119
|
+
previous = current;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async #open(providerId, entryDigest, fields, items, epochLease) {
|
|
123
|
+
if (items.length !== fields.length)
|
|
124
|
+
throw new CredentialWait("denied", false);
|
|
125
|
+
const values = new Map();
|
|
126
|
+
try {
|
|
127
|
+
for (const [index, field] of fields.entries()) {
|
|
128
|
+
const position = { accountId: this.#deps.accountId, providerId, entryDigest, field, lease: epochLease };
|
|
129
|
+
values.set(field, await this.#deps.openItem(items[index], position));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
for (const bytes of values.values())
|
|
134
|
+
bytes.fill(0);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
return values;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=sealed-source.js.map
|
|
@@ -39,7 +39,7 @@ function parseRecord(value) {
|
|
|
39
39
|
if (!isRecord(value))
|
|
40
40
|
return undefined;
|
|
41
41
|
const raw = value;
|
|
42
|
-
if (!Object.keys(raw).every((key) => RECORD_KEYS.
|
|
42
|
+
if (!Object.keys(raw).every((key) => RECORD_KEYS.some((known) => known === key)))
|
|
43
43
|
return undefined;
|
|
44
44
|
const { kind, accountId, generation, sigKeyId, sigPkSha256, kemKeyId, kemPkSha256, kmsKeyArns, templateVersion, verifierKeyId, vouchedAt, supersedes, checkedAt, } = raw;
|
|
45
45
|
if (kind !== "custody" && kind !== "placement-registry")
|
|
@@ -14,9 +14,11 @@ import type { ServiceKeyEntry } from "./roster-entries.ts";
|
|
|
14
14
|
/**
|
|
15
15
|
* The stream options a brain may send a hand: serializable request shaping only. Never secrets or headers, callbacks,
|
|
16
16
|
* signals or live objects, or the loop-only `commitStep` (4.3, 2.3 options hygiene). `env` stays hand-local because
|
|
17
|
-
*
|
|
18
|
-
* the
|
|
19
|
-
* `
|
|
17
|
+
* some adapters derive the dial target from it: the Cloudflare providers of `anthropic-messages` and `openai-responses`
|
|
18
|
+
* fill the account and gateway of the model `baseUrl` from it (`resolveCloudflareBaseUrl`), `azure-openai-responses`
|
|
19
|
+
* reads its base URL or resource name from it, and `bedrock-converse-stream` its HTTP proxy. A brain-supplied `env`
|
|
20
|
+
* could therefore redirect the hand's dial, with the hand's own credential, to a host of the brain's choosing. `cwd`
|
|
21
|
+
* stays hand-local because the start message's `cwd` is the one source the hand interprets.
|
|
20
22
|
*/
|
|
21
23
|
export declare const WIRE_STREAM_OPTION_KEYS: readonly ["abortServerSideFallback", "affinitySessionId", "cacheRetention", "deferred", "extraBody", "maxRetries", "maxRetryDelayMs", "maxTokens", "metadata", "reasoning", "refusalFallbacks", "samplingParams", "serviceTier", "sessionAffinityKey", "sessionId", "streamKind", "temperature", "thinkingBudgets", "thinkingSelection", "timeoutMs", "toolChoice", "transport", "websocketConnectTimeoutMs"];
|
|
22
24
|
export type WireStreamOptions = Pick<SimpleStreamOptions, (typeof WIRE_STREAM_OPTION_KEYS)[number]>;
|
package/dist/core/egress/wire.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The stream options a brain may send a hand: serializable request shaping only. Never secrets or headers, callbacks,
|
|
3
3
|
* signals or live objects, or the loop-only `commitStep` (4.3, 2.3 options hygiene). `env` stays hand-local because
|
|
4
|
-
*
|
|
5
|
-
* the
|
|
6
|
-
* `
|
|
4
|
+
* some adapters derive the dial target from it: the Cloudflare providers of `anthropic-messages` and `openai-responses`
|
|
5
|
+
* fill the account and gateway of the model `baseUrl` from it (`resolveCloudflareBaseUrl`), `azure-openai-responses`
|
|
6
|
+
* reads its base URL or resource name from it, and `bedrock-converse-stream` its HTTP proxy. A brain-supplied `env`
|
|
7
|
+
* could therefore redirect the hand's dial, with the hand's own credential, to a host of the brain's choosing. `cwd`
|
|
8
|
+
* stays hand-local because the start message's `cwd` is the one source the hand interprets.
|
|
7
9
|
*/
|
|
8
10
|
export const WIRE_STREAM_OPTION_KEYS = [
|
|
9
11
|
"abortServerSideFallback",
|
|
@@ -5,6 +5,32 @@ export declare const CONTINUATION_HOLD_STATE_EVENT = "continuation_hold_state";
|
|
|
5
5
|
/** Live widgets of a session's long background jobs; payload `JobWidgetState` (job-widgets/view-types.ts). */
|
|
6
6
|
export declare const JOB_WIDGET_STATE_EVENT = "job_widget_state";
|
|
7
7
|
export declare const MONITOR_NOTIFICATION_CUSTOM_TYPE = "senpi-monitor:notification";
|
|
8
|
+
/** A background terminal session's age notice or auto-reap; payload `TerminalBackgroundAgeEvent` (senpi#6201). */
|
|
9
|
+
export declare const TERMINAL_BACKGROUND_AGE_EVENT = "terminal_background_age";
|
|
10
|
+
/**
|
|
11
|
+
* `running`: the session is still running at `ageMinutes` (first at 10 minutes, then every 30). `reaped`: the opt-in
|
|
12
|
+
* auto-reap stopped it at `autoReapMinutes`. `reap_failed`: the auto-reap's one stop at `autoReapMinutes` did not end
|
|
13
|
+
* it, for `reason`; it keeps running and keeps reporting. `stop` is the call that stops it; `autoReapMinutes` is null
|
|
14
|
+
* when nothing will stop it automatically.
|
|
15
|
+
*/
|
|
16
|
+
export interface TerminalBackgroundAgeEvent {
|
|
17
|
+
readonly kind: "running" | "reaped" | "reap_failed";
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly command?: string;
|
|
21
|
+
readonly startedAtMs: number;
|
|
22
|
+
readonly ageMs: number;
|
|
23
|
+
readonly ageMinutes: number;
|
|
24
|
+
readonly autoReapMinutes: number | null;
|
|
25
|
+
/** Why the auto-reap failed; set on `reap_failed` only. */
|
|
26
|
+
readonly reason?: string;
|
|
27
|
+
readonly stop: {
|
|
28
|
+
readonly tool: "kill_bash";
|
|
29
|
+
readonly args: {
|
|
30
|
+
readonly bash_id: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
}
|
|
8
34
|
/** Per-monitor entry in a monitor notification's `details.monitors`. */
|
|
9
35
|
export interface MonitorNotificationDetail {
|
|
10
36
|
readonly id: string;
|
|
@@ -5,6 +5,8 @@ export const CONTINUATION_HOLD_STATE_EVENT = "continuation_hold_state";
|
|
|
5
5
|
/** Live widgets of a session's long background jobs; payload `JobWidgetState` (job-widgets/view-types.ts). */
|
|
6
6
|
export const JOB_WIDGET_STATE_EVENT = "job_widget_state";
|
|
7
7
|
export const MONITOR_NOTIFICATION_CUSTOM_TYPE = "senpi-monitor:notification";
|
|
8
|
+
/** A background terminal session's age notice or auto-reap; payload `TerminalBackgroundAgeEvent` (senpi#6201). */
|
|
9
|
+
export const TERMINAL_BACKGROUND_AGE_EVENT = "terminal_background_age";
|
|
8
10
|
/** True for a delivered monitor notification that carries a person's words (omo#10280). */
|
|
9
11
|
export function isHumanInputMonitorNotification(message) {
|
|
10
12
|
if (typeof message !== "object" || message === null)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Age notices of still-running background sessions (senpi#6201): one at 10 minutes, then one every 30 minutes (10, 40,
|
|
3
|
+
* 70, ...). One schedule per session bundle serves every session with ONE unref'd timer armed for the earliest due
|
|
4
|
+
* notice, so it never keeps the process alive and costs nothing per session; it is cleared when the last session
|
|
5
|
+
* leaves and on dispose. A late timer (the machine slept) or a session registered after it started reports at most
|
|
6
|
+
* once for the milestones it missed, never a burst.
|
|
7
|
+
*/
|
|
8
|
+
import type { TerminalBackgroundAgeEvent } from "../monitor-state-event.ts";
|
|
9
|
+
export interface BackgroundAgeSubject {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly description: string;
|
|
12
|
+
readonly command?: string;
|
|
13
|
+
readonly startedAtMs: number;
|
|
14
|
+
readonly autoReapMinutes?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function backgroundAgeEvent(kind: TerminalBackgroundAgeEvent["kind"], subject: BackgroundAgeSubject, nowMs: number): TerminalBackgroundAgeEvent;
|
|
17
|
+
export declare class BackgroundAgeSchedule {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(onDue: (event: TerminalBackgroundAgeEvent) => void);
|
|
20
|
+
add(subject: BackgroundAgeSubject): void;
|
|
21
|
+
get(id: string): BackgroundAgeSubject | undefined;
|
|
22
|
+
remove(id: string): BackgroundAgeSubject | undefined;
|
|
23
|
+
dispose(): void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=background-age.d.ts.map
|