@sema-agent/core 5.1.0 → 5.2.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 +33 -0
- package/dist/agents/roster-store.js +6 -1
- package/dist/bin/sema-tb.js +3 -4
- package/dist/brain/openai.js +3 -5
- package/dist/brain/terminal-cause.d.ts +1 -1
- package/dist/core/hooks.js +8 -3
- package/dist/core/mcp.d.ts +1 -1
- package/dist/core/memory-engine/dual-root.js +2 -1
- package/dist/core/memory-engine/engine.d.ts +4 -0
- package/dist/core/memory-engine/engine.js +25 -6
- package/dist/core/memory-engine/file-backend.d.ts +7 -5
- package/dist/core/memory-engine/file-backend.js +2 -2
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/layout.d.ts +6 -2
- package/dist/core/memory-engine/layout.js +73 -31
- package/dist/core/memory.js +6 -0
- package/dist/core/protocol-table.d.ts +10 -7
- package/dist/core/protocol-table.js +28 -14
- package/dist/core/runner/prepare-memory.js +4 -4
- package/dist/core/runner/prepare-task.js +30 -14
- package/dist/core/runner/runtask.js +11 -5
- package/dist/core/runner/tool-disclosure.js +6 -1
- package/dist/core/tool-policy.d.ts +1 -0
- package/dist/core/tool-policy.js +12 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/orchestration/run-workflow-tool.js +5 -1
- package/dist/prompt-assembly/assemble.js +0 -1
- package/dist/stores/cc/mailbox-store.js +58 -14
- package/dist/stores/file/file-snapshot-store.d.ts +9 -1
- package/dist/stores/file/file-snapshot-store.js +28 -5
- package/dist/stores/file/fs-atomic.d.ts +4 -1
- package/dist/stores/file/fs-atomic.js +2 -1
- package/dist/stores/file/index.d.ts +10 -3
- package/dist/stores/file/index.js +4 -3
- package/dist/stores/file/mailbox-store.d.ts +5 -0
- package/dist/stores/file/mailbox-store.js +15 -3
- package/dist/stores/file/session-policy-store.d.ts +11 -8
- package/dist/stores/file/session-policy-store.js +21 -4
- package/dist/stores/file/session-store.d.ts +9 -1
- package/dist/stores/file/session-store.js +19 -4
- package/dist/tools/fs/fs-search-tools.js +9 -0
- package/dist/tools/fs/fs-shared.d.ts +1 -1
- package/dist/tools/fs/fs-shared.js +1 -1
- package/dist/tools/todo.js +13 -6
- package/package.json +1 -1
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
const NAME_SEP = "__";
|
|
2
|
+
const MCP_PREFIX_NAME = `mcp${NAME_SEP}`;
|
|
3
|
+
const parseMcpName = (name) => {
|
|
4
|
+
if (!name.startsWith(MCP_PREFIX_NAME))
|
|
5
|
+
return undefined;
|
|
6
|
+
const rest = name.slice(MCP_PREFIX_NAME.length);
|
|
7
|
+
const sep = rest.indexOf(NAME_SEP);
|
|
8
|
+
if (sep <= 0)
|
|
9
|
+
return undefined;
|
|
10
|
+
const tool = rest.slice(sep + NAME_SEP.length);
|
|
11
|
+
if (tool.length === 0)
|
|
12
|
+
return undefined;
|
|
13
|
+
return { peer: rest.slice(0, sep), tool };
|
|
14
|
+
};
|
|
15
|
+
const makeMcpName = (peer, tool) => {
|
|
16
|
+
if (peer.length === 0 || tool.length === 0) {
|
|
17
|
+
throw new Error(`protocol table (mcp): peer and tool must both be non-empty (got peer=${JSON.stringify(peer)}, tool=${JSON.stringify(tool)})`);
|
|
18
|
+
}
|
|
19
|
+
if (peer.includes(NAME_SEP)) {
|
|
20
|
+
throw new Error(`protocol table (mcp): peer must not contain the ${NAME_SEP} separator (got ${JSON.stringify(peer)}) — the composed name would parse back as a DIFFERENT peer`);
|
|
21
|
+
}
|
|
22
|
+
return `${MCP_PREFIX_NAME}${peer}${NAME_SEP}${tool}`;
|
|
23
|
+
};
|
|
2
24
|
export const MCP_NAMESPACE = {
|
|
3
25
|
id: "mcp",
|
|
4
|
-
prefix:
|
|
5
|
-
makeName:
|
|
6
|
-
parse
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const sep = rest.indexOf(NAME_SEP);
|
|
11
|
-
if (sep <= 0)
|
|
12
|
-
return undefined;
|
|
13
|
-
return { peer: rest.slice(0, sep), tool: rest.slice(sep + NAME_SEP.length) };
|
|
14
|
-
},
|
|
15
|
-
displayGroupKey(name) {
|
|
16
|
-
const p = this.parse(name);
|
|
17
|
-
return p === undefined ? name : `${this.prefix}${p.peer}${NAME_SEP}*`;
|
|
26
|
+
prefix: MCP_PREFIX_NAME,
|
|
27
|
+
makeName: makeMcpName,
|
|
28
|
+
parse: parseMcpName,
|
|
29
|
+
displayGroupKey: (name) => {
|
|
30
|
+
const p = parseMcpName(name);
|
|
31
|
+
return p === undefined ? name : `${MCP_PREFIX_NAME}${p.peer}${NAME_SEP}*`;
|
|
18
32
|
},
|
|
19
33
|
};
|
|
20
34
|
export const PROTOCOL_TABLE = [MCP_NAMESPACE];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { adoptLegacyRepoDirs,
|
|
1
|
+
import { adoptLegacyRepoDirs, deriveRepoControlPlaneDir, deriveProjectControlDir, deriveProjectMemoryDir, deriveRepoMemoryDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, lookupProjectIdHint, recordProjectIdHint, resolveMemoryEngineRoot } from "../memory-engine/layout.js";
|
|
2
2
|
import { classifyScopePlanes, derivePersonalControlDir, derivePersonalMemoryDir, mergeHarvestReports, mergeInjections, needsDualRoots, parsedProjectPlane } from "../memory-engine/dual-root.js";
|
|
3
3
|
import { normalizeMemorySpec } from "../memory.js";
|
|
4
4
|
import { MemoryEngine } from "../memory-engine/engine.js";
|
|
@@ -38,7 +38,7 @@ export async function prepareMemory(input) {
|
|
|
38
38
|
recordProjectIdHint(engineRoot, repoRoot, marker.projectId);
|
|
39
39
|
if (firstSwitch) {
|
|
40
40
|
try {
|
|
41
|
-
const oldCtl =
|
|
41
|
+
const oldCtl = deriveRepoControlPlaneDir(engineRoot, repoRoot);
|
|
42
42
|
const newCtl = deriveProjectControlDir(engineRoot, marker.projectId);
|
|
43
43
|
const drained = drainMemoryAnnouncements(oldCtl);
|
|
44
44
|
for (const ann of drained.queue)
|
|
@@ -100,7 +100,7 @@ export async function prepareMemory(input) {
|
|
|
100
100
|
const projectEngine = new MemoryEngine({
|
|
101
101
|
backend,
|
|
102
102
|
memoryDir,
|
|
103
|
-
controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) :
|
|
103
|
+
controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot),
|
|
104
104
|
});
|
|
105
105
|
const personalEngine = createPersonalEngine();
|
|
106
106
|
const p = planes;
|
|
@@ -140,7 +140,7 @@ export async function prepareMemory(input) {
|
|
|
140
140
|
const engine = new MemoryEngine({
|
|
141
141
|
backend,
|
|
142
142
|
memoryDir,
|
|
143
|
-
controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) :
|
|
143
|
+
controlDir: identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot),
|
|
144
144
|
});
|
|
145
145
|
const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope);
|
|
146
146
|
writeEngine = engine;
|
|
@@ -16,7 +16,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents
|
|
|
16
16
|
import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
|
|
17
17
|
import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
18
18
|
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
19
|
-
import { combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
19
|
+
import { combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
20
20
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
21
21
|
import { computeCallCap, createCallCapRef, softExecDeadlineMs, toolCutDeadlineMs, WALLTIME_STALL_CONNECT_MS, WALLTIME_STALL_FIRST_TOKEN_MS, WALLTIME_STALL_IDLE_MS } from "./call-cap.js";
|
|
22
22
|
import { createCutKillRegistry } from "./cut-kill.js";
|
|
@@ -2364,7 +2364,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2364
2364
|
toolName: creq.toolName,
|
|
2365
2365
|
toolCallId: creq.toolCallId,
|
|
2366
2366
|
args: editArgs,
|
|
2367
|
-
message: re.message ??
|
|
2367
|
+
message: re.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2368
2368
|
...askSourceIdentity(),
|
|
2369
2369
|
...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
2370
2370
|
}, onAskOf, csignal ?? abortController.signal);
|
|
@@ -2410,7 +2410,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2410
2410
|
toolName: creq.toolName,
|
|
2411
2411
|
toolCallId: creq.toolCallId,
|
|
2412
2412
|
args: presentedArgs,
|
|
2413
|
-
message: first.message ??
|
|
2413
|
+
message: first.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2414
2414
|
...askSourceIdentity(),
|
|
2415
2415
|
...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
2416
2416
|
}, pc.onAsk, csignal ?? abortController.signal);
|
|
@@ -2466,7 +2466,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2466
2466
|
toolName: creq.toolName,
|
|
2467
2467
|
toolCallId: creq.toolCallId,
|
|
2468
2468
|
args: presentedArgs,
|
|
2469
|
-
message: decision.message ??
|
|
2469
|
+
message: decision.message ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2470
2470
|
...askSourceIdentity(),
|
|
2471
2471
|
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
2472
2472
|
}, pc.onAsk, csignal ?? abortController.signal);
|
|
@@ -2706,7 +2706,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2706
2706
|
const preview = approvalPreviewOf(req.toolName, req.args);
|
|
2707
2707
|
return preview !== undefined ? { preview } : {};
|
|
2708
2708
|
})(),
|
|
2709
|
-
message: decision.message ??
|
|
2709
|
+
message: decision.message ?? `approval required for "${req.toolName}"`,
|
|
2710
2710
|
...askSourceIdentity(),
|
|
2711
2711
|
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
2712
2712
|
}, onAsk, abortController.signal);
|
|
@@ -2750,17 +2750,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2750
2750
|
const checkpointStore = spec.checkpointStore ?? deps.checkpointStore;
|
|
2751
2751
|
const durableApproval = spec.durableApproval ??
|
|
2752
2752
|
(runtimeCaps?.forceDurableGate ? { scope: spec.principal || DEFAULT_IRREVERSIBLE_SCOPE } : undefined);
|
|
2753
|
-
const
|
|
2753
|
+
const inFlightSpendMicroUsd = () => {
|
|
2754
|
+
const own = liveSpendRef.get?.().costMicroUsd ?? 0;
|
|
2755
|
+
const seededNested = resume?.seed.nestedStats.costMicroUsd;
|
|
2756
|
+
const nestedDelta = nestedStats.costMicroUsd - (typeof seededNested === "number" && Number.isFinite(seededNested) ? seededNested : 0);
|
|
2757
|
+
return (Number.isFinite(own) ? own : 0) + Math.max(0, Number.isFinite(nestedDelta) ? nestedDelta : 0);
|
|
2758
|
+
};
|
|
2759
|
+
const repairBundleForCheckpoint = (parkedSpendMicroUsd) => {
|
|
2760
|
+
const carried = internals?.repairBundle !== undefined
|
|
2761
|
+
? structuredClone(internals.repairBundle)
|
|
2762
|
+
: resume?.seed.repairBundle !== undefined
|
|
2763
|
+
? structuredClone(resume.seed.repairBundle)
|
|
2764
|
+
: undefined;
|
|
2765
|
+
if (carried === undefined)
|
|
2766
|
+
return undefined;
|
|
2767
|
+
if (parkedSpendMicroUsd === undefined || !Number.isFinite(parkedSpendMicroUsd) || parkedSpendMicroUsd <= 0)
|
|
2768
|
+
return carried;
|
|
2769
|
+
const prior = typeof carried.spentMicroUsd === "number" && Number.isFinite(carried.spentMicroUsd) ? Math.max(0, carried.spentMicroUsd) : 0;
|
|
2770
|
+
carried.spentMicroUsd = prior + parkedSpendMicroUsd;
|
|
2771
|
+
return carried;
|
|
2772
|
+
};
|
|
2773
|
+
const serializeCheckpointState = (workspaceHandle, parkedSpendMicroUsd) => ({
|
|
2754
2774
|
activeTools: [...activeTools],
|
|
2755
2775
|
outputRef: { value: outputRef.value, set: outputRef.set },
|
|
2756
2776
|
nestedStats: { ...nestedStats },
|
|
2757
2777
|
consolidationNotes: undefined,
|
|
2758
2778
|
readFileState: readFileStateForCheckpoint ? [...readFileStateForCheckpoint.entries()] : undefined,
|
|
2759
|
-
repairBundle:
|
|
2760
|
-
? structuredClone(internals.repairBundle)
|
|
2761
|
-
: resume?.seed.repairBundle !== undefined
|
|
2762
|
-
? structuredClone(resume.seed.repairBundle)
|
|
2763
|
-
: undefined,
|
|
2779
|
+
repairBundle: repairBundleForCheckpoint(parkedSpendMicroUsd),
|
|
2764
2780
|
workspaceHandle,
|
|
2765
2781
|
handsCwd: handsCwdRef?.current,
|
|
2766
2782
|
activeWorktree: worktreeSessionRef?.current ? { ...worktreeSessionRef.current } : undefined,
|
|
@@ -2978,7 +2994,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2978
2994
|
leafId,
|
|
2979
2995
|
gate,
|
|
2980
2996
|
pendingAction: { kind: "plan_review" },
|
|
2981
|
-
state: serializeCheckpointState(remoteHandle),
|
|
2997
|
+
state: serializeCheckpointState(remoteHandle, inFlightSpendMicroUsd()),
|
|
2982
2998
|
status: "pending",
|
|
2983
2999
|
createdAt: mintedAt,
|
|
2984
3000
|
suspendedAt: now(),
|
|
@@ -3092,7 +3108,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3092
3108
|
};
|
|
3093
3109
|
const scope = durableApproval?.scope || checkpointScopeOf({ principal: spec.principal });
|
|
3094
3110
|
const ttlMs = sanitizedTtlMs(durableApproval?.ttlMs);
|
|
3095
|
-
const checkpointState = serializeCheckpointState(remoteHandle);
|
|
3111
|
+
const checkpointState = serializeCheckpointState(remoteHandle, inFlightSpendMicroUsd());
|
|
3096
3112
|
const approvalLedger = debitLedger(priorLedger, liveSpendRef.get?.() ?? { costMicroUsd: 0, tokens: 0, turns: 0, walltimeMs: 0 }, resourceTotal, { countSlice: false });
|
|
3097
3113
|
const mintedAt = Date.now();
|
|
3098
3114
|
cp = {
|
|
@@ -3396,7 +3412,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3396
3412
|
? undefined
|
|
3397
3413
|
: async (path) => {
|
|
3398
3414
|
try {
|
|
3399
|
-
const d = await denyNarrowingPolicy.check({ toolName: "Read", args: { file_path: path }, toolCallId: "changed-files-visibility-probe" }, abortController.signal);
|
|
3415
|
+
const d = refuseOutOfContractDecision(await denyNarrowingPolicy.check({ toolName: "Read", args: { file_path: path }, toolCallId: "changed-files-visibility-probe" }, abortController.signal));
|
|
3400
3416
|
return d.action === "deny";
|
|
3401
3417
|
}
|
|
3402
3418
|
catch {
|
|
@@ -36,7 +36,7 @@ import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "../unt
|
|
|
36
36
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
37
37
|
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
38
38
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
39
|
-
import { toolPolicyNameSets } from "../tool-policy.js";
|
|
39
|
+
import { refuseOutOfContractDecision, toolPolicyNameSets } from "../tool-policy.js";
|
|
40
40
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
41
41
|
import { discloseDroppedPending, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
42
42
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
@@ -1674,13 +1674,19 @@ export class Runner {
|
|
|
1674
1674
|
}
|
|
1675
1675
|
rs.telemetry.taskStart = Date.now();
|
|
1676
1676
|
rs.telemetry.taskStartMonotonic = performance.now();
|
|
1677
|
-
|
|
1677
|
+
const discloseNoteTaskRunFailure = (err) => {
|
|
1678
1678
|
try {
|
|
1679
1679
|
this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "config", sessionId: prepared.sessionId });
|
|
1680
1680
|
}
|
|
1681
1681
|
catch {
|
|
1682
1682
|
}
|
|
1683
|
-
}
|
|
1683
|
+
};
|
|
1684
|
+
try {
|
|
1685
|
+
void Promise.resolve(this.sessions.noteTaskRun?.(prepared.sessionId, rs.telemetry.taskId)).catch(discloseNoteTaskRunFailure);
|
|
1686
|
+
}
|
|
1687
|
+
catch (err) {
|
|
1688
|
+
discloseNoteTaskRunFailure(err);
|
|
1689
|
+
}
|
|
1684
1690
|
rs.degrade.recordDegraded = (info, toModel) => {
|
|
1685
1691
|
if (rs.degrade.degraded !== undefined)
|
|
1686
1692
|
return;
|
|
@@ -3347,7 +3353,7 @@ export class Runner {
|
|
|
3347
3353
|
return;
|
|
3348
3354
|
}
|
|
3349
3355
|
if (outcome.decision === "allow" && outcome.updatedInput !== undefined && prepared.basePolicyForResumeEdit) {
|
|
3350
|
-
const rechecked = await prepared.basePolicyForResumeEdit.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal);
|
|
3356
|
+
const rechecked = refuseOutOfContractDecision(await prepared.basePolicyForResumeEdit.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal));
|
|
3351
3357
|
if (rechecked.action === "deny") {
|
|
3352
3358
|
emitEnd(true);
|
|
3353
3359
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(`The approver EDITED this call's input; the edited call is denied by the deployment's tool policy and was not executed${rechecked.message ? `: ${rechecked.message}` : ""}.`), true));
|
|
@@ -3356,7 +3362,7 @@ export class Runner {
|
|
|
3356
3362
|
}
|
|
3357
3363
|
}
|
|
3358
3364
|
if (prepared.denyNarrowingPolicy) {
|
|
3359
|
-
const narrowed = await prepared.denyNarrowingPolicy.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal);
|
|
3365
|
+
const narrowed = refuseOutOfContractDecision(await prepared.denyNarrowingPolicy.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal));
|
|
3360
3366
|
if (narrowed.action === "deny") {
|
|
3361
3367
|
emitEnd(true);
|
|
3362
3368
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`), true));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { Value } from "typebox/value";
|
|
3
|
-
import { defineTool } from "../tools.js";
|
|
3
|
+
import { defineTool, errorResult } from "../tools.js";
|
|
4
4
|
export const TOOL_SEARCH_NAME = "ToolSearch";
|
|
5
5
|
const DEFER_AUTO_FRACTION = 0.1;
|
|
6
6
|
const CHARS_PER_TOKEN = 4;
|
|
@@ -247,6 +247,11 @@ export function createToolSearchTool(opts) {
|
|
|
247
247
|
}),
|
|
248
248
|
effect: "read",
|
|
249
249
|
execute: async (raw) => {
|
|
250
|
+
const staleSelect = (raw ?? {});
|
|
251
|
+
if (staleSelect.select !== undefined) {
|
|
252
|
+
return errorResult(`Error (${TOOL_SEARCH_NAME}): \`select\` is not a parameter (the array form is retired) — put the ` +
|
|
253
|
+
`selection in \`query\` instead: {"query":"select:ToolA,ToolB"}. Nothing was activated.`);
|
|
254
|
+
}
|
|
250
255
|
const args = (raw ?? {});
|
|
251
256
|
const { matched, missing } = resolveToolSearchDetailed(args, registry);
|
|
252
257
|
const mounted = mountedNames?.() ?? new Set();
|
|
@@ -30,6 +30,7 @@ export declare function decisionText(d: PermissionResult): string | undefined;
|
|
|
30
30
|
export interface ToolPolicy {
|
|
31
31
|
check(req: ToolCallRequest, signal?: AbortSignal): PermissionResult | Promise<PermissionResult>;
|
|
32
32
|
}
|
|
33
|
+
export declare function refuseOutOfContractDecision(d: PermissionResult): PermissionResult;
|
|
33
34
|
export interface ToolPolicyNameSets {
|
|
34
35
|
readonly allow?: readonly string[];
|
|
35
36
|
readonly deny?: readonly string[];
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -6,6 +6,16 @@ export function decisionText(d) {
|
|
|
6
6
|
return d.message;
|
|
7
7
|
}
|
|
8
8
|
const ALLOW = { action: "allow" };
|
|
9
|
+
const RETIRED_TEXT_FIELD = "reason";
|
|
10
|
+
const RETIRED_TEXT_FIELD_DENY_MESSAGE = `a permission decision carries the retired "${RETIRED_TEXT_FIELD}" field — rename it to "message" (the one text field ` +
|
|
11
|
+
`a decision carries); denied fail-closed rather than executing a decision whose text this layer cannot read`;
|
|
12
|
+
export function refuseOutOfContractDecision(d) {
|
|
13
|
+
if (typeof d !== "object" || d === null)
|
|
14
|
+
return d;
|
|
15
|
+
if (!Object.prototype.hasOwnProperty.call(d, RETIRED_TEXT_FIELD))
|
|
16
|
+
return d;
|
|
17
|
+
return { action: "deny", message: RETIRED_TEXT_FIELD_DENY_MESSAGE, decisionReason: "rule" };
|
|
18
|
+
}
|
|
9
19
|
function withTimeout(p, ms, onTimeout) {
|
|
10
20
|
if (ms === undefined) {
|
|
11
21
|
return p;
|
|
@@ -86,7 +96,7 @@ export function createApprovalPolicy(opts) {
|
|
|
86
96
|
if (okRaw === true)
|
|
87
97
|
return ALLOW;
|
|
88
98
|
if (okRaw !== false) {
|
|
89
|
-
return { action: "deny", message: `approval callback for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (return true
|
|
99
|
+
return { action: "deny", message: `approval callback for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (this policy's \`approve\` returns a boolean: return true or false)` };
|
|
90
100
|
}
|
|
91
101
|
return { action: "deny", message: `approval denied for "${req.toolName}"` };
|
|
92
102
|
}
|
|
@@ -106,7 +116,7 @@ export function combinePolicies(...policies) {
|
|
|
106
116
|
let current = req;
|
|
107
117
|
let rewrite;
|
|
108
118
|
for (const p of policies) {
|
|
109
|
-
const d = await p.check(current, signal);
|
|
119
|
+
const d = refuseOutOfContractDecision(await p.check(current, signal));
|
|
110
120
|
if (d.action === "deny") {
|
|
111
121
|
return rewrite?.updatedInput !== undefined ? { ...d, updatedInput: rewrite.updatedInput } : d;
|
|
112
122
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpoi
|
|
|
77
77
|
export { InMemoryFileSnapshotStore, DEFAULT_SNAPSHOT_BOUNDS } from "./core/file-snapshot-store.js";
|
|
78
78
|
export { captureManifest, applyManifest } from "./core/file-snapshot-store.js";
|
|
79
79
|
export type { FileSnapshotStore, FileSnapshotResult, FileSnapshotError, FileSnapshotBounds } from "./core/file-snapshot-store.js";
|
|
80
|
-
export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileFileSnapshotStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, type FileStorageBackendOptions, type FileCheckpointStoreOptions, } from "./stores/file/index.js";
|
|
80
|
+
export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileFileSnapshotStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, type FileStorageBackendOptions, type FileStorageCorruptReadInfo, type FileSessionRepoOptions, type FileFileSnapshotStoreOptions, type FileCheckpointStoreOptions, } from "./stores/file/index.js";
|
|
81
81
|
export { CacheBreakDetector, type CacheBreakFinding, type ToolFingerprintInput } from "./core/cache-break-detector.js";
|
|
82
82
|
export { maybeCompact, type MaybeCompactOptions, type CompactionWindowSafetyInfo } from "./core/auto-compaction.js";
|
|
83
83
|
export { brainToRuntime } from "./core/runtime.js";
|
|
@@ -113,7 +113,7 @@ export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/au
|
|
|
113
113
|
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, type PermissionRule, type ParsedPermissionRule, type PermissionRuleIssue, type PermissionRuleCaps, type PermissionRulePolicyOptions, } from "./core/permission-rules.js";
|
|
114
114
|
export { formatHookFeedback, runToolGate, type Hooks, type HookToolContext, type HookToolOutput, type PreToolUseResult, type PostToolUseResult, type UserPromptSubmitResult, type HookToolFailure, type PostToolUseFailureResult, type PostToolBatchCall, type PostToolBatchResult, type PreCompactContext, type PreCompactResult, type PostCompactContext, type StopFailureContext, type PermissionDeniedPayload, type PermissionDeniedSource, } from "./core/hooks.js";
|
|
115
115
|
export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, } from "./core/memory.js";
|
|
116
|
-
export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
|
|
116
|
+
export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
|
|
117
117
|
export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
|
|
118
118
|
export { encodeSurfacedKey, buildManifestText, validateSelectedIds, composeSelectiveBody, formatMemoryAge, resolveLinkedIds, RECALL_CAVEAT, DEFAULT_MAX_SELECTED, DEFAULT_MAX_LINKED, type MemorySelector, type MemorySelectRequest, type SelectiveRecallOptions, type SelectiveRecallResult, type LayeredRecallOptions, type LayeredRecallResult, type ScopedNoteHeader, type ScopedNoteRecord, } from "./core/memory-recall.js";
|
|
119
119
|
export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATION_SEARCH_LIMIT, DEFAULT_CONSOLIDATION_MAX_NOTES, DEFAULT_CONSOLIDATION_TIMEOUT_SEC, normalizeForExactMatch, type ConsolidationParams, type ConsolidationStats, type ConsolidationNote, type ConsolidationLLM, } from "./core/runner/memory-consolidation.js";
|
package/dist/index.js
CHANGED
|
@@ -100,7 +100,7 @@ export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/au
|
|
|
100
100
|
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, } from "./core/permission-rules.js";
|
|
101
101
|
export { formatHookFeedback, runToolGate, } from "./core/hooks.js";
|
|
102
102
|
export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, } from "./core/memory.js";
|
|
103
|
-
export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
|
|
103
|
+
export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
|
|
104
104
|
export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
|
|
105
105
|
export { encodeSurfacedKey, buildManifestText, validateSelectedIds, composeSelectiveBody, formatMemoryAge, resolveLinkedIds, RECALL_CAVEAT, DEFAULT_MAX_SELECTED, DEFAULT_MAX_LINKED, } from "./core/memory-recall.js";
|
|
106
106
|
export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATION_SEARCH_LIMIT, DEFAULT_CONSOLIDATION_MAX_NOTES, DEFAULT_CONSOLIDATION_TIMEOUT_SEC, normalizeForExactMatch, } from "./core/runner/memory-consolidation.js";
|
|
@@ -338,6 +338,10 @@ export async function createRunWorkflowTool(d) {
|
|
|
338
338
|
catch (err) {
|
|
339
339
|
return structuredError(`failed to resolve workflow name: ${redactSecrets(err instanceof Error ? err.message : String(err)).slice(0, 300)}`);
|
|
340
340
|
}
|
|
341
|
+
const resolvedShape = resolved;
|
|
342
|
+
if (resolvedShape !== undefined && (resolvedShape === null || typeof resolvedShape !== "object")) {
|
|
343
|
+
return structuredError("the wired workflow script store's resolveName returned the retired bare-string form — the resolution shape is now { script, defaultArgs? } (one shape); upgrade the store implementation");
|
|
344
|
+
}
|
|
341
345
|
}
|
|
342
346
|
if (resolved === undefined && builtinsEnabled) {
|
|
343
347
|
resolved = resolveBuiltinWorkflow(rawName);
|
|
@@ -347,7 +351,7 @@ export async function createRunWorkflowTool(d) {
|
|
|
347
351
|
script = resolved.script;
|
|
348
352
|
if ("defaultArgs" in resolved)
|
|
349
353
|
registeredDefaultArgs = resolved.defaultArgs;
|
|
350
|
-
if (typeof resolved
|
|
354
|
+
if (typeof resolved.stringArgKey === "string" && resolved.stringArgKey.length > 0) {
|
|
351
355
|
registeredStringArgKey = resolved.stringArgKey;
|
|
352
356
|
}
|
|
353
357
|
}
|
|
@@ -101,7 +101,6 @@ export function assemblePrompt(inputs) {
|
|
|
101
101
|
userSystemPrompt: inputs.userSystemPrompt,
|
|
102
102
|
userAppendSystemPrompt: inputs.userAppendSystemPrompt,
|
|
103
103
|
tools: inputs.tools,
|
|
104
|
-
consolidationEnabled: false,
|
|
105
104
|
...facts,
|
|
106
105
|
};
|
|
107
106
|
let pack = SEMA_DEFAULT_PACK;
|
|
@@ -34,28 +34,66 @@ export function createCcFileMailboxStore(opts) {
|
|
|
34
34
|
catch {
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
-
const
|
|
37
|
+
const deferredNotes = () => {
|
|
38
|
+
const pending = [];
|
|
39
|
+
return {
|
|
40
|
+
note: (path, reason) => {
|
|
41
|
+
pending.push({ path, reason });
|
|
42
|
+
},
|
|
43
|
+
flush: () => {
|
|
44
|
+
const batch = pending.splice(0, pending.length);
|
|
45
|
+
for (const n of batch)
|
|
46
|
+
discloseCorrupt(n.path, n.reason);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const entryDefect = (e) => {
|
|
51
|
+
if (e === null)
|
|
52
|
+
return "null entry";
|
|
53
|
+
if (Array.isArray(e))
|
|
54
|
+
return "array entry";
|
|
55
|
+
if (typeof e !== "object")
|
|
56
|
+
return `${typeof e} entry`;
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
const loadBox = (path, note) => {
|
|
38
60
|
let raw;
|
|
39
61
|
try {
|
|
40
62
|
raw = readFileSync(path, "utf8");
|
|
41
63
|
}
|
|
42
64
|
catch (err) {
|
|
43
65
|
if (err.code !== "ENOENT")
|
|
44
|
-
|
|
66
|
+
note(path, `read failed (non-ENOENT): ${err.message}`);
|
|
45
67
|
return [];
|
|
46
68
|
}
|
|
69
|
+
let parsed;
|
|
47
70
|
try {
|
|
48
|
-
|
|
49
|
-
if (!Array.isArray(parsed)) {
|
|
50
|
-
discloseCorrupt(path, "inbox document is not an array");
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
return parsed;
|
|
71
|
+
parsed = JSON.parse(raw);
|
|
54
72
|
}
|
|
55
73
|
catch {
|
|
56
|
-
|
|
74
|
+
note(path, "unparseable inbox JSON (torn write?)");
|
|
57
75
|
return [];
|
|
58
76
|
}
|
|
77
|
+
if (!Array.isArray(parsed)) {
|
|
78
|
+
note(path, "inbox document is not an array");
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const kept = [];
|
|
82
|
+
let dropped = 0;
|
|
83
|
+
let firstReason;
|
|
84
|
+
for (const e of parsed) {
|
|
85
|
+
const defect = entryDefect(e);
|
|
86
|
+
if (defect === undefined) {
|
|
87
|
+
kept.push(e);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
dropped += 1;
|
|
91
|
+
if (firstReason === undefined)
|
|
92
|
+
firstReason = defect;
|
|
93
|
+
}
|
|
94
|
+
if (dropped > 0)
|
|
95
|
+
note(path, `${dropped} malformed inbox entries skipped (first: ${firstReason})`);
|
|
96
|
+
return kept;
|
|
59
97
|
};
|
|
60
98
|
const saveBox = (path, box) => {
|
|
61
99
|
atomicWriteFile(inboxDir, path, JSON.stringify(box, null, 2));
|
|
@@ -65,8 +103,9 @@ export function createCcFileMailboxStore(opts) {
|
|
|
65
103
|
requireDefaultScope(scope);
|
|
66
104
|
mkdirSync(inboxDir, { recursive: true });
|
|
67
105
|
const path = inboxPath(handle);
|
|
106
|
+
const notes = deferredNotes();
|
|
68
107
|
return withCcLock(path, () => {
|
|
69
|
-
const box = loadBox(path);
|
|
108
|
+
const box = loadBox(path, notes.note);
|
|
70
109
|
box.push({
|
|
71
110
|
type: "message",
|
|
72
111
|
...(msg.from !== undefined ? { from: msg.from } : {}),
|
|
@@ -78,6 +117,8 @@ export function createCcFileMailboxStore(opts) {
|
|
|
78
117
|
});
|
|
79
118
|
saveBox(path, box);
|
|
80
119
|
return box.length;
|
|
120
|
+
}).finally(() => {
|
|
121
|
+
notes.flush();
|
|
81
122
|
});
|
|
82
123
|
},
|
|
83
124
|
claimLease: (scope, handle, owner, ttlMs, now) => {
|
|
@@ -87,7 +128,7 @@ export function createCcFileMailboxStore(opts) {
|
|
|
87
128
|
const held = leases.get(key);
|
|
88
129
|
if (held !== undefined && held.expiresAt > t && held.owner !== owner)
|
|
89
130
|
return Promise.resolve(null);
|
|
90
|
-
const box = loadBox(key);
|
|
131
|
+
const box = loadBox(key, discloseCorrupt);
|
|
91
132
|
const maxSeq = box.length;
|
|
92
133
|
const pending = box
|
|
93
134
|
.map((e, i) => ({ e, seq: i + 1 }))
|
|
@@ -111,8 +152,9 @@ export function createCcFileMailboxStore(opts) {
|
|
|
111
152
|
return Promise.resolve();
|
|
112
153
|
if (!existsSync(path))
|
|
113
154
|
return Promise.resolve();
|
|
155
|
+
const notes = deferredNotes();
|
|
114
156
|
return withCcLock(path, () => {
|
|
115
|
-
const box = loadBox(path);
|
|
157
|
+
const box = loadBox(path, notes.note);
|
|
116
158
|
let changed = false;
|
|
117
159
|
for (let i = 0; i < Math.min(upToSeq, box.length); i++) {
|
|
118
160
|
if (box[i].read !== true) {
|
|
@@ -124,6 +166,8 @@ export function createCcFileMailboxStore(opts) {
|
|
|
124
166
|
saveBox(path, box);
|
|
125
167
|
if (held.maxSeq <= upToSeq)
|
|
126
168
|
leases.delete(path);
|
|
169
|
+
}).finally(() => {
|
|
170
|
+
notes.flush();
|
|
127
171
|
});
|
|
128
172
|
},
|
|
129
173
|
releaseLease: (scope, handle, owner) => {
|
|
@@ -136,7 +180,7 @@ export function createCcFileMailboxStore(opts) {
|
|
|
136
180
|
},
|
|
137
181
|
peekCount: (scope, handle) => {
|
|
138
182
|
requireDefaultScope(scope);
|
|
139
|
-
return Promise.resolve(loadBox(inboxPath(handle)).filter((e) => e.read !== true).length);
|
|
183
|
+
return Promise.resolve(loadBox(inboxPath(handle), discloseCorrupt).filter((e) => e.read !== true).length);
|
|
140
184
|
},
|
|
141
185
|
drop: (scope, handle) => {
|
|
142
186
|
requireDefaultScope(scope);
|
|
@@ -160,7 +204,7 @@ export function createCcFileMailboxStore(opts) {
|
|
|
160
204
|
if (!f.endsWith(".json"))
|
|
161
205
|
continue;
|
|
162
206
|
const path = join(inboxDir, f);
|
|
163
|
-
const box = loadBox(path);
|
|
207
|
+
const box = loadBox(path, discloseCorrupt);
|
|
164
208
|
const newest = box.reduce((acc, e) => {
|
|
165
209
|
const t = typeof e.timestamp === "string" ? Date.parse(e.timestamp) : Number.NaN;
|
|
166
210
|
return Number.isFinite(t) ? Math.max(acc, t) : Number.POSITIVE_INFINITY;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
2
|
import { type FileSnapshotBounds, type FileSnapshotResult, type FileSnapshotStore } from "../../core/file-snapshot-store.js";
|
|
3
|
+
export interface FileFileSnapshotStoreOptions {
|
|
4
|
+
onCorruptRead?: (info: {
|
|
5
|
+
path: string;
|
|
6
|
+
reason: string;
|
|
7
|
+
}) => void;
|
|
8
|
+
}
|
|
3
9
|
export declare class FileFileSnapshotStore implements FileSnapshotStore {
|
|
4
10
|
private readonly base;
|
|
5
11
|
private readonly blobsDir;
|
|
@@ -7,7 +13,9 @@ export declare class FileFileSnapshotStore implements FileSnapshotStore {
|
|
|
7
13
|
private get inFlight();
|
|
8
14
|
private readonly inFlightKey;
|
|
9
15
|
private readonly bounds;
|
|
10
|
-
|
|
16
|
+
private readonly onCorruptRead;
|
|
17
|
+
constructor(root: string, bounds?: Partial<FileSnapshotBounds>, opts?: FileFileSnapshotStoreOptions);
|
|
18
|
+
private disclose;
|
|
11
19
|
private scopeDir;
|
|
12
20
|
private manifestPath;
|
|
13
21
|
private tryManifestPath;
|