@sema-agent/core 3.0.0 → 4.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 +2345 -0
- package/dist/agents/cascade.d.ts +2 -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.js +27 -19
- package/dist/agents/retain-ledger.d.ts +2 -0
- package/dist/agents/retain-ledger.js +6 -5
- package/dist/agents/roster-store.js +3 -5
- package/dist/agents/subagent.js +66 -64
- package/dist/agents/teacher.d.ts +1 -9
- package/dist/agents/teacher.js +21 -24
- 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/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/hooks.d.ts +3 -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 -3
- 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/runner/active-skill-scope.d.ts +0 -1
- package/dist/core/runner/active-skill-scope.js +1 -15
- 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 +20 -53
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +65 -156
- package/dist/core/runner/session-rule-policy.js +4 -1
- 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 +2 -1
- 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 +22 -0
- package/dist/core/task-registry-shared.js +16 -0
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +40 -57
- package/dist/core/tool-errors.js +5 -4
- package/dist/core/tool-name-aliases.js +2 -1
- package/dist/core/tool-policy.js +10 -5
- 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 +2 -9
- package/dist/core/types.d.ts +15 -12
- package/dist/core/untrusted-egress.js +7 -5
- 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/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 +11 -4
- 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 +3 -1
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +3 -1
- package/dist/orchestration/run-spec.js +7 -4
- package/dist/orchestration/run-workflow-tool.js +3 -35
- package/dist/orchestration/workflow.d.ts +3 -11
- package/dist/orchestration/workflow.js +164 -204
- package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
- package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
- 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 +324 -96
- package/dist/tools/fs/fs-pdf.d.ts +2 -12
- package/dist/tools/fs/fs-shared.js +2 -2
- 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
|
@@ -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));
|
|
@@ -1008,7 +1011,8 @@ function buildResourceTools(resourceServers) {
|
|
|
1008
1011
|
const res = await rs.client
|
|
1009
1012
|
.listResources(undefined, { signal: watchdog.combinedSignal, timeout: timeoutMs })
|
|
1010
1013
|
.catch((err) => rethrowHonestMcpError(err, { server, what, timeoutMs, writeEffect: false, signal, attributeServer: true, idle: { signal: watchdog.idleSignal, idleMs } }));
|
|
1011
|
-
const
|
|
1014
|
+
const dirPrefix = uri.endsWith("/") ? uri : `${uri}/`;
|
|
1015
|
+
const children = res.resources.filter((r) => typeof r.uri === "string" && r.uri !== uri && r.uri.startsWith(dirPrefix));
|
|
1012
1016
|
return renderDirChildren(server, uri, children);
|
|
1013
1017
|
}
|
|
1014
1018
|
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;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync,
|
|
1
|
+
import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, openSync, fsyncSync, closeSync } from "node:fs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { dirname, join, relative } from "node:path";
|
|
4
4
|
import { jaccardDistance, termSet } from "../memory-vector.js";
|
|
5
5
|
import { MAX_MEMORY_BYTES } from "../memory.js";
|
|
6
6
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
7
7
|
import { computeEntryRev, entryFromFile, isValidEntryId, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
|
|
8
|
-
import { ControlPlaneCorruptError, CURSORS_FILE, atomicWriteFileSync, claimRootScope, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirName, } from "./layout.js";
|
|
8
|
+
import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, atomicWriteFileSync, quarantineAndTombstone, claimRootScope, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirName, writeAllSync, } from "./layout.js";
|
|
9
9
|
import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
|
|
10
10
|
export const MEMORY_INDEX_FILENAME = "MEMORY.md";
|
|
11
11
|
export const DEFAULT_MAX_ENTRY_DEPTH = 3;
|
|
@@ -18,6 +18,8 @@ export function scanEntryFiles(dir, opts = {}) {
|
|
|
18
18
|
names = readdirSync(d);
|
|
19
19
|
}
|
|
20
20
|
catch {
|
|
21
|
+
if (d !== dir)
|
|
22
|
+
opts.onSkip?.(d, "unreadable");
|
|
21
23
|
return;
|
|
22
24
|
}
|
|
23
25
|
for (const name of names.sort()) {
|
|
@@ -32,6 +34,7 @@ export function scanEntryFiles(dir, opts = {}) {
|
|
|
32
34
|
st = lstatSync(p);
|
|
33
35
|
}
|
|
34
36
|
catch {
|
|
37
|
+
opts.onSkip?.(p, "unreadable");
|
|
35
38
|
continue;
|
|
36
39
|
}
|
|
37
40
|
if (st.isSymbolicLink()) {
|
|
@@ -65,7 +68,6 @@ const TXN_LOCK_STALE_MS = 30_000;
|
|
|
65
68
|
const TXN_LOCK_WAIT_MS = 15_000;
|
|
66
69
|
const TXN_LOCK_STEAL_GRACE_MS = 250;
|
|
67
70
|
const SHADOW_DIR = "shadow";
|
|
68
|
-
const QUARANTINE_DIR = "quarantine";
|
|
69
71
|
export class FileMemoryEngineBackend {
|
|
70
72
|
directoryRoot;
|
|
71
73
|
controlPlaneRoot;
|
|
@@ -98,35 +100,6 @@ export class FileMemoryEngineBackend {
|
|
|
98
100
|
shadowPath(id) {
|
|
99
101
|
return join(this.controlPlaneRoot, SHADOW_DIR, `${id}.md`);
|
|
100
102
|
}
|
|
101
|
-
quarantineFile(path, content) {
|
|
102
|
-
const dest = join(this.controlPlaneRoot, QUARANTINE_DIR, `${Date.now()}-${basename(path)}`);
|
|
103
|
-
let captured;
|
|
104
|
-
let detail;
|
|
105
|
-
try {
|
|
106
|
-
ensureDirExists(dirname(dest));
|
|
107
|
-
writeFileSync(dest, content, "utf8");
|
|
108
|
-
captured = dest;
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
detail = `quarantine copy failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
112
|
-
}
|
|
113
|
-
let removed = false;
|
|
114
|
-
try {
|
|
115
|
-
rmSync(path, { force: true });
|
|
116
|
-
removed = true;
|
|
117
|
-
}
|
|
118
|
-
catch (rmErr) {
|
|
119
|
-
try {
|
|
120
|
-
writeFileSync(path, "---\ndeleted: true\n---\n", "utf8");
|
|
121
|
-
removed = true;
|
|
122
|
-
detail = `${detail !== undefined ? `${detail}; ` : ""}delete failed (${rmErr instanceof Error ? rmErr.message : String(rmErr)}) — tombstoned in place`;
|
|
123
|
-
}
|
|
124
|
-
catch (clearErr) {
|
|
125
|
-
detail = `${detail !== undefined ? `${detail}; ` : ""}suspect file could not be deleted or cleared: ${clearErr instanceof Error ? clearErr.message : String(clearErr)}`;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return { ...(captured !== undefined ? { dest: captured } : {}), removed, ...(detail !== undefined ? { detail } : {}) };
|
|
129
|
-
}
|
|
130
103
|
loadLedger() {
|
|
131
104
|
if (this.ledger)
|
|
132
105
|
return this.ledger;
|
|
@@ -278,12 +251,14 @@ export class FileMemoryEngineBackend {
|
|
|
278
251
|
const ledger = sync ? this.loadLedger() : undefined;
|
|
279
252
|
let ledgerChanged = false;
|
|
280
253
|
const adoptedExternal = [];
|
|
254
|
+
const skippedUnreadable = [];
|
|
281
255
|
for (const f of files) {
|
|
282
256
|
let text;
|
|
283
257
|
try {
|
|
284
258
|
text = readFileSync(f.path, "utf8");
|
|
285
259
|
}
|
|
286
260
|
catch {
|
|
261
|
+
skippedUnreadable.push(relative(this.directoryRoot, f.path));
|
|
287
262
|
continue;
|
|
288
263
|
}
|
|
289
264
|
const parsed = parseEntryFile(text);
|
|
@@ -312,7 +287,7 @@ export class FileMemoryEngineBackend {
|
|
|
312
287
|
}
|
|
313
288
|
if (finding) {
|
|
314
289
|
this.inboundFindings.push(finding);
|
|
315
|
-
const q =
|
|
290
|
+
const q = quarantineAndTombstone(f.path, text, join(this.controlPlaneRoot, QUARANTINE_DIR), this.now);
|
|
316
291
|
const shadowText = committed !== undefined ? this.readCommittedShadow(entry.id) : undefined;
|
|
317
292
|
let restoredShadow = false;
|
|
318
293
|
if (shadowText !== undefined) {
|
|
@@ -378,6 +353,20 @@ export class FileMemoryEngineBackend {
|
|
|
378
353
|
catch {
|
|
379
354
|
}
|
|
380
355
|
}
|
|
356
|
+
if (skippedUnreadable.length > 0) {
|
|
357
|
+
try {
|
|
358
|
+
enqueueMemoryAnnouncement(this.controlPlaneRoot, {
|
|
359
|
+
kind: "external",
|
|
360
|
+
at: this.now(),
|
|
361
|
+
items: skippedUnreadable
|
|
362
|
+
.slice(0, 12)
|
|
363
|
+
.map((rel) => `committed memory entry could not be read and is MISSING from this session's memory: ${JSON.stringify(inlineUntrusted(rel))} — check the file (permissions/fs)`)
|
|
364
|
+
.concat(skippedUnreadable.length > 12 ? [`…and ${skippedUnreadable.length - 12} more unreadable entr(ies)`] : []),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
}
|
|
369
|
+
}
|
|
381
370
|
if (!sync && this.batchScan !== undefined)
|
|
382
371
|
this.batchScan.set(scope, entries);
|
|
383
372
|
return entries;
|
|
@@ -458,7 +447,7 @@ export class FileMemoryEngineBackend {
|
|
|
458
447
|
try {
|
|
459
448
|
const fd = openSync(ownerPath, "w", 0o600);
|
|
460
449
|
try {
|
|
461
|
-
|
|
450
|
+
writeAllSync(fd, token);
|
|
462
451
|
fsyncSync(fd);
|
|
463
452
|
}
|
|
464
453
|
finally {
|
|
@@ -584,7 +573,7 @@ export class FileMemoryEngineBackend {
|
|
|
584
573
|
mkdirSync(dirname(op.target), { recursive: true });
|
|
585
574
|
const fd = openSync(staged, "w", 0o600);
|
|
586
575
|
try {
|
|
587
|
-
|
|
576
|
+
writeAllSync(fd, op.content);
|
|
588
577
|
fsyncSync(fd);
|
|
589
578
|
}
|
|
590
579
|
finally {
|
|
@@ -23,6 +23,13 @@ export declare function scopeDirFor(memoryDir: string, controlDir: string, scope
|
|
|
23
23
|
export declare function canonicalize(p: string): string;
|
|
24
24
|
export declare function isContainedIn(root: string, child: string): boolean;
|
|
25
25
|
export declare function ensureDirExists(dir: string): void;
|
|
26
|
+
export declare const QUARANTINE_DIR = "quarantine";
|
|
27
|
+
export interface QuarantineOutcome {
|
|
28
|
+
dest?: string;
|
|
29
|
+
removed: boolean;
|
|
30
|
+
detail?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function quarantineAndTombstone(path: string, content: string, quarantineDir: string, now: () => number): QuarantineOutcome;
|
|
26
33
|
export declare const ANNOUNCEMENTS_FILE = "announcements.json";
|
|
27
34
|
export declare const MEMORY_ANNOUNCEMENTS_MAX = 20;
|
|
28
35
|
export declare const SCAN_FUSE_FILE = "scan-fuse.json";
|
|
@@ -39,5 +46,6 @@ export declare function readIndexRevs(controlDir: string): Record<string, string
|
|
|
39
46
|
export declare function writeIndexRevs(controlDir: string, revs: Record<string, string>): void;
|
|
40
47
|
export declare function scanFuseCount(controlDir: string, key: string): number;
|
|
41
48
|
export declare function clearScanFuse(controlDir: string, keys: Iterable<string>): void;
|
|
49
|
+
export declare function writeAllSync(fd: number, data: string): void;
|
|
42
50
|
export declare function atomicWriteFileSync(path: string, data: string): void;
|
|
43
51
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
1
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
5
5
|
const SCOPES_FILE = "scopes.json";
|
|
6
6
|
export const CURSORS_FILE = "cursors.json";
|
|
7
7
|
export class ControlPlaneCorruptError extends Error {
|
|
@@ -190,6 +190,52 @@ export function ensureDirExists(dir) {
|
|
|
190
190
|
if (!existsSync(dir))
|
|
191
191
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
192
192
|
}
|
|
193
|
+
export const QUARANTINE_DIR = "quarantine";
|
|
194
|
+
const DELETED_TOMBSTONE = "---\ndeleted: true\n---\n";
|
|
195
|
+
let quarantineSeq = 0;
|
|
196
|
+
export function quarantineAndTombstone(path, content, quarantineDir, now) {
|
|
197
|
+
let captured;
|
|
198
|
+
let detail;
|
|
199
|
+
try {
|
|
200
|
+
ensureDirExists(quarantineDir);
|
|
201
|
+
const base = `${now()}-${basename(path)}`;
|
|
202
|
+
for (let attempt = 0; captured === undefined && attempt < 10; attempt++) {
|
|
203
|
+
const candidate = join(quarantineDir, attempt === 0 ? base : `${base}.${attempt}`);
|
|
204
|
+
try {
|
|
205
|
+
writeFileSync(candidate, content, { encoding: "utf8", flag: "wx" });
|
|
206
|
+
captured = candidate;
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
if (!(err instanceof Error && "code" in err && err.code === "EEXIST"))
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (captured === undefined) {
|
|
214
|
+
const last = join(quarantineDir, `${base}.${process.pid}.${quarantineSeq++}`);
|
|
215
|
+
writeFileSync(last, content, { encoding: "utf8", flag: "wx" });
|
|
216
|
+
captured = last;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
detail = `quarantine copy failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
221
|
+
}
|
|
222
|
+
let removed = false;
|
|
223
|
+
try {
|
|
224
|
+
rmSync(path, { force: true });
|
|
225
|
+
removed = true;
|
|
226
|
+
}
|
|
227
|
+
catch (rmErr) {
|
|
228
|
+
try {
|
|
229
|
+
writeFileSync(path, DELETED_TOMBSTONE, "utf8");
|
|
230
|
+
removed = true;
|
|
231
|
+
detail = `${detail !== undefined ? `${detail}; ` : ""}delete failed (${rmErr instanceof Error ? rmErr.message : String(rmErr)}) — tombstoned in place`;
|
|
232
|
+
}
|
|
233
|
+
catch (clearErr) {
|
|
234
|
+
detail = `${detail !== undefined ? `${detail}; ` : ""}suspect file could not be deleted or cleared: ${clearErr instanceof Error ? clearErr.message : String(clearErr)}`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { ...(captured !== undefined ? { dest: captured } : {}), removed, ...(detail !== undefined ? { detail } : {}) };
|
|
238
|
+
}
|
|
193
239
|
export const ANNOUNCEMENTS_FILE = "announcements.json";
|
|
194
240
|
export const MEMORY_ANNOUNCEMENTS_MAX = 20;
|
|
195
241
|
export const SCAN_FUSE_FILE = "scan-fuse.json";
|
|
@@ -219,7 +265,7 @@ function acquireSidecarLock(lockDir, opts = {}) {
|
|
|
219
265
|
let fd;
|
|
220
266
|
try {
|
|
221
267
|
fd = openSync(join(lockDir, "owner"), "w");
|
|
222
|
-
|
|
268
|
+
writeAllSync(fd, token);
|
|
223
269
|
}
|
|
224
270
|
catch {
|
|
225
271
|
}
|
|
@@ -444,12 +490,22 @@ export function clearScanFuse(controlDir, keys) {
|
|
|
444
490
|
return changed ? counts : undefined;
|
|
445
491
|
});
|
|
446
492
|
}
|
|
493
|
+
export function writeAllSync(fd, data) {
|
|
494
|
+
const buf = Buffer.from(data, "utf8");
|
|
495
|
+
let written = 0;
|
|
496
|
+
while (written < buf.length) {
|
|
497
|
+
const n = writeSync(fd, buf, written, buf.length - written);
|
|
498
|
+
if (n <= 0)
|
|
499
|
+
throw new Error(`writeAllSync: write made no progress (${written}/${buf.length} bytes) — refusing to leave a truncated file`);
|
|
500
|
+
written += n;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
447
503
|
export function atomicWriteFileSync(path, data) {
|
|
448
504
|
ensureDirExists(dirname(path));
|
|
449
505
|
const tmp = join(dirname(path), `.tmp-${process.pid}-${Math.random().toString(36).slice(2, 10)}`);
|
|
450
506
|
const fd = openSync(tmp, "w", 0o600);
|
|
451
507
|
try {
|
|
452
|
-
|
|
508
|
+
writeAllSync(fd, data);
|
|
453
509
|
fsyncSync(fd);
|
|
454
510
|
}
|
|
455
511
|
finally {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { canonicalize } from "./layout.js";
|
|
4
|
+
function pathSegmentsDualFamily(p) {
|
|
5
|
+
return p.split(/[\\/]/).filter(Boolean);
|
|
6
|
+
}
|
|
7
|
+
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
8
|
+
const foldPathCase = (s) => (CASE_INSENSITIVE_FS ? s.toLowerCase() : s);
|
|
3
9
|
export const SCOPE_SEGMENT_MAX_ENCODED = 64;
|
|
4
10
|
const SEGMENT_SAFE = /^[A-Za-z0-9._-]$/;
|
|
5
11
|
export function encodeScopeSegment(raw) {
|
|
@@ -117,9 +123,9 @@ export function assertScopeContractPlacement(args) {
|
|
|
117
123
|
const personal = args.parsedScopes.filter(isCentralAuthorityScope);
|
|
118
124
|
if (personal.length === 0)
|
|
119
125
|
return;
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
126
|
+
const rootSegs = pathSegmentsDualFamily(canonicalize(resolve(args.repoRoot))).map(foldPathCase);
|
|
127
|
+
const dirSegs = pathSegmentsDualFamily(canonicalize(resolve(args.memoryDir))).map(foldPathCase);
|
|
128
|
+
if (rootSegs.length <= dirSegs.length && rootSegs.every((s, i) => s === dirSegs[i])) {
|
|
123
129
|
const e = new Error(`memory scope contract v2: central-authority scope(s) ${personal
|
|
124
130
|
.map((p) => `"${p.kind}"`)
|
|
125
131
|
.join(", ")} cannot materialize into an IN-REPO memory dir (${args.memoryDir}) — ` +
|
|
@@ -88,7 +88,7 @@ export interface MemorySessionHandle {
|
|
|
88
88
|
indexBaselineLines: number;
|
|
89
89
|
indexText: string;
|
|
90
90
|
}
|
|
91
|
-
export type HarvestRejectionCode = "outside_root" | "symlink" | "secret" | "injection" | "filename" | "too_large" | "file_cap" | "readonly_layer" | "stub_modified" | "nested_too_deep" | "quarantine_failed" | "invalid";
|
|
91
|
+
export type HarvestRejectionCode = "outside_root" | "symlink" | "secret" | "injection" | "filename" | "too_large" | "file_cap" | "readonly_layer" | "stub_modified" | "nested_too_deep" | "quarantine_failed" | "unreadable" | "invalid";
|
|
92
92
|
export interface HarvestRejection {
|
|
93
93
|
path: string;
|
|
94
94
|
code: HarvestRejectionCode;
|
|
@@ -16,7 +16,6 @@ export declare class ActiveSkillScope {
|
|
|
16
16
|
active(): readonly ActiveSkillFrame[];
|
|
17
17
|
get size(): number;
|
|
18
18
|
}
|
|
19
|
-
export declare function isWithin(root: string, p: string): boolean;
|
|
20
19
|
export declare function createActiveSkillScopePolicy(opts: {
|
|
21
20
|
scope: ActiveSkillScope;
|
|
22
21
|
env: ExecutionEnv;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalizeTarget, fileArgPath } from "../../tools/fs/safety.js";
|
|
2
|
+
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
2
3
|
import { canonicalToolName } from "../tool-name-aliases.js";
|
|
3
4
|
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
4
5
|
export class ActiveSkillScope {
|
|
@@ -16,21 +17,6 @@ export class ActiveSkillScope {
|
|
|
16
17
|
return this.frames.length;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
|
-
const PATH_WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit"]);
|
|
20
|
-
export function isWithin(root, p) {
|
|
21
|
-
const norm = (x) => {
|
|
22
|
-
let n = x.replace(/\\/g, "/");
|
|
23
|
-
if (/^[A-Za-z]:(\/|$)/.test(n))
|
|
24
|
-
n = n[0].toLowerCase() + n.slice(1);
|
|
25
|
-
return n;
|
|
26
|
-
};
|
|
27
|
-
const r = norm(root);
|
|
28
|
-
const c = norm(p);
|
|
29
|
-
if (c === r)
|
|
30
|
-
return true;
|
|
31
|
-
const base = r.endsWith("/") ? r : r + "/";
|
|
32
|
-
return c.startsWith(base);
|
|
33
|
-
}
|
|
34
20
|
export function createActiveSkillScopePolicy(opts) {
|
|
35
21
|
const { scope, env, rootPath, toolEffects } = opts;
|
|
36
22
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AssistantMessage } from "../../internal/llm.js";
|
|
2
|
-
import type { TaskResult, TaskSpec } from "../types.js";
|
|
2
|
+
import type { NestedUsage, TaskResult, TaskSpec } from "../types.js";
|
|
3
3
|
export interface Stats {
|
|
4
4
|
turns: number;
|
|
5
5
|
tokens: number;
|
|
@@ -12,12 +12,7 @@ export interface Stats {
|
|
|
12
12
|
cacheHitRate?: number;
|
|
13
13
|
totalInputTokens: number;
|
|
14
14
|
costMicroUsd: number;
|
|
15
|
-
nested?:
|
|
16
|
-
tokens: number;
|
|
17
|
-
turns: number;
|
|
18
|
-
tasks: number;
|
|
19
|
-
costMicroUsd?: number;
|
|
20
|
-
};
|
|
15
|
+
nested?: NestedUsage;
|
|
21
16
|
memory?: {
|
|
22
17
|
tokens: number;
|
|
23
18
|
costMicroUsd: number;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { WALLTIME_CUTOFF_MESSAGE } from "../../brain/walltime.js";
|
|
1
|
+
import { isDegenerateCutMessage, isWalltimeCutMessage } from "../../brain/terminal-cause.js";
|
|
3
2
|
import { extractErrorCode, stripErrorCodePrefix } from "../../brain/errors.js";
|
|
4
3
|
export function errorCodeOf(err) {
|
|
5
4
|
let cur = err;
|
|
@@ -54,8 +53,8 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
54
53
|
let checkpointToken;
|
|
55
54
|
let checkpointGate;
|
|
56
55
|
let workspaceRestoreMode;
|
|
57
|
-
const isDegenerate = final?.stopReason === "error" && final
|
|
58
|
-
const isWalltimeCutoff = final?.stopReason === "error" && final
|
|
56
|
+
const isDegenerate = final?.stopReason === "error" && isDegenerateCutMessage(final);
|
|
57
|
+
const isWalltimeCutoff = final?.stopReason === "error" && isWalltimeCutMessage(final);
|
|
59
58
|
if (flags.outputInvalid) {
|
|
60
59
|
status = "failed";
|
|
61
60
|
errorCode = "output.invalid";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type TracerHook } from "../trace.js";
|
|
2
|
+
import type { MaybeCompactOptions } from "../auto-compaction.js";
|
|
3
|
+
import type { TaskSpec } from "../types.js";
|
|
4
|
+
import type { Prepared } from "./prepare-task.js";
|
|
5
|
+
export declare function buildWorkingFileAttachments(spec: TaskSpec, prepared: Prepared): MaybeCompactOptions["workingFileAttachments"];
|
|
6
|
+
export declare function centerAdoptionOption(prepared: Prepared): Partial<Pick<MaybeCompactOptions, "centerAdoption">>;
|
|
7
|
+
export declare function emitInputTruncated(tracer: TracerHook | undefined, taskId: string): NonNullable<MaybeCompactOptions["onInputTruncated"]>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { emitTrace } from "../trace.js";
|
|
2
|
+
export function buildWorkingFileAttachments(spec, prepared) {
|
|
3
|
+
if (spec.compaction?.attachWorkingFiles === false || !prepared.readTaskFile)
|
|
4
|
+
return undefined;
|
|
5
|
+
return {
|
|
6
|
+
readFile: prepared.readTaskFile,
|
|
7
|
+
...(prepared.recentlyReadFiles ? { recentlyReadFiles: prepared.recentlyReadFiles } : {}),
|
|
8
|
+
...(prepared.normalizeAttachmentPath ? { normalizePath: prepared.normalizeAttachmentPath } : {}),
|
|
9
|
+
...(prepared.isDedupStubResult ? { isDedupStubResult: prepared.isDedupStubResult } : {}),
|
|
10
|
+
...(prepared.instructionSources
|
|
11
|
+
? { instructionSourcePaths: prepared.instructionSources.filter((s) => s.contentHash !== null).map((s) => s.path) }
|
|
12
|
+
: {}),
|
|
13
|
+
...(typeof spec.compaction?.attachWorkingFiles === "object" ? spec.compaction.attachWorkingFiles : undefined),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function centerAdoptionOption(prepared) {
|
|
17
|
+
const ca = prepared.centerCompactionCandidate?.();
|
|
18
|
+
return ca !== undefined ? { centerAdoption: ca } : {};
|
|
19
|
+
}
|
|
20
|
+
export function emitInputTruncated(tracer, taskId) {
|
|
21
|
+
return (info) => emitTrace(tracer, () => ({
|
|
22
|
+
kind: "compaction.input_truncated",
|
|
23
|
+
version: 1,
|
|
24
|
+
taskId,
|
|
25
|
+
label: info.label,
|
|
26
|
+
droppedChars: info.droppedChars,
|
|
27
|
+
keptChars: info.keptChars,
|
|
28
|
+
ts: Date.now(),
|
|
29
|
+
}));
|
|
30
|
+
}
|