@sema-agent/core 7.56.0 → 7.57.0
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 +541 -0
- package/LICENSE +1 -1
- package/dist/agents/org-delivery-gate.js +3 -4
- package/dist/agents/peer-held-queue.d.ts +69 -49
- package/dist/agents/peer-held-queue.js +255 -106
- package/dist/agents/peer-session-drain.js +63 -43
- package/dist/agents/send-message-tool.js +3 -2
- package/dist/agents/subagent.js +8 -8
- package/dist/brain/anthropic.js +16 -8
- package/dist/brain/open-responses.js +10 -8
- package/dist/brain/openai.js +11 -9
- package/dist/brain/provider-dialect.d.ts +3 -2
- package/dist/brain/provider-dialect.js +2 -2
- package/dist/brain/request-params.d.ts +6 -0
- package/dist/brain/request-params.js +14 -1
- package/dist/brain/stream-engine.d.ts +9 -0
- package/dist/brain/stream-engine.js +24 -10
- package/dist/brain/stream-shared.d.ts +5 -4
- package/dist/brain/stream-shared.js +4 -4
- package/dist/core/allow-entry.d.ts +3 -10
- package/dist/core/allow-entry.js +17 -25
- package/dist/core/approval-object.d.ts +21 -5
- package/dist/core/approval-object.js +44 -17
- package/dist/core/ask-origin.d.ts +2 -2
- package/dist/core/auto-compaction.d.ts +71 -6
- package/dist/core/auto-compaction.js +42 -26
- package/dist/core/background-agent-store.d.ts +6 -2
- package/dist/core/background-agent-store.js +1 -1
- package/dist/core/checkpoint-requirements.d.ts +7 -1
- package/dist/core/checkpoint-requirements.js +2 -0
- package/dist/core/checkpoint-store.d.ts +17 -29
- package/dist/core/checkpoint-store.js +19 -11
- package/dist/core/cross-boundary-taint.js +4 -2
- package/dist/core/engine-notice.d.ts +3 -2
- package/dist/core/engine-notice.js +6 -0
- package/dist/core/file-history-store.d.ts +14 -8
- package/dist/core/file-history-store.js +14 -4
- package/dist/core/gate-lanes.js +2 -3
- package/dist/core/hooks.d.ts +2 -2
- package/dist/core/mailbox-store.d.ts +62 -1
- package/dist/core/mailbox-store.js +59 -0
- package/dist/core/mcp-name-judge.d.ts +46 -0
- package/dist/core/mcp-name-judge.js +61 -0
- package/dist/core/mcp-probe.d.ts +1 -1
- package/dist/core/mcp-probe.js +2 -5
- package/dist/core/mcp.js +10 -30
- package/dist/core/memory-engine/consolidation-driver.js +4 -3
- package/dist/core/memory-engine/distiller.js +26 -17
- package/dist/core/memory-engine/layout.js +5 -2
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/permission-rule-consent.d.ts +5 -3
- package/dist/core/permission-rule-consent.js +20 -15
- package/dist/core/permission-rule-model.d.ts +43 -2
- package/dist/core/permission-rule-model.js +52 -8
- package/dist/core/permission-rule-org.js +1 -1
- package/dist/core/protocol-http-auth.d.ts +16 -2
- package/dist/core/protocol-http-auth.js +42 -11
- package/dist/core/protocol-naming.d.ts +20 -0
- package/dist/core/protocol-naming.js +20 -3
- package/dist/core/read-only-shell.d.ts +20 -5
- package/dist/core/read-only-shell.js +29 -14
- package/dist/core/remote-env.d.ts +37 -1
- package/dist/core/remote-env.js +11 -0
- package/dist/core/resume-startup-cause.d.ts +24 -2
- package/dist/core/resume-startup-cause.js +20 -1
- package/dist/core/roles.js +50 -7
- package/dist/core/runner/compact-session.d.ts +10 -0
- package/dist/core/runner/compact-session.js +202 -0
- package/dist/core/runner/compaction-call-options.d.ts +2 -2
- package/dist/core/runner/compaction-seams.d.ts +21 -1
- package/dist/core/runner/compaction-seams.js +11 -8
- package/dist/core/runner/contracts.d.ts +23 -16
- package/dist/core/runner/derived-route-fallback.d.ts +13 -0
- package/dist/core/runner/derived-route-fallback.js +6 -0
- package/dist/core/runner/env-capability-census.d.ts +3 -2
- package/dist/core/runner/env-capability-census.js +9 -3
- package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
- package/dist/core/runner/permission-rule-lanes.js +17 -11
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
- package/dist/core/runner/prepare-hands-readface.js +2 -0
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +2 -2
- package/dist/core/runner/prepare-policy-chain.js +2 -4
- package/dist/core/runner/prepare-task.d.ts +4 -1
- package/dist/core/runner/prepare-task.js +23 -27
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
- package/dist/core/runner/rewind-files.d.ts +2 -5
- package/dist/core/runner/rewind-files.js +9 -51
- package/dist/core/runner/run-leg.js +10 -21
- package/dist/core/runner/run-notification-lane.d.ts +2 -0
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/run-recovery-lanes.js +3 -4
- package/dist/core/runner/run-settle-and-teardown.js +5 -11
- package/dist/core/runner/run-terminal-adoption.js +3 -4
- package/dist/core/runner/run-turn-boundary.js +3 -5
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +28 -34
- package/dist/core/runner/send-message-continuation.js +1 -1
- package/dist/core/runner/session-verb-doors.d.ts +28 -0
- package/dist/core/runner/session-verb-doors.js +48 -0
- package/dist/core/runner/session-write-ledger.d.ts +30 -0
- package/dist/core/runner/session-write-ledger.js +101 -0
- package/dist/core/runner/stream-settle-backstop.js +11 -14
- package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
- package/dist/core/runner/sub-agent-deny-lists.js +21 -10
- package/dist/core/runner/teardown-bounded.d.ts +1 -0
- package/dist/core/runner/teardown-bounded.js +1 -0
- package/dist/core/runner-deps.d.ts +2 -2
- package/dist/core/secret-env.d.ts +4 -0
- package/dist/core/secret-env.js +3 -0
- package/dist/core/session-policy-store.d.ts +7 -2
- package/dist/core/session-policy-store.js +1 -1
- package/dist/core/shell-wrapper-projection.d.ts +1 -1
- package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/store-contracts/file-history-store-contract.js +9 -0
- package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
- package/dist/core/task-registry-agent.d.ts +8 -4
- package/dist/core/task-registry-agent.js +14 -8
- package/dist/core/task-registry-shared.d.ts +35 -6
- package/dist/core/task-registry-shared.js +11 -0
- package/dist/core/task-registry.d.ts +1 -1
- package/dist/core/task-registry.js +1 -1
- package/dist/core/tool-catalog-entries.d.ts +6 -0
- package/dist/core/tool-catalog-entries.js +1 -0
- package/dist/core/tool-name-aliases.d.ts +32 -2
- package/dist/core/tool-name-aliases.js +23 -0
- package/dist/core/tool-name-readings.d.ts +4 -0
- package/dist/core/tool-name-readings.js +1 -0
- package/dist/core/tool-policy.d.ts +10 -6
- package/dist/core/tool-policy.js +4 -7
- package/dist/core/trace.d.ts +4 -1
- package/dist/core/trace.js +2 -1
- package/dist/core/untrusted-egress.d.ts +20 -0
- package/dist/core/untrusted-egress.js +77 -3
- package/dist/engine/harness/agent-harness.d.ts +7 -5
- package/dist/engine/harness/agent-harness.js +11 -2
- package/dist/engine/loop/agent-loop.d.ts +1 -4
- package/dist/engine/loop/agent-loop.js +26 -4
- package/dist/index.d.ts +12 -7
- package/dist/index.js +7 -4
- package/dist/internal/harness.d.ts +2 -1
- package/dist/internal/harness.js +1 -0
- package/dist/primitives/credential-ref.d.ts +89 -0
- package/dist/primitives/credential-ref.js +125 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/stores/file/checkpoint-store.d.ts +0 -4
- package/dist/stores/file/checkpoint-store.js +0 -1
- package/dist/stores/file/file-history-store.js +12 -4
- package/dist/stores/file/mailbox-store.d.ts +2 -1
- package/dist/stores/file/mailbox-store.js +26 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
- package/dist/tools/fs/bash-readonly-classifier.js +5 -10
- package/dist/tools/fs/fs-bash.js +10 -3
- package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
- package/dist/tools/fs/pattern-kill-guard.js +68 -36
- package/dist/tools/fs/shell-read-check.d.ts +10 -0
- package/dist/tools/fs/shell-read-check.js +7 -1
- package/dist/tools/fs/shell-read-identity.d.ts +12 -9
- package/dist/tools/fs/shell-read-identity.js +9 -4
- package/dist/tools/web.js +9 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +79 -3
- package/wire-changes/core-stores-sql@7.57.0.json +29 -0
- package/wire-changes/core@7.57.0.json +1487 -0
- package/wire-changes/mods-kernel@7.57.0.json +29 -0
- package/wire-changes/primitives@7.57.0.json +245 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const CREDENTIAL_REF_KINDS = Object.freeze(["env", "vault", "handle", "assertion"]);
|
|
2
|
+
export const CREDENTIAL_REF_TRUST = trustTable();
|
|
3
|
+
function trustTable() {
|
|
4
|
+
const rows = Object.create(null);
|
|
5
|
+
rows.env = Object.freeze({ request: "refused" });
|
|
6
|
+
rows.vault = Object.freeze({ request: "own_entry" });
|
|
7
|
+
rows.handle = Object.freeze({ request: "refused" });
|
|
8
|
+
rows.assertion = Object.freeze({ request: "refused" });
|
|
9
|
+
return Object.freeze(rows);
|
|
10
|
+
}
|
|
11
|
+
export const CREDENTIAL_REF_TEXT_MAX = 2048;
|
|
12
|
+
export const CREDENTIAL_REF_MAP_MAX = 64;
|
|
13
|
+
export const CREDENTIAL_REF_PROBLEM_REASONS = Object.freeze(["not_a_record", "unknown_kind", "missing_field", "extra_field", "bad_value"]);
|
|
14
|
+
const MEMBERS = {
|
|
15
|
+
env: ["headers", "env"],
|
|
16
|
+
vault: ["entry"],
|
|
17
|
+
handle: ["id"],
|
|
18
|
+
assertion: ["audience"],
|
|
19
|
+
};
|
|
20
|
+
const TEXT_MEMBER = { vault: "entry", handle: "id", assertion: "audience" };
|
|
21
|
+
const HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
22
|
+
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
23
|
+
const ECHO_MAX = 64;
|
|
24
|
+
const UNSAFE = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{DI}\p{NChar}\p{Co}\ud800-\udfff]/u;
|
|
25
|
+
const UNSAFE_ALL = new RegExp(UNSAFE.source, "gu");
|
|
26
|
+
const ABSENT = Object.freeze({ found: "absent" });
|
|
27
|
+
const ACCESSOR = Object.freeze({ found: "accessor" });
|
|
28
|
+
const NOT_A_RECORD = Object.freeze({ ok: false, problem: Object.freeze({ reason: "not_a_record" }) });
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
31
|
+
return false;
|
|
32
|
+
const proto = Object.getPrototypeOf(value);
|
|
33
|
+
return proto === null || Object.getPrototypeOf(proto) === null;
|
|
34
|
+
}
|
|
35
|
+
function member(record, key) {
|
|
36
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
37
|
+
if (descriptor === undefined || descriptor.enumerable !== true)
|
|
38
|
+
return ABSENT;
|
|
39
|
+
return Object.hasOwn(descriptor, "value") ? { found: "value", value: descriptor.value } : ACCESSOR;
|
|
40
|
+
}
|
|
41
|
+
const echo = (text) => text.slice(0, ECHO_MAX).replace(UNSAFE_ALL, "?");
|
|
42
|
+
const refused = (problem) => Object.freeze({ ok: false, problem: Object.freeze(problem) });
|
|
43
|
+
const accepted = (ref) => Object.freeze({ ok: true, ref });
|
|
44
|
+
const isText = (value) => typeof value === "string" && value.length > 0 && value.length <= CREDENTIAL_REF_TEXT_MAX && !UNSAFE.test(value) && value === value.trim();
|
|
45
|
+
const isName = (value, form) => value.length <= CREDENTIAL_REF_TEXT_MAX && form.test(value);
|
|
46
|
+
function nameMap(value, keyForm, foldCase) {
|
|
47
|
+
try {
|
|
48
|
+
if (!isRecord(value))
|
|
49
|
+
return undefined;
|
|
50
|
+
const names = Object.keys(value);
|
|
51
|
+
if (names.length === 0 || names.length > CREDENTIAL_REF_MAP_MAX)
|
|
52
|
+
return undefined;
|
|
53
|
+
const out = Object.create(null);
|
|
54
|
+
const folded = new Set();
|
|
55
|
+
for (const name of names) {
|
|
56
|
+
const found = member(value, name);
|
|
57
|
+
if (found.found !== "value" || typeof found.value !== "string")
|
|
58
|
+
return undefined;
|
|
59
|
+
if (!isName(name, keyForm) || !isName(found.value, ENV_NAME))
|
|
60
|
+
return undefined;
|
|
61
|
+
if (foldCase) {
|
|
62
|
+
const key = name.toLowerCase();
|
|
63
|
+
if (folded.has(key))
|
|
64
|
+
return undefined;
|
|
65
|
+
folded.add(key);
|
|
66
|
+
}
|
|
67
|
+
out[name] = found.value;
|
|
68
|
+
}
|
|
69
|
+
return Object.freeze(out);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const mapOf = (found, keyForm, foldCase) => (found.found === "value" ? nameMap(found.value, keyForm, foldCase) : undefined);
|
|
76
|
+
function envRef(record) {
|
|
77
|
+
const given = { headers: member(record, "headers"), env: member(record, "env") };
|
|
78
|
+
if (given.headers.found === "absent" && given.env.found === "absent")
|
|
79
|
+
return refused({ reason: "missing_field", kind: "env", field: "headers|env" });
|
|
80
|
+
const headers = mapOf(given.headers, HEADER_NAME, true);
|
|
81
|
+
if (given.headers.found !== "absent" && headers === undefined)
|
|
82
|
+
return refused({ reason: "bad_value", kind: "env", field: "headers" });
|
|
83
|
+
const env = mapOf(given.env, ENV_NAME, false);
|
|
84
|
+
if (given.env.found !== "absent" && env === undefined)
|
|
85
|
+
return refused({ reason: "bad_value", kind: "env", field: "env" });
|
|
86
|
+
return accepted(Object.freeze({ kind: "env", ...(headers === undefined ? {} : { headers }), ...(env === undefined ? {} : { env }) }));
|
|
87
|
+
}
|
|
88
|
+
function read(value) {
|
|
89
|
+
if (!isRecord(value))
|
|
90
|
+
return NOT_A_RECORD;
|
|
91
|
+
const named = member(value, "kind");
|
|
92
|
+
if (named.found === "absent")
|
|
93
|
+
return refused({ reason: "missing_field", field: "kind" });
|
|
94
|
+
if (named.found !== "value" || typeof named.value !== "string")
|
|
95
|
+
return refused({ reason: "bad_value", field: "kind" });
|
|
96
|
+
const text = named.value;
|
|
97
|
+
const kind = CREDENTIAL_REF_KINDS.find((k) => k === text);
|
|
98
|
+
if (kind === undefined)
|
|
99
|
+
return refused({ reason: "unknown_kind", kind: echo(text) });
|
|
100
|
+
for (const key of Object.keys(value)) {
|
|
101
|
+
if (key !== "kind" && !MEMBERS[kind].includes(key))
|
|
102
|
+
return refused({ reason: "extra_field", kind, field: echo(key) });
|
|
103
|
+
}
|
|
104
|
+
if (kind === "env")
|
|
105
|
+
return envRef(value);
|
|
106
|
+
const field = TEXT_MEMBER[kind];
|
|
107
|
+
const found = member(value, field);
|
|
108
|
+
if (found.found === "absent")
|
|
109
|
+
return refused({ reason: "missing_field", kind, field });
|
|
110
|
+
if (found.found !== "value" || !isText(found.value))
|
|
111
|
+
return refused({ reason: "bad_value", kind, field });
|
|
112
|
+
if (kind === "vault")
|
|
113
|
+
return accepted(Object.freeze({ kind, entry: found.value }));
|
|
114
|
+
if (kind === "handle")
|
|
115
|
+
return accepted(Object.freeze({ kind, id: found.value }));
|
|
116
|
+
return accepted(Object.freeze({ kind, audience: found.value }));
|
|
117
|
+
}
|
|
118
|
+
export function readCredentialRef(value) {
|
|
119
|
+
try {
|
|
120
|
+
return read(value);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return NOT_A_RECORD;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** The package entry of `@sema-agent/primitives`; core reads the same modules directly. */
|
|
2
|
+
export { CREDENTIAL_REF_KINDS, CREDENTIAL_REF_TRUST, CREDENTIAL_REF_TEXT_MAX, CREDENTIAL_REF_MAP_MAX, CREDENTIAL_REF_PROBLEM_REASONS, readCredentialRef, type CredentialRef, type CredentialRefKind, type CredentialRefRequestAdmission, type CredentialRefProblem, type CredentialRefProblemReason, type CredentialRefReading, } from "./credential-ref.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CREDENTIAL_REF_KINDS, CREDENTIAL_REF_TRUST, CREDENTIAL_REF_TEXT_MAX, CREDENTIAL_REF_MAP_MAX, CREDENTIAL_REF_PROBLEM_REASONS, readCredentialRef, } from "./credential-ref.js";
|
|
@@ -35,10 +35,6 @@ export declare class FileCheckpointStore implements CheckpointStore {
|
|
|
35
35
|
* same JSON reading as the ledger line); the declaration is what makes the loss visible to the mint BEFORE the
|
|
36
36
|
* row is filed. */
|
|
37
37
|
readonly fidelity: "json";
|
|
38
|
-
/** F-012 L2: `reopen` here is a real resolved→pending CAS journaled to the ledger — declared, not sniffed. */
|
|
39
|
-
readonly redecision: {
|
|
40
|
-
readonly reopen: true;
|
|
41
|
-
};
|
|
42
38
|
/** The execution-outcome record verb below is the contract's CAS (journaled), and `reopen` honours the record. */
|
|
43
39
|
readonly execution: {
|
|
44
40
|
readonly outcome: true;
|
|
@@ -1147,6 +1147,14 @@ export class FileFileHistoryStore {
|
|
|
1147
1147
|
const shaped = validateFileHistoryExport(data);
|
|
1148
1148
|
if (!shaped.ok)
|
|
1149
1149
|
return shaped;
|
|
1150
|
+
for (const b of data.boundaries) {
|
|
1151
|
+
try {
|
|
1152
|
+
historyPathComponent(b.entryId);
|
|
1153
|
+
}
|
|
1154
|
+
catch (err) {
|
|
1155
|
+
return { ok: false, error: { code: "unsupported_format", message: `importHistory refused a boundary entry id this backend cannot hold as a record name: ${thrownText(err)} — nothing was fetched or published` } };
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1150
1158
|
const target = await this.importTargetOf(scope, data, "before");
|
|
1151
1159
|
if (typeof target === "object")
|
|
1152
1160
|
return target;
|
|
@@ -1164,7 +1172,7 @@ export class FileFileHistoryStore {
|
|
|
1164
1172
|
return await this.runImport(scope, data, srcGetBlob, importJournal, identicalRepublish);
|
|
1165
1173
|
}
|
|
1166
1174
|
catch (err) {
|
|
1167
|
-
return { ok: false, error: { code: "
|
|
1175
|
+
return { ok: false, error: { code: "adopt_failed", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
1168
1176
|
}
|
|
1169
1177
|
}
|
|
1170
1178
|
async runImport(scope, data, srcGetBlob, importJournal, identicalRepublish) {
|
|
@@ -1191,12 +1199,12 @@ export class FileFileHistoryStore {
|
|
|
1191
1199
|
bytes = await srcGetBlob(v.blobHash);
|
|
1192
1200
|
}
|
|
1193
1201
|
catch (err) {
|
|
1194
|
-
return { ok: false, error: { code: "
|
|
1202
|
+
return { ok: false, error: { code: "adopt_failed", message: `source blob ${v.blobHash} fetch failed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
1195
1203
|
}
|
|
1196
1204
|
if (bytes === undefined)
|
|
1197
1205
|
return { ok: false, error: { code: "adopt_failed", message: `source blob ${v.blobHash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
|
|
1198
1206
|
if (createHash("sha256").update(bytes).digest("hex") !== v.blobHash) {
|
|
1199
|
-
return { ok: false, error: { code: "
|
|
1207
|
+
return { ok: false, error: { code: "adopt_failed", message: `source blob ${v.blobHash} fails content-address integrity — refused, nothing published` } };
|
|
1200
1208
|
}
|
|
1201
1209
|
this.putBlob(v.blobHash, bytes);
|
|
1202
1210
|
}
|
|
@@ -1250,7 +1258,7 @@ export class FileFileHistoryStore {
|
|
|
1250
1258
|
return { ok: true };
|
|
1251
1259
|
}
|
|
1252
1260
|
catch (err) {
|
|
1253
|
-
return { ok: false, error: { code: "
|
|
1261
|
+
return { ok: false, error: { code: "adopt_failed", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
|
|
1254
1262
|
}
|
|
1255
1263
|
finally {
|
|
1256
1264
|
safeRm(importJournal);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MailboxAckResult, type MailboxAppendMessage, type MailboxLease, type MailboxStore } from "../../core/mailbox-store.js";
|
|
1
|
+
import { type MailboxAckResult, type MailboxAppendMessage, type MailboxLease, type MailboxDecisionOutcome, type MailboxDecision, type MailboxStore } from "../../core/mailbox-store.js";
|
|
2
2
|
import { type CorruptReadInfo } from "./corrupt-read-sink.js";
|
|
3
3
|
/** The waiter's refusal when a box lock stays held by a LIVE process past `lockWaitMs`. Carried as
|
|
4
4
|
* `.code` on a plain Error (consumers branch on the string); the message names the lock and the holder. */
|
|
@@ -113,6 +113,7 @@ export declare class FileMailboxStore implements MailboxStore {
|
|
|
113
113
|
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
114
114
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
115
115
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
|
|
116
|
+
moveDecision(scope: string, handle: string, seq: number, expect: MailboxDecision | undefined, next: MailboxDecision): Promise<MailboxDecisionOutcome>;
|
|
116
117
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
117
118
|
peekCount(scope: string, handle: string): Promise<number>;
|
|
118
119
|
drop(scope: string, handle: string): Promise<void>;
|
|
@@ -2,7 +2,7 @@ import { join, resolve, sep } from "node:path";
|
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { assertRetentionPolicy } from "../../core/retention-policy.js";
|
|
4
4
|
import { existsSync, linkSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { detachMailboxMessage, newestSentAt, readMailboxPeerMeta, } from "../../core/mailbox-store.js";
|
|
5
|
+
import { assertMailboxDecisionMove, detachMailboxMessage, newestSentAt, readStoredMailboxDecision, readMailboxPeerMeta, } from "../../core/mailbox-store.js";
|
|
6
6
|
import { AppendLog, atomicWriteFile, canonicalStoreKey, ensureDir, readJsonlRecords, sanitizeScope, sanitizePathComponent } from "./fs-atomic.js";
|
|
7
7
|
import { guardCorruptSink } from "./corrupt-read-sink.js";
|
|
8
8
|
import { physicalPathOf } from "../../core/physical-path.js";
|
|
@@ -478,6 +478,19 @@ export class FileMailboxStore {
|
|
|
478
478
|
return { acked: true };
|
|
479
479
|
});
|
|
480
480
|
}
|
|
481
|
+
async moveDecision(scope, handle, seq, expect, next) {
|
|
482
|
+
assertMailboxDecisionMove(seq, expect, next);
|
|
483
|
+
return this.withBox(this.lockKey(scope, handle), this.boxPath(scope, handle), () => {
|
|
484
|
+
const b = this.load(scope, handle);
|
|
485
|
+
const m = b.messages.find((x) => x.seq === seq);
|
|
486
|
+
if (m === undefined)
|
|
487
|
+
return { ok: false, current: "gone" };
|
|
488
|
+
if (m.decision !== expect)
|
|
489
|
+
return { ok: false, current: m.decision ?? "unset" };
|
|
490
|
+
this.commit(b, this.boxPath(scope, handle), { t: "decision", seq, state: next });
|
|
491
|
+
return { ok: true };
|
|
492
|
+
});
|
|
493
|
+
}
|
|
481
494
|
async releaseLease(scope, handle, owner) {
|
|
482
495
|
return this.withBox(this.lockKey(scope, handle), this.boxPath(scope, handle), () => {
|
|
483
496
|
const b = this.load(scope, handle);
|
|
@@ -592,12 +605,19 @@ const MAILBOX_EVENT_JUDGE = {
|
|
|
592
605
|
return typeof ev.owner === "string" && num(ev.maxSeq) && num(ev.expiresAt);
|
|
593
606
|
if (ev.t === "ack")
|
|
594
607
|
return num(ev.upToSeq);
|
|
608
|
+
if (ev.t === "decision")
|
|
609
|
+
return num(r.seq) && typeof r.state === "string";
|
|
595
610
|
return ev.t === "release" && typeof ev.owner === "string";
|
|
596
611
|
},
|
|
597
612
|
what: "a mailbox record is not one of this box's events — an unknown tag, or a known tag whose payload is missing",
|
|
598
613
|
};
|
|
599
614
|
function applyEvent(b, ev) {
|
|
600
615
|
if (ev.t === "append") {
|
|
616
|
+
const decision = readStoredMailboxDecision(ev.m.decision);
|
|
617
|
+
if (decision === undefined)
|
|
618
|
+
delete ev.m.decision;
|
|
619
|
+
else
|
|
620
|
+
ev.m.decision = decision;
|
|
601
621
|
b.messages.push(ev.m);
|
|
602
622
|
if (ev.m.seq >= b.nextSeq)
|
|
603
623
|
b.nextSeq = ev.m.seq + 1;
|
|
@@ -613,6 +633,11 @@ function applyEvent(b, ev) {
|
|
|
613
633
|
if (b.lease !== undefined && b.lease.maxSeq <= ev.upToSeq)
|
|
614
634
|
b.lease = undefined;
|
|
615
635
|
}
|
|
636
|
+
else if (ev.t === "decision") {
|
|
637
|
+
const m = b.messages.find((x) => x.seq === ev.seq);
|
|
638
|
+
if (m !== undefined)
|
|
639
|
+
m.decision = readStoredMailboxDecision(ev.state) ?? "held";
|
|
640
|
+
}
|
|
616
641
|
else if (b.lease?.owner === ev.owner) {
|
|
617
642
|
b.lease = undefined;
|
|
618
643
|
}
|
|
@@ -2,15 +2,15 @@ import { type LexedCommand, type LexedSegment, type LexedWord, type ShellLex, ty
|
|
|
2
2
|
import { type ProgramRow } from "./program-read-model.js";
|
|
3
3
|
/**
|
|
4
4
|
* Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
5
|
+
* {@link import("./fs-bash.js").bashReversibilityProbe} classifier. On the read-only face and in the transcript gate the
|
|
6
|
+
* list only NARROWS the read-only reader ({@link import("../../core/read-only-shell.js").readOnlyShellVerdict}'s
|
|
7
|
+
* `readers`): a listed program runs there only in a call the reader reads as read-only, so a deployment that widens the
|
|
8
|
+
* list with `sort` still has `sort -o out f` refused. The reversibility classifier reads the list as its allowlist: a
|
|
9
|
+
* listed program with a program-model row has its writing options refused by the row; one without a row is admitted
|
|
10
|
+
* by its name. The default is curated to commands with NO write/mutation mode under ANY args, so the `effect:"read"`
|
|
11
|
+
* declaration (relied on by wake/resume reconcile + the verifier read-only boundary) AND the classifier's
|
|
12
|
+
* "reversible" promise both stay truthful. The deployment's tool-policy gate is the authoritative control;
|
|
13
|
+
* anything that can mutate state or run arbitrary code belongs on the full `bash` (effect:write, gated), not here.
|
|
14
14
|
*
|
|
15
15
|
* Deliberately ABSENT: `date` (`-s`/`--set` → CLOCK_SETTIME), `hostname` (`<name>` → kernel
|
|
16
16
|
* hostname), and `file` (`-C -m` → compiles/writes a magic file) were REMOVED — each is read-only by NAME
|
|
@@ -112,7 +112,7 @@ export interface LeadingCommandNameOptions {
|
|
|
112
112
|
pathPrefixedNameIsText?: boolean;
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
|
-
* The SINGLE fail-closed simple-command parser shared by `bash_readonly`
|
|
115
|
+
* The SINGLE fail-closed simple-command parser shared by `bash_readonly`'s single-command admission, the
|
|
116
116
|
* `bash` reversibility classifier ({@link import("./fs-bash.js").bashReversibilityProbe}), and the coarse command-name policy
|
|
117
117
|
* ({@link import("../../core/tool-policy.js").createCoarseCommandNamePolicy}). It extracts the leading
|
|
118
118
|
* `argv[0]` command NAME of a SINGLE simple command, rejecting anything that could chain past or escape an
|
|
@@ -131,13 +131,9 @@ export declare function parseLeadingCommandName(command: string, options?: Leadi
|
|
|
131
131
|
} | {
|
|
132
132
|
reject: string;
|
|
133
133
|
};
|
|
134
|
-
/**
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
* the policy gate are what make `effect:"read"` truthful. Returns a reason when rejected, else undefined.
|
|
138
|
-
* This `effect:"read"` DECLARATION face deliberately stays strict-single-command; only the
|
|
139
|
-
* classify face ({@link classifyCompoundReadonly} via {@link import("./fs-bash.js").bashReversibilityProbe}) segments compounds. */
|
|
140
|
-
export declare function coarseReadonlyCheck(command: string, allow: ReadonlySet<string>, options?: LeadingCommandNameOptions): string | undefined;
|
|
134
|
+
/** The one sentence for a program a face's list of programs does not name — the compound classifier's list check and
|
|
135
|
+
* the read-only reader's `readers` narrowing both mint it here. */
|
|
136
|
+
export declare function unlistedReaderSentence(program: string): string;
|
|
141
137
|
/**
|
|
142
138
|
* Does this text carry a bare redirection operator — the ONE metacharacter family
|
|
143
139
|
* {@link splitShellCompoundSegments} can be asked to tolerate at its whole-string gate
|
|
@@ -226,7 +222,7 @@ export declare function splitShellCompoundSegments(source: string, options?: {
|
|
|
226
222
|
/**
|
|
227
223
|
* Compound read-only classification for the `bash` full-shell face (the shape tier).
|
|
228
224
|
* Splits a command on the connector set `;` `&&` `||` `|` and passes every segment through
|
|
229
|
-
*
|
|
225
|
+
* the single {@link parseLeadingCommandName} parser and the bare-name allowlist.
|
|
230
226
|
* All segments green ⇒ read-only. Returns a reason when rejected, else undefined.
|
|
231
227
|
*
|
|
232
228
|
* G2 — the split CARRIES QUOTE STATE (the one lexer): a connector character inside a quoted region is
|
|
@@ -548,7 +544,7 @@ export declare const POLL_LOOP_MAX_SLEEP_SECONDS = 600;
|
|
|
548
544
|
* -f`, unbounded devices all reject), and `sleep` must be a literal ≤
|
|
549
545
|
* {@link POLL_LOOP_MAX_SLEEP_SECONDS}. Bounded beats × terminating body ⇒ the loop halts.
|
|
550
546
|
* G2 NO WRITE OR EGRESS CHANNEL. Every non-`sleep` body segment must pass the SAME
|
|
551
|
-
* {@link
|
|
547
|
+
* {@link parseLeadingCommandName} parser + allowlist the compound classifier reads (one parser, no drift);
|
|
552
548
|
* `sleep` touches neither filesystem nor network. No redirection (`>` `<`), no `$`/backtick
|
|
553
549
|
* substitution, no subshell exists anywhere: the body bans them wholesale
|
|
554
550
|
* ({@link POLL_BODY_HARD_REJECT}), and the head's single `$(seq <int> <int>)` exemption is an
|
|
@@ -71,13 +71,8 @@ export function parseLeadingCommandName(command, options) {
|
|
|
71
71
|
return { reject: "the command name must be a bare token (no quotes, braces, globs, or ~)" };
|
|
72
72
|
return { name: first };
|
|
73
73
|
}
|
|
74
|
-
export function
|
|
75
|
-
|
|
76
|
-
if ("reject" in parsed)
|
|
77
|
-
return parsed.reject;
|
|
78
|
-
if (!allow.has(parsed.name))
|
|
79
|
-
return `command "${parsed.name}" is not in the read-only allowlist`;
|
|
80
|
-
return undefined;
|
|
74
|
+
export function unlistedReaderSentence(program) {
|
|
75
|
+
return `command "${program}" is not in the read-only allowlist`;
|
|
81
76
|
}
|
|
82
77
|
const SHELL_SEGMENT_QUOTE_BLIND_REJECT = /[<>()`\n\r\\]/;
|
|
83
78
|
const SHELL_EXPANSION_CHARS = /[$]/;
|
|
@@ -761,7 +756,7 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
761
756
|
return { reason: "`coproc` backgrounds a command — a backgrounded process outlives the command" };
|
|
762
757
|
if (isBareControlWord(words, from, BLOCK_WORD_NAMES)) {
|
|
763
758
|
unlistedSegments.add(si);
|
|
764
|
-
nameRefusal ??=
|
|
759
|
+
nameRefusal ??= unlistedReaderSentence(written);
|
|
765
760
|
continue;
|
|
766
761
|
}
|
|
767
762
|
const program = pos.program;
|
|
@@ -783,10 +778,10 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
783
778
|
if (pathPrefixed || from > 0)
|
|
784
779
|
reheaded.set(si, { from, head: name });
|
|
785
780
|
if (from > 0)
|
|
786
|
-
nameRefusal ??=
|
|
781
|
+
nameRefusal ??= unlistedReaderSentence(folded[0]);
|
|
787
782
|
continue;
|
|
788
783
|
}
|
|
789
|
-
nameRefusal ??=
|
|
784
|
+
nameRefusal ??= unlistedReaderSentence(parsed.name);
|
|
790
785
|
if (runsShellText(name))
|
|
791
786
|
return { reason: unjudgedProgramTextRefusal(parsed.name, "runs its argument as shell text") };
|
|
792
787
|
if (pos.launchers.length > 0) {
|
package/dist/tools/fs/fs-bash.js
CHANGED
|
@@ -16,7 +16,8 @@ import { boundaryIsTheSandbox, hasDestroy, isIsolated } from "../../core/remote-
|
|
|
16
16
|
import { ghRateLimitHint } from "./gh-rate-limit.js";
|
|
17
17
|
import { backgroundLifetimeNote } from "./fs-bash-background-text.js";
|
|
18
18
|
import { resolveBashTimeoutCaps, bashTimeoutCapsSec, bashTimeoutArgRefusal, bashTimeoutParamDescription, envErrorDetail, bashMaxOutputChars, clipShellOutput, writeShellOverflowFile, shellRecoveryHint, CWD_SENTINEL, stripCwdSentinel, } from "./fs-shared.js";
|
|
19
|
-
import { bashClassifyDefaultAllow, BASH_READONLY_DEFAULT_ALLOW,
|
|
19
|
+
import { bashClassifyDefaultAllow, BASH_READONLY_DEFAULT_ALLOW, classifyBoundedReadonlyPollLoopDetailed, classifyCompoundReadonlyDetailed, parseLeadingCommandName } from "./bash-readonly-classifier.js";
|
|
20
|
+
import { readOnlyShellVerdict } from "../../core/read-only-shell.js";
|
|
20
21
|
import { mountRuleFace, toolFace } from "../../core/tool-catalog-entries.js";
|
|
21
22
|
import { argvOf, compoundValueHidesRun, expansionHidesRun, expansionsInHeredocBody, lexShell } from "../../shell/lex.js";
|
|
22
23
|
import { arithmeticAssignedNames } from "./shell-read-bindings.js";
|
|
@@ -26,6 +27,12 @@ function shellCommandOf(args) {
|
|
|
26
27
|
const command = args?.command;
|
|
27
28
|
return typeof command === "string" ? command : undefined;
|
|
28
29
|
}
|
|
30
|
+
const CONCURRENT_READERS = new Set(BASH_READONLY_DEFAULT_ALLOW);
|
|
31
|
+
function readOnlyFaceRefusal(command, allow) {
|
|
32
|
+
const parsed = parseLeadingCommandName(command);
|
|
33
|
+
const verdict = "reject" in parsed ? { readOnly: false, reason: parsed.reject } : readOnlyShellVerdict(command, { readers: allow });
|
|
34
|
+
return verdict.readOnly ? undefined : verdict.reason;
|
|
35
|
+
}
|
|
29
36
|
function isBackgroundShellCall(args) {
|
|
30
37
|
return args?.run_in_background === true;
|
|
31
38
|
}
|
|
@@ -578,7 +585,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
578
585
|
const command = shellCommandOf(args);
|
|
579
586
|
if (command === undefined || isBackgroundShellCall(args))
|
|
580
587
|
return false;
|
|
581
|
-
return
|
|
588
|
+
return readOnlyShellVerdict(command, { readers: CONCURRENT_READERS }).readOnly;
|
|
582
589
|
},
|
|
583
590
|
execute: async (args, ctx) => {
|
|
584
591
|
const { command, timeout, run_in_background, description } = args;
|
|
@@ -797,7 +804,7 @@ export function createBashReadonlyTool(env, rootCanonical, allow, opts) {
|
|
|
797
804
|
const timeoutRefusal = bashTimeoutArgRefusal(timeout, timeoutCaps);
|
|
798
805
|
if (timeoutRefusal !== undefined)
|
|
799
806
|
return errorResult(timeoutRefusal, { type: "bash_invalid_timeout", code: "bash_invalid_timeout" });
|
|
800
|
-
const reason =
|
|
807
|
+
const reason = readOnlyFaceRefusal(command, allow);
|
|
801
808
|
if (reason)
|
|
802
809
|
return errorResult(`Error (Bash): ${reason}`);
|
|
803
810
|
const refused = await opts?.shellReadAdmission?.("Bash", ctx.toolCallId, command, ctx.signal);
|
|
@@ -12,8 +12,9 @@ export interface PatternKillReading {
|
|
|
12
12
|
export declare function patternKillReadings(command: string, cwd: string): PatternKillReading[];
|
|
13
13
|
/**
|
|
14
14
|
* The refusal for a command whose pattern-kill would select this agent's own process or one above it — or undefined (run it).
|
|
15
|
-
* `cwd`: where the command stands, for the plan's read of its words (nothing is read from disk).
|
|
16
|
-
*
|
|
15
|
+
* `cwd`: where the command stands, for the plan's read of its words (nothing is read from disk). Where the question is put is
|
|
16
|
+
* the env's own declaration ({@link processTableOf}): this process's table is asked, another table is not, and an env that does
|
|
17
|
+
* not say is refused. Windows is not asked: this process's table is not read there.
|
|
17
18
|
*/
|
|
18
19
|
export declare function patternKillRefusal(command: string, env: ExecutionEnv, cwd: string, signal?: AbortSignal): Promise<{
|
|
19
20
|
content: string;
|