@sema-agent/core 5.7.0 → 5.9.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 (102) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cascade.js +24 -0
  4. package/dist/agents/subagent.d.ts +7 -7
  5. package/dist/agents/subagent.js +74 -33
  6. package/dist/agents/teacher.js +3 -3
  7. package/dist/agents/team.d.ts +1 -1
  8. package/dist/agents/team.js +1 -1
  9. package/dist/agents/verify.js +1 -1
  10. package/dist/brain/anthropic.js +24 -35
  11. package/dist/brain/open-responses.d.ts +11 -0
  12. package/dist/brain/open-responses.js +721 -0
  13. package/dist/brain/openai.js +22 -33
  14. package/dist/brain/request-params.d.ts +1 -0
  15. package/dist/brain/request-params.js +16 -0
  16. package/dist/brain/stream-engine.d.ts +0 -2
  17. package/dist/brain/stream-engine.js +5 -47
  18. package/dist/brain/stream-shared.d.ts +0 -10
  19. package/dist/brain/stream-shared.js +0 -23
  20. package/dist/brain/terminal-cause.d.ts +0 -1
  21. package/dist/brain/terminal-cause.js +0 -3
  22. package/dist/brain/timeout.d.ts +4 -2
  23. package/dist/brain/timeout.js +4 -14
  24. package/dist/config/catalog.d.ts +3 -11
  25. package/dist/config/catalog.js +30 -69
  26. package/dist/config/defaults.d.ts +0 -3
  27. package/dist/config/defaults.js +0 -3
  28. package/dist/core/auto-compaction.d.ts +2 -11
  29. package/dist/core/auto-compaction.js +3 -67
  30. package/dist/core/checkpoint-store.d.ts +10 -8
  31. package/dist/core/checkpoint-store.js +7 -7
  32. package/dist/core/fs-write-gate-policy.js +2 -2
  33. package/dist/core/lsp-diagnostics.d.ts +3 -2
  34. package/dist/core/lsp-diagnostics.js +20 -7
  35. package/dist/core/memory-engine/file-backend.js +20 -17
  36. package/dist/core/memory-engine/layout.d.ts +5 -0
  37. package/dist/core/memory-engine/layout.js +72 -47
  38. package/dist/core/runner/assemble-result.d.ts +3 -8
  39. package/dist/core/runner/assemble-result.js +24 -18
  40. package/dist/core/runner/prepare-task.d.ts +27 -6
  41. package/dist/core/runner/prepare-task.js +209 -144
  42. package/dist/core/runner/runtask.d.ts +0 -2
  43. package/dist/core/runner/runtask.js +228 -266
  44. package/dist/core/runner/session-rule-policy.d.ts +1 -0
  45. package/dist/core/runner/session-rule-policy.js +4 -3
  46. package/dist/core/session-policy-store.d.ts +5 -0
  47. package/dist/core/session-policy-store.js +3 -1
  48. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  49. package/dist/core/task-registry-agent.d.ts +2 -0
  50. package/dist/core/task-registry-agent.js +33 -1
  51. package/dist/core/task-registry-shared.d.ts +0 -1
  52. package/dist/core/task-registry.d.ts +2 -0
  53. package/dist/core/task-registry.js +9 -1
  54. package/dist/core/tool-errors.js +10 -2
  55. package/dist/core/tool-policy.d.ts +8 -0
  56. package/dist/core/tool-policy.js +11 -0
  57. package/dist/core/trace.d.ts +1 -10
  58. package/dist/core/types.d.ts +27 -30
  59. package/dist/core/usage-window-store.d.ts +39 -0
  60. package/dist/core/usage-window-store.js +115 -0
  61. package/dist/engine/execution-env/node-execution-env.js +0 -22
  62. package/dist/engine/harness/agent-harness.d.ts +1 -7
  63. package/dist/engine/harness/agent-harness.js +16 -53
  64. package/dist/engine/harness/types.d.ts +3 -15
  65. package/dist/engine/llm/types.d.ts +3 -75
  66. package/dist/engine/loop/agent-loop.d.ts +1 -1
  67. package/dist/engine/loop/agent-loop.js +7 -173
  68. package/dist/engine/loop/types.d.ts +1 -18
  69. package/dist/engine/session/repo-utils.d.ts +1 -2
  70. package/dist/engine/session/repo-utils.js +0 -7
  71. package/dist/index.d.ts +13 -7
  72. package/dist/index.js +10 -5
  73. package/dist/internal/llm.d.ts +1 -1
  74. package/dist/orchestration/goal.js +1 -5
  75. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  76. package/dist/orchestration/run-workflow-tool.js +1 -2
  77. package/dist/orchestration/workflow-governance.d.ts +6 -6
  78. package/dist/orchestration/workflow-governance.js +33 -47
  79. package/dist/orchestration/workflow.js +14 -5
  80. package/dist/prompt-assembly/event-registry.js +2 -3
  81. package/dist/stores/cc/lockfile.js +9 -11
  82. package/dist/stores/cc/mailbox-store.js +49 -38
  83. package/dist/stores/file/index.d.ts +3 -0
  84. package/dist/stores/file/index.js +4 -0
  85. package/dist/stores/file/usage-window-store.d.ts +9 -0
  86. package/dist/stores/file/usage-window-store.js +86 -0
  87. package/dist/tools/fs/fs-bash.d.ts +1 -10
  88. package/dist/tools/fs/fs-bash.js +20 -60
  89. package/dist/tools/fs/index.d.ts +0 -2
  90. package/dist/tools/fs/index.js +1 -2
  91. package/dist/tools/web.js +20 -19
  92. package/package.json +4 -5
  93. package/dist/bin/sema-tb.d.ts +0 -31
  94. package/dist/bin/sema-tb.js +0 -448
  95. package/dist/bin/tb-env.d.ts +0 -2
  96. package/dist/bin/tb-env.js +0 -17
  97. package/dist/brain/walltime.d.ts +0 -1
  98. package/dist/brain/walltime.js +0 -1
  99. package/dist/core/runner/call-cap.d.ts +0 -67
  100. package/dist/core/runner/call-cap.js +0 -145
  101. package/dist/core/runner/cut-kill.d.ts +0 -10
  102. package/dist/core/runner/cut-kill.js +0 -37
