@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.
Files changed (127) hide show
  1. package/CHANGELOG.md +2345 -0
  2. package/dist/agents/cascade.d.ts +2 -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.js +27 -19
  9. package/dist/agents/retain-ledger.d.ts +2 -0
  10. package/dist/agents/retain-ledger.js +6 -5
  11. package/dist/agents/roster-store.js +3 -5
  12. package/dist/agents/subagent.js +66 -64
  13. package/dist/agents/teacher.d.ts +1 -9
  14. package/dist/agents/teacher.js +21 -24
  15. package/dist/agents/verify.js +15 -14
  16. package/dist/brain/anthropic.js +24 -21
  17. package/dist/brain/openai.js +24 -21
  18. package/dist/brain/stream-engine.js +5 -2
  19. package/dist/brain/stream-shared.d.ts +1 -0
  20. package/dist/brain/stream-shared.js +14 -0
  21. package/dist/brain/terminal-cause.d.ts +4 -0
  22. package/dist/brain/terminal-cause.js +12 -0
  23. package/dist/brain/tool-call-id.d.ts +1 -0
  24. package/dist/brain/tool-call-id.js +3 -0
  25. package/dist/brain/tool-call-repair.js +2 -1
  26. package/dist/core/auto-compaction.js +16 -25
  27. package/dist/core/background-agent-store.d.ts +22 -22
  28. package/dist/core/background-agent-store.js +19 -23
  29. package/dist/core/checkpoint-store.d.ts +6 -13
  30. package/dist/core/checkpoint-store.js +11 -5
  31. package/dist/core/file-snapshot-store.d.ts +8 -0
  32. package/dist/core/file-snapshot-store.js +4 -3
  33. package/dist/core/hooks.d.ts +3 -1
  34. package/dist/core/hooks.js +8 -18
  35. package/dist/core/lsp.js +4 -3
  36. package/dist/core/mailbox-store.d.ts +7 -10
  37. package/dist/core/mcp.d.ts +7 -10
  38. package/dist/core/mcp.js +7 -3
  39. package/dist/core/memory-engine/engine.d.ts +0 -1
  40. package/dist/core/memory-engine/engine.js +24 -34
  41. package/dist/core/memory-engine/file-backend.d.ts +1 -2
  42. package/dist/core/memory-engine/file-backend.js +25 -36
  43. package/dist/core/memory-engine/layout.d.ts +8 -0
  44. package/dist/core/memory-engine/layout.js +60 -4
  45. package/dist/core/memory-engine/scope-contract.js +9 -3
  46. package/dist/core/memory-engine/types.d.ts +1 -1
  47. package/dist/core/runner/active-skill-scope.d.ts +0 -1
  48. package/dist/core/runner/active-skill-scope.js +1 -15
  49. package/dist/core/runner/assemble-result.d.ts +2 -7
  50. package/dist/core/runner/assemble-result.js +3 -4
  51. package/dist/core/runner/compaction-call-options.d.ts +7 -0
  52. package/dist/core/runner/compaction-call-options.js +30 -0
  53. package/dist/core/runner/prepare-task.d.ts +6 -23
  54. package/dist/core/runner/prepare-task.js +20 -53
  55. package/dist/core/runner/runtask.d.ts +1 -0
  56. package/dist/core/runner/runtask.js +65 -156
  57. package/dist/core/runner/session-rule-policy.js +4 -1
  58. package/dist/core/runner/teardown-bounded.d.ts +7 -0
  59. package/dist/core/runner/teardown-bounded.js +36 -0
  60. package/dist/core/runner/tool-disclosure.d.ts +2 -5
  61. package/dist/core/runner/turn-attachments.d.ts +5 -15
  62. package/dist/core/runner/turn-attachments.js +1 -1
  63. package/dist/core/safe-notify.d.ts +17 -0
  64. package/dist/core/safe-notify.js +59 -0
  65. package/dist/core/sensitive-path-policy.js +2 -1
  66. package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
  67. package/dist/core/stub-env.d.ts +2 -6
  68. package/dist/core/surrogate-safe-slice.d.ts +4 -0
  69. package/dist/core/surrogate-safe-slice.js +18 -0
  70. package/dist/core/task-registry-monitor.js +8 -13
  71. package/dist/core/task-registry-shared.d.ts +22 -0
  72. package/dist/core/task-registry-shared.js +16 -0
  73. package/dist/core/task-registry.d.ts +1 -0
  74. package/dist/core/task-registry.js +40 -57
  75. package/dist/core/tool-errors.js +5 -4
  76. package/dist/core/tool-name-aliases.js +2 -1
  77. package/dist/core/tool-policy.js +10 -5
  78. package/dist/core/tool-result-store.d.ts +1 -0
  79. package/dist/core/tool-result-store.js +17 -1
  80. package/dist/core/trace.d.ts +2 -9
  81. package/dist/core/types.d.ts +15 -12
  82. package/dist/core/untrusted-egress.js +7 -5
  83. package/dist/core/workflow-journal-store.d.ts +9 -20
  84. package/dist/core/workflow-run-store.d.ts +11 -10
  85. package/dist/core/workflow-run-store.js +22 -16
  86. package/dist/engine/compaction/utils.js +4 -2
  87. package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
  88. package/dist/engine/harness/agent-harness.d.ts +1 -0
  89. package/dist/engine/harness/agent-harness.js +13 -33
  90. package/dist/engine/harness/types.d.ts +29 -67
  91. package/dist/engine/llm/types.d.ts +1 -1
  92. package/dist/engine/loop/agent-loop.js +11 -4
  93. package/dist/engine/loop/types.d.ts +11 -9
  94. package/dist/engine/session/memory-repo.d.ts +2 -6
  95. package/dist/engine/session/session.d.ts +5 -30
  96. package/dist/index.d.ts +3 -1
  97. package/dist/index.js +2 -1
  98. package/dist/internal/harness-types.d.ts +3 -1
  99. package/dist/orchestration/run-spec.js +7 -4
  100. package/dist/orchestration/run-workflow-tool.js +3 -35
  101. package/dist/orchestration/workflow.d.ts +3 -11
  102. package/dist/orchestration/workflow.js +164 -204
  103. package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
  104. package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
  105. package/dist/stores/file/background-agent-store.d.ts +3 -12
  106. package/dist/stores/file/background-agent-store.js +3 -41
  107. package/dist/stores/file/checkpoint-store.js +6 -6
  108. package/dist/stores/file/mailbox-store.d.ts +2 -6
  109. package/dist/stores/file/session-policy-store.js +15 -1
  110. package/dist/stores/file/session-store.d.ts +2 -6
  111. package/dist/stores/file/workflow-journal-store.d.ts +3 -11
  112. package/dist/stores/file/workflow-run-store.d.ts +3 -7
  113. package/dist/stores/file/workflow-run-store.js +4 -17
  114. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -0
  115. package/dist/tools/fs/bash-readonly-classifier.js +60 -25
  116. package/dist/tools/fs/fs-bash.js +324 -96
  117. package/dist/tools/fs/fs-pdf.d.ts +2 -12
  118. package/dist/tools/fs/fs-shared.js +2 -2
  119. package/dist/tools/fs/notebook.d.ts +2 -5
  120. package/dist/tools/fs/safety.d.ts +3 -0
  121. package/dist/tools/fs/safety.js +43 -14
  122. package/dist/tools/fs/search.js +5 -5
  123. package/dist/tools/monitor.d.ts +1 -0
  124. package/dist/tools/monitor.js +7 -5
  125. package/dist/tools/worktree.d.ts +2 -7
  126. package/dist/tools/worktree.js +3 -1
  127. package/package.json +6 -2
