@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.
Files changed (153) hide show
  1. package/CHANGELOG.md +2382 -0
  2. package/dist/agents/cascade.d.ts +3 -1
  3. package/dist/agents/cascade.js +31 -32
  4. package/dist/agents/cumulative-stats.d.ts +29 -0
  5. package/dist/agents/cumulative-stats.js +29 -0
  6. package/dist/agents/observer.d.ts +4 -0
  7. package/dist/agents/observer.js +11 -10
  8. package/dist/agents/repair-loop.d.ts +2 -0
  9. package/dist/agents/repair-loop.js +48 -24
  10. package/dist/agents/retain-ledger.d.ts +2 -0
  11. package/dist/agents/retain-ledger.js +6 -5
  12. package/dist/agents/roster-store.d.ts +1 -0
  13. package/dist/agents/roster-store.js +3 -5
  14. package/dist/agents/send-message-tool.d.ts +1 -0
  15. package/dist/agents/send-message-tool.js +2 -0
  16. package/dist/agents/subagent.d.ts +3 -1
  17. package/dist/agents/subagent.js +79 -94
  18. package/dist/agents/teacher.d.ts +1 -9
  19. package/dist/agents/teacher.js +21 -24
  20. package/dist/agents/tool-filter.js +6 -7
  21. package/dist/agents/verify.d.ts +1 -0
  22. package/dist/agents/verify.js +15 -14
  23. package/dist/brain/anthropic.js +24 -21
  24. package/dist/brain/openai.js +24 -21
  25. package/dist/brain/stream-engine.js +5 -2
  26. package/dist/brain/stream-shared.d.ts +1 -0
  27. package/dist/brain/stream-shared.js +14 -0
  28. package/dist/brain/terminal-cause.d.ts +4 -0
  29. package/dist/brain/terminal-cause.js +12 -0
  30. package/dist/brain/tool-call-id.d.ts +1 -0
  31. package/dist/brain/tool-call-id.js +3 -0
  32. package/dist/brain/tool-call-repair.js +2 -1
  33. package/dist/core/arg-summary.d.ts +21 -1
  34. package/dist/core/arg-summary.js +61 -14
  35. package/dist/core/auto-compaction.d.ts +1 -0
  36. package/dist/core/auto-compaction.js +16 -25
  37. package/dist/core/background-agent-store.d.ts +22 -22
  38. package/dist/core/background-agent-store.js +19 -23
  39. package/dist/core/checkpoint-store.d.ts +6 -13
  40. package/dist/core/checkpoint-store.js +11 -5
  41. package/dist/core/file-snapshot-store.d.ts +8 -0
  42. package/dist/core/file-snapshot-store.js +4 -3
  43. package/dist/core/fs-write-gate-policy.js +2 -3
  44. package/dist/core/hooks.d.ts +4 -1
  45. package/dist/core/hooks.js +8 -18
  46. package/dist/core/lsp.js +4 -3
  47. package/dist/core/mailbox-store.d.ts +7 -10
  48. package/dist/core/mcp.d.ts +7 -10
  49. package/dist/core/mcp.js +7 -9
  50. package/dist/core/memory-engine/engine.d.ts +0 -1
  51. package/dist/core/memory-engine/engine.js +24 -34
  52. package/dist/core/memory-engine/file-backend.d.ts +1 -2
  53. package/dist/core/memory-engine/file-backend.js +25 -36
  54. package/dist/core/memory-engine/layout.d.ts +8 -0
  55. package/dist/core/memory-engine/layout.js +60 -4
  56. package/dist/core/memory-engine/scope-contract.js +9 -3
  57. package/dist/core/memory-engine/types.d.ts +1 -1
  58. package/dist/core/permission-rules.js +2 -3
  59. package/dist/core/runner/active-skill-scope.d.ts +0 -1
  60. package/dist/core/runner/active-skill-scope.js +2 -17
  61. package/dist/core/runner/assemble-result.d.ts +2 -7
  62. package/dist/core/runner/assemble-result.js +3 -4
  63. package/dist/core/runner/compaction-call-options.d.ts +7 -0
  64. package/dist/core/runner/compaction-call-options.js +30 -0
  65. package/dist/core/runner/prepare-task.d.ts +6 -23
  66. package/dist/core/runner/prepare-task.js +36 -61
  67. package/dist/core/runner/runtask.d.ts +1 -0
  68. package/dist/core/runner/runtask.js +84 -172
  69. package/dist/core/runner/session-rule-policy.js +7 -5
  70. package/dist/core/runner/teardown-bounded.d.ts +7 -0
  71. package/dist/core/runner/teardown-bounded.js +36 -0
  72. package/dist/core/runner/tool-disclosure.d.ts +2 -5
  73. package/dist/core/runner/turn-attachments.d.ts +5 -15
  74. package/dist/core/runner/turn-attachments.js +1 -1
  75. package/dist/core/safe-notify.d.ts +17 -0
  76. package/dist/core/safe-notify.js +59 -0
  77. package/dist/core/sensitive-path-policy.js +4 -4
  78. package/dist/core/session-reconcile.js +1 -2
  79. package/dist/core/skill-tool-specifier.js +2 -3
  80. package/dist/core/skills-directory.js +2 -3
  81. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  82. package/dist/core/stub-env.d.ts +2 -6
  83. package/dist/core/surrogate-safe-slice.d.ts +4 -0
  84. package/dist/core/surrogate-safe-slice.js +18 -0
  85. package/dist/core/task-registry-monitor.js +8 -13
  86. package/dist/core/task-registry-shared.d.ts +23 -1
  87. package/dist/core/task-registry-shared.js +16 -0
  88. package/dist/core/task-registry.d.ts +6 -1
  89. package/dist/core/task-registry.js +52 -87
  90. package/dist/core/task-tool-shape.d.ts +0 -2
  91. package/dist/core/task-tool-shape.js +2 -5
  92. package/dist/core/tool-errors.js +5 -4
  93. package/dist/core/tool-name-aliases.d.ts +1 -2
  94. package/dist/core/tool-name-aliases.js +42 -59
  95. package/dist/core/tool-policy.js +21 -17
  96. package/dist/core/tool-result-store.d.ts +1 -0
  97. package/dist/core/tool-result-store.js +17 -1
  98. package/dist/core/trace.d.ts +9 -9
  99. package/dist/core/types.d.ts +15 -12
  100. package/dist/core/untrusted-egress.d.ts +4 -2
  101. package/dist/core/untrusted-egress.js +28 -14
  102. package/dist/core/workflow-journal-store.d.ts +9 -20
  103. package/dist/core/workflow-run-store.d.ts +11 -10
  104. package/dist/core/workflow-run-store.js +22 -16
  105. package/dist/engine/compaction/utils.js +4 -2
  106. package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
  107. package/dist/engine/execution-env/node-execution-env.js +1 -1
  108. package/dist/engine/harness/agent-harness.d.ts +1 -0
  109. package/dist/engine/harness/agent-harness.js +13 -33
  110. package/dist/engine/harness/types.d.ts +29 -67
  111. package/dist/engine/llm/types.d.ts +1 -1
  112. package/dist/engine/loop/agent-loop.js +12 -14
  113. package/dist/engine/loop/types.d.ts +11 -9
  114. package/dist/engine/session/memory-repo.d.ts +2 -6
  115. package/dist/engine/session/session.d.ts +5 -30
  116. package/dist/index.d.ts +6 -3
  117. package/dist/index.js +4 -3
  118. package/dist/internal/harness-types.d.ts +3 -1
  119. package/dist/orchestration/run-spec.js +6 -4
  120. package/dist/orchestration/run-workflow-tool.js +3 -36
  121. package/dist/orchestration/workflow-governance.js +2 -1
  122. package/dist/orchestration/workflow.d.ts +4 -11
  123. package/dist/orchestration/workflow.js +164 -204
  124. package/dist/prompt-assembly/packs/sema-default.js +1 -3
  125. package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
  126. package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
  127. package/dist/prompts/default.js +1 -3
  128. package/dist/prompts/simple-sections.d.ts +0 -1
  129. package/dist/prompts/simple-sections.js +0 -1
  130. package/dist/stores/file/background-agent-store.d.ts +3 -12
  131. package/dist/stores/file/background-agent-store.js +3 -41
  132. package/dist/stores/file/checkpoint-store.js +6 -6
  133. package/dist/stores/file/mailbox-store.d.ts +2 -6
  134. package/dist/stores/file/session-policy-store.js +15 -1
  135. package/dist/stores/file/session-store.d.ts +2 -6
  136. package/dist/stores/file/workflow-journal-store.d.ts +3 -11
  137. package/dist/stores/file/workflow-run-store.d.ts +3 -7
  138. package/dist/stores/file/workflow-run-store.js +4 -17
  139. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
  140. package/dist/tools/fs/bash-readonly-classifier.js +60 -25
  141. package/dist/tools/fs/fs-bash.js +330 -104
  142. package/dist/tools/fs/fs-pdf.d.ts +2 -12
  143. package/dist/tools/fs/fs-shared.js +2 -2
  144. package/dist/tools/fs/fs-write.js +0 -1
  145. package/dist/tools/fs/notebook.d.ts +2 -5
  146. package/dist/tools/fs/safety.d.ts +3 -0
  147. package/dist/tools/fs/safety.js +43 -14
  148. package/dist/tools/fs/search.js +5 -5
  149. package/dist/tools/monitor.d.ts +1 -0
  150. package/dist/tools/monitor.js +7 -5
  151. package/dist/tools/worktree.d.ts +2 -7
  152. package/dist/tools/worktree.js +3 -1
  153. package/package.json +6 -2