@@ -3,6 +3,7 @@ import type { ToolEffect } from "../types.js";
3
3
  import { type NamedToolPolicy } from "../tool-policy.js";
4
4
  import type { SessionPermissionRules } from "../session-policy-store.js";
5
5
  export declare const PATH_WRITE_TOOLS: ReadonlySet<string>;
6
+ export declare const PATH_CONFINABLE_WRITE_TOOLS: ReadonlySet<string>;
6
7
  export declare function isWithin(root: string, p: string): boolean;
7
8
  export declare function createSessionRulePolicy(rules: SessionPermissionRules, opts: {
8
9
  env: ExecutionEnv;
@@ -1,7 +1,8 @@
1
- import { canonicalizeTarget, fileArgPath } from "../../tools/fs/safety.js";
1
+ import { canonicalizeTarget, writeTargetPath } from "../../tools/fs/safety.js";
2
2
  import { isWinFormPath } from "../../tools/fs/safety.js";
3
3
  import { createCoarseCommandNamePolicy } from "../tool-policy.js";
4
4
  export const PATH_WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit"]);
5
+ export const PATH_CONFINABLE_WRITE_TOOLS = new Set([...PATH_WRITE_TOOLS, "NotebookEdit"]);
5
6
  export function isWithin(root, p) {
6
7
  if (!root)
7
8
  return false;
@@ -46,14 +47,14 @@ export function createSessionRulePolicy(rules, opts) {
46
47
  return d;
47
48
  }
48
49
  if (allowDirs) {
49
- if (!PATH_WRITE_TOOLS.has(toolName)) {
50
+ if (!PATH_CONFINABLE_WRITE_TOOLS.has(toolName)) {
50
51
  const eff = toolEffects?.get(toolName) ?? "write";
51
52
  if (eff !== "read") {
52
53
  return deny(`write-capable tool "${req.toolName}" denied: session rule confines writes to allowDirs but this tool cannot be path-confined`);
53
54
  }
54
55
  }
55
56
  else {
56
- const path = fileArgPath(req.args);
57
+ const path = writeTargetPath(toolName, req.args);
57
58
  if (typeof path !== "string" || path.length === 0) {
58
59
  return deny(`write tool "${req.toolName}" denied: session rule confines writes to allowDirs but the call has no resolvable path`);
59
60
  }
@@ -26,6 +26,7 @@ export interface SessionPolicyStore {
26
26
  listBySession?(sessionId: string): Promise<SessionRulesRecord[]>;
27
27
  deleteBySession?(sessionId: string): Promise<void>;
28
28
  }
29
+ declare function lexWithin(root: string, p: string): boolean;
29
30
  export declare function normalizeRules(rules: SessionPermissionRules): SessionPermissionRules;
30
31
  export declare function loosenReasons(prior: SessionPermissionRules, next: SessionPermissionRules): string[];
31
32
  export declare function stripRev(rules: SessionPermissionRules): SessionPermissionRules;
@@ -37,3 +38,7 @@ export declare class InMemorySessionPolicyStore implements SessionPolicyStore {
37
38
  listBySession(sessionId: string): Promise<SessionRulesRecord[]>;
38
39
  deleteBySession(sessionId: string): Promise<void>;
39
40
  }
41
+ export declare const __testables: {
42
+ lexWithin: typeof lexWithin;
43
+ };
44
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { posix } from "node:path";
2
+ import { isWinFormPath } from "../tools/fs/safety.js";
2
3
  export class SessionPolicyError extends Error {
3
4
  code;
4
5
  constructor(code, message) {
@@ -9,7 +10,7 @@ export class SessionPolicyError extends Error {
9
10
  }
10
11
  function lexWithin(root, p) {
11
12
  const fold = (x) => {
12
- let n = x.replace(/\\/g, "/");
13
+ let n = isWinFormPath(x) ? x.replace(/\\/g, "/") : x;
13
14
  if (/^[A-Za-z]:(\/|$)/.test(n))
14
15
  n = n[0].toLowerCase() + n.slice(1);
15
16
  return n;
@@ -116,3 +117,4 @@ export class InMemorySessionPolicyStore {
116
117
  }
117
118
  }
118
119
  }
120
+ export const __testables = { lexWithin };
@@ -157,8 +157,8 @@ export async function checkpointListByScopeSummaries(store) {
157
157
  scope: "tenant-a",
158
158
  sessionId: "out-of-budget",
159
159
  createdAt: 1_700_000_000_001,
160
- gate: { kind: "resource_limit", reason: "budget" },
161
- pendingAction: { kind: "resource_limit", reason: "budget" },
160
+ gate: { kind: "resource_limit", reason: "budget_tokens" },
161
+ pendingAction: { kind: "resource_limit", reason: "budget_tokens" },
162
162
  resourceLedger: { totalBudgetMicroUsd: 10_000_000, spentMicroUsd: 4_200_000, spentTokens: 9, spentTurns: 3, sliceCount: 2 },
163
163
  });
164
164
  const resolved = createCheckpointFixture({ token: "tok-resolved", scope: "tenant-a", sessionId: "done" });
@@ -106,6 +106,8 @@ export declare function settleRevivedAgentLane(core: DurableAgentCore, id: strin
106
106
  retryable?: boolean;
107
107
  errorKind?: string;
108
108
  }): "completed" | "failed" | "killed" | undefined;
109
+ export declare function noteBackgroundAgentActivityLane(core: DurableAgentCore, id: string, now?: number): void;
110
+ export declare function reapStaleSessionBackgroundAgentsLane(core: DurableAgentCore, staleMs: number, now?: number, onTerminal?: (note: () => void) => void): number;
109
111
  export declare function unmarkRetainedContinuationLane(core: DurableAgentCore, id: string): void;
110
112
  export declare function attachAgentNotifyLane(core: DurableAgentCore, id: string, notify: NonNullable<BackgroundAgentTaskHandle["notify"]>, cycle?: number): void;
111
113
  export declare function deliverToRunningAgentLane(core: DurableAgentCore, id: string, access: TaskAccess, notification: import("./task-notification.js").TaskNotificationPayload, opts?: {
@@ -1,6 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { uuidv7 } from "../internal/harness.js";
3
- import { canAccessAgentRecord, BackgroundAgentStoreError, REVIVED_ROW_CLEARED_FIELDS, } from "./background-agent-store.js";
3
+ import { canAccessAgentRecord, BackgroundAgentStoreError, REVIVED_ROW_CLEARED_FIELDS, STALE_RUNNING_REAP_ATTRIBUTION, } from "./background-agent-store.js";
4
4
  import { shutdownDebug } from "./shutdown-debug.js";
5
5
  import { delimitUntrusted } from "./untrusted-text.js";
6
6
  import { boundedRedactedSummary } from "./untrusted-egress.js";
@@ -870,6 +870,38 @@ export function reviveBackgroundAgentLane(core, id, access, abort) {
870
870
  export function settleRevivedAgentLane(core, id, cycle, outcome) {
871
871
  return settleBackgroundAgentLane(core, id, { ...outcome, cycle });
872
872
  }
873
+ export function noteBackgroundAgentActivityLane(core, id, now = Date.now()) {
874
+ const handle = core.handles.get(id);
875
+ if (!handle || handle.type !== "background_agent" || handle.status !== "running")
876
+ return;
877
+ handle.updatedAt = now;
878
+ }
879
+ export function reapStaleSessionBackgroundAgentsLane(core, staleMs, now = Date.now(), onTerminal) {
880
+ if (!Number.isFinite(staleMs) || staleMs <= 0) {
881
+ throw new Error(`reapStaleSessionBackgroundAgents: staleMs must be a positive finite number of milliseconds (got ${String(staleMs)})`);
882
+ }
883
+ let reaped = 0;
884
+ for (const handle of core.handles.values()) {
885
+ if (handle.type !== "background_agent" || handle.status !== "running" || handle.sessionScoped !== true)
886
+ continue;
887
+ if (now - handle.updatedAt <= staleMs)
888
+ continue;
889
+ core.markStopSource(handle.id, "system");
890
+ const reapTerminalNote = handle.onReapTerminal;
891
+ handle.abort.abort();
892
+ settleBackgroundAgentLane(core, handle.id, {
893
+ status: "killed",
894
+ error: STALE_RUNNING_REAP_ATTRIBUTION,
895
+ cycle: handle.reviveCycle ?? 0,
896
+ });
897
+ if (reapTerminalNote !== undefined && handle.terminalNotified !== true) {
898
+ handle.terminalNotified = true;
899
+ onTerminal?.(reapTerminalNote);
900
+ }
901
+ reaped++;
902
+ }
903
+ return reaped;
904
+ }
873
905
  export function unmarkRetainedContinuationLane(core, id) {
874
906
  const handle = core.handles.get(id);
875
907
  if (handle && handle.type === "background_agent")
@@ -40,7 +40,6 @@ export interface SemaTaskHandle {
40
40
  createdAt: number;
41
41
  updatedAt: number;
42
42
  outputFile?: string;
43
- outputOffset?: number;
44
43
  completionId?: string;
45
44
  }
46
45
  export declare function mintCompletionId(target: {
@@ -145,6 +145,8 @@ export declare class TaskRegistry {
145
145
  seq?: number;
146
146
  cycle?: number;
147
147
  }): "completed" | "failed" | "killed" | undefined;
148
+ noteBackgroundAgentActivity(id: string, now?: number): void;
149
+ reapStaleSessionBackgroundAgents(staleMs: number, now?: number): number;
148
150
  abortBackgroundAgentsForOwner(access: TaskAccess, opts?: {
149
151
  skipSessionScoped?: boolean;
150
152
  sessionScopedOnly?: boolean;
@@ -10,7 +10,7 @@ import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task
10
10
  import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
11
11
  export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
12
12
  import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
13
- import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
13
+ import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
14
14
  export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
15
15
  const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
16
16
  const BLOCK_MAX_TIMEOUT_MS = 600_000;
@@ -167,6 +167,14 @@ export class TaskRegistry {
167
167
  settleBackgroundAgent(id, outcome) {
168
168
  return settleBackgroundAgentLane(this.core, id, outcome);
169
169
  }
170
+ noteBackgroundAgentActivity(id, now) {
171
+ return noteBackgroundAgentActivityLane(this.core, id, now);
172
+ }
173
+ reapStaleSessionBackgroundAgents(staleMs, now) {
174
+ return reapStaleSessionBackgroundAgentsLane(this.core, staleMs, now, (note) => {
175
+ this.notifier.notify(() => note(), "taskRegistry.reapTerminalNote");
176
+ });
177
+ }
170
178
  abortBackgroundAgentsForOwner(access, opts) {
171
179
  return abortBackgroundAgentsForOwnerLane(this.core, access, opts);
172
180
  }
@@ -65,9 +65,14 @@ const BRAIN_FLAT_CODES = new Set([
65
65
  "refusal",
66
66
  "length_empty",
67
67
  ]);
68
+ const EXACT_CODE_CLASS = new Map([
69
+ ["limits.max_tokens_exceeded", "budget"],
70
+ ["limits.max_cost_exceeded", "budget"],
71
+ ["limits.max_turns_exceeded", "limit"],
72
+ ["limits.max_walltime_exceeded", "limit"],
73
+ ]);
68
74
  const DOTTED_PREFIX_CLASS = [
69
- ["budget.", "budget"],
70
- ["limit.", "limit"],
75
+ ["limits.", "limit"],
71
76
  ["output.", "output"],
72
77
  ["suspend.", "suspend"],
73
78
  ["review.", "review"],
@@ -79,6 +84,9 @@ const DOTTED_PREFIX_CLASS = [
79
84
  export function errorClassOf(errorCode) {
80
85
  if (!errorCode)
81
86
  return "unknown";
87
+ const exact = EXACT_CODE_CLASS.get(errorCode);
88
+ if (exact !== undefined)
89
+ return exact;
82
90
  for (const [prefix, cls] of DOTTED_PREFIX_CLASS) {
83
91
  if (errorCode.startsWith(prefix))
84
92
  return cls;
@@ -70,6 +70,11 @@ export declare function createTranscriptIntegrityPolicy(opts?: {
70
70
  readAllow?: readonly string[];
71
71
  tools?: string[];
72
72
  }): ToolPolicy;
73
+ export interface AskDelegationProvenance {
74
+ readonly parentToolCallId: string;
75
+ readonly depth: number;
76
+ readonly agentName?: string;
77
+ }
73
78
  export interface AskRequest {
74
79
  toolName: string;
75
80
  toolCallId: string;
@@ -81,12 +86,15 @@ export interface AskRequest {
81
86
  readonly fromSubagent?: true;
82
87
  readonly sourceAgentName?: string;
83
88
  readonly requiresRealApproval?: boolean;
89
+ readonly delegation?: AskDelegationProvenance;
84
90
  }
85
91
  export type OnAsk = "deny" | "allow" | ((req: AskRequest, signal?: AbortSignal) => AskOutcome | Promise<AskOutcome>);
86
92
  export type AskOutcome = boolean | "unavailable" | {
87
93
  allow: boolean;
88
94
  updatedInput?: unknown;
89
95
  };
96
+ export declare function withDelegationProvenance(onAsk: OnAsk, delegation: AskDelegationProvenance): OnAsk;
97
+ export declare function askApproverIdentity(onAsk: unknown): unknown;
90
98
  export type ResolvedAsk = PermissionResult & {
91
99
  approverUnavailable?: true;
92
100
  presentedInput?: unknown;
@@ -516,6 +516,17 @@ export function createTranscriptIntegrityPolicy(opts) {
516
516
  },
517
517
  };
518
518
  }
519
+ const delegatedApproverRoot = new WeakMap();
520
+ export function withDelegationProvenance(onAsk, delegation) {
521
+ if (typeof onAsk !== "function")
522
+ return onAsk;
523
+ const wrapper = (req, signal) => req.delegation !== undefined ? onAsk(req, signal) : onAsk({ ...req, delegation }, signal);
524
+ delegatedApproverRoot.set(wrapper, askApproverIdentity(onAsk));
525
+ return wrapper;
526
+ }
527
+ export function askApproverIdentity(onAsk) {
528
+ return typeof onAsk === "function" ? (delegatedApproverRoot.get(onAsk) ?? onAsk) : onAsk;
529
+ }
519
530
  function tryCloneArgs(v) {
520
531
  try {
521
532
  const value = structuredClone(v);
@@ -2,17 +2,10 @@ import type { TaskStatus, ToolEffect } from "./types.js";
2
2
  import type { ThinkingLevel } from "../internal/harness-types.js";
3
3
  import type { ToolManifestRow } from "../prompt-assembly/tool-catalog.js";
4
4
  export interface TraceMechanismsSummary {
5
- finalizeInjected?: true;
6
- nudgesSent?: number;
7
- capShrinks?: number;
8
- callCutoffs?: number;
9
- toolClamps?: number;
10
- toolCuts?: number;
11
- toolCutKills?: number;
12
- pendingCutTools?: number;
13
5
  finalVerifyInjected?: true;
14
6
  finalVerifyInjections?: number;
15
7
  attachmentsInjected?: number;
8
+ approachNoticesSent?: number;
16
9
  repetitionCuts?: number;
17
10
  repetitionSpared?: number;
18
11
  }
@@ -141,8 +134,6 @@ export type TraceEvent = {
141
134
  firstTokenMs?: number;
142
135
  callStartedAt?: number;
143
136
  costMicroUsd?: number;
144
- callCap?: number;
145
- capThinkingSkipped?: boolean;
146
137
  stopReason?: string;
147
138
  ts: number;
148
139
  } | {
@@ -98,6 +98,7 @@ export interface ToolExecuteContext {
98
98
  model?: Model;
99
99
  thinkingLevel?: ThinkingLevel;
100
100
  principal?: string;
101
+ onAsk?: import("./tool-policy.js").OnAsk;
101
102
  oneShot?: boolean;
102
103
  clientContext?: TaskSpec["clientContext"];
103
104
  excludeTools?: readonly string[];
@@ -244,6 +245,22 @@ export type ImageInput = {
244
245
  } | {
245
246
  url: string;
246
247
  };
248
+ export interface TaskLimits {
249
+ maxTokens?: number;
250
+ maxCostUsd?: number;
251
+ maxTurns?: number;
252
+ maxWalltimeMs?: number;
253
+ maxOutputTokens?: number;
254
+ approachNotice?: false | {
255
+ at?: [number, number];
256
+ };
257
+ budgetStreamCancel?: boolean;
258
+ degrade?: {
259
+ to: ModelRef;
260
+ atCostFraction: number;
261
+ };
262
+ brainCallGuardrailMs?: import("../brain/timeout.js").BrainCallGuardrailKnob;
263
+ }
247
264
  export interface TaskSpec {
248
265
  taskId?: string;
249
266
  objective: string;
@@ -293,7 +310,7 @@ export interface TaskSpec {
293
310
  resourceSuspend?: {
294
311
  scope: string;
295
312
  totalBudgetUsd?: number;
296
- totalWalltimeSec?: number;
313
+ totalTokens?: number;
297
314
  maxSlices?: number;
298
315
  ttlMs?: number;
299
316
  };
@@ -324,17 +341,7 @@ export interface TaskSpec {
324
341
  note?: string;
325
342
  };
326
343
  };
327
- limits?: {
328
- maxTurns?: number;
329
- timeoutSec?: number;
330
- maxOutputTokens?: number;
331
- deadlineNudge?: false | {
332
- at?: [number, number];
333
- };
334
- callCapByDeadline?: false;
335
- gracefulFinalize?: false;
336
- brainCallGuardrailMs?: import("../brain/timeout.js").BrainCallGuardrailKnob;
337
- };
344
+ limits?: TaskLimits;
338
345
  configOverrides?: ReadonlyArray<import("../config/catalog.js").ConfigOverrideDeclaration>;
339
346
  forwardSubagentEvents?: boolean;
340
347
  retainSubagentSessions?: boolean | {
@@ -343,13 +350,6 @@ export interface TaskSpec {
343
350
  };
344
351
  retainBackgroundProcesses?: boolean;
345
352
  streamingToolExecution?: boolean;
346
- maxCostUsd?: number;
347
- maxTokens?: number;
348
- budgetStreamCancel?: boolean;
349
- degrade?: {
350
- to: ModelRef;
351
- atCostFraction: number;
352
- };
353
353
  outputSchema?: TSchema;
354
354
  outputRetries?: number;
355
355
  tracer?: import("./trace.js").TracerHook;
@@ -389,7 +389,7 @@ export interface TaskSpec {
389
389
  } | undefined>;
390
390
  signal?: AbortSignal;
391
391
  }
392
- export type TaskStatus = "completed" | "blocked" | "failed" | "timeout" | "suspended" | "needs_review";
392
+ export type TaskStatus = "completed" | "blocked" | "failed" | "suspended" | "needs_review";
393
393
  export interface RemoteEnvFailureNote {
394
394
  op: "suspendVM" | "resumeVM" | "postResumeInit";
395
395
  code: import("./remote-env.js").RemoteExecutionErrorCode;
@@ -411,6 +411,7 @@ export interface TaskResult {
411
411
  remoteEnvFailures?: RemoteEnvFailureNote[];
412
412
  errorMessage?: string;
413
413
  errorCode?: string;
414
+ retryAfterMs?: number;
414
415
  degraded?: {
415
416
  from: string;
416
417
  to: string;
@@ -446,17 +447,10 @@ export interface TaskResult {
446
447
  costMicroUsd: number;
447
448
  };
448
449
  mechanisms?: {
449
- finalizeInjected?: true;
450
- nudgesSent?: number;
451
- capShrinks?: number;
452
- callCutoffs?: number;
453
- toolClamps?: number;
454
- toolCuts?: number;
455
- toolCutKills?: number;
456
- pendingCutTools?: number;
457
450
  finalVerifyInjected?: true;
458
451
  finalVerifyInjections?: number;
459
452
  attachmentsInjected?: number;
453
+ approachNoticesSent?: number;
460
454
  repetitionCuts?: number;
461
455
  repetitionSpared?: number;
462
456
  repetitionEvents?: Array<{
@@ -531,6 +525,7 @@ export type TaskEvent = ({
531
525
  isError: boolean;
532
526
  output?: unknown;
533
527
  structured?: unknown;
528
+ errorCode?: string;
534
529
  truncated?: boolean;
535
530
  totalChars?: number;
536
531
  } & TaskEventIdentity) | ({
@@ -568,7 +563,7 @@ export type TaskEvent = ({
568
563
  modelFallback?: true;
569
564
  fallbackReason?: "window";
570
565
  clampedRatio?: number;
571
- clampReason?: "budget" | "walltime" | "tolerance";
566
+ clampReason?: "budget" | "tolerance";
572
567
  phaseDurations?: import("./auto-compaction.js").CompactionPhaseDurations;
573
568
  } & TaskEventIdentity) | ({
574
569
  type: "compaction_outcome";
@@ -577,7 +572,7 @@ export type TaskEvent = ({
577
572
  reason?: string;
578
573
  } & TaskEventIdentity) | ({
579
574
  type: "steering_injected";
580
- source: "deadline_nudge" | "finalize" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
575
+ source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
581
576
  preview: string;
582
577
  } & TaskEventIdentity) | ({
583
578
  type: "diagnostics";
@@ -750,6 +745,8 @@ export interface RunnerDeps {
750
745
  workflowRunStore?: import("./workflow-run-store.js").WorkflowRunStore;
751
746
  backgroundAgentStore?: import("./background-agent-store.js").BackgroundAgentStore;
752
747
  mailboxStore?: import("./mailbox-store.js").MailboxStore;
748
+ usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
749
+ usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
753
750
  workflowJournalStore?: import("./workflow-journal-store.js").WorkflowJournalStore;
754
751
  workflowScriptStore?: import("../orchestration/workflow-script-store.js").WorkflowScriptStore;
755
752
  builtinWorkflows?: boolean;
@@ -0,0 +1,39 @@
1
+ export interface UsageWindow {
2
+ windowMs: number;
3
+ maxTokens: number;
4
+ anchor: "first-use" | "rolling";
5
+ }
6
+ export interface UsageSlot {
7
+ at: number;
8
+ tokens: number;
9
+ }
10
+ export interface UsageBucketRow {
11
+ windowMs: number;
12
+ openedAt: number;
13
+ tokens: number;
14
+ }
15
+ export interface UsageWindowRecord {
16
+ slots: readonly UsageSlot[];
17
+ buckets: readonly UsageBucketRow[];
18
+ }
19
+ export interface UsageWindowReading {
20
+ window: UsageWindow;
21
+ tokens: number;
22
+ exhausted: boolean;
23
+ retryAfterMs: number;
24
+ }
25
+ export interface UsageWindowStore {
26
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
27
+ read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
28
+ }
29
+ export declare const GLOBAL_USAGE_KEY = "__usage_global__";
30
+ export declare const EMPTY_USAGE_WINDOW_RECORD: UsageWindowRecord;
31
+ export declare function resolveUsageWindows(windows: readonly UsageWindow[] | undefined): readonly UsageWindow[] | undefined;
32
+ export declare function chargeUsageRecord(record: UsageWindowRecord, tokens: number, at: number, windows: readonly UsageWindow[]): UsageWindowRecord;
33
+ export declare function readUsageRecord(record: UsageWindowRecord, windows: readonly UsageWindow[], now: number): UsageWindowReading[];
34
+ export declare function usageRetryAfterMs(readings: readonly UsageWindowReading[]): number | undefined;
35
+ export declare class InMemoryUsageWindowStore implements UsageWindowStore {
36
+ private readonly records;
37
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
38
+ read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
39
+ }
@@ -0,0 +1,115 @@
1
+ export const GLOBAL_USAGE_KEY = "__usage_global__";
2
+ export const EMPTY_USAGE_WINDOW_RECORD = Object.freeze({
3
+ slots: Object.freeze([]),
4
+ buckets: Object.freeze([]),
5
+ });
6
+ function usageConfigError(code, message) {
7
+ const e = new Error(message);
8
+ e.code = code;
9
+ return e;
10
+ }
11
+ export function resolveUsageWindows(windows) {
12
+ if (windows === undefined)
13
+ return undefined;
14
+ if (!Array.isArray(windows)) {
15
+ throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows must be an array (got ${typeof windows})`);
16
+ }
17
+ for (const w of windows) {
18
+ if (w === null || typeof w !== "object") {
19
+ throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows entries must be objects (got ${String(w)})`);
20
+ }
21
+ if (typeof w.windowMs !== "number" || !Number.isFinite(w.windowMs) || w.windowMs <= 0) {
22
+ throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].windowMs must be a finite number of milliseconds > 0 (got ${String(w.windowMs)})`);
23
+ }
24
+ if (typeof w.maxTokens !== "number" || !Number.isFinite(w.maxTokens) || w.maxTokens < 0) {
25
+ throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].maxTokens must be a finite, non-negative number (got ${String(w.maxTokens)})`);
26
+ }
27
+ if (w.anchor !== "first-use" && w.anchor !== "rolling") {
28
+ throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].anchor must be "first-use" or "rolling" (got ${String(w.anchor)})`);
29
+ }
30
+ }
31
+ return windows;
32
+ }
33
+ function rollingHorizonMs(windows) {
34
+ let widest;
35
+ for (const w of windows) {
36
+ if (w.anchor !== "rolling")
37
+ continue;
38
+ if (widest === undefined || w.windowMs > widest)
39
+ widest = w.windowMs;
40
+ }
41
+ return widest;
42
+ }
43
+ export function chargeUsageRecord(record, tokens, at, windows) {
44
+ const horizon = rollingHorizonMs(windows);
45
+ const keptSlots = horizon === undefined ? [] : record.slots.filter((s) => s.at > at - horizon).map((s) => ({ at: s.at, tokens: s.tokens }));
46
+ if (horizon !== undefined && tokens > 0)
47
+ keptSlots.push({ at, tokens });
48
+ const buckets = [];
49
+ for (const w of windows) {
50
+ if (w.anchor !== "first-use")
51
+ continue;
52
+ if (buckets.some((b) => b.windowMs === w.windowMs))
53
+ continue;
54
+ const prior = record.buckets.find((b) => b.windowMs === w.windowMs);
55
+ const lapsed = prior === undefined || at >= prior.openedAt + w.windowMs;
56
+ if (lapsed) {
57
+ if (tokens > 0)
58
+ buckets.push({ windowMs: w.windowMs, openedAt: at, tokens });
59
+ continue;
60
+ }
61
+ buckets.push({ windowMs: w.windowMs, openedAt: prior.openedAt, tokens: prior.tokens + tokens });
62
+ }
63
+ return { slots: keptSlots, buckets };
64
+ }
65
+ export function readUsageRecord(record, windows, now) {
66
+ const readings = [];
67
+ for (const w of windows) {
68
+ let tokens = 0;
69
+ let freesAt;
70
+ if (w.anchor === "rolling") {
71
+ const floor = now - w.windowMs;
72
+ let oldest;
73
+ for (const s of record.slots) {
74
+ if (s.at <= floor)
75
+ continue;
76
+ tokens += s.tokens;
77
+ if (oldest === undefined || s.at < oldest)
78
+ oldest = s.at;
79
+ }
80
+ if (oldest !== undefined)
81
+ freesAt = oldest + w.windowMs;
82
+ }
83
+ else {
84
+ const row = record.buckets.find((b) => b.windowMs === w.windowMs);
85
+ if (row !== undefined && now < row.openedAt + w.windowMs) {
86
+ tokens = row.tokens;
87
+ freesAt = row.openedAt + w.windowMs;
88
+ }
89
+ }
90
+ const exhausted = tokens >= w.maxTokens;
91
+ const retryAfterMs = exhausted ? Math.max(1, freesAt === undefined ? w.windowMs : freesAt - now) : 0;
92
+ readings.push({ window: w, tokens, exhausted, retryAfterMs });
93
+ }
94
+ return readings;
95
+ }
96
+ export function usageRetryAfterMs(readings) {
97
+ let worst;
98
+ for (const r of readings) {
99
+ if (!r.exhausted)
100
+ continue;
101
+ if (worst === undefined || r.retryAfterMs > worst)
102
+ worst = r.retryAfterMs;
103
+ }
104
+ return worst;
105
+ }
106
+ export class InMemoryUsageWindowStore {
107
+ records = new Map();
108
+ async charge(key, tokens, at, windows) {
109
+ const prior = this.records.get(key) ?? EMPTY_USAGE_WINDOW_RECORD;
110
+ this.records.set(key, chargeUsageRecord(prior, tokens, at, windows));
111
+ }
112
+ async read(key, windows, now) {
113
+ return readUsageRecord(this.records.get(key) ?? EMPTY_USAGE_WINDOW_RECORD, windows, now);
114
+ }
115
+ }
@@ -432,23 +432,6 @@ export class NodeExecutionEnv {
432
432
  return;
433
433
  }
434
434
  closeSpoolFds();
435
- if (options?.onSpawn && child.pid !== undefined) {
436
- const spawnedPid = child.pid;
437
- const spawnedChild = child;
438
- try {
439
- options.onSpawn({
440
- pid: spawnedPid,
441
- kill: () => {
442
- abortKillInFlight = true;
443
- killProcessTree(spawnedPid, {
444
- stillRunning: () => spawnedChild.exitCode === null && spawnedChild.signalCode === null,
445
- });
446
- },
447
- });
448
- }
449
- catch {
450
- }
451
- }
452
435
  const timeoutMs = resolveExecTimeoutMs(options?.timeout);
453
436
  timeoutRef.current =
454
437
  timeoutMs === undefined
@@ -580,11 +563,6 @@ export class NodeExecutionEnv {
580
563
  return false;
581
564
  if (spoolPumpRef.current)
582
565
  clearInterval(spoolPumpRef.current);
583
- try {
584
- options?.onDetachAdopted?.();
585
- }
586
- catch {
587
- }
588
566
  if (timeoutRef.current)
589
567
  clearTimeout(timeoutRef.current);
590
568
  const dso = stdoutTail.result();
@@ -17,11 +17,6 @@ export interface HarnessLoopRecovery {
17
17
  detect: (message: AssistantMessage) => boolean;
18
18
  maxContinues?: number;
19
19
  };
20
- walltimeCutoff?: {
21
- detect: (message: AssistantMessage) => boolean;
22
- maxContinues?: number;
23
- hasLiveWriteoutWindow?: () => boolean;
24
- };
25
20
  promptTooLong?: {
26
21
  recover: (attempt: number) => Promise<boolean>;
27
22
  detect?: (message: AssistantMessage) => boolean;
@@ -54,9 +49,8 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
54
49
  private steeringQueueMode;
55
50
  private maxOutputTokens?;
56
51
  private maxOutputTokensPerCall?;
57
- private callDeadlineMsPerCall?;
58
52
  private stallTimeoutsPerCall?;
59
- private toolDeadline?;
53
+ private abortResultDetails?;
60
54
  private loopTrace?;
61
55
  private resilience?;
62
56
  private maxToolConcurrency?;