@@ -21,22 +21,27 @@ export declare class WorkflowRunStoreError extends Error {
21
21
  readonly code: "workflow_run.already_exists";
22
22
  constructor(code: "workflow_run.already_exists", message: string);
23
23
  }
24
+ export interface WorkflowRunListByScopeOptions {
25
+ status?: WorkflowRunStatus;
26
+ limit?: number;
27
+ session?: string;
28
+ }
24
29
  export interface WorkflowRunStore {
25
30
  put(id: string, run: WorkflowRun): Promise<void>;
26
31
  get(id: string): Promise<WorkflowRun | null>;
27
32
  update(id: string, scope: string, run: WorkflowRun, expect?: {
28
33
  rev: number;
29
34
  }): Promise<boolean>;
30
- listByScope(scope: string, opts?: {
31
- status?: WorkflowRunStatus;
32
- limit?: number;
33
- session?: string;
34
- }): Promise<WorkflowRunSummary[]>;
35
+ listByScope(scope: string, opts?: WorkflowRunListByScopeOptions): Promise<WorkflowRunSummary[]>;
35
36
  reap(scope: string, now: number, opts?: {
36
37
  maxAgeMs?: number;
37
38
  keep?: number;
38
39
  }): Promise<number>;
39
40
  }
41
+ export declare function nextWorkflowRunOnUpdate(cur: WorkflowRun | undefined, id: string, scope: string, run: WorkflowRun, expect?: {
42
+ rev: number;
43
+ }): WorkflowRun | null;
44
+ export declare function queryWorkflowRuns(runs: Iterable<WorkflowRun>, scope: string, opts?: WorkflowRunListByScopeOptions): WorkflowRunSummary[];
40
45
  export declare class InMemoryWorkflowRunStore implements WorkflowRunStore {
41
46
  private runs;
42
47
  put(id: string, run: WorkflowRun): Promise<void>;
@@ -44,11 +49,7 @@ export declare class InMemoryWorkflowRunStore implements WorkflowRunStore {
44
49
  update(id: string, scope: string, run: WorkflowRun, expect?: {
45
50
  rev: number;
46
51
  }): Promise<boolean>;
47
- listByScope(scope: string, opts?: {
48
- status?: WorkflowRunStatus;
49
- limit?: number;
50
- session?: string;
51
- }): Promise<WorkflowRunSummary[]>;
52
+ listByScope(scope: string, opts?: WorkflowRunListByScopeOptions): Promise<WorkflowRunSummary[]>;
52
53
  reap(scope: string, now: number, opts?: {
53
54
  maxAgeMs?: number;
54
55
  keep?: number;
@@ -28,6 +28,25 @@ export class WorkflowRunStoreError extends Error {
28
28
  this.name = "WorkflowRunStoreError";
29
29
  }
30
30
  }
31
+ export function nextWorkflowRunOnUpdate(cur, id, scope, run, expect) {
32
+ if (!cur || cur.scope !== scope)
33
+ return null;
34
+ if (expect !== undefined && (cur.rev ?? 0) !== expect.rev)
35
+ return null;
36
+ const next = structuredClone(run);
37
+ next.id = id;
38
+ next.scope = scope;
39
+ next.rev = (cur.rev ?? 0) + 1;
40
+ return next;
41
+ }
42
+ export function queryWorkflowRuns(runs, scope, opts) {
43
+ const rows = [...runs].filter((r) => r.scope === scope &&
44
+ (opts?.status === undefined || r.status === opts.status) &&
45
+ (opts?.session === undefined || r.originatingSessionId === opts.session));
46
+ rows.sort((a, b) => b.createdAt - a.createdAt);
47
+ const limited = opts?.limit !== undefined ? rows.slice(0, Math.max(0, opts.limit)) : rows;
48
+ return limited.map(summarizeWorkflowRun);
49
+ }
31
50
  export class InMemoryWorkflowRunStore {
32
51
  runs = new Map();
33
52
  async put(id, run) {
@@ -44,27 +63,14 @@ export class InMemoryWorkflowRunStore {
44
63
  return run ? structuredClone(run) : null;
45
64
  }
46
65
  async update(id, scope, run, expect) {
47
- const cur = this.runs.get(id);
48
- if (!cur || cur.scope !== scope) {
66
+ const next = nextWorkflowRunOnUpdate(this.runs.get(id), id, scope, run, expect);
67
+ if (!next)
49
68
  return false;
50
- }
51
- if (expect !== undefined && (cur.rev ?? 0) !== expect.rev) {
52
- return false;
53
- }
54
- const next = structuredClone(run);
55
- next.id = id;
56
- next.scope = scope;
57
- next.rev = (cur.rev ?? 0) + 1;
58
69
  this.runs.set(id, next);
59
70
  return true;
60
71
  }
61
72
  async listByScope(scope, opts) {
62
- const rows = [...this.runs.values()].filter((r) => r.scope === scope &&
63
- (opts?.status === undefined || r.status === opts.status) &&
64
- (opts?.session === undefined || r.originatingSessionId === opts.session));
65
- rows.sort((a, b) => b.createdAt - a.createdAt);
66
- const limited = opts?.limit !== undefined ? rows.slice(0, Math.max(0, opts.limit)) : rows;
67
- return limited.map(summarizeWorkflowRun);
73
+ return queryWorkflowRuns(this.runs.values(), scope, opts);
68
74
  }
69
75
  async reap(scope, now, opts) {
70
76
  if (opts?.maxAgeMs === undefined && opts?.keep === undefined)
@@ -1,4 +1,5 @@
1
1
  import { normalizeLlmMessageContent } from "../harness/messages.js";
2
+ import { sliceHeadSafe } from "../../core/surrogate-safe-slice.js";
2
3
  import { cutEnginePrefix, cutEngineSegments, flattenableUserText } from "../../core/untrusted-text.js";
3
4
  import { fileArgPath } from "../../tools/fs/safety.js";
4
5
  export function createFileOps() {
@@ -287,11 +288,12 @@ function truncateForSummary(text, maxChars) {
287
288
  let keep = maxChars;
288
289
  let out = "";
289
290
  for (let i = 0; i < 4; i++) {
290
- out = `${text.slice(0, keep)}\n\n[... ${text.length - keep} more characters truncated]`;
291
+ const kept = sliceHeadSafe(text, keep);
292
+ out = `${kept}\n\n[... ${text.length - kept.length} more characters truncated]`;
291
293
  if (out.length <= maxChars)
292
294
  break;
293
295
  if (keep === 0)
294
- return maxChars >= 1 ? `${text.slice(0, maxChars - 1)}…` : "";
296
+ return maxChars >= 1 ? `${sliceHeadSafe(text, maxChars - 1)}…` : "";
295
297
  keep = Math.max(0, keep - (out.length - maxChars));
296
298
  }
297
299
  return out.length < text.length ? out : text;
@@ -1,4 +1,4 @@
1
- import { type ExecutionEnv, ExecutionError, FileError, type FileInfo, type Result } from "../harness/types.js";
1
+ import { type ExecResult, type ExecutionEnv, ExecutionError, type ExecutionEnvExecOptions, FileError, type FileInfo, type Result } from "../harness/types.js";
2
2
  import type { BackgroundShellCapability, BackgroundShellId, BackgroundPoll, BackgroundSpawnOptions } from "../../core/background-shell.js";
3
3
  import { BackgroundShellError } from "../../core/background-shell.js";
4
4
  import type { SchedulerCapability, ScheduledIntent, SchedulerContext, ScheduledTaskId, ScheduledTaskSummary } from "../../core/scheduler.js";
@@ -54,29 +54,7 @@ export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCa
54
54
  list(ctx: SchedulerContext): Promise<Result<ScheduledTaskSummary[], SchedulerError>>;
55
55
  absolutePath(path: string): Promise<Result<string, FileError>>;
56
56
  joinPath(parts: string[]): Promise<Result<string, FileError>>;
57
- exec(command: string, options?: {
58
- cwd?: string;
59
- env?: Record<string, string>;
60
- timeout?: number;
61
- abortSignal?: AbortSignal;
62
- onStdout?: (chunk: string) => void;
63
- onStderr?: (chunk: string) => void;
64
- detachSignal?: AbortSignal;
65
- onSpawn?: (handle: {
66
- pid: number;
67
- kill: () => void;
68
- }) => void;
69
- onDetachAdopted?: () => void;
70
- autoBackgroundOnTimeout?: boolean;
71
- }): Promise<Result<{
72
- stdout: string;
73
- stderr: string;
74
- exitCode: number;
75
- detached?: {
76
- shellId: string;
77
- cause?: "timeout";
78
- };
79
- }, ExecutionError>>;
57
+ exec(command: string, options?: ExecutionEnvExecOptions): Promise<Result<ExecResult, ExecutionError>>;
80
58
  readTextFile(path: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
81
59
  readTextLines(path: string, options?: {
82
60
  maxLines?: number;
@@ -92,6 +92,7 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
92
92
  images?: ImageContent[];
93
93
  } & UserMessageProvenance): Promise<AssistantMessage>;
94
94
  private static emptyInjection;
95
+ private enqueueInjection;
95
96
  steer(text: string, options?: {
96
97
  images?: ImageContent[];
97
98
  } & UserMessageProvenance): Promise<void>;
@@ -478,14 +478,7 @@ export class AgentHarness {
478
478
  details: result.details,
479
479
  isError,
480
480
  });
481
- return patch
482
- ? {
483
- content: patch.content,
484
- details: patch.details,
485
- isError: patch.isError,
486
- terminate: patch.terminate,
487
- }
488
- : undefined;
481
+ return patch;
489
482
  },
490
483
  prepareNextTurn: async () => {
491
484
  await this.flushPendingSessionWrites();
@@ -725,42 +718,29 @@ export class AgentHarness {
725
718
  static emptyInjection(text, options) {
726
719
  return text.trim().length === 0 && (options?.images === undefined || options.images.length === 0);
727
720
  }
721
+ async enqueueInjection(queue, text, options) {
722
+ if (AgentHarness.emptyInjection(text, options))
723
+ return;
724
+ const m = createUserMessage(text, options?.images, options);
725
+ if (options?.enginePayload !== undefined)
726
+ engineNotePayloads.set(m, options.enginePayload);
727
+ queue.push(m);
728
+ await this.emitQueueUpdate();
729
+ }
728
730
  async steer(text, options) {
729
731
  if (this.phase === "idle") {
730
732
  throw new AgentHarnessError("invalid_state", "Cannot steer while idle");
731
733
  }
732
- if (AgentHarness.emptyInjection(text, options))
733
- return;
734
- {
735
- const m = createUserMessage(text, options?.images, options);
736
- if (options?.enginePayload !== undefined)
737
- engineNotePayloads.set(m, options.enginePayload);
738
- this.steerQueue.push(m);
739
- }
740
- await this.emitQueueUpdate();
734
+ await this.enqueueInjection(this.steerQueue, text, options);
741
735
  }
742
736
  async followUp(text, options) {
743
737
  if (this.phase === "idle") {
744
738
  throw new AgentHarnessError("invalid_state", "Cannot follow up while idle");
745
739
  }
746
- if (AgentHarness.emptyInjection(text, options))
747
- return;
748
- {
749
- const m = createUserMessage(text, options?.images, options);
750
- if (options?.enginePayload !== undefined)
751
- engineNotePayloads.set(m, options.enginePayload);
752
- this.followUpQueue.push(m);
753
- }
754
- await this.emitQueueUpdate();
740
+ await this.enqueueInjection(this.followUpQueue, text, options);
755
741
  }
756
742
  async nextTurn(text, options) {
757
- if (AgentHarness.emptyInjection(text, options))
758
- return;
759
- const m = createUserMessage(text, options?.images, options);
760
- if (options?.enginePayload !== undefined)
761
- engineNotePayloads.set(m, options.enginePayload);
762
- this.nextTurnQueue.push(m);
763
- await this.emitQueueUpdate();
743
+ await this.enqueueInjection(this.nextTurnQueue, text, options);
764
744
  }
765
745
  async appendMessage(message) {
766
746
  try {
@@ -1,5 +1,6 @@
1
+ import type { PromptEpochDescriptor } from "../../prompt-assembly/epoch.js";
1
2
  import type { DocumentContent, ImageContent, Model, SimpleStreamOptions, TextContent, Transport } from "../llm/index.js";
2
- import type { AgentEvent, AgentMessage, AgentTool, QueueMode, ThinkingLevel } from "../loop/types.js";
3
+ import type { AfterToolCallResult, AgentEvent, AgentMessage, AgentTool, BeforeToolCallResult, QueueMode, ThinkingLevel } from "../loop/types.js";
3
4
  import type { AgentCoreRuntimeDeps } from "../loop/runtime-deps.js";
4
5
  export type Result<TValue, TError> = {
5
6
  ok: true;
@@ -129,16 +130,17 @@ export interface FileSystem {
129
130
  }): Promise<Result<string, FileError>>;
130
131
  cleanup(): Promise<void>;
131
132
  }
133
+ export interface ExecResult {
134
+ stdout: string;
135
+ stderr: string;
136
+ exitCode: number;
137
+ detached?: {
138
+ shellId: string;
139
+ cause?: "timeout";
140
+ };
141
+ }
132
142
  export interface Shell {
133
- exec(command: string, options?: ExecutionEnvExecOptions): Promise<Result<{
134
- stdout: string;
135
- stderr: string;
136
- exitCode: number;
137
- detached?: {
138
- shellId: string;
139
- cause?: "timeout";
140
- };
141
- }, ExecutionError>>;
143
+ exec(command: string, options?: ExecutionEnvExecOptions): Promise<Result<ExecResult, ExecutionError>>;
142
144
  cleanup(): Promise<void>;
143
145
  }
144
146
  export interface ExecutionEnv extends FileSystem, Shell {
@@ -195,15 +197,8 @@ export interface LeafEntry extends SessionTreeEntryBase {
195
197
  type: "leaf";
196
198
  targetId: string | null;
197
199
  }
198
- export interface PromptEpochEntry extends SessionTreeEntryBase {
200
+ export interface PromptEpochEntry extends SessionTreeEntryBase, PromptEpochDescriptor {
199
201
  type: "prompt_epoch";
200
- epoch: number;
201
- artifactDigest: string;
202
- packId: string;
203
- assemblyApi: number;
204
- activatedBy: "session_start" | "compaction" | "legacy_migration";
205
- centerArtifactDigest?: string;
206
- sourceRevision?: string;
207
202
  }
208
203
  export interface AnnouncedListingEntry extends SessionTreeEntryBase {
209
204
  type: "announced_listing";
@@ -211,28 +206,22 @@ export interface AnnouncedListingEntry extends SessionTreeEntryBase {
211
206
  skills?: string[];
212
207
  models?: string[];
213
208
  }
214
- export interface WorkspaceStateEntry extends SessionTreeEntryBase {
215
- type: "workspace_state";
209
+ export interface ActiveWorktreeSession {
210
+ worktreeDir: string;
211
+ originalCwd: string;
212
+ baseSha: string;
213
+ entered?: boolean;
214
+ }
215
+ export interface WorkspaceState {
216
216
  taskRoot: string;
217
217
  handsCwd?: string;
218
- activeWorktree?: {
219
- worktreeDir: string;
220
- originalCwd: string;
221
- baseSha: string;
222
- entered?: boolean;
223
- };
218
+ activeWorktree?: ActiveWorktreeSession;
219
+ }
220
+ export interface WorkspaceStateEntry extends SessionTreeEntryBase, WorkspaceState {
221
+ type: "workspace_state";
224
222
  }
225
223
  export declare const WORKSPACE_STATE_MAX_PATH_CHARS = 4096;
226
- export declare function normalizeWorkspaceState(value: unknown): {
227
- taskRoot: string;
228
- handsCwd?: string;
229
- activeWorktree?: {
230
- worktreeDir: string;
231
- originalCwd: string;
232
- baseSha: string;
233
- entered?: boolean;
234
- };
235
- } | undefined;
224
+ export declare function normalizeWorkspaceState(value: unknown): WorkspaceState | undefined;
236
225
  export declare const ANNOUNCED_LISTING_MAX_NAMES = 500;
237
226
  export declare const ANNOUNCED_LISTING_MAX_NAME_CHARS = 200;
238
227
  export declare function normalizeCompactionStateCarrier(v: unknown): {
@@ -331,26 +320,8 @@ export interface Session<TMetadata extends SessionMetadata = SessionMetadata> {
331
320
  skills?: string[];
332
321
  models?: string[];
333
322
  } | undefined>;
334
- appendWorkspaceState(state: {
335
- taskRoot: string;
336
- handsCwd?: string;
337
- activeWorktree?: {
338
- worktreeDir: string;
339
- originalCwd: string;
340
- baseSha: string;
341
- entered?: boolean;
342
- };
343
- }): Promise<string>;
344
- getWorkspaceState(): Promise<{
345
- taskRoot: string;
346
- handsCwd?: string;
347
- activeWorktree?: {
348
- worktreeDir: string;
349
- originalCwd: string;
350
- baseSha: string;
351
- entered?: boolean;
352
- };
353
- } | undefined>;
323
+ appendWorkspaceState(state: WorkspaceState): Promise<string>;
324
+ getWorkspaceState(): Promise<WorkspaceState | undefined>;
354
325
  getPromptEpoch?(): Promise<import("../../prompt-assembly/epoch.js").PromptEpochDescriptor | undefined>;
355
326
  }
356
327
  export interface SessionCreateOptions {
@@ -487,17 +458,8 @@ export interface BeforeProviderRequestResult {
487
458
  export interface BeforeProviderPayloadResult {
488
459
  payload: unknown;
489
460
  }
490
- export interface ToolCallResult {
491
- block?: boolean;
492
- reason?: string;
493
- updatedInput?: unknown;
494
- }
495
- export interface ToolResultPatch {
496
- content?: Array<TextContent | ImageContent | DocumentContent>;
497
- details?: unknown;
498
- isError?: boolean;
499
- terminate?: boolean;
500
- }
461
+ export type ToolCallResult = BeforeToolCallResult;
462
+ export type ToolResultPatch = AfterToolCallResult;
501
463
  export interface SessionBeforeCompactResult {
502
464
  cancel?: boolean;
503
465
  compaction?: CompactResult;
@@ -143,7 +143,7 @@ export interface AssistantMessage {
143
143
  usageMissing?: true;
144
144
  stopReason: StopReason;
145
145
  errorMessage?: string;
146
- errorKind?: "length_empty";
146
+ errorKind?: "length_empty" | "degenerate" | "walltime_cutoff";
147
147
  partialFinalized?: true;
148
148
  repetition?: {
149
149
  cut?: RepetitionEvent;
@@ -647,10 +647,16 @@ function partitionToolCalls(toolCalls, tools) {
647
647
  }
648
648
  return batches;
649
649
  }
650
+ function resolveToolConcurrency(configured) {
651
+ if (configured === undefined)
652
+ return 10;
653
+ return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1;
654
+ }
650
655
  async function runCapped(thunks, limit) {
651
656
  const results = new Array(thunks.length);
652
657
  let next = 0;
653
- const workers = Array.from({ length: Math.max(1, Math.min(limit, thunks.length)) }, async () => {
658
+ const width = Number.isFinite(limit) ? Math.max(1, Math.min(limit, thunks.length)) : 1;
659
+ const workers = Array.from({ length: width }, async () => {
654
660
  while (next < thunks.length) {
655
661
  const i = next++;
656
662
  results[i] = await thunks[i]();
@@ -660,7 +666,7 @@ async function runCapped(thunks, limit) {
660
666
  return results;
661
667
  }
662
668
  async function executeToolCallsPartitioned(currentContext, assistantMessage, toolCalls, config, signal, emit, executor) {
663
- const cap = config.maxToolConcurrency ?? 10;
669
+ const cap = resolveToolConcurrency(config.maxToolConcurrency);
664
670
  const allFinalized = [];
665
671
  const messages = [];
666
672
  let engineCutSeen = false;
@@ -798,7 +804,7 @@ class StreamToolExecutor {
798
804
  this.signal?.aborted ||
799
805
  partialAssistant === null ||
800
806
  !isBatchSafeToolCall(findToolByName(this.context.tools, toolCall.name), toolCall) ||
801
- this.inFlight >= (this.config.maxToolConcurrency ?? 10) ||
807
+ this.inFlight >= resolveToolConcurrency(this.config.maxToolConcurrency) ||
802
808
  this.entries.has(toolCall.id)) {
803
809
  this.barrier = true;
804
810
  return;
@@ -927,7 +933,8 @@ function prepareToolCallArguments(tool, toolCall) {
927
933
  async function prepareToolCall(currentContext, assistantMessage, toolCall, config, signal) {
928
934
  const tool = findToolByName(currentContext.tools, toolCall.name);
929
935
  if (!tool) {
930
- const tombstone = TOMBSTONED_TOOLS[toolCall.name];
936
+ const tombstoneEntry = Object.hasOwn(TOMBSTONED_TOOLS, toolCall.name) ? TOMBSTONED_TOOLS[toolCall.name] : undefined;
937
+ const tombstone = typeof tombstoneEntry === "string" ? tombstoneEntry : undefined;
931
938
  if (tombstone !== undefined) {
932
939
  return {
933
940
  kind: "immediate",
@@ -74,21 +74,23 @@ export interface LoopRecoveryOptions {
74
74
  hasLiveWriteoutWindow?: () => boolean;
75
75
  };
76
76
  }
77
+ export interface ToolCutInfo {
78
+ toolCallId: string;
79
+ toolName: string;
80
+ elapsedMs: number;
81
+ settled: Promise<void>;
82
+ }
83
+ export interface LoopToolDeadline {
84
+ deadlineMs: () => number | undefined;
85
+ onCut?: (info: ToolCutInfo) => void;
86
+ }
77
87
  export interface AgentLoopConfig extends SimpleStreamOptions {
78
88
  model: Model;
79
89
  recovery?: LoopRecoveryOptions;
80
90
  maxTokensPerCall?: () => number | undefined;
81
91
  callDeadlineMsPerCall?: () => number | undefined;
82
92
  stallTimeoutsPerCall?: () => import("../llm/types.js").StallTimeouts | undefined;
83
- toolDeadline?: {
84
- deadlineMs: () => number | undefined;
85
- onCut?: (info: {
86
- toolCallId: string;
87
- toolName: string;
88
- elapsedMs: number;
89
- settled: Promise<void>;
90
- }) => void;
91
- };
93
+ toolDeadline?: LoopToolDeadline;
92
94
  maxToolConcurrency?: number;
93
95
  streamingToolExecution?: boolean;
94
96
  convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
@@ -1,4 +1,4 @@
1
- import { type Session, type SessionMetadata, type SessionRepo, type SessionTreeEntry } from "../harness/types.js";
1
+ import { type Session, type SessionForkOptions, type SessionMetadata, type SessionRepo, type SessionTreeEntry } from "../harness/types.js";
2
2
  export declare class InMemorySessionRepo implements SessionRepo<SessionMetadata, {
3
3
  id?: string;
4
4
  }> {
@@ -9,11 +9,7 @@ export declare class InMemorySessionRepo implements SessionRepo<SessionMetadata,
9
9
  open(metadata: SessionMetadata): Promise<Session>;
10
10
  list(): Promise<SessionMetadata[]>;
11
11
  delete(metadata: SessionMetadata): Promise<void>;
12
- fork(sourceMetadata: SessionMetadata, options: {
13
- entryId?: string;
14
- position?: "before" | "at";
15
- id?: string;
16
- }): Promise<Session>;
12
+ fork(sourceMetadata: SessionMetadata, options: SessionForkOptions): Promise<Session>;
17
13
  exportEntries(sessionId: string): Promise<SessionTreeEntry[]>;
18
14
  importEntries(sessionId: string, owner: string | undefined, entries: SessionTreeEntry[]): Promise<void>;
19
15
  }
@@ -1,7 +1,8 @@
1
1
  import type { ImageContent, TextContent } from "../llm/index.js";
2
2
  import type { AgentMessage } from "../loop/types.js";
3
- import type { Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry } from "../harness/types.js";
3
+ import type { Session, SessionContext, SessionMetadata, SessionStorage, SessionTreeEntry, WorkspaceState } from "../harness/types.js";
4
4
  import type { BuildContextOptions } from "../harness/types.js";
5
+ import type { PromptEpochDescriptor } from "../../prompt-assembly/epoch.js";
5
6
  export declare function buildSessionContext(pathEntries: SessionTreeEntry[], opts?: BuildContextOptions): SessionContext;
6
7
  export declare class StoredSession<TMetadata extends SessionMetadata = SessionMetadata> implements Session<TMetadata> {
7
8
  private storage;
@@ -19,40 +20,14 @@ export declare class StoredSession<TMetadata extends SessionMetadata = SessionMe
19
20
  appendMessage(message: AgentMessage): Promise<string>;
20
21
  appendThinkingLevelChange(thinkingLevel: string): Promise<string>;
21
22
  appendModelChange(provider: string, modelId: string): Promise<string>;
22
- appendPromptEpoch(descriptor: {
23
- epoch: number;
24
- artifactDigest: string;
25
- packId: string;
26
- assemblyApi: number;
27
- activatedBy: "session_start" | "compaction" | "legacy_migration";
28
- centerArtifactDigest?: string;
29
- sourceRevision?: string;
30
- }): Promise<string>;
23
+ appendPromptEpoch(descriptor: PromptEpochDescriptor): Promise<string>;
31
24
  appendAnnouncedListing(snapshot: {
32
25
  agents?: readonly string[];
33
26
  skills?: readonly string[];
34
27
  models?: readonly string[];
35
28
  }): Promise<string>;
36
- appendWorkspaceState(state: {
37
- taskRoot: string;
38
- handsCwd?: string;
39
- activeWorktree?: {
40
- worktreeDir: string;
41
- originalCwd: string;
42
- baseSha: string;
43
- entered?: boolean;
44
- };
45
- }): Promise<string>;
46
- getWorkspaceState(): Promise<{
47
- taskRoot: string;
48
- handsCwd?: string;
49
- activeWorktree?: {
50
- worktreeDir: string;
51
- originalCwd: string;
52
- baseSha: string;
53
- entered?: boolean;
54
- };
55
- } | undefined>;
29
+ appendWorkspaceState(state: WorkspaceState): Promise<string>;
30
+ getWorkspaceState(): Promise<WorkspaceState | undefined>;
56
31
  getAnnouncedListing(): Promise<{
57
32
  agents?: string[];
58
33
  skills?: string[];
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { assembleCodeTools, type CodeToolsConfig, CODE_ROLE } from "./scenarios/
14
14
  export { capAggregateToolResults, AGGREGATE_TOOL_RESULT_BUDGET_CHARS, DEFAULT_BUDGET_EXEMPT_TOOLS, type AggregateBudgetOptions, } from "./core/tool-result-budget.js";
15
15
  export { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES, type MediaStripInfo } from "./core/media-byte-cap.js";
16
16
  export { createAskUserQuestionTool, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, type OnQuestion, type AskQuestion, type AskQuestionOption, type AskQuestionRequest, type QuestionAnswer, type QuestionAnswerItem, } from "./core/ask-question.js";
17
- export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, type LspNoneReason, type LspOperation, type LspServerManager, type LspSession, type LspResult, type LspLocation, type LspSymbolInfo, type LspRequestParams, type LspToolOptions, type LspTransport, type LspReadText, } from "./core/lsp.js";
17
+ export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, SharedAbortScope, settleOnAbort, type LspNoneReason, type LspOperation, type LspServerManager, type LspSession, type LspResult, type LspLocation, type LspSymbolInfo, type LspRequestParams, type LspToolOptions, type LspTransport, type LspReadText, } from "./core/lsp.js";
18
18
  export { buildRequest, parseResult, callHierarchyMethod, pathToUri } from "./core/lsp-protocol.js";
19
19
  export { TransportLspSession, type SessionWarmup } from "./core/lsp-session.js";
20
20
  export { LspDiagnosticsRegistry, formatDiagnosticsBlock, formatDiagnosticsSummary, type LspDiagnostic, type LspFileDiagnostics, } from "./core/lsp-diagnostics.js";
@@ -27,6 +27,7 @@ export { BrainError, classifyHttp, type BrainErrorCode } from "./brain/errors.js
27
27
  export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
28
28
  export type { RepetitionEvent, RepetitionInspection } from "./brain/repetition.js";
29
29
  export { computeCostMicroUsd, modelCostToPricing, type ModelPricing, type TokenCounts, } from "./core/pricing.js";
30
+ export { cacheFamilyOf, promptTokensOf, uncachedInputTokensOf, type CacheFamily } from "./core/runner/usage-accounting.js";
30
31
  export { emitTrace, type TraceEvent, type TracerHook } from "./core/trace.js";
31
32
  export { InMemoryStrategyStore, type StrategyStore, type StoredStrategy } from "./core/strategy-store.js";
32
33
  export { createSqlTool, validateReadOnlySql, type SqlToolOptions } from "./tools/sql.js";
@@ -56,6 +57,7 @@ export { getShellConfig, isWslBashLauncher } from "./engine/execution-env/node-e
56
57
  export { isSecretEnvKey, scrubSecretEnv } from "./core/secret-env.js";
57
58
  export { MAX_EXEC_OUTPUT_BYTES, RollingTailBuffer, markTruncated } from "./core/exec-output-tail.js";
58
59
  export type { ExecutionEnv, FileInfo, Result, FileErrorCode, ExecutionErrorCode } from "./internal/harness.js";
60
+ export type { ExecResult } from "./internal/harness.js";
59
61
  export { RemoteExecutionError, hasDestroy, isRemoteExecutionEnv, isSuspendable, isIsolated, missingRestoreSurface, isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES, } from "./core/remote-env.js";
60
62
  export { withRetry } from "./core/with-retry.js";
61
63
  export type { RetryPolicy, RetryResult } from "./core/with-retry.js";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ export { assembleCodeTools, CODE_ROLE } from "./scenarios/full-body.js";
12
12
  export { capAggregateToolResults, AGGREGATE_TOOL_RESULT_BUDGET_CHARS, DEFAULT_BUDGET_EXEMPT_TOOLS, } from "./core/tool-result-budget.js";
13
13
  export { capAggregateMediaBytes, AGGREGATE_MEDIA_BUDGET_BYTES } from "./core/media-byte-cap.js";
14
14
  export { createAskUserQuestionTool, createDurableQuestionPolicy, QUESTION_AWAITS_RESUME, } from "./core/ask-question.js";
15
- export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, } from "./core/lsp.js";
15
+ export { createLspTool, gitCheckIgnoreFilter, LSP_OPERATIONS, LSP_FAILURE_BRAND, brandLspFailure, lspFailureOf, SharedAbortScope, settleOnAbort, } from "./core/lsp.js";
16
16
  export { buildRequest, parseResult, callHierarchyMethod, pathToUri } from "./core/lsp-protocol.js";
17
17
  export { TransportLspSession } from "./core/lsp-session.js";
18
18
  export { LspDiagnosticsRegistry, formatDiagnosticsBlock, formatDiagnosticsSummary, } from "./core/lsp-diagnostics.js";
@@ -24,6 +24,7 @@ export { selectModel, selectModelOrThrow } from "./core/select-model.js";
24
24
  export { BrainError, classifyHttp } from "./brain/errors.js";
25
25
  export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
26
26
  export { computeCostMicroUsd, modelCostToPricing, } from "./core/pricing.js";
27
+ export { cacheFamilyOf, promptTokensOf, uncachedInputTokensOf } from "./core/runner/usage-accounting.js";
27
28
  export { emitTrace } from "./core/trace.js";
28
29
  export { InMemoryStrategyStore } from "./core/strategy-store.js";
29
30
  export { createSqlTool, validateReadOnlySql } from "./tools/sql.js";
@@ -3,5 +3,7 @@ export type { InvokedSkillRetention } from "../engine/compaction/utils.js";
3
3
  export type { AgentCoreRuntimeDeps } from "../engine/loop/runtime-deps.js";
4
4
  export type { AgentMessage, AgentTool, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel, ToolExecutionMode, } from "../engine/loop/types.js";
5
5
  export type { AgentHarnessEvent, CompactionSettings, ExecutionEnv, ExecutionErrorCode, FileErrorCode, FileInfo, Result, Session, SessionMetadata, SessionRepo, SessionStorage, SessionTreeEntry, Skill, } from "../engine/harness/types.js";
6
- export type { ExecutionEnvExecOptions } from "../engine/harness/types.js";
6
+ export type { ExecutionEnvExecOptions, ExecResult } from "../engine/harness/types.js";
7
7
  export type { SessionWriteOptions, CompactionEntry } from "../engine/harness/types.js";
8
+ export type { ActiveWorktreeSession, WorkspaceState } from "../engine/harness/types.js";
9
+ export type { SessionForkOptions } from "../engine/harness/types.js";
@@ -4,8 +4,10 @@ import { isAbsolute, resolve, sep } from "node:path";
4
4
  import { combinePolicies } from "../core/tool-policy.js";
5
5
  import { delimitUntrusted } from "../core/untrusted-text.js";
6
6
  import { isDurablePause } from "../agents/suspend-guard.js";
7
+ import { isAbsolutePathForm, writeTargetPath } from "../tools/fs/safety.js";
8
+ import { canonicalToolName } from "../core/tool-name-aliases.js";
7
9
  import { captureBaseline, resolveFrozenPaths, restoreFrozenPaths, runOracle, snapshotFrozenPaths, specError, } from "../core/spec-contract.js";
8
- const WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
10
+ const WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"].map(canonicalToolName));
9
11
  const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
10
12
  const pathKey = (p) => {
11
13
  let n = p.replace(/\\/g, "/");
@@ -17,13 +19,14 @@ function frozenDenyPolicy(rootDir, frozenResolved) {
17
19
  const frozen = new Set(frozenResolved.map(pathKey));
18
20
  return {
19
21
  check(req) {
20
- if (!WRITE_TOOLS.has(req.toolName))
22
+ const toolName = canonicalToolName(req.toolName);
23
+ if (!WRITE_TOOLS.has(toolName))
21
24
  return { action: "allow" };
22
25
  const args = req.args;
23
- const raw = args?.file_path ?? args?.path ?? args?.notebook_path;
26
+ const raw = writeTargetPath(toolName, args);
24
27
  if (typeof raw !== "string" || raw.length === 0)
25
28
  return { action: "allow" };
26
- const absForm = raw.startsWith("/") || /^[A-Za-z]:[\\/]/.test(raw);
29
+ const absForm = isAbsolutePathForm(raw);
27
30
  const abs = pathKey(absForm ? raw : resolve(rootDir, raw));
28
31
  if (frozen.has(abs)) {
29
32
  return { action: "deny", reason: `${raw} is part of the frozen specification surface (read-only).` };