@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
|
@@ -308,7 +308,7 @@ export async function checkpointStoreContract(make, runAssertion, options = {})
|
|
|
308
308
|
const stored = (await store.get(bitless.token)).pendingAction;
|
|
309
309
|
assert.equal("hasBidiControls" in stored, false, "projection backfill must not write back to the row");
|
|
310
310
|
});
|
|
311
|
-
run("the approval object's members: a row's identity + face group and its targets (capabilityId/contractId/shapeDigest/implementationRevision + family/pathTarget/hostTarget/names + targets + engineMounted) survive the round-trip verbatim; an absent group stays absent", async () => {
|
|
311
|
+
run("the approval object's members: a row's identity + face group and its targets (capabilityId/contractId/shapeDigest/implementationRevision + family/pathTarget/hostTarget/names + targets + targetReads + engineMounted) survive the round-trip verbatim; an absent group stays absent", async () => {
|
|
312
312
|
const store = make();
|
|
313
313
|
const carried = createCheckpointFixture({ token: mintCheckpointToken(), sessionId: "id-carried" });
|
|
314
314
|
const group = {
|
|
@@ -320,6 +320,7 @@ export async function checkpointStoreContract(make, runAssertion, options = {})
|
|
|
320
320
|
pathTarget: { param: "destination", aliases: ["path"], access: "create", skillScopeEligible: true },
|
|
321
321
|
names: ["AcmeWrite", "acme_write"],
|
|
322
322
|
targets: { protective: "/srv/work/out.txt", declared: "/srv/work/out.txt" },
|
|
323
|
+
targetReads: { ["0123456789abcdef".repeat(4)]: "fedcba9876543210".repeat(4) },
|
|
323
324
|
engineMounted: true,
|
|
324
325
|
};
|
|
325
326
|
Object.assign(carried.pendingAction, group);
|
|
@@ -473,9 +473,18 @@ export async function fileHistoryStoreContract(make, runAssertion) {
|
|
|
473
473
|
throw Object.assign(new Error("source blob unreadable"), { code: "EACCES" });
|
|
474
474
|
});
|
|
475
475
|
assert.equal(thrown.ok, false);
|
|
476
|
+
if (!thrown.ok)
|
|
477
|
+
assert.equal(thrown.error.code, "adopt_failed", "a source whose read throws cannot supply a referenced blob");
|
|
476
478
|
if (!thrown.ok)
|
|
477
479
|
assertPermanenceWord(thrown.error, "importHistory whose source fetch threw");
|
|
478
480
|
assert.equal(await store.canRestore("sc", "b2"), false);
|
|
481
|
+
const wrongBytes = await store.importHistory("sc", missingBlob, async () => new TextEncoder().encode("bytes that are not the content this address names"));
|
|
482
|
+
assert.equal(wrongBytes.ok, false);
|
|
483
|
+
if (!wrongBytes.ok)
|
|
484
|
+
assert.equal(wrongBytes.error.code, "adopt_failed", "a source whose bytes fail their content address cannot supply a referenced blob");
|
|
485
|
+
if (!wrongBytes.ok)
|
|
486
|
+
assertPermanenceWord(wrongBytes.error, "importHistory whose source served the wrong bytes");
|
|
487
|
+
assert.equal(await store.canRestore("sc", "b2"), false);
|
|
479
488
|
const duplicated = wireEnvelope({
|
|
480
489
|
formatVersion: "file-history/1",
|
|
481
490
|
tracked: [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { strict as assert } from "node:assert";
|
|
2
|
-
import { MAILBOX_INVALID_PEER_META_CODE, MAILBOX_TOMBSTONED_RECIPIENT_CODE } from "../mailbox-store.js";
|
|
2
|
+
import { MAILBOX_INVALID_PEER_META_CODE, MAILBOX_INVALID_DECISION_MOVE_CODE, MAILBOX_TOMBSTONED_RECIPIENT_CODE } from "../mailbox-store.js";
|
|
3
3
|
import { beginContract } from "./contract-harness.js";
|
|
4
4
|
import { PRINCIPAL_INBOX_HANDLE } from "../../agents/cross-session-ref.js";
|
|
5
5
|
import { mintCrossPrincipalRecord } from "../../agents/org-delivery-gate.js";
|
|
@@ -183,6 +183,54 @@ export async function mailboxStoreContract(mk, runAssertion) {
|
|
|
183
183
|
const again = await s.claimLease(S, "a1", "w2", 1_000, 20_000);
|
|
184
184
|
assert.deepEqual(again.messages[0].peerMeta, { fromMode: "bypass", fromSession: "sess_1" }, "重投的记录必须是原样");
|
|
185
185
|
});
|
|
186
|
+
run("#972b hold decision:CAS 一胜一负、负者拿到真值;不受租约围栏;每个租约快照都带它", async (make) => {
|
|
187
|
+
const s = make();
|
|
188
|
+
await s.append(S, "a1", msg("m1"));
|
|
189
|
+
await s.append(S, "a1", msg("m2"));
|
|
190
|
+
const held = await s.claimLease(S, "a1", "holder", 60_000, 10_000);
|
|
191
|
+
assert.equal(held.messages[0].decision, undefined, "从未 held 的记录没有 decision");
|
|
192
|
+
assert.deepEqual(await s.moveDecision(S, "a1", 1, undefined, "held"), { ok: true });
|
|
193
|
+
assert.deepEqual(await s.moveDecision(S, "a1", 1, undefined, "held"), { ok: false, current: "held" }, "同一步再走一次:负者拿到当前值");
|
|
194
|
+
assert.deepEqual(await s.moveDecision(S, "a1", 1, "held", "denied"), { ok: true }, "不持租约的一方也能落人的决定");
|
|
195
|
+
assert.deepEqual(await s.moveDecision(S, "a1", 1, "held", "approved"), { ok: false, current: "denied" }, "后到的答复输给先到的决定");
|
|
196
|
+
assert.deepEqual(await s.moveDecision(S, "a1", 2, "held", "approved"), { ok: false, current: "unset" }, "未 held 的记录答 unset");
|
|
197
|
+
const snap = await s.claimLease(S, "a1", "holder", 60_000, 11_000);
|
|
198
|
+
assert.deepEqual(snap.messages.map((m) => [m.seq, m.decision]), [[1, "denied"], [2, undefined]], "租约快照带每条记录的 decision");
|
|
199
|
+
await s.ack(S, "a1", "holder", 1);
|
|
200
|
+
assert.deepEqual(await s.moveDecision(S, "a1", 1, "denied", "denied").catch((e) => e.code), MAILBOX_INVALID_DECISION_MOVE_CODE, "终局不再移动(先于存在性判)");
|
|
201
|
+
assert.deepEqual(await s.moveDecision(S, "a1", 1, "held", "approved"), { ok: false, current: "gone" }, "ack 掉的记录答 gone");
|
|
202
|
+
});
|
|
203
|
+
run("#972b decision 律:只向前走,终局不动;坏词 / 坏 seq / 非法边先于任何副作用被拒(带码)", async (make) => {
|
|
204
|
+
const s = make();
|
|
205
|
+
await s.append(S, "a1", msg("m1"));
|
|
206
|
+
const codeOf = async (p) => {
|
|
207
|
+
try {
|
|
208
|
+
await p;
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
return e.code;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
for (const [label, seq, expect, next] of [
|
|
216
|
+
["unset → approved", 1, undefined, "approved"],
|
|
217
|
+
["unset → delivered", 1, undefined, "delivered"],
|
|
218
|
+
["held → held", 1, "held", "held"],
|
|
219
|
+
["held → dropped", 1, "held", "dropped"],
|
|
220
|
+
["delivered → denied", 1, "delivered", "denied"],
|
|
221
|
+
["denied → approved", 1, "denied", "approved"],
|
|
222
|
+
["unknown word", 1, undefined, "consumed"],
|
|
223
|
+
["seq 0", 0, undefined, "held"],
|
|
224
|
+
["seq 1.5", 1.5, undefined, "held"],
|
|
225
|
+
]) {
|
|
226
|
+
assert.equal(await codeOf(s.moveDecision(S, "a1", seq, expect, next)), MAILBOX_INVALID_DECISION_MOVE_CODE, label);
|
|
227
|
+
}
|
|
228
|
+
const snap = await s.claimLease(S, "a1", "w", 60_000, 10_000);
|
|
229
|
+
assert.equal(snap.messages[0].decision, undefined, "被拒的调用零副作用");
|
|
230
|
+
for (const [from, to] of [[undefined, "held"], ["held", "approved"], ["approved", "delivered"], ["delivered", "dropped"]]) {
|
|
231
|
+
assert.deepEqual(await s.moveDecision(S, "a1", 1, from, to), { ok: true }, `${String(from)} → ${to}`);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
186
234
|
await settle();
|
|
187
235
|
}
|
|
188
236
|
export async function mailboxAckOwnershipContract(mk, runAssertion) {
|
|
@@ -443,19 +443,23 @@ export declare function deliverToRunningAgentLane(core: DurableAgentCore, id: st
|
|
|
443
443
|
* counterpart rows here). This variant is the SENDMESSAGE footer's source and lists EVERY running
|
|
444
444
|
* agent, named or not: SendMessage addresses by name AND by id, so dropping named rows here (the way
|
|
445
445
|
* {@link runningAgentFooterLane} does for the task faces, per CC `rxo`'s `!n.has(i.id)`) would hide the
|
|
446
|
-
* very rows a retry needs. The
|
|
446
|
+
* very rows a retry needs. The label is the name someone chose, else the description: the name an agent
|
|
447
|
+
* carries only because it must carry one ({@link chosenAgentName}) says nothing about what it does.
|
|
448
|
+
* The task faces' split lives in `runningAgentFooterLane` (RB-332). */
|
|
447
449
|
export declare function runningBackgroundAgentLabelsLane(core: DurableAgentCore, access: TaskAccess): string[];
|
|
448
450
|
/**
|
|
449
451
|
* The not-found FOOTER's two rows, split the way the reference assembles them:
|
|
450
452
|
* - `named` = CC `uG_` (220:467941-467953): the agentNameRegistry's KEYS whose task is a running local
|
|
451
453
|
* agent, listed as BARE NAMES — the string a retry should pass verbatim. sema's counterpart
|
|
452
|
-
* of "has a registry entry" is the handle carrying
|
|
453
|
-
*
|
|
454
|
+
* of "has a registry entry" is the handle carrying a CHOSEN name (the spawn-time label; the
|
|
455
|
+
* name an agent carries only because every agent has one is not a registry entry —
|
|
456
|
+
* {@link chosenAgentName}).
|
|
454
457
|
* - `background` = CC `rxo` (220:467937-467951): the remaining running background agents as `id (label)`
|
|
455
458
|
* rows. CC filters `!n.has(i.id)` — a NAMED agent is deliberately NOT repeated here, because
|
|
456
459
|
* its addressable form is the name, not the id. The pre-RB-332 sema code merged both legs
|
|
457
460
|
* into one list and justified it with "named has no counterpart", a claim [c209-D] retired
|
|
458
|
-
* when the explicit `name` field landed.
|
|
461
|
+
* when the explicit `name` field landed. The id stays in these rows because TaskOutput
|
|
462
|
+
* resolves exact ids only: an agent nobody named is the one a retry can reach by its id alone.
|
|
459
463
|
* CC's `YEd` "Running teammates:" leg has no sema counterpart (no teammate registry on this face) and is
|
|
460
464
|
* honestly absent rather than faked.
|
|
461
465
|
*/
|
|
@@ -5,7 +5,7 @@ import { canAccessAgentRecord, BackgroundAgentStoreError, clearRevivedRowTermina
|
|
|
5
5
|
import { shutdownDebug } from "./shutdown-debug.js";
|
|
6
6
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
7
7
|
import { boundedRedactedSummary } from "./untrusted-egress.js";
|
|
8
|
-
import { mintCompletionId, commitCompletionIdIfEmpty, clipTaskOutput, assertOwnership, sleepPollStep, alreadyTerminalStopNote, canAccess, normalizeAgentName, closestName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR, } from "./task-registry-shared.js";
|
|
8
|
+
import { mintCompletionId, commitCompletionIdIfEmpty, clipTaskOutput, assertOwnership, sleepPollStep, alreadyTerminalStopNote, canAccess, normalizeAgentName, engineAgentName, chosenAgentName, closestName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR, } from "./task-registry-shared.js";
|
|
9
9
|
import { buildToolResultRef, OFFLOAD_TOOL_NAME, toolResultProvenanceOf } from "./tool-result-store.js";
|
|
10
10
|
import { thrownLabel } from "./thrown-value.js";
|
|
11
11
|
import { agentStateSourceOfHandle, agentStateSummary, readAgentState } from "./agent-state.js";
|
|
@@ -367,6 +367,10 @@ export function registerBackgroundAgentLane(core, input) {
|
|
|
367
367
|
if (core.handles.has(id))
|
|
368
368
|
throw new Error(`TaskRegistry: task id already registered: ${id}`);
|
|
369
369
|
const now = input.now ?? Date.now();
|
|
370
|
+
if (input.name !== undefined && (typeof input.name !== "string" || input.name === "")) {
|
|
371
|
+
throw new Error(`TaskRegistry: background-agent name must be a non-empty string (got ${JSON.stringify(input.name)}); omit it to take the derived one`);
|
|
372
|
+
}
|
|
373
|
+
const name = input.name ?? engineAgentName(id);
|
|
370
374
|
core.handles.set(id, {
|
|
371
375
|
id,
|
|
372
376
|
type: "background_agent",
|
|
@@ -375,7 +379,7 @@ export function registerBackgroundAgentLane(core, input) {
|
|
|
375
379
|
...(input.resolveParkedStop !== undefined ? { resolveParkedStop: input.resolveParkedStop } : {}),
|
|
376
380
|
...(input.sessionScoped ? { sessionScoped: true } : {}),
|
|
377
381
|
...(input.description !== undefined ? { description: input.description } : {}),
|
|
378
|
-
|
|
382
|
+
name,
|
|
379
383
|
...(input.agentType !== undefined ? { agentType: input.agentType } : {}),
|
|
380
384
|
...(input.parentTaskId !== undefined ? { parentTaskId: input.parentTaskId } : {}),
|
|
381
385
|
...(input.parentSessionId !== undefined ? { parentSessionId: input.parentSessionId } : {}),
|
|
@@ -396,6 +400,7 @@ export function registerBackgroundAgentLane(core, input) {
|
|
|
396
400
|
if (input.durableSeed !== undefined) {
|
|
397
401
|
const seeded = structuredClone(input.durableSeed.record);
|
|
398
402
|
seeded.rev = input.durableSeed.rev;
|
|
403
|
+
seeded.name = name;
|
|
399
404
|
handle.durable = {
|
|
400
405
|
store: input.store,
|
|
401
406
|
record: seeded,
|
|
@@ -417,7 +422,7 @@ export function registerBackgroundAgentLane(core, input) {
|
|
|
417
422
|
...(input.parentSessionId !== undefined ? { parentSessionId: input.parentSessionId } : {}),
|
|
418
423
|
...(input.rootSessionId !== undefined ? { rootSessionId: input.rootSessionId } : {}),
|
|
419
424
|
...(input.parentTaskId !== undefined ? { parentTaskId: input.parentTaskId } : {}),
|
|
420
|
-
|
|
425
|
+
name,
|
|
421
426
|
...(input.agentType !== undefined ? { agentType: input.agentType } : {}),
|
|
422
427
|
...(input.description !== undefined ? { description: input.description } : {}),
|
|
423
428
|
...(input.model !== undefined ? { model: input.model } : {}),
|
|
@@ -892,7 +897,7 @@ export function resolveBackgroundAgentByNameLane(core, name, access, opts) {
|
|
|
892
897
|
};
|
|
893
898
|
const named = [];
|
|
894
899
|
for (const h of core.handles.values()) {
|
|
895
|
-
if (h.type !== "background_agent" ||
|
|
900
|
+
if (h.type !== "background_agent" || !canAccess(h, access))
|
|
896
901
|
continue;
|
|
897
902
|
if (h.name === name || normalizeAgentName(h.name) === key)
|
|
898
903
|
named.push(h);
|
|
@@ -902,7 +907,7 @@ export function resolveBackgroundAgentByNameLane(core, name, access, opts) {
|
|
|
902
907
|
if (namedRunning.length > 1) {
|
|
903
908
|
return {
|
|
904
909
|
status: "ambiguous",
|
|
905
|
-
message: `Multiple RUNNING background agents match "${name}": ${namedRunning.map((h) => `${h.id} (${h.name
|
|
910
|
+
message: `Multiple RUNNING background agents match "${name}": ${namedRunning.map((h) => `${h.id} (${h.name})`).join(", ")}. Use the task ID.`,
|
|
906
911
|
};
|
|
907
912
|
}
|
|
908
913
|
if (namedRunning.length === 1)
|
|
@@ -1256,7 +1261,7 @@ export function runningBackgroundAgentLabelsLane(core, access) {
|
|
|
1256
1261
|
for (const h of core.handles.values()) {
|
|
1257
1262
|
if (h.type !== "background_agent" || h.status !== "running" || !canAccess(h, access))
|
|
1258
1263
|
continue;
|
|
1259
|
-
const label = h.name ?? h.description;
|
|
1264
|
+
const label = chosenAgentName(h.id, h.name) ?? h.description;
|
|
1260
1265
|
out.push(label ? `${h.id} (${label})` : h.id);
|
|
1261
1266
|
}
|
|
1262
1267
|
return out;
|
|
@@ -1267,8 +1272,9 @@ export function runningAgentFooterLane(core, access) {
|
|
|
1267
1272
|
for (const h of core.handles.values()) {
|
|
1268
1273
|
if (h.type !== "background_agent" || h.status !== "running" || !canAccess(h, access))
|
|
1269
1274
|
continue;
|
|
1270
|
-
|
|
1271
|
-
|
|
1275
|
+
const chosen = chosenAgentName(h.id, h.name);
|
|
1276
|
+
if (chosen !== undefined) {
|
|
1277
|
+
named.push(chosen);
|
|
1272
1278
|
continue;
|
|
1273
1279
|
}
|
|
1274
1280
|
background.push(h.description ? `${h.id} (${h.description})` : h.id);
|
|
@@ -321,11 +321,12 @@ export interface BackgroundAgentTaskHandle extends SemaTaskHandle {
|
|
|
321
321
|
reason: "not_found" | "not_running" | "no_channel";
|
|
322
322
|
}) => void) | undefined)?
|
|
323
323
|
]>;
|
|
324
|
-
/**
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
|
|
324
|
+
/** The addressing name: the spawner's explicit one (CC Agent.name parity, validated at spawn: charset
|
|
325
|
+
* regex + reserved set), or — when the spawn named none — the one {@link engineAgentName} derives from
|
|
326
|
+
* the handle, written by the registration itself, so every background agent has exactly one. Distinct
|
|
327
|
+
* from `description` (the display/legacy-matching label): the name layer resolves latest-wins ("a newer
|
|
328
|
+
* agent took the name"), the description layers keep their running-preferred + ambiguous posture. */
|
|
329
|
+
name: string;
|
|
329
330
|
/** design/129: this child OUTLIVES its parent turn — owner is the SESSION id; the parent-teardown reap
|
|
330
331
|
* skips it; `reapSessionBackground` is its terminal anchor. Explicit flag, never inferred from key values
|
|
331
332
|
* (hostTaskId===sessionId deployments would otherwise be ambiguous). */
|
|
@@ -836,6 +837,32 @@ export declare const DURABLE_AGENT_HEARTBEAT_MS = 60000;
|
|
|
836
837
|
* durable arm (a bare exact-key miss lets padded/legacy/name-shaped ids leak into live branches —
|
|
837
838
|
* the server 1.250 HIGH) — one shared regex kills the drift, roster-normalize precedent. */
|
|
838
839
|
export declare const DURABLE_AGENT_HANDLE_RE: RegExp;
|
|
840
|
+
/**
|
|
841
|
+
* The name a background agent carries when its spawner named none: `agent-` + the first twelve hex
|
|
842
|
+
* digits of its handle (`a3fa9c2d1b7e04a15` → `agent-3fa9c2d1b7e0`; a handle with fewer digits keeps
|
|
843
|
+
* them all). A pure function of the handle, so the same agent id reads the same name in every process
|
|
844
|
+
* and every store — a host that finds a row from an earlier version without a name derives it here, it
|
|
845
|
+
* does not invent one.
|
|
846
|
+
*
|
|
847
|
+
* Shorter than the id's sixteen digits on purpose: a name of the SHAPE of an agent id (`a`, an optional
|
|
848
|
+
* `<label>-`, sixteen hex) already addresses an agent directly and is refused as a name everywhere
|
|
849
|
+
* (`reservedPeerNameReason`), so a minted name is an ordinary admissible name — nothing in the name
|
|
850
|
+
* grammar singles it out. Twelve digits (48 bits) because the name layer resolves a name to the NEWEST
|
|
851
|
+
* agent that holds it: two agents of one caller would have to share twelve digits of their ids to collide.
|
|
852
|
+
*
|
|
853
|
+
* The registry's registration is the one place this is called (see `registerBackgroundAgentLane`); every
|
|
854
|
+
* reader that needs to know whether SOMEONE chose a name goes through {@link chosenAgentName}.
|
|
855
|
+
*/
|
|
856
|
+
export declare function engineAgentName(handle: string): string;
|
|
857
|
+
/**
|
|
858
|
+
* The name SOMEONE chose for an agent — `undefined` when the agent's name is the one {@link engineAgentName}
|
|
859
|
+
* derives from its handle (or there is none). Every agent has a name to be addressed by; only a chosen one is
|
|
860
|
+
* also a spawn identity (the teammate key, `spawnName` on frames, a roster entry, the unnamed-child tool pool),
|
|
861
|
+
* and a reader that hands a row's name back into a place that reads it as an identity — a revival's `name`
|
|
862
|
+
* argument, a retained resume's row name — goes through here, so an agent nobody named stays unnamed in every
|
|
863
|
+
* later cycle. A reader that only needs to ADDRESS the agent reads `name` as it is.
|
|
864
|
+
*/
|
|
865
|
+
export declare function chosenAgentName(handle: string, name: string | undefined): string | undefined;
|
|
839
866
|
/** Synthetic settle error the subagent reap path passes after a stop/parent teardown. It is NOT a
|
|
840
867
|
* real child error — settleBackgroundAgent never stores it (status "killed" + stoppedBy already say
|
|
841
868
|
* it); exported so the subagent call sites and the registry agree on the exact sentinel. */
|
|
@@ -937,7 +964,9 @@ export interface RegisterBackgroundAgentInput extends TaskAccess {
|
|
|
937
964
|
description?: string;
|
|
938
965
|
/** [c209-D] explicit addressing name (CC Agent.name parity) — the CALLER validates it (charset +
|
|
939
966
|
* reserved "main") before registration; the registry stores it verbatim. Duplicate names are legal
|
|
940
|
-
* (latest-wins at resolution; older rows stay task_id-addressable).
|
|
967
|
+
* (latest-wins at resolution; older rows stay task_id-addressable). ABSENT = the spawner named none:
|
|
968
|
+
* the registration gives the agent the name {@link engineAgentName} derives from its handle (this is
|
|
969
|
+
* the only place a name is minted), on the live handle and the durable row alike. */
|
|
941
970
|
name?: string;
|
|
942
971
|
/** the RESOLVED subagent type ("general-purpose"/"fork"/a roster name), persisted
|
|
943
972
|
* so a REVIVE cycle's observer frames can reproduce the row's TYPE column (never the description). */
|
|
@@ -279,6 +279,17 @@ export function formatWorkflowRun(run) {
|
|
|
279
279
|
}
|
|
280
280
|
export const DURABLE_AGENT_HEARTBEAT_MS = 60_000;
|
|
281
281
|
export const DURABLE_AGENT_HANDLE_RE = /^a[0-9a-f]{6,32}$/;
|
|
282
|
+
export function engineAgentName(handle) {
|
|
283
|
+
if (!DURABLE_AGENT_HANDLE_RE.test(handle)) {
|
|
284
|
+
throw new TypeError(`engineAgentName: ${JSON.stringify(handle)} is not an agent handle (expected "a" followed by 6-32 hex digits)`);
|
|
285
|
+
}
|
|
286
|
+
return `agent-${handle.slice(1, 13)}`;
|
|
287
|
+
}
|
|
288
|
+
export function chosenAgentName(handle, name) {
|
|
289
|
+
if (name === undefined)
|
|
290
|
+
return undefined;
|
|
291
|
+
return DURABLE_AGENT_HANDLE_RE.test(handle) && name === engineAgentName(handle) ? undefined : name;
|
|
292
|
+
}
|
|
282
293
|
export const BG_AGENT_REAP_STOP_ERROR = "stopped (parent task ended or TaskStop)";
|
|
283
294
|
export function normalizeAgentName(s) {
|
|
284
295
|
return s
|
|
@@ -7,7 +7,7 @@ import { type BackgroundAgentStore } from "./background-agent-store.js";
|
|
|
7
7
|
import type { TaskNotificationPayload } from "./task-notification.js";
|
|
8
8
|
import type { ToolResultStore } from "./tool-result-store.js";
|
|
9
9
|
import { type SemaTaskType, type StopSource, type SemaTaskStatus, type TaskRetrievalStatus, type UnifiedTaskOutput, type UnifiedTaskResult, type SemaTaskHandle, type TaskAccess, type BackgroundAgentTaskHandle, type MonitorTimers, type RegisterMonitorInput, type RegisterWorkflowInput, canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE } from "./task-registry-shared.js";
|
|
10
|
-
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
10
|
+
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR, engineAgentName, chosenAgentName } from "./task-registry-shared.js";
|
|
11
11
|
export type { ParkedClaimTicket, RegisterBackgroundAgentInput } from "./task-registry-shared.js";
|
|
12
12
|
import type { RegisterBackgroundAgentInput, ParkedClaimTicket } from "./task-registry-shared.js";
|
|
13
13
|
import { type AgentStateSource } from "./agent-state.js";
|
|
@@ -9,7 +9,7 @@ import { stripCwdSentinel } from "../tools/fs/fs-shared.js";
|
|
|
9
9
|
import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-registry-monitor.js";
|
|
10
10
|
import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
|
|
11
11
|
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, stdoutHeadingOf, REPLAY_FACE, SPOOL_REPLAY, INCREMENT_REPLAY, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
12
|
-
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
12
|
+
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR, engineAgentName, chosenAgentName } from "./task-registry-shared.js";
|
|
13
13
|
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
14
14
|
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, backgroundAgentCycleSeqLane, activeDelegationHandlesLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
15
15
|
import { mountRuleFace, toolFace } from "./tool-catalog-entries.js";
|
|
@@ -71,6 +71,12 @@ export declare const isEngineShellFace: (face: {
|
|
|
71
71
|
readonly engineMounted?: true;
|
|
72
72
|
readonly contractId: string;
|
|
73
73
|
} | undefined) => boolean;
|
|
74
|
+
/** Is this call's face one of the content-routing tools the engine mounts (question, answer, blocked report)? They route content,
|
|
75
|
+
* act on nothing, and no stored rule governs them: the persisted-rule lane does not judge them and their card offers no rule. */
|
|
76
|
+
export declare const routesContent: (face: {
|
|
77
|
+
readonly engineMounted?: true;
|
|
78
|
+
readonly contractId: string;
|
|
79
|
+
} | undefined) => boolean;
|
|
74
80
|
/**
|
|
75
81
|
* The rule face a definition whose schema is BUILT PER MOUNT puts on its instance: its row's primaries only (none ⇒ no rule
|
|
76
82
|
* face). The row's `params` is the static view's vocabulary, the fullest mount's; a mount's own is read off the schema it
|
|
@@ -207,6 +207,7 @@ export function engineMountedAs(ids) {
|
|
|
207
207
|
return (face) => face?.engineMounted === true && contracts.has(face.contractId);
|
|
208
208
|
}
|
|
209
209
|
export const isEngineShellFace = engineMountedAs(["bash", "bash-readonly"]);
|
|
210
|
+
export const routesContent = engineMountedAs(["ask-user-question", "structured-output", "report-blocked"]);
|
|
210
211
|
export function mountRuleFace(id) {
|
|
211
212
|
const primaryParams = toolFace(id).ruleFace?.primaryParams;
|
|
212
213
|
return primaryParams === undefined ? undefined : { primaryParams: [...primaryParams] };
|
|
@@ -16,8 +16,8 @@ import type { RuleBehavior } from "./permission-rule-model.js";
|
|
|
16
16
|
* with the successor, where any other unknown name stays the generic roster miss.
|
|
17
17
|
*
|
|
18
18
|
* What REMAINS is validation data: {@link RETIRED_TOOL_NAMES} maps every name the engine ever
|
|
19
|
-
* retired to a one-line guidance string, and {@link judgeRuleToolName} is the ONE
|
|
20
|
-
* tool name gets. They exist because deleting the mapping flips stale POLICY entries from "silently
|
|
19
|
+
* retired to a one-line guidance string, and {@link judgeRuleToolName} is the ONE judgment of it a rule's
|
|
20
|
+
* tool name gets ({@link toolNameFaultOf} adds the near-miss step: the one reading). They exist because deleting the mapping flips stale POLICY entries from "silently
|
|
21
21
|
* matched" to "silently matches nothing" — and for a deny/requireApproval entry that direction WIDENS
|
|
22
22
|
* the surface. `validatePermissionRules` (permission-rules.ts) does NOT classify retired names itself:
|
|
23
23
|
* which names a deployment mounts its OWN tool under is a roster fact, and the judgment takes the roster.
|
|
@@ -100,6 +100,36 @@ export declare function grantWildcardRefusalOf(name: string): string | undefined
|
|
|
100
100
|
* as a glob ({@link readsNameGlob}) — such a name outside every namespace, or with a glob in its peer segment, is a pattern and
|
|
101
101
|
* `current`; with `allow` or none a wildcard peer names no peer and is `unmintable_segment`. */
|
|
102
102
|
export declare function judgeRuleToolName(name: string, roster: ReadonlySet<string>, behavior?: RuleBehavior): RuleToolNameVerdict;
|
|
103
|
+
/**
|
|
104
|
+
* THE near-miss reading of a tool name the judgment calls current: the spelling the roster carries when it carries no tool of this
|
|
105
|
+
* exact name but one that differs from it only by case (`BASH` beside `Bash`), else `undefined`. Two kinds of name are never a near
|
|
106
|
+
* miss, because the roster they would be read against is not the final one: an engine tool's (the engine mounts some of its tools
|
|
107
|
+
* after a list is read) and a protocol tool's (a refresh mounts and unmounts them while the run goes on) — in both the exact
|
|
108
|
+
* spelling can be the right one by the time it matters. Every station that asks "is this name a case twin of a carried tool" reads
|
|
109
|
+
* this answer: an entry list and the persisted grammar's whole-tool rule through {@link toolNameFaultOf}, and a tool policy's name
|
|
110
|
+
* lists (the prepare-time policy-name audit, which reads two rosters and skips an inherited layer) directly.
|
|
111
|
+
*/
|
|
112
|
+
export declare function nearMissSpellingOf(name: string, roster: ReadonlySet<string>): string | undefined;
|
|
113
|
+
/** What {@link toolNameFaultOf} finds wrong with a rule's tool name: the judgment's stale words (`tool_name`, with the verdict and
|
|
114
|
+
* its facts), or another spelling of a carried tool (`near_miss`, the spelling to write). */
|
|
115
|
+
export type ToolNameFault = {
|
|
116
|
+
readonly reason: "tool_name";
|
|
117
|
+
readonly verdict: Exclude<RuleToolNameVerdict["kind"], "current">;
|
|
118
|
+
readonly guidance?: string;
|
|
119
|
+
readonly currentName?: string;
|
|
120
|
+
} | {
|
|
121
|
+
readonly reason: "near_miss";
|
|
122
|
+
readonly currentName: string;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* THE ONE reading of a rule's tool name against a roster — the name judgment's stale words, then, for a name the judgment calls
|
|
126
|
+
* current but the roster does not carry exactly, whether the roster carries it under another case ({@link nearMissSpellingOf}).
|
|
127
|
+
* `undefined` = no fault: the name is carried, or nothing carried is another spelling of it. A name the roster simply does not
|
|
128
|
+
* carry is silent: a tool of that name may be mounted later, and a list shared across runs is not wrong because one run lacks a
|
|
129
|
+
* tool. A list entry (`readRuleEntry`) and a stored whole-tool rule (`parseRuleText`) read a name through this and nothing else;
|
|
130
|
+
* the policy-name audit composes the same two steps itself over two rosters (a candidate to fold in).
|
|
131
|
+
*/
|
|
132
|
+
export declare function toolNameFaultOf(name: string, roster: ReadonlySet<string>, behavior: RuleBehavior | undefined): ToolNameFault | undefined;
|
|
103
133
|
/** The two kinds of tool-name miss — a closed set. `unknown`: the name is none the engine ever mounted (a typo, a
|
|
104
134
|
* guess). `expected-absent`: the name is one the engine RETIRED ({@link RETIRED_TOOL_NAMES}), so its absence is no
|
|
105
135
|
* surprise and the table says what replaced it (the reference spells the same two words for its render-time miss). */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { carriesNamespaceSeparator, protocolOf } from "./protocol-table.js";
|
|
2
2
|
import { mintNamespacePrefix } from "./protocol-naming.js";
|
|
3
|
+
import { catalogToolNames } from "./tool-registry.js";
|
|
3
4
|
export const RETIRED_TOOL_NAMES = new Map([
|
|
4
5
|
["read_file", 'renamed "Read"'],
|
|
5
6
|
["edit_file", 'renamed "Edit"'],
|
|
@@ -98,6 +99,28 @@ export function judgeRuleToolName(name, roster, behavior) {
|
|
|
98
99
|
return carriesNamespaceSeparator(name) && !(readsNameGlob(behavior) && name.includes("*")) ? UNPREFIXED : CURRENT;
|
|
99
100
|
return unmintableSegmentOf(split, behavior) ?? CURRENT;
|
|
100
101
|
}
|
|
102
|
+
function mountedSpellingOf(name, roster) {
|
|
103
|
+
const folded = name.toLowerCase();
|
|
104
|
+
for (const mounted of roster)
|
|
105
|
+
if (mounted.toLowerCase() === folded)
|
|
106
|
+
return mounted;
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
export function nearMissSpellingOf(name, roster) {
|
|
110
|
+
if (roster.has(name) || catalogToolNames().has(name) || splitNamespacedName(name) !== undefined)
|
|
111
|
+
return undefined;
|
|
112
|
+
return mountedSpellingOf(name, roster);
|
|
113
|
+
}
|
|
114
|
+
export function toolNameFaultOf(name, roster, behavior) {
|
|
115
|
+
const verdict = judgeRuleToolName(name, roster, behavior);
|
|
116
|
+
if (verdict.kind === "unprefixed_namespace")
|
|
117
|
+
return { reason: "tool_name", verdict: verdict.kind };
|
|
118
|
+
if (verdict.kind !== "current") {
|
|
119
|
+
return { reason: "tool_name", verdict: verdict.kind, guidance: verdict.guidance, ...(verdict.currentName !== undefined ? { currentName: verdict.currentName } : {}) };
|
|
120
|
+
}
|
|
121
|
+
const mounted = nearMissSpellingOf(name, roster);
|
|
122
|
+
return mounted === undefined ? undefined : { reason: "near_miss", currentName: mounted };
|
|
123
|
+
}
|
|
101
124
|
export const TOOL_MISS_KINDS = Object.freeze(["unknown", "expected-absent"]);
|
|
102
125
|
const [UNKNOWN_MISS, ABSENT_MISS] = TOOL_MISS_KINDS;
|
|
103
126
|
export function judgeToolNameMiss(name, roster) {
|
|
@@ -127,6 +127,10 @@ export declare const NAME_READINGS: {
|
|
|
127
127
|
readonly kind: "any";
|
|
128
128
|
readonly file: string;
|
|
129
129
|
};
|
|
130
|
+
readonly "policy.persistedToolRule": {
|
|
131
|
+
readonly kind: "any";
|
|
132
|
+
readonly file: string;
|
|
133
|
+
};
|
|
130
134
|
readonly "policy.sensitivePathGuardedByName": {
|
|
131
135
|
readonly kind: "any";
|
|
132
136
|
readonly file: string;
|
|
@@ -33,6 +33,7 @@ export const NAME_READINGS = {
|
|
|
33
33
|
"gate.skillActivationSeat": wire(GATE),
|
|
34
34
|
"policy.permissionRules": any("src/core/permission-rules.ts"),
|
|
35
35
|
"policy.persistedGrammar": any("src/core/permission-rule-model.ts"),
|
|
36
|
+
"policy.persistedToolRule": any("src/core/permission-rule-model.ts"),
|
|
36
37
|
"policy.sensitivePathGuardedByName": any("src/core/sensitive-path-policy.ts"),
|
|
37
38
|
"floor.protectivePathTarget": seat("member", "src/core/tool-registry.ts"),
|
|
38
39
|
"floor.protectiveHostTarget": seat("member", "src/core/host-target.ts"),
|
|
@@ -1125,7 +1125,8 @@ export declare function createUnverifiableDeletePolicy(opts?: {
|
|
|
1125
1125
|
export declare function createTranscriptIntegrityPolicy(opts?: {
|
|
1126
1126
|
/** Transcript directories to protect. Default: `($AGENT_DATA_DIR ?? ~/.ai-agent)/sessions`. */
|
|
1127
1127
|
sessionsDirs?: readonly string[];
|
|
1128
|
-
/**
|
|
1128
|
+
/** The programs a read-only command over the transcript dir may run (default {@link BASH_READONLY_DEFAULT_ALLOW}); it only
|
|
1129
|
+
* narrows the read-only reader — a listed program whose call writes still asks. */
|
|
1129
1130
|
readAllow?: readonly string[];
|
|
1130
1131
|
/** Override the shell tool names this gate parses (default {@link COARSE_SHELL_TOOLS}, the shell face). */
|
|
1131
1132
|
tools?: string[];
|
|
@@ -1328,13 +1329,16 @@ export interface AskRequest {
|
|
|
1328
1329
|
* command whose exact form the validator refuses offers nothing rather than promoting a lone
|
|
1329
1330
|
* prefix to index 0 — see `suggestRulesForCommand`). The seat says "this card has no rule to
|
|
1330
1331
|
* offer you", which is true of both; it does not claim the grammar is empty, and a surface must
|
|
1331
|
-
* not word it as though no rule could ever be written for the command.
|
|
1332
|
+
* not word it as though no rule could ever be written for the command. It is also the word of a
|
|
1333
|
+
* card whose tool's form mints no offer: a path tool's, a shell-face sibling's, and a tool that
|
|
1334
|
+
* answers to one of those under an alias.
|
|
1332
1335
|
*
|
|
1333
1336
|
* ABSENT is not a claim, exactly as {@link previewWithheld}'s absence is not: it also covers the
|
|
1334
|
-
* ordinary "there are offers" case and the
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1337
|
-
* a lane that does not exist. Read
|
|
1337
|
+
* ordinary "there are offers" case and the STRUCTURAL door that answers with silence on purpose — no
|
|
1338
|
+
* rule speaks for this call: no rule lane wired, a task that cannot hold a rule at all, or a call the
|
|
1339
|
+
* lane does not judge (a content-routing tool the engine mounts) — a reason line on every ask of a
|
|
1340
|
+
* deployment that never had the feature would be noise about a lane that does not exist. Read
|
|
1341
|
+
* presence, never absence.
|
|
1338
1342
|
*
|
|
1339
1343
|
* ADVISORY disclosure metadata, never adjudication input: it changes nothing about what is allowed,
|
|
1340
1344
|
* and a surface that ignores it renders exactly the card it rendered before. The durable park row
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -12,6 +12,7 @@ export { policyCannotAnswer, rowDemandsAPerson } from "./real-approval.js";
|
|
|
12
12
|
import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
13
13
|
import { join, normalize as normalizePath, sep } from "node:path";
|
|
14
14
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
15
|
+
import { readOnlyShellVerdict } from "./read-only-shell.js";
|
|
15
16
|
import { protectivePathTargetOf, shellFaceToolNames } from "./tool-registry.js";
|
|
16
17
|
import { heldBy } from "./tool-name-readings.js";
|
|
17
18
|
import { bindableValueOf, boundInputHashOf, describeBindingFault, exactBoundInputHashOf } from "./canonical-json.js";
|
|
@@ -830,14 +831,10 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
830
831
|
const cwdInsideProtected = req.cwd !== undefined && inProtectedDir(req.cwd);
|
|
831
832
|
if (!referencesProtectedDir(cmd) && !cwdInsideProtected)
|
|
832
833
|
return ALLOW;
|
|
833
|
-
const
|
|
834
|
-
if (
|
|
834
|
+
const verdict = readOnlyShellVerdict(command, { cwd: req.cwd, readers: readAllow });
|
|
835
|
+
if (verdict.readOnly)
|
|
835
836
|
return ALLOW;
|
|
836
|
-
return askReason(req, "
|
|
837
|
-
? `\`${parsed.name}\` is not a read-only command${cwdInsideProtected ? " and the shell's working directory is inside the transcript directory" : ""}`
|
|
838
|
-
: cwdInsideProtected
|
|
839
|
-
? "the shell's working directory is inside the transcript directory and this is not a single read-only command (fail-closed)"
|
|
840
|
-
: "the command references the transcript directory and is not a single read-only command (fail-closed)");
|
|
837
|
+
return askReason(req, `${cwdInsideProtected ? "the shell's working directory is inside the transcript directory" : "the command references the transcript directory"} and is not read-only: ${verdict.reason}`);
|
|
841
838
|
}
|
|
842
839
|
const writeTarget = protectivePathTargetOf(req);
|
|
843
840
|
if (writeTarget !== undefined && writeTarget.access !== "read") {
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -984,5 +984,8 @@ export type TraceEvent = {
|
|
|
984
984
|
* await it; the default (absent) hook is a no-op the JIT can elide. Must not throw or do sync I/O.
|
|
985
985
|
*/
|
|
986
986
|
export type TracerHook = (e: TraceEvent) => void;
|
|
987
|
-
/** Invoke a tracer without ever letting it break the task (absent → no-op; throws swallowed).
|
|
987
|
+
/** Invoke a tracer without ever letting it break the task (absent → no-op; throws swallowed). A frame is the engine's own account: it
|
|
988
|
+
* leaves minted HERE, the one throat — every string leaf through the same-principal tier's text pass, member names as written
|
|
989
|
+
* (`egressAccount`) — so a frame that carries a host's, a store's or a remote's words (a thrown text, a failure reason) needs no
|
|
990
|
+
* remembering at its mint site. */
|
|
988
991
|
export declare function emitTrace(tracer: TracerHook | undefined, build: () => TraceEvent): void;
|
package/dist/core/trace.js
CHANGED
|
@@ -60,6 +60,26 @@ export declare function redactHostLeaks(s: string, report?: RedactionReport): Eg
|
|
|
60
60
|
* - cycles and shared members stay shared on the snapshot.
|
|
61
61
|
*/
|
|
62
62
|
export declare function egressValue<T extends EgressTier, V>(tier: T, value: V): V;
|
|
63
|
+
/**
|
|
64
|
+
* Tier `tier`'s pass over an engine ACCOUNT value — a notice's detail, a trace frame: data the engine shaped, carrying words that may have
|
|
65
|
+
* come from a host, a store or a remote. Returned as a new value, never the one handed in. The rules, one each:
|
|
66
|
+
* - a string, at any depth, goes through {@link egressText};
|
|
67
|
+
* - a member NAME of plain data is the engine's own closed word and is kept as written (a detail's `key` or `tokenCount` is not a
|
|
68
|
+
* credential name, and reading it as one would blank the engine's own facts);
|
|
69
|
+
* - plain data is an array or an object whose prototype is `Object.prototype` or `null`, and is not a proxy (whose traps answer what
|
|
70
|
+
* they choose, a fresh value at every read included); its own enumerable members are read once, a function member is code and is
|
|
71
|
+
* not carried;
|
|
72
|
+
* - any other object (an error, a class instance, a `Map`, a `Date`, a proxy) came from elsewhere and leaves as
|
|
73
|
+
* {@link egressValue} reads it — there, and only there, a credential-named member's string is the marker whole, and there, and
|
|
74
|
+
* only there, a member bound applies (one per such value);
|
|
75
|
+
* - no member bound applies to plain data: the account is the engine's own, and its size is the engine's to answer for;
|
|
76
|
+
* - one walk: an object reached twice — through the data and through a value from elsewhere included — is read once and answers one
|
|
77
|
+
* snapshot (cycles and shared members stay shared); past {@link VALUE_DEPTH_MAX} the marker stands in.
|
|
78
|
+
* Every pass it runs is a fixed point, so an account value minted twice reads the same.
|
|
79
|
+
*/
|
|
80
|
+
export declare function egressAccount<T extends EgressTier, V>(tier: T, value: V): V;
|
|
81
|
+
/** Where the markers sit in `s`, as [start, end) spans, leftmost first. */
|
|
82
|
+
export declare function redactionMarkerSpans(s: string): Array<[number, number]>;
|
|
63
83
|
/** The `same-principal` tier's pass + size-bound (NO injection fence): workflow outputs/results/errors riding back to
|
|
64
84
|
* the launching model/script, run rows behind scope-gated observe. Paths/URLs survive (the assertion pattern depends on
|
|
65
85
|
* them); secrets never do. */
|