@@ -1,11 +1,11 @@
1
- import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, openSync, writeSync, fsyncSync, closeSync } from "node:fs";
1
+ import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, openSync, fsyncSync, closeSync } from "node:fs";
2
2
  import { randomUUID } from "node:crypto";
3
- import { basename, dirname, join, relative } from "node:path";
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 = this.quarantineFile(f.path, text);
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
- writeSync(fd, token);
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
- writeSync(fd, op.content);
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
- writeSync(fd, token);
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
- writeSync(fd, data);
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 root = resolve(args.repoRoot);
121
- const dir = resolve(args.memoryDir);
122
- if (dir === root || dir.startsWith(root + "/")) {
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;
@@ -1,4 +1,3 @@
1
- import { canonicalToolName } from "./tool-name-aliases.js";
2
1
  export const BASH_GENERIC_PARAMS = new Set(["command", "timeout", "description", "run_in_background"]);
3
2
  const DEFAULT_CAPS = {
4
3
  maxRules: 256,
@@ -142,7 +141,7 @@ function compile(rules, caps, primaryFieldGeneric) {
142
141
  continue;
143
142
  }
144
143
  }
145
- const toolName = canonicalToolName(parsed.toolName);
144
+ const toolName = parsed.toolName;
146
145
  if (toolName.startsWith("mcp__") && (parsed.ruleContent !== undefined || indexOfUnescaped(text, "(") !== -1)) {
147
146
  bad(text, "unsupported.mcp_paren", "MCP rules do not support patterns in parentheses (CC parity); use the toolAxes system");
148
147
  continue;
@@ -262,7 +261,7 @@ export function createPermissionRulePolicy(rules, opts) {
262
261
  const defaultAction = opts?.defaultAction ?? "allow";
263
262
  return {
264
263
  check(req) {
265
- const entry = byTool.get(canonicalToolName(req.toolName));
264
+ const entry = byTool.get(req.toolName);
266
265
  if (entry) {
267
266
  if (entry.bare.deny) {
268
267
  return { action: "deny", reason: ruleMessage("denied", entry.bare.deny.ruleText, entry.bare.deny.source) };
@@ -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,5 +1,5 @@
1
1
  import { canonicalizeTarget, fileArgPath } from "../../tools/fs/safety.js";
2
- import { canonicalToolName } from "../tool-name-aliases.js";
2
+ import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
3
3
  import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
4
4
  export class ActiveSkillScope {
5
5
  frames = [];
@@ -16,26 +16,11 @@ export class ActiveSkillScope {
16
16
  return this.frames.length;
17
17
  }
18
18
  }
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
19
  export function createActiveSkillScopePolicy(opts) {
35
20
  const { scope, env, rootPath, toolEffects } = opts;
36
21
  return {
37
22
  async check(req, signal) {
38
- const toolName = canonicalToolName(req.toolName);
23
+ const toolName = req.toolName;
39
24
  const frames = scope.active();
40
25
  if (frames.length === 0)
41
26
  return { action: "allow" };
@@ -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 { DEGENERATE_MESSAGE } from "../../brain/repetition.js";
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.errorMessage === DEGENERATE_MESSAGE;
58
- const isWalltimeCutoff = final?.stopReason === "error" && final.errorMessage === WALLTIME_CUTOFF_MESSAGE;
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
+ }
@@ -12,12 +12,13 @@ import type { SessionPermissionRules } from "../session-policy-store.js";
12
12
  import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js";
13
13
  import { type OutputRef, type BlockedRef, type SkillListingEntry } from "./synthetic-tools.js";
14
14
  import type { MemoryEngine } from "../memory-engine/engine.js";
15
+ import { type ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
15
16
  import type { TaskNotificationPayload } from "../task-notification.js";
16
17
  import { type CwdRef } from "../../tools/fs/index.js";
17
18
  import type { Runner } from "./runtask.js";
18
19
  import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type ResourceLimitReason } from "../checkpoint-store.js";
19
- import type { AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
20
- import type { RunnerDeps, TaskEvent, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
20
+ import type { ActiveWorktreeSession, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
21
+ import type { NestedUsageAccum, RunnerDeps, TaskEvent, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
21
22
  import type { RepairBundle } from "../../agents/repair-loop.js";
22
23
  export declare const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
23
24
  export declare function checkpointScopeOf(spec: {
@@ -42,21 +43,11 @@ export interface Prepared {
42
43
  hit: boolean;
43
44
  };
44
45
  blockedToolCalls: Set<string>;
45
- nestedStats: {
46
- tokens: number;
47
- turns: number;
48
- tasks: number;
49
- costMicroUsd: number;
50
- };
46
+ nestedStats: NestedUsageAccum;
51
47
  rewindNotes?: NonNullable<TaskResult["rewindNotes"]>;
52
48
  cwdRef?: CwdRef;
53
49
  worktreeSessionRef?: {
54
- current?: {
55
- worktreeDir: string;
56
- originalCwd: string;
57
- baseSha: string;
58
- entered?: boolean;
59
- };
50
+ current?: ActiveWorktreeSession;
60
51
  };
61
52
  workspaceStateSettle?: {
62
53
  rootCanonical: string;
@@ -89,15 +80,7 @@ export interface Prepared {
89
80
  hash: string;
90
81
  contentHash?: string;
91
82
  }>;
92
- tools?: Array<{
93
- wireName: string;
94
- aliases: string[];
95
- contractId: string;
96
- implementationRevision: string;
97
- cardId: string;
98
- shapeDigest: string;
99
- wireSchemaDigest: string;
100
- }>;
83
+ tools?: ToolManifestRow[];
101
84
  snapshot?: {
102
85
  cacheIdentity: string;
103
86
  elements: import("../../prompt-assembly/turn-snapshot.js").CacheIdentityElements;