@sema-agent/core 3.0.0 → 5.0.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 +2382 -0
- package/dist/agents/cascade.d.ts +3 -1
- package/dist/agents/cascade.js +31 -32
- package/dist/agents/cumulative-stats.d.ts +29 -0
- package/dist/agents/cumulative-stats.js +29 -0
- package/dist/agents/observer.d.ts +4 -0
- package/dist/agents/observer.js +11 -10
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +48 -24
- package/dist/agents/retain-ledger.d.ts +2 -0
- package/dist/agents/retain-ledger.js +6 -5
- package/dist/agents/roster-store.d.ts +1 -0
- package/dist/agents/roster-store.js +3 -5
- package/dist/agents/send-message-tool.d.ts +1 -0
- package/dist/agents/send-message-tool.js +2 -0
- package/dist/agents/subagent.d.ts +3 -1
- package/dist/agents/subagent.js +79 -94
- package/dist/agents/teacher.d.ts +1 -9
- package/dist/agents/teacher.js +21 -24
- package/dist/agents/tool-filter.js +6 -7
- package/dist/agents/verify.d.ts +1 -0
- package/dist/agents/verify.js +15 -14
- package/dist/brain/anthropic.js +24 -21
- package/dist/brain/openai.js +24 -21
- package/dist/brain/stream-engine.js +5 -2
- package/dist/brain/stream-shared.d.ts +1 -0
- package/dist/brain/stream-shared.js +14 -0
- package/dist/brain/terminal-cause.d.ts +4 -0
- package/dist/brain/terminal-cause.js +12 -0
- package/dist/brain/tool-call-id.d.ts +1 -0
- package/dist/brain/tool-call-id.js +3 -0
- package/dist/brain/tool-call-repair.js +2 -1
- package/dist/core/arg-summary.d.ts +21 -1
- package/dist/core/arg-summary.js +61 -14
- package/dist/core/auto-compaction.d.ts +1 -0
- package/dist/core/auto-compaction.js +16 -25
- package/dist/core/background-agent-store.d.ts +22 -22
- package/dist/core/background-agent-store.js +19 -23
- package/dist/core/checkpoint-store.d.ts +6 -13
- package/dist/core/checkpoint-store.js +11 -5
- package/dist/core/file-snapshot-store.d.ts +8 -0
- package/dist/core/file-snapshot-store.js +4 -3
- package/dist/core/fs-write-gate-policy.js +2 -3
- package/dist/core/hooks.d.ts +4 -1
- package/dist/core/hooks.js +8 -18
- package/dist/core/lsp.js +4 -3
- package/dist/core/mailbox-store.d.ts +7 -10
- package/dist/core/mcp.d.ts +7 -10
- package/dist/core/mcp.js +7 -9
- package/dist/core/memory-engine/engine.d.ts +0 -1
- package/dist/core/memory-engine/engine.js +24 -34
- package/dist/core/memory-engine/file-backend.d.ts +1 -2
- package/dist/core/memory-engine/file-backend.js +25 -36
- package/dist/core/memory-engine/layout.d.ts +8 -0
- package/dist/core/memory-engine/layout.js +60 -4
- package/dist/core/memory-engine/scope-contract.js +9 -3
- package/dist/core/memory-engine/types.d.ts +1 -1
- package/dist/core/permission-rules.js +2 -3
- package/dist/core/runner/active-skill-scope.d.ts +0 -1
- package/dist/core/runner/active-skill-scope.js +2 -17
- package/dist/core/runner/assemble-result.d.ts +2 -7
- package/dist/core/runner/assemble-result.js +3 -4
- package/dist/core/runner/compaction-call-options.d.ts +7 -0
- package/dist/core/runner/compaction-call-options.js +30 -0
- package/dist/core/runner/prepare-task.d.ts +6 -23
- package/dist/core/runner/prepare-task.js +36 -61
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +84 -172
- package/dist/core/runner/session-rule-policy.js +7 -5
- package/dist/core/runner/teardown-bounded.d.ts +7 -0
- package/dist/core/runner/teardown-bounded.js +36 -0
- package/dist/core/runner/tool-disclosure.d.ts +2 -5
- package/dist/core/runner/turn-attachments.d.ts +5 -15
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/safe-notify.d.ts +17 -0
- package/dist/core/safe-notify.js +59 -0
- package/dist/core/sensitive-path-policy.js +4 -4
- package/dist/core/session-reconcile.js +1 -2
- package/dist/core/skill-tool-specifier.js +2 -3
- package/dist/core/skills-directory.js +2 -3
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/stub-env.d.ts +2 -6
- package/dist/core/surrogate-safe-slice.d.ts +4 -0
- package/dist/core/surrogate-safe-slice.js +18 -0
- package/dist/core/task-registry-monitor.js +8 -13
- package/dist/core/task-registry-shared.d.ts +23 -1
- package/dist/core/task-registry-shared.js +16 -0
- package/dist/core/task-registry.d.ts +6 -1
- package/dist/core/task-registry.js +52 -87
- package/dist/core/task-tool-shape.d.ts +0 -2
- package/dist/core/task-tool-shape.js +2 -5
- package/dist/core/tool-errors.js +5 -4
- package/dist/core/tool-name-aliases.d.ts +1 -2
- package/dist/core/tool-name-aliases.js +42 -59
- package/dist/core/tool-policy.js +21 -17
- package/dist/core/tool-result-store.d.ts +1 -0
- package/dist/core/tool-result-store.js +17 -1
- package/dist/core/trace.d.ts +9 -9
- package/dist/core/types.d.ts +15 -12
- package/dist/core/untrusted-egress.d.ts +4 -2
- package/dist/core/untrusted-egress.js +28 -14
- package/dist/core/workflow-journal-store.d.ts +9 -20
- package/dist/core/workflow-run-store.d.ts +11 -10
- package/dist/core/workflow-run-store.js +22 -16
- package/dist/engine/compaction/utils.js +4 -2
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
- package/dist/engine/execution-env/node-execution-env.js +1 -1
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +13 -33
- package/dist/engine/harness/types.d.ts +29 -67
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +12 -14
- package/dist/engine/loop/types.d.ts +11 -9
- package/dist/engine/session/memory-repo.d.ts +2 -6
- package/dist/engine/session/session.d.ts +5 -30
- package/dist/index.d.ts +6 -3
- package/dist/index.js +4 -3
- package/dist/internal/harness-types.d.ts +3 -1
- package/dist/orchestration/run-spec.js +6 -4
- package/dist/orchestration/run-workflow-tool.js +3 -36
- package/dist/orchestration/workflow-governance.js +2 -1
- package/dist/orchestration/workflow.d.ts +4 -11
- package/dist/orchestration/workflow.js +164 -204
- package/dist/prompt-assembly/packs/sema-default.js +1 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
- package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
- package/dist/prompts/default.js +1 -3
- package/dist/prompts/simple-sections.d.ts +0 -1
- package/dist/prompts/simple-sections.js +0 -1
- package/dist/stores/file/background-agent-store.d.ts +3 -12
- package/dist/stores/file/background-agent-store.js +3 -41
- package/dist/stores/file/checkpoint-store.js +6 -6
- package/dist/stores/file/mailbox-store.d.ts +2 -6
- package/dist/stores/file/session-policy-store.js +15 -1
- package/dist/stores/file/session-store.d.ts +2 -6
- package/dist/stores/file/workflow-journal-store.d.ts +3 -11
- package/dist/stores/file/workflow-run-store.d.ts +3 -7
- package/dist/stores/file/workflow-run-store.js +4 -17
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
- package/dist/tools/fs/bash-readonly-classifier.js +60 -25
- package/dist/tools/fs/fs-bash.js +330 -104
- package/dist/tools/fs/fs-pdf.d.ts +2 -12
- package/dist/tools/fs/fs-shared.js +2 -2
- package/dist/tools/fs/fs-write.js +0 -1
- package/dist/tools/fs/notebook.d.ts +2 -5
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +43 -14
- package/dist/tools/fs/search.js +5 -5
- package/dist/tools/monitor.d.ts +1 -0
- package/dist/tools/monitor.js +7 -5
- package/dist/tools/worktree.d.ts +2 -7
- package/dist/tools/worktree.js +3 -1
- package/package.json +6 -2
|
@@ -3,7 +3,8 @@ import type { RepairBundle } from "../agents/repair-loop.js";
|
|
|
3
3
|
import type { WorkspaceHandle } from "./remote-env.js";
|
|
4
4
|
import type { ConsolidationNote } from "./runner/memory-consolidation.js";
|
|
5
5
|
import type { SessionPermissionRules } from "./session-policy-store.js";
|
|
6
|
-
import type { TaskResult } from "./types.js";
|
|
6
|
+
import type { NestedUsageAccum, TaskResult } from "./types.js";
|
|
7
|
+
import type { ActiveWorktreeSession } from "../internal/harness-types.js";
|
|
7
8
|
export type CheckpointToken = string & {
|
|
8
9
|
readonly __brand: "CheckpointToken";
|
|
9
10
|
};
|
|
@@ -112,12 +113,7 @@ export interface CheckpointState {
|
|
|
112
113
|
value?: unknown;
|
|
113
114
|
set?: boolean;
|
|
114
115
|
};
|
|
115
|
-
nestedStats:
|
|
116
|
-
tokens: number;
|
|
117
|
-
turns: number;
|
|
118
|
-
tasks: number;
|
|
119
|
-
costMicroUsd: number;
|
|
120
|
-
};
|
|
116
|
+
nestedStats: NestedUsageAccum;
|
|
121
117
|
consolidationNotes?: ConsolidationNote[];
|
|
122
118
|
readFileState?: Array<[string, ReadEntry]>;
|
|
123
119
|
repairBundle?: RepairBundle;
|
|
@@ -131,12 +127,7 @@ export interface CheckpointState {
|
|
|
131
127
|
description?: string;
|
|
132
128
|
}>;
|
|
133
129
|
handsCwd?: string;
|
|
134
|
-
activeWorktree?:
|
|
135
|
-
worktreeDir: string;
|
|
136
|
-
originalCwd: string;
|
|
137
|
-
baseSha: string;
|
|
138
|
-
entered?: boolean;
|
|
139
|
-
};
|
|
130
|
+
activeWorktree?: ActiveWorktreeSession;
|
|
140
131
|
inheritedGate?: {
|
|
141
132
|
ancestorRules?: ReadonlyArray<{
|
|
142
133
|
sessionId: string;
|
|
@@ -273,6 +264,8 @@ export declare function validatePendingSteer(steer: {
|
|
|
273
264
|
text: string;
|
|
274
265
|
trusted: boolean;
|
|
275
266
|
};
|
|
267
|
+
export declare function checkpointRowMatches(cp: Checkpoint | undefined, scope: string, status: Checkpoint["status"]): cp is Checkpoint;
|
|
268
|
+
export declare function checkpointOccMatches(cp: Checkpoint, expect?: ResolveExpectation): boolean;
|
|
276
269
|
export type CheckpointFaultMode = "resolve-after-commit" | "resolve-before-commit";
|
|
277
270
|
export declare class InMemoryCheckpointStore implements CheckpointStore {
|
|
278
271
|
private cps;
|
|
@@ -237,6 +237,12 @@ export function validatePendingSteer(steer) {
|
|
|
237
237
|
}
|
|
238
238
|
return { text: steer.text, trusted: steer.trusted };
|
|
239
239
|
}
|
|
240
|
+
export function checkpointRowMatches(cp, scope, status) {
|
|
241
|
+
return cp !== undefined && cp.scope === scope && cp.status === status;
|
|
242
|
+
}
|
|
243
|
+
export function checkpointOccMatches(cp, expect) {
|
|
244
|
+
return expect === undefined || (cp.rev ?? 0) === expect.rev;
|
|
245
|
+
}
|
|
240
246
|
export class InMemoryCheckpointStore {
|
|
241
247
|
cps = new Map();
|
|
242
248
|
fault = null;
|
|
@@ -256,10 +262,10 @@ export class InMemoryCheckpointStore {
|
|
|
256
262
|
throw new Error("injected fault: resolve before commit");
|
|
257
263
|
}
|
|
258
264
|
const cp = this.cps.get(token);
|
|
259
|
-
if (!cp
|
|
265
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
260
266
|
return false;
|
|
261
267
|
}
|
|
262
|
-
if (
|
|
268
|
+
if (!checkpointOccMatches(cp, expect)) {
|
|
263
269
|
return false;
|
|
264
270
|
}
|
|
265
271
|
cp.status = "resolved";
|
|
@@ -276,7 +282,7 @@ export class InMemoryCheckpointStore {
|
|
|
276
282
|
}
|
|
277
283
|
async reopen(token, scope, reason) {
|
|
278
284
|
const cp = this.cps.get(token);
|
|
279
|
-
if (!cp
|
|
285
|
+
if (!checkpointRowMatches(cp, scope, "resolved")) {
|
|
280
286
|
return false;
|
|
281
287
|
}
|
|
282
288
|
cp.status = "pending";
|
|
@@ -287,7 +293,7 @@ export class InMemoryCheckpointStore {
|
|
|
287
293
|
async setPendingSteer(token, scope, steer) {
|
|
288
294
|
const clean = validatePendingSteer(steer);
|
|
289
295
|
const cp = this.cps.get(token);
|
|
290
|
-
if (!cp
|
|
296
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
291
297
|
return false;
|
|
292
298
|
}
|
|
293
299
|
cp.state.pendingSteer = clean;
|
|
@@ -295,7 +301,7 @@ export class InMemoryCheckpointStore {
|
|
|
295
301
|
}
|
|
296
302
|
async expire(token, scope) {
|
|
297
303
|
const cp = this.cps.get(token);
|
|
298
|
-
if (!cp
|
|
304
|
+
if (!checkpointRowMatches(cp, scope, "pending")) {
|
|
299
305
|
return false;
|
|
300
306
|
}
|
|
301
307
|
cp.status = "expired";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
declare function relUnder(root: string, child: string): string | null;
|
|
2
|
+
declare function joinUnder(root: string, rel: string): string;
|
|
1
3
|
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
2
4
|
export interface FileSnapshotError {
|
|
3
5
|
code: "too_large" | "enumerate_failed" | "read_failed" | "restore_failed" | "not_found" | "aborted";
|
|
@@ -51,3 +53,9 @@ export declare class InMemoryFileSnapshotStore implements FileSnapshotStore {
|
|
|
51
53
|
importManifest(scope: string, key: string, manifest: Map<string, string>, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileSnapshotResult>;
|
|
52
54
|
reap(scope: string, keepKeys: string[]): Promise<number>;
|
|
53
55
|
}
|
|
56
|
+
export declare const __testables: {
|
|
57
|
+
joinUnder: typeof joinUnder;
|
|
58
|
+
relUnder: typeof relUnder;
|
|
59
|
+
stripTrailSep: (p: string) => string;
|
|
60
|
+
};
|
|
61
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAbsolutePathForm, isWinFormPath } from "../tools/fs/safety.js";
|
|
2
3
|
const normSlash = (p) => p.replace(/\\/g, "/");
|
|
3
|
-
const stripTrailSep = (p) => p.replace(/[\\/]+$/, "");
|
|
4
|
+
const stripTrailSep = (p) => (isWinFormPath(p) ? p.replace(/[\\/]+$/, "") : p.replace(/\/+$/, ""));
|
|
4
5
|
const foldDrive = (p) => (/^[A-Za-z]:(\/|$)/.test(p) ? p[0].toLowerCase() + p.slice(1) : p);
|
|
5
|
-
const isWinFormPath = (p) => /^[A-Za-z]:[\\/]/.test(p) || p.startsWith("\\\\") || (!p.startsWith("/") && p.includes("\\"));
|
|
6
6
|
function relUnder(root, child) {
|
|
7
7
|
const fold = isWinFormPath(root) ? normSlash : (p) => p;
|
|
8
8
|
const r = foldDrive(fold(stripTrailSep(root)));
|
|
@@ -18,7 +18,7 @@ function joinUnder(root, rel) {
|
|
|
18
18
|
return stripTrailSep(root) + (win ? "\\" : "/") + (win ? rel.replace(/\//g, "\\") : rel);
|
|
19
19
|
}
|
|
20
20
|
function isUnsafeRelPath(relN) {
|
|
21
|
-
return relN
|
|
21
|
+
return isAbsolutePathForm(relN) || /^[A-Za-z]:/.test(relN) || relN.split("/").includes("..");
|
|
22
22
|
}
|
|
23
23
|
export const DEFAULT_SNAPSHOT_BOUNDS = {
|
|
24
24
|
maxFiles: 10000,
|
|
@@ -256,3 +256,4 @@ export class InMemoryFileSnapshotStore {
|
|
|
256
256
|
return removed;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
+
export const __testables = { joinUnder, relUnder, stripTrailSep };
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { canonicalizeTarget, writeTargetPath } from "../tools/fs/safety.js";
|
|
2
|
-
import { canonicalToolName } from "./tool-name-aliases.js";
|
|
3
2
|
import { PATH_WRITE_TOOLS, isWithin } from "./runner/session-rule-policy.js";
|
|
4
3
|
const ask = (message) => ({ action: "ask", message, decisionReason: "rule" });
|
|
5
4
|
export function createFsWriteGatePolicy(opts) {
|
|
6
5
|
const { env, rootPath, defaultWrite } = opts;
|
|
7
|
-
const gated = new Set([...PATH_WRITE_TOOLS, "NotebookEdit"]
|
|
6
|
+
const gated = new Set([...PATH_WRITE_TOOLS, "NotebookEdit"]);
|
|
8
7
|
const acceptDirs = opts.acceptDirs && opts.acceptDirs.length > 0 ? opts.acceptDirs : undefined;
|
|
9
8
|
const exemptDirs = opts.exemptDirs && opts.exemptDirs.length > 0 ? opts.exemptDirs : undefined;
|
|
10
9
|
return {
|
|
11
10
|
async check(req, signal) {
|
|
12
|
-
const canonical =
|
|
11
|
+
const canonical = req.toolName;
|
|
13
12
|
if (!gated.has(canonical))
|
|
14
13
|
return { action: "allow" };
|
|
15
14
|
const path = writeTargetPath(canonical, req.args);
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export interface Hooks {
|
|
|
7
7
|
userPromptSubmit?(prompt: string): UserPromptSubmitResult | undefined | Promise<UserPromptSubmitResult | undefined>;
|
|
8
8
|
stop?(ctx: StopHookContext): StopHookResult | undefined | Promise<StopHookResult | undefined>;
|
|
9
9
|
postToolUseFailure?(toolName: string, input: unknown, failure: HookToolFailure, ctx: HookToolContext): PostToolUseFailureResult | undefined | Promise<PostToolUseFailureResult | undefined>;
|
|
10
|
-
postToolBatch?(batch: PostToolBatchCall[]
|
|
10
|
+
postToolBatch?(batch: PostToolBatchCall[], meta?: {
|
|
11
|
+
injectedThisTurn: "final_verification" | "finalize";
|
|
12
|
+
}): PostToolBatchResult | undefined | Promise<PostToolBatchResult | undefined>;
|
|
11
13
|
preCompact?(ctx: PreCompactContext): PreCompactResult | undefined | Promise<PreCompactResult | undefined>;
|
|
12
14
|
postCompact?(ctx: PostCompactContext): void | Promise<void>;
|
|
13
15
|
stopFailure?(ctx: StopFailureContext): void | Promise<void>;
|
|
@@ -102,6 +104,7 @@ export interface ToolGateResult {
|
|
|
102
104
|
preToolContext: string[];
|
|
103
105
|
}
|
|
104
106
|
export interface ToolGateInput {
|
|
107
|
+
onNotifyError?: (failure: import("./safe-notify.js").SafeNotifyFailure) => void;
|
|
105
108
|
event: {
|
|
106
109
|
toolCallId: string;
|
|
107
110
|
toolName: string;
|
package/dist/core/hooks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { decisionText } from "./tool-policy.js";
|
|
2
2
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
3
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
4
|
+
import { createSafeNotifier } from "./safe-notify.js";
|
|
4
5
|
export function cloneObserverInput(input) {
|
|
5
6
|
try {
|
|
6
7
|
return structuredClone(input);
|
|
@@ -23,12 +24,8 @@ function preToolUseCrashReason(subject, err) {
|
|
|
23
24
|
`This is a failure of the deployment's hook, NOT of the tool itself — an identical retry reaches the ` +
|
|
24
25
|
`same hook and fails the same way. Hook error: ${cause || "(no message)"}`);
|
|
25
26
|
}
|
|
26
|
-
function traceHookCrash(input, err) {
|
|
27
|
-
|
|
28
|
-
input.onHookError?.(err);
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
}
|
|
27
|
+
function traceHookCrash(input, err, notifier) {
|
|
28
|
+
notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
|
|
32
29
|
}
|
|
33
30
|
function withProbeTimeout(p, ms, signal) {
|
|
34
31
|
if (ms === undefined && signal === undefined)
|
|
@@ -62,6 +59,7 @@ export async function runToolGate(input) {
|
|
|
62
59
|
let currentInput = event.input;
|
|
63
60
|
const preToolContext = [];
|
|
64
61
|
let hookAsk;
|
|
62
|
+
const notifier = createSafeNotifier(input.onNotifyError !== undefined ? { onError: input.onNotifyError } : undefined);
|
|
65
63
|
if (preToolUse) {
|
|
66
64
|
let r;
|
|
67
65
|
try {
|
|
@@ -69,13 +67,9 @@ export async function runToolGate(input) {
|
|
|
69
67
|
}
|
|
70
68
|
catch (err) {
|
|
71
69
|
const reason = preToolUseCrashReason(`this call to "${toolName}"`, err);
|
|
72
|
-
traceHookCrash(input, err);
|
|
70
|
+
traceHookCrash(input, err, notifier);
|
|
73
71
|
if (input.permissionDenied) {
|
|
74
|
-
|
|
75
|
-
await input.permissionDenied({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, source: "hook" });
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
}
|
|
72
|
+
await notifier.notifyAsync(() => input.permissionDenied?.({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason, source: "hook" }), "toolGate.permissionDenied");
|
|
79
73
|
}
|
|
80
74
|
return { block: true, reason: formatHookFeedback(reason), preToolContext };
|
|
81
75
|
}
|
|
@@ -216,7 +210,7 @@ export async function runToolGate(input) {
|
|
|
216
210
|
hr = await preToolUse(toolName, editArgs, { toolCallId, toolName });
|
|
217
211
|
}
|
|
218
212
|
catch (err) {
|
|
219
|
-
traceHookCrash(input, err);
|
|
213
|
+
traceHookCrash(input, err, notifier);
|
|
220
214
|
editDenied = { action: "deny", reason: preToolUseCrashReason(`the approved edit for "${toolName}"`, err) };
|
|
221
215
|
denySource = "hook";
|
|
222
216
|
break;
|
|
@@ -288,11 +282,7 @@ export async function runToolGate(input) {
|
|
|
288
282
|
currentInput = decision.updatedInput;
|
|
289
283
|
}
|
|
290
284
|
if (input.permissionDenied) {
|
|
291
|
-
|
|
292
|
-
await input.permissionDenied({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason: denyReason, source: denySource });
|
|
293
|
-
}
|
|
294
|
-
catch {
|
|
295
|
-
}
|
|
285
|
+
await notifier.notifyAsync(() => input.permissionDenied?.({ toolName, input: cloneObserverInput(currentInput), toolCallId, reason: denyReason, source: denySource }), "toolGate.permissionDenied");
|
|
296
286
|
}
|
|
297
287
|
return {
|
|
298
288
|
block: true,
|
package/dist/core/lsp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { uriToPath } from "./lsp-protocol.js";
|
|
2
|
+
import { foldRedundantPathSegments, isAbsolutePathForm } from "../tools/fs/safety.js";
|
|
2
3
|
import { Type } from "typebox";
|
|
3
4
|
import { defineTool, errorResult } from "./tools.js";
|
|
4
5
|
export const LSP_OPERATIONS = [
|
|
@@ -67,10 +68,10 @@ export function settleOnAbort(job, signal, onAbort) {
|
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
export function resolveLspPath(filePath, cwd) {
|
|
70
|
-
const isAbsForm =
|
|
71
|
+
const isAbsForm = isAbsolutePathForm(filePath);
|
|
71
72
|
if (isAbsForm || !cwd)
|
|
72
|
-
return filePath;
|
|
73
|
-
return cwd.replace(/[\\/]+$/, "") + (cwd.includes("\\") ? "\\" : "/") + filePath;
|
|
73
|
+
return foldRedundantPathSegments(filePath);
|
|
74
|
+
return foldRedundantPathSegments(cwd.replace(/[\\/]+$/, "") + (cwd.includes("\\") ? "\\" : "/") + filePath);
|
|
74
75
|
}
|
|
75
76
|
export const MAX_LSP_FILE_BYTES = 10 * 1000 * 1000;
|
|
76
77
|
function fmt(result, ctx) {
|
|
@@ -8,12 +8,13 @@ export interface MailboxLease {
|
|
|
8
8
|
messages: MailboxMessage[];
|
|
9
9
|
maxSeq: number;
|
|
10
10
|
}
|
|
11
|
+
export interface MailboxAppendMessage {
|
|
12
|
+
from?: string;
|
|
13
|
+
content: string;
|
|
14
|
+
sentAt: number;
|
|
15
|
+
}
|
|
11
16
|
export interface MailboxStore {
|
|
12
|
-
append(scope: string, handle: string, msg:
|
|
13
|
-
from?: string;
|
|
14
|
-
content: string;
|
|
15
|
-
sentAt: number;
|
|
16
|
-
}): Promise<number>;
|
|
17
|
+
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
17
18
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
18
19
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
19
20
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
@@ -28,11 +29,7 @@ export declare class InMemoryMailboxStore implements MailboxStore {
|
|
|
28
29
|
private boxes;
|
|
29
30
|
private key;
|
|
30
31
|
private box;
|
|
31
|
-
append(scope: string, handle: string, msg:
|
|
32
|
-
from?: string;
|
|
33
|
-
content: string;
|
|
34
|
-
sentAt: number;
|
|
35
|
-
}): Promise<number>;
|
|
32
|
+
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
36
33
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
37
34
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
38
35
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -8,6 +8,11 @@ export interface McpToolAxis {
|
|
|
8
8
|
egress?: true;
|
|
9
9
|
effect?: ToolEffect;
|
|
10
10
|
}
|
|
11
|
+
export interface McpDroppedTool {
|
|
12
|
+
server: string;
|
|
13
|
+
tool: string;
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
11
16
|
export interface MaterializedMcp {
|
|
12
17
|
tools: AgentTool[];
|
|
13
18
|
toolAxes: McpToolAxis[];
|
|
@@ -23,11 +28,7 @@ export interface MaterializedMcp {
|
|
|
23
28
|
pendingRemovals: string[];
|
|
24
29
|
};
|
|
25
30
|
warnings: Error[];
|
|
26
|
-
droppedTools:
|
|
27
|
-
server: string;
|
|
28
|
-
tool: string;
|
|
29
|
-
reason: string;
|
|
30
|
-
}>;
|
|
31
|
+
droppedTools: McpDroppedTool[];
|
|
31
32
|
statuses: McpServerStatus[];
|
|
32
33
|
refresh: (server?: string) => Promise<McpRefreshResult[]>;
|
|
33
34
|
dispose: () => Promise<void>;
|
|
@@ -41,11 +42,7 @@ export interface McpRefreshResult {
|
|
|
41
42
|
removed: string[];
|
|
42
43
|
tools?: AgentTool[];
|
|
43
44
|
axes?: McpToolAxis[];
|
|
44
|
-
dropped?:
|
|
45
|
-
server: string;
|
|
46
|
-
tool: string;
|
|
47
|
-
reason: string;
|
|
48
|
-
}>;
|
|
45
|
+
dropped?: McpDroppedTool[];
|
|
49
46
|
error?: string;
|
|
50
47
|
}
|
|
51
48
|
export interface McpServerStatus {
|
package/dist/core/mcp.js
CHANGED
|
@@ -7,6 +7,7 @@ import { tmpdir } from "node:os";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { CallToolResultSchema, ElicitRequestSchema, ErrorCode, ListResourcesResultSchema, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { MCP_IMAGE_MAX_BASE64, sharpImageResizer } from "./image-downsample.js";
|
|
10
|
+
import { sliceHeadSafe, sliceTailSafe } from "./surrogate-safe-slice.js";
|
|
10
11
|
import { truncateError } from "./tool-errors.js";
|
|
11
12
|
import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
12
13
|
import { validateJsonSchemaShape } from "./runner/strict-output-schema.js";
|
|
@@ -75,8 +76,10 @@ export function truncateMcpErrorText(s) {
|
|
|
75
76
|
let head = MCP_ERROR_HEAD_CHARS;
|
|
76
77
|
let out = "";
|
|
77
78
|
for (let i = 0; i < 4; i++) {
|
|
78
|
-
const
|
|
79
|
-
|
|
79
|
+
const headText = sliceHeadSafe(s, head);
|
|
80
|
+
const tailText = sliceTailSafe(s, MCP_ERROR_TAIL_CHARS);
|
|
81
|
+
const removed = s.length - headText.length - tailText.length;
|
|
82
|
+
out = `${headText}\n\n... [${removed} characters truncated] ...\n\n${tailText}`;
|
|
80
83
|
if (out.length <= max)
|
|
81
84
|
break;
|
|
82
85
|
head = Math.max(0, head - (out.length - max));
|
|
@@ -753,9 +756,6 @@ function applyCallerAxisOverride(name, hint, override) {
|
|
|
753
756
|
const LIST_MCP_RESOURCES = "ListMcpResourcesTool";
|
|
754
757
|
const READ_MCP_RESOURCE = "ReadMcpResourceTool";
|
|
755
758
|
const READ_MCP_RESOURCE_DIR = "ReadMcpResourceDirTool";
|
|
756
|
-
const LEGACY_LIST_MCP_RESOURCES = "ListMcpResources";
|
|
757
|
-
const LEGACY_READ_MCP_RESOURCE = "ReadMcpResource";
|
|
758
|
-
const LEGACY_READ_MCP_RESOURCE_DIR = "ReadMcpResourceDir";
|
|
759
759
|
const MCP_SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
|
|
760
760
|
const MAX_DIR_READ_PAGES = 20;
|
|
761
761
|
const DIR_READ_NOT_A_DIRECTORY_RE = /not a directory|isn'?t a directory|not a folder/i;
|
|
@@ -840,7 +840,6 @@ function buildResourceTools(resourceServers) {
|
|
|
840
840
|
if (listable.length > 0) {
|
|
841
841
|
tools.push({
|
|
842
842
|
name: LIST_MCP_RESOURCES,
|
|
843
|
-
aliases: [LEGACY_LIST_MCP_RESOURCES],
|
|
844
843
|
description: "List the resources available from connected MCP servers (uri / name / description). Pass `server` to limit " +
|
|
845
844
|
"to one server, or omit it to list across all. Resource metadata is external/untrusted data.",
|
|
846
845
|
label: LIST_MCP_RESOURCES,
|
|
@@ -900,7 +899,6 @@ function buildResourceTools(resourceServers) {
|
|
|
900
899
|
if (readable.length > 0) {
|
|
901
900
|
tools.push({
|
|
902
901
|
name: READ_MCP_RESOURCE,
|
|
903
|
-
aliases: [LEGACY_READ_MCP_RESOURCE],
|
|
904
902
|
description: "Read a specific MCP resource by `server` + `uri` (from ListMcpResourcesTool). Its content is external/untrusted data.",
|
|
905
903
|
label: READ_MCP_RESOURCE,
|
|
906
904
|
executionMode: "parallel",
|
|
@@ -950,7 +948,6 @@ function buildResourceTools(resourceServers) {
|
|
|
950
948
|
if (listable.length > 0) {
|
|
951
949
|
tools.push({
|
|
952
950
|
name: READ_MCP_RESOURCE_DIR,
|
|
953
|
-
aliases: [LEGACY_READ_MCP_RESOURCE_DIR],
|
|
954
951
|
description: "List the MCP resources UNDER a directory `uri` on `server` — its child resources (those whose uri is " +
|
|
955
952
|
"nested below it; subdirectories carry mimeType \"inode/directory\"). Use it to browse a hierarchical " +
|
|
956
953
|
"resource namespace after ListMcpResourcesTool. Resource metadata is external/untrusted data.",
|
|
@@ -1008,7 +1005,8 @@ function buildResourceTools(resourceServers) {
|
|
|
1008
1005
|
const res = await rs.client
|
|
1009
1006
|
.listResources(undefined, { signal: watchdog.combinedSignal, timeout: timeoutMs })
|
|
1010
1007
|
.catch((err) => rethrowHonestMcpError(err, { server, what, timeoutMs, writeEffect: false, signal, attributeServer: true, idle: { signal: watchdog.idleSignal, idleMs } }));
|
|
1011
|
-
const
|
|
1008
|
+
const dirPrefix = uri.endsWith("/") ? uri : `${uri}/`;
|
|
1009
|
+
const children = res.resources.filter((r) => typeof r.uri === "string" && r.uri !== uri && r.uri.startsWith(dirPrefix));
|
|
1012
1010
|
return renderDirChildren(server, uri, children);
|
|
1013
1011
|
}
|
|
1014
1012
|
finally {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
3
3
|
import { uuidv7 } from "../../internal/harness.js";
|
|
4
4
|
import { MAX_MEMORY_BYTES, composeMemoryBlock, firstSentence } from "../memory.js";
|
|
5
5
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
6
6
|
import { formatMemoryAge } from "../memory-recall.js";
|
|
7
7
|
import { computeEntryRev, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
|
|
8
8
|
import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, scanEntryFiles } from "./file-backend.js";
|
|
9
|
-
import { SCAN_FUSE_THRESHOLD, readIndexRevs, writeIndexRevs, bumpScanFuse, canonicalize, claimRootScope, clearScanFuse, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, isContainedIn, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, } from "./layout.js";
|
|
9
|
+
import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, readIndexRevs, writeIndexRevs, bumpScanFuse, canonicalize, claimRootScope, clearScanFuse, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, isContainedIn, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, } from "./layout.js";
|
|
10
10
|
import { scanMemoryFileName, scanMemoryWrite, scanRemediation } from "./scan.js";
|
|
11
11
|
export const MEMORY_INSTRUCTION_TEMPLATE = `# Memory
|
|
12
12
|
|
|
@@ -290,6 +290,8 @@ export class MemoryEngine {
|
|
|
290
290
|
report.rejections.push({ path: rel, code: "symlink", reason: "symlinks are not harvested into memory (containment gate)" });
|
|
291
291
|
else if (kind === "depth")
|
|
292
292
|
report.rejections.push({ path: rel, code: "nested_too_deep", reason: `memory files nested deeper than ${this.maxDepth} levels are not harvested` });
|
|
293
|
+
else if (kind === "unreadable")
|
|
294
|
+
report.rejections.push({ path: rel, code: "unreadable", reason: "the path could not be read (lstat/readdir failed) — its content was NOT harvested this pass" });
|
|
293
295
|
},
|
|
294
296
|
});
|
|
295
297
|
const records = [];
|
|
@@ -414,7 +416,7 @@ export class MemoryEngine {
|
|
|
414
416
|
scanFindings.push(...scanMemoryWrite(f.text));
|
|
415
417
|
const finding = scanFindings[0];
|
|
416
418
|
if (finding !== undefined) {
|
|
417
|
-
const q =
|
|
419
|
+
const q = quarantineAndTombstone(f.canonical, f.text, join(this.controlDir, QUARANTINE_DIR), this.now);
|
|
418
420
|
let contained = q.removed;
|
|
419
421
|
const priorId = idByBasePath.get(f.canonical);
|
|
420
422
|
if (priorId !== undefined) {
|
|
@@ -704,8 +706,8 @@ export class MemoryEngine {
|
|
|
704
706
|
return undefined;
|
|
705
707
|
let detail = "";
|
|
706
708
|
try {
|
|
707
|
-
const dest = join(this.controlDir,
|
|
708
|
-
|
|
709
|
+
const dest = join(this.controlDir, QUARANTINE_DIR, `${this.now()}-${MEMORY_INDEX_FILENAME}`);
|
|
710
|
+
ensureDirExists(dirname(dest));
|
|
709
711
|
writeFileSync(dest, text, "utf8");
|
|
710
712
|
}
|
|
711
713
|
catch (err) {
|
|
@@ -725,35 +727,6 @@ export class MemoryEngine {
|
|
|
725
727
|
reason: `memory index blocked: ${finding.reason} (${cleared ? "index cleared and rebuilt from entry frontmatter; the offending prose was captured to quarantine" : "index still carries the offending content"})${detail}`,
|
|
726
728
|
};
|
|
727
729
|
}
|
|
728
|
-
quarantineFile(path, content) {
|
|
729
|
-
const dest = join(this.controlDir, "quarantine", `${this.now()}-${basename(path)}`);
|
|
730
|
-
let captured;
|
|
731
|
-
let detail;
|
|
732
|
-
try {
|
|
733
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
734
|
-
writeFileSync(dest, content, "utf8");
|
|
735
|
-
captured = dest;
|
|
736
|
-
}
|
|
737
|
-
catch (err) {
|
|
738
|
-
detail = `quarantine copy failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
739
|
-
}
|
|
740
|
-
let removed = false;
|
|
741
|
-
try {
|
|
742
|
-
rmSync(path, { force: true });
|
|
743
|
-
removed = true;
|
|
744
|
-
}
|
|
745
|
-
catch (rmErr) {
|
|
746
|
-
try {
|
|
747
|
-
writeFileSync(path, "---\ndeleted: true\n---\n", "utf8");
|
|
748
|
-
removed = true;
|
|
749
|
-
detail = `${detail !== undefined ? `${detail}; ` : ""}delete failed (${rmErr instanceof Error ? rmErr.message : String(rmErr)}) — tombstoned in place`;
|
|
750
|
-
}
|
|
751
|
-
catch (clearErr) {
|
|
752
|
-
detail = `${detail !== undefined ? `${detail}; ` : ""}suspect file could not be deleted or cleared: ${clearErr instanceof Error ? clearErr.message : String(clearErr)}`;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return { ...(captured !== undefined ? { dest: captured } : {}), removed, ...(detail !== undefined ? { detail } : {}) };
|
|
756
|
-
}
|
|
757
730
|
readonlyDirNamesUnderRoot(handle) {
|
|
758
731
|
const names = new Set();
|
|
759
732
|
for (const [scope, dirName] of Object.entries(registeredScopes(this.controlDir))) {
|
|
@@ -797,12 +770,14 @@ export class MemoryEngine {
|
|
|
797
770
|
chmodScopeTree(dir, dirMode, fileMode, opts = {}) {
|
|
798
771
|
if (!existsSync(dir))
|
|
799
772
|
return;
|
|
773
|
+
const sweepSkipped = [];
|
|
800
774
|
const walk = (d, isRoot) => {
|
|
801
775
|
let names = [];
|
|
802
776
|
try {
|
|
803
777
|
names = readdirSync(d);
|
|
804
778
|
}
|
|
805
779
|
catch {
|
|
780
|
+
sweepSkipped.push(d);
|
|
806
781
|
return;
|
|
807
782
|
}
|
|
808
783
|
for (const name of names) {
|
|
@@ -814,6 +789,7 @@ export class MemoryEngine {
|
|
|
814
789
|
st = statSync(p);
|
|
815
790
|
}
|
|
816
791
|
catch {
|
|
792
|
+
sweepSkipped.push(p);
|
|
817
793
|
continue;
|
|
818
794
|
}
|
|
819
795
|
if (st.isDirectory())
|
|
@@ -825,6 +801,20 @@ export class MemoryEngine {
|
|
|
825
801
|
chmodSafe(d, dirMode);
|
|
826
802
|
};
|
|
827
803
|
walk(dir, true);
|
|
804
|
+
if (sweepSkipped.length > 0) {
|
|
805
|
+
try {
|
|
806
|
+
enqueueMemoryAnnouncement(this.controlDir, {
|
|
807
|
+
kind: "gate",
|
|
808
|
+
at: this.now(),
|
|
809
|
+
items: sweepSkipped
|
|
810
|
+
.slice(0, 12)
|
|
811
|
+
.map((p) => `mode sweep could not read ${JSON.stringify(inlineUntrusted(relative(dir, p) || "."))} — files under it keep their previous mode`)
|
|
812
|
+
.concat(sweepSkipped.length > 12 ? [`…and ${sweepSkipped.length - 12} more sweepSkipped path(s)`] : []),
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
catch {
|
|
816
|
+
}
|
|
817
|
+
}
|
|
828
818
|
}
|
|
829
819
|
}
|
|
830
820
|
const MAX_ANNOUNCEMENT_ITEMS = 12;
|
|
@@ -9,7 +9,7 @@ export interface ScannedEntryFile {
|
|
|
9
9
|
export declare function scanEntryFiles(dir: string, opts?: {
|
|
10
10
|
maxDepth?: number;
|
|
11
11
|
exclude?: ReadonlySet<string>;
|
|
12
|
-
onSkip?: (path: string, kind: "symlink" | "depth" | "nonmd" | "dotfile") => void;
|
|
12
|
+
onSkip?: (path: string, kind: "symlink" | "depth" | "nonmd" | "dotfile" | "unreadable") => void;
|
|
13
13
|
}): ScannedEntryFile[];
|
|
14
14
|
export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
15
15
|
readonly directoryRoot: string;
|
|
@@ -26,7 +26,6 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
26
26
|
drainInboundFindings(): HarvestRejection[];
|
|
27
27
|
readCommittedShadow(id: string): string | undefined;
|
|
28
28
|
private shadowPath;
|
|
29
|
-
private quarantineFile;
|
|
30
29
|
private loadLedger;
|
|
31
30
|
private saveLedger;
|
|
32
31
|
private recoverJournal;
|