@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
@@ -29,6 +29,10 @@ export class InMemoryToolResultStore {
29
29
  totalChars = 0;
30
30
  constructor(opts) {
31
31
  this.opts = opts;
32
+ const cap = opts?.maxTotalChars;
33
+ if (cap !== undefined && Number.isNaN(cap)) {
34
+ throw new Error("InMemoryToolResultStore: maxTotalChars must be a number (got NaN) — omit it for an unbounded store");
35
+ }
32
36
  }
33
37
  put(ref, content) {
34
38
  assertSafeToolResultRef(ref);
@@ -37,7 +41,7 @@ export class InMemoryToolResultStore {
37
41
  this.map.set(ref, content);
38
42
  this.totalChars += content.length;
39
43
  const cap = this.opts?.maxTotalChars;
40
- if (cap !== undefined && cap > 0) {
44
+ if (cap !== undefined && Number.isFinite(cap)) {
41
45
  for (const [oldRef, oldContent] of this.map) {
42
46
  if (this.totalChars <= cap || oldRef === ref)
43
47
  break;
@@ -93,6 +97,16 @@ export function isVolatileOffloadStore(store) {
93
97
  return store instanceof InMemoryToolResultStore;
94
98
  }
95
99
  export const OFFLOAD_TOOL_NAME = "ReadToolResult";
100
+ export function createOffloadPersist(store, sessionId) {
101
+ return (toolCallId, fullText) => {
102
+ const ref = buildToolResultRef(sessionId, toolCallId);
103
+ void Promise.resolve(store.put(ref, fullText)).catch((err) => {
104
+ const cause = err instanceof Error ? err.message : String(err);
105
+ void Promise.resolve(store.put(ref, `[offload LOST at write time: ${cause} — the inline preview is all that survived]`)).catch(() => undefined);
106
+ });
107
+ return ref;
108
+ };
109
+ }
96
110
  export function offloadPagebackHint(ref, form, reachableTools) {
97
111
  const activate = reachableTools !== undefined && !reachableTools.has(OFFLOAD_TOOL_NAME)
98
112
  ? `${OFFLOAD_TOOL_NAME} is not active yet — call ${TOOL_SEARCH_NAME} with {"query":"select:${OFFLOAD_TOOL_NAME}"} to activate it, then `
@@ -144,6 +158,8 @@ export function buildPreview(full, ref, sizes, reachableTools) {
144
158
  `</persisted-output>`);
145
159
  }
146
160
  export function withToolResultOffload(tool, store, thresholdChars, sessionId, reachableTools) {
161
+ if (!(Number.isFinite(thresholdChars) && thresholdChars > 0))
162
+ return tool;
147
163
  const wrappedExecute = async (toolCallId, params, signal, onUpdate) => {
148
164
  const res = await tool.execute(toolCallId, params, signal, onUpdate);
149
165
  if (totalTextChars(res.content) <= thresholdChars)
@@ -1,5 +1,6 @@
1
1
  import type { TaskStatus, ToolEffect } from "./types.js";
2
2
  import type { ThinkingLevel } from "../internal/harness-types.js";
3
+ import type { ToolManifestRow } from "../prompt-assembly/tool-catalog.js";
3
4
  export interface TraceMechanismsSummary {
4
5
  finalizeInjected?: true;
5
6
  nudgesSent?: number;
@@ -42,15 +43,7 @@ export type TraceEvent = {
42
43
  hash: string;
43
44
  contentHash?: string;
44
45
  }>;
45
- tools?: Array<{
46
- wireName: string;
47
- aliases: string[];
48
- contractId: string;
49
- implementationRevision: string;
50
- cardId: string;
51
- shapeDigest: string;
52
- wireSchemaDigest: string;
53
- }>;
46
+ tools?: ToolManifestRow[];
54
47
  totalChars: number;
55
48
  snapshot?: {
56
49
  cacheIdentity: string;
@@ -183,6 +176,13 @@ export type TraceEvent = {
183
176
  reason: "breaker_open" | "rate_limit" | "budget" | "server_error" | "last_resort";
184
177
  atTurn: number;
185
178
  ts: number;
179
+ } | {
180
+ kind: "observer.notify_failed";
181
+ version: 1;
182
+ taskId: string;
183
+ site: string;
184
+ message: string;
185
+ ts: number;
186
186
  } | {
187
187
  kind: "brain.failover";
188
188
  version: 1;
@@ -57,6 +57,19 @@ export type ToolReturn = string | {
57
57
  terminate?: boolean;
58
58
  isError?: boolean;
59
59
  };
60
+ export interface NestedUsage {
61
+ tokens: number;
62
+ turns: number;
63
+ tasks: number;
64
+ costMicroUsd?: number;
65
+ }
66
+ export interface NestedUsageAccum {
67
+ tokens: number;
68
+ turns: number;
69
+ tasks: number;
70
+ costMicroUsd: number;
71
+ anyUnpriced: boolean;
72
+ }
60
73
  export interface ToolExecuteContext {
61
74
  toolCallId: string;
62
75
  signal?: AbortSignal;
@@ -81,12 +94,7 @@ export interface ToolExecuteContext {
81
94
  release: () => Promise<void>;
82
95
  } | null>;
83
96
  parentCwd?: string;
84
- reportUsage?: (usage: {
85
- tokens: number;
86
- turns: number;
87
- tasks: number;
88
- costMicroUsd?: number;
89
- }) => void;
97
+ reportUsage?: (usage: NestedUsage) => void;
90
98
  model?: Model;
91
99
  thinkingLevel?: ThinkingLevel;
92
100
  principal?: string;
@@ -412,12 +420,7 @@ export interface TaskResult {
412
420
  outputTokens?: number;
413
421
  cacheWriteTokensLong?: number;
414
422
  costMicroUsd?: number;
415
- nested?: {
416
- tokens: number;
417
- turns: number;
418
- tasks: number;
419
- costMicroUsd?: number;
420
- };
423
+ nested?: NestedUsage;
421
424
  memory?: {
422
425
  tokens: number;
423
426
  costMicroUsd: number;
@@ -1,5 +1,7 @@
1
- export declare function redactSecrets(s: string): string;
2
- export declare function redactHostLeaks(s: string): string;
1
+ import { type RedactionReport } from "./arg-summary.js";
2
+ export type { RedactionFinding, RedactionReport, RedactionConfidence } from "./arg-summary.js";
3
+ export declare function redactSecrets(s: string, report?: RedactionReport): string;
4
+ export declare function redactHostLeaks(s: string, report?: RedactionReport): string;
3
5
  export declare function boundedRedactedSummary(value: unknown, max: number): string;
4
6
  export declare function untrustedEgressForHuman(value: unknown, opts: {
5
7
  label: string;
@@ -1,5 +1,6 @@
1
1
  import { delimitUntrusted } from "./untrusted-text.js";
2
- import { scrubSecrets } from "./arg-summary.js";
2
+ import { scrubSecrets, SECRET_PASSES, runRedactionPasses } from "./arg-summary.js";
3
+ import { sliceHeadSafe } from "./surrogate-safe-slice.js";
3
4
  const URI_SCHEME = String.raw `[A-Za-z][A-Za-z0-9+.\-_]{0,63}`;
4
5
  const CREDENTIALS_IN_AUTHORITY = new RegExp(String.raw `(${URI_SCHEME}:\/\/)((?:[^/\s"'@]*@)+)`, "gi");
5
6
  const URL_OF_ANY_PROTOCOL = new RegExp(String.raw `${URI_SCHEME}:\/\/[^\s"'\`]+`, "gi");
@@ -14,15 +15,27 @@ function redactUserinfo(scheme, userinfoWithAt) {
14
15
  }
15
16
  return `${scheme}${userinfo.slice(0, colonIdx)}:[redacted-credentials]@`;
16
17
  }
17
- export function redactSecrets(s) {
18
- return scrubSecrets(s)
19
- .replace(CREDENTIALS_IN_AUTHORITY, (_m, scheme, userinfo) => redactUserinfo(scheme, userinfo))
20
- .replace(/([?&#](?:sig|signature|sas|code)=)[^&\s"'#]+/gi, "$1[redacted]");
18
+ const SECRET_TIER_PASSES = [
19
+ ...SECRET_PASSES,
20
+ {
21
+ kind: "url-credentials",
22
+ confidence: "medium",
23
+ marker: "[redacted-credentials]",
24
+ re: CREDENTIALS_IN_AUTHORITY,
25
+ replace: (_m, scheme, userinfo) => redactUserinfo(scheme, userinfo),
26
+ },
27
+ { kind: "signed-url-param", confidence: "high", marker: "[redacted]", re: /([?&#](?:sig|signature|sas|code)=)[^&\s"'#]+/gi, replace: "$1[redacted]" },
28
+ ];
29
+ export function redactSecrets(s, report) {
30
+ return runRedactionPasses(s, SECRET_TIER_PASSES, report);
21
31
  }
22
- export function redactHostLeaks(s) {
23
- return redactSecrets(s)
24
- .replace(URL_OF_ANY_PROTOCOL, "[redacted-url]")
25
- .replace(/(?:\/[A-Za-z0-9_.-]+){2,}/g, "[redacted-path]");
32
+ const HOST_LEAK_PASSES = [
33
+ ...SECRET_TIER_PASSES,
34
+ { kind: "url", confidence: "medium", marker: "[redacted-url]", re: URL_OF_ANY_PROTOCOL, replace: "[redacted-url]" },
35
+ { kind: "fs-path", confidence: "low", marker: "[redacted-path]", re: /(?:\/[A-Za-z0-9_.-]+){2,}/g, replace: "[redacted-path]" },
36
+ ];
37
+ export function redactHostLeaks(s, report) {
38
+ return runRedactionPasses(s, HOST_LEAK_PASSES, report);
26
39
  }
27
40
  const REDACTION_MARKER_RE = /\[redacted(?:-[a-z]+)?\]/g;
28
41
  function countRedactionMarkers(s) {
@@ -52,16 +65,17 @@ function boundedString(value, max, redact) {
52
65
  if (s.length <= max)
53
66
  return s;
54
67
  const totalMarkers = countRedactionMarkers(s);
55
- const plainSuffix = (keepN) => `…[+${s.length - keepN} chars]`;
56
- const enrichedSuffix = (keepN) => {
57
- const lost = totalMarkers - countRedactionMarkers(s.slice(0, keepN));
58
- return lost > 0 ? `…[+${s.length - keepN} chars, ${lost} redaction marker(s) among them]` : plainSuffix(keepN);
68
+ const plainSuffix = (kept) => `…[+${s.length - kept.length} chars]`;
69
+ const enrichedSuffix = (kept) => {
70
+ const lost = totalMarkers - countRedactionMarkers(kept);
71
+ return lost > 0 ? `…[+${s.length - kept.length} chars, ${lost} redaction marker(s) among them]` : plainSuffix(kept);
59
72
  };
60
73
  const converge = (suffixFor) => {
61
74
  let keep = max;
62
75
  let out = "";
63
76
  for (let i = 0; i < 4; i++) {
64
- out = `${s.slice(0, keep)}${suffixFor(keep)}`;
77
+ const kept = sliceHeadSafe(s, keep);
78
+ out = `${kept}${suffixFor(kept)}`;
65
79
  if (out.length <= max)
66
80
  return out;
67
81
  if (keep === 0)
@@ -3,23 +3,20 @@ export interface WorkflowJournalEntry {
3
3
  callKey: string;
4
4
  result: TaskResult;
5
5
  }
6
+ export interface ResumeClaimArgs {
7
+ sourceRunId: string;
8
+ newRunId: string;
9
+ scope: string;
10
+ }
6
11
  export interface WorkflowJournalStore {
7
12
  load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
8
13
  append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
9
14
  locator?(runId: string, scope: string): string | undefined;
10
- resumeClaim?(input: {
11
- sourceRunId: string;
12
- newRunId: string;
13
- scope: string;
14
- }): Promise<{
15
+ resumeClaim?(input: ResumeClaimArgs): Promise<{
15
16
  granted: boolean;
16
17
  holder?: string;
17
18
  }>;
18
- releaseResumeClaim?(input: {
19
- sourceRunId: string;
20
- newRunId: string;
21
- scope: string;
22
- }): Promise<void>;
19
+ releaseResumeClaim?(input: ResumeClaimArgs): Promise<void>;
23
20
  }
24
21
  export declare const MAX_JOURNAL_RESULT_BYTES: number;
25
22
  export declare function oversizeJournalResult(serialized: string): boolean;
@@ -31,19 +28,11 @@ export declare class InMemoryWorkflowJournalStore implements WorkflowJournalStor
31
28
  private readonly runs;
32
29
  private readonly resumeClaims;
33
30
  private claimsForScope;
34
- resumeClaim(input: {
35
- sourceRunId: string;
36
- newRunId: string;
37
- scope: string;
38
- }): Promise<{
31
+ resumeClaim(input: ResumeClaimArgs): Promise<{
39
32
  granted: boolean;
40
33
  holder?: string;
41
34
  }>;
42
- releaseResumeClaim(input: {
43
- sourceRunId: string;
44
- newRunId: string;
45
- scope: string;
46
- }): Promise<void>;
35
+ releaseResumeClaim(input: ResumeClaimArgs): Promise<void>;
47
36
  load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
48
37
  append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
49
38
  }
@@ -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;
@@ -270,7 +270,7 @@ export class NodeExecutionEnv {
270
270
  this.backgroundCapabilities = {
271
271
  supported: true,
272
272
  maxConcurrent: 8,
273
- defaultBgTimeoutSec: 30 * 60,
273
+ defaultBgTimeoutSec: 60 * 60,
274
274
  maxBgTimeoutSec: 2 * 60 * 60,
275
275
  supportsDetach: true,
276
276
  retainBackgroundProcesses: this.retainBackgroundProcesses,
@@ -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;