@sema-agent/core 5.6.0 → 5.8.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 (88) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/subagent.d.ts +4 -7
  4. package/dist/agents/subagent.js +10 -19
  5. package/dist/agents/teacher.js +3 -3
  6. package/dist/agents/team.d.ts +1 -1
  7. package/dist/agents/team.js +1 -1
  8. package/dist/agents/verify.js +1 -1
  9. package/dist/brain/anthropic.js +24 -35
  10. package/dist/brain/openai.js +22 -33
  11. package/dist/brain/stream-engine.d.ts +0 -2
  12. package/dist/brain/stream-engine.js +5 -47
  13. package/dist/brain/stream-shared.d.ts +0 -10
  14. package/dist/brain/stream-shared.js +0 -23
  15. package/dist/brain/terminal-cause.d.ts +0 -1
  16. package/dist/brain/terminal-cause.js +0 -3
  17. package/dist/brain/timeout.d.ts +4 -2
  18. package/dist/brain/timeout.js +4 -14
  19. package/dist/config/catalog.d.ts +3 -11
  20. package/dist/config/catalog.js +30 -69
  21. package/dist/config/defaults.d.ts +0 -3
  22. package/dist/config/defaults.js +0 -3
  23. package/dist/core/auto-compaction.d.ts +2 -11
  24. package/dist/core/auto-compaction.js +3 -67
  25. package/dist/core/checkpoint-store.d.ts +15 -10
  26. package/dist/core/checkpoint-store.js +13 -10
  27. package/dist/core/memory-engine/file-backend.js +20 -17
  28. package/dist/core/memory-engine/layout.d.ts +5 -0
  29. package/dist/core/memory-engine/layout.js +72 -47
  30. package/dist/core/runner/assemble-result.d.ts +2 -8
  31. package/dist/core/runner/assemble-result.js +15 -16
  32. package/dist/core/runner/prepare-task.d.ts +26 -6
  33. package/dist/core/runner/prepare-task.js +203 -141
  34. package/dist/core/runner/runtask.d.ts +0 -2
  35. package/dist/core/runner/runtask.js +267 -253
  36. package/dist/core/secret-env.d.ts +2 -0
  37. package/dist/core/secret-env.js +16 -4
  38. package/dist/core/session-policy-store.d.ts +5 -0
  39. package/dist/core/session-policy-store.js +3 -1
  40. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  41. package/dist/core/task-notification.d.ts +1 -0
  42. package/dist/core/task-notification.js +3 -0
  43. package/dist/core/task-registry-agent.d.ts +2 -0
  44. package/dist/core/task-registry-agent.js +33 -1
  45. package/dist/core/task-registry.d.ts +2 -0
  46. package/dist/core/task-registry.js +9 -1
  47. package/dist/core/tool-errors.js +10 -2
  48. package/dist/core/trace.d.ts +1 -8
  49. package/dist/core/types.d.ts +24 -30
  50. package/dist/core/usage-window-store.d.ts +39 -0
  51. package/dist/core/usage-window-store.js +115 -0
  52. package/dist/engine/execution-env/node-execution-env.js +0 -22
  53. package/dist/engine/harness/agent-harness.d.ts +0 -7
  54. package/dist/engine/harness/agent-harness.js +13 -53
  55. package/dist/engine/harness/types.d.ts +2 -15
  56. package/dist/engine/llm/types.d.ts +1 -2
  57. package/dist/engine/loop/agent-loop.d.ts +1 -1
  58. package/dist/engine/loop/agent-loop.js +5 -171
  59. package/dist/engine/loop/types.d.ts +0 -18
  60. package/dist/index.d.ts +10 -5
  61. package/dist/index.js +8 -4
  62. package/dist/orchestration/goal.js +1 -5
  63. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  64. package/dist/orchestration/run-workflow-tool.js +1 -2
  65. package/dist/orchestration/workflow-governance.d.ts +6 -6
  66. package/dist/orchestration/workflow-governance.js +33 -47
  67. package/dist/prompt-assembly/event-registry.js +2 -3
  68. package/dist/stores/cc/lockfile.js +9 -11
  69. package/dist/stores/cc/mailbox-store.js +49 -38
  70. package/dist/stores/file/index.d.ts +3 -0
  71. package/dist/stores/file/index.js +4 -0
  72. package/dist/stores/file/usage-window-store.d.ts +9 -0
  73. package/dist/stores/file/usage-window-store.js +86 -0
  74. package/dist/tools/fs/fs-bash.d.ts +1 -10
  75. package/dist/tools/fs/fs-bash.js +20 -60
  76. package/dist/tools/fs/index.d.ts +0 -2
  77. package/dist/tools/fs/index.js +1 -2
  78. package/package.json +1 -4
  79. package/dist/bin/sema-tb.d.ts +0 -31
  80. package/dist/bin/sema-tb.js +0 -448
  81. package/dist/bin/tb-env.d.ts +0 -2
  82. package/dist/bin/tb-env.js +0 -17
  83. package/dist/brain/walltime.d.ts +0 -1
  84. package/dist/brain/walltime.js +0 -1
  85. package/dist/core/runner/call-cap.d.ts +0 -67
  86. package/dist/core/runner/call-cap.js +0 -145
  87. package/dist/core/runner/cut-kill.d.ts +0 -10
  88. package/dist/core/runner/cut-kill.js +0 -37
@@ -6,5 +6,7 @@ export interface SecretEnvFinding {
6
6
  confidence: RedactionConfidence;
7
7
  source: RedactionSource;
8
8
  }
9
+ export type ExactEnvNameVerdict = "credential" | "exempt" | "unknown";
10
+ export declare function classifyExactEnvName(key: string, credentialNames: ReadonlySet<string>, exemptNames: ReadonlySet<string>): ExactEnvNameVerdict;
9
11
  export declare function isSecretEnvKey(key: string): boolean;
10
12
  export declare function scrubSecretEnv(env: NodeJS.ProcessEnv, findings?: SecretEnvFinding[]): NodeJS.ProcessEnv;
@@ -4,16 +4,28 @@ const SECRET_ENV_EXACT_NAMES = new Set([
4
4
  "PGPASSWORD", "MYSQL_PWD", "SSHPASS", "GPG_PASSPHRASE", "GH_PAT", "GITHUB_PAT",
5
5
  "NPM_CONFIG__AUTH", "NPM_CONFIG__AUTHTOKEN",
6
6
  ]);
7
+ const NON_SECRET_ENV_EXACT_NAMES = new Set([
8
+ "PARTITION_KEY", "SORT_KEY", "RANGE_KEY", "IDEMPOTENCY_KEY", "CACHE_KEY", "ROUTING_KEY",
9
+ ]);
7
10
  const SECRET_ENV_KIND_CONFIDENCE = {
8
11
  "exact-name": "high",
9
12
  "suffix-rule": "medium",
10
13
  };
14
+ export function classifyExactEnvName(key, credentialNames, exemptNames) {
15
+ const upper = key.toUpperCase();
16
+ if (credentialNames.has(upper))
17
+ return "credential";
18
+ if (exemptNames.has(upper))
19
+ return "exempt";
20
+ return "unknown";
21
+ }
11
22
  function classifySecretEnvKey(key) {
12
- if (SECRET_ENV_RE.test(key))
13
- return "suffix-rule";
14
- if (SECRET_ENV_EXACT_NAMES.has(key.toUpperCase()))
23
+ const byExactName = classifyExactEnvName(key, SECRET_ENV_EXACT_NAMES, NON_SECRET_ENV_EXACT_NAMES);
24
+ if (byExactName === "credential")
15
25
  return "exact-name";
16
- return undefined;
26
+ if (byExactName === "exempt")
27
+ return undefined;
28
+ return SECRET_ENV_RE.test(key) ? "suffix-rule" : undefined;
17
29
  }
18
30
  export function isSecretEnvKey(key) {
19
31
  return classifySecretEnvKey(key) !== undefined;
@@ -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" });
@@ -45,6 +45,7 @@ export declare function renderTaskNotificationXml(n: TaskNotificationPayload): s
45
45
  export declare const MAX_PENDING_EVENTS_PER_TASK = 50;
46
46
  export declare const MAX_PENDING_EVENTS_PER_SESSION = 200;
47
47
  export declare const MAX_PENDING_SESSIONS = 100;
48
+ export declare function isDelegatedAgentTerminal(n: Pick<TaskNotificationPayload, "task_type" | "status">): boolean;
48
49
  export interface DrainedPendingNotifications {
49
50
  items: TaskNotificationPayload[];
50
51
  dropped: Map<string, {
@@ -83,6 +83,9 @@ export const MAX_PENDING_EVENTS_PER_SESSION = 200;
83
83
  export const MAX_PENDING_SESSIONS = 100;
84
84
  const MAX_TOMBSTONES = 5_000;
85
85
  const TERMINAL_STATUSES = new Set(["completed", "failed", "killed", "cancelled"]);
86
+ export function isDelegatedAgentTerminal(n) {
87
+ return n.task_type === "background_agent" && TERMINAL_STATUSES.has(n.status);
88
+ }
86
89
  export class PendingSessionNotifications {
87
90
  sessions = new Map();
88
91
  droppedSessions = 0;
@@ -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")
@@ -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;
@@ -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
  }
@@ -244,6 +244,22 @@ export type ImageInput = {
244
244
  } | {
245
245
  url: string;
246
246
  };
247
+ export interface TaskLimits {
248
+ maxTokens?: number;
249
+ maxCostUsd?: number;
250
+ maxTurns?: number;
251
+ maxWalltimeMs?: number;
252
+ maxOutputTokens?: number;
253
+ approachNotice?: false | {
254
+ at?: [number, number];
255
+ };
256
+ budgetStreamCancel?: boolean;
257
+ degrade?: {
258
+ to: ModelRef;
259
+ atCostFraction: number;
260
+ };
261
+ brainCallGuardrailMs?: import("../brain/timeout.js").BrainCallGuardrailKnob;
262
+ }
247
263
  export interface TaskSpec {
248
264
  taskId?: string;
249
265
  objective: string;
@@ -293,7 +309,7 @@ export interface TaskSpec {
293
309
  resourceSuspend?: {
294
310
  scope: string;
295
311
  totalBudgetUsd?: number;
296
- totalWalltimeSec?: number;
312
+ totalTokens?: number;
297
313
  maxSlices?: number;
298
314
  ttlMs?: number;
299
315
  };
@@ -324,17 +340,7 @@ export interface TaskSpec {
324
340
  note?: string;
325
341
  };
326
342
  };
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
- };
343
+ limits?: TaskLimits;
338
344
  configOverrides?: ReadonlyArray<import("../config/catalog.js").ConfigOverrideDeclaration>;
339
345
  forwardSubagentEvents?: boolean;
340
346
  retainSubagentSessions?: boolean | {
@@ -343,13 +349,6 @@ export interface TaskSpec {
343
349
  };
344
350
  retainBackgroundProcesses?: boolean;
345
351
  streamingToolExecution?: boolean;
346
- maxCostUsd?: number;
347
- maxTokens?: number;
348
- budgetStreamCancel?: boolean;
349
- degrade?: {
350
- to: ModelRef;
351
- atCostFraction: number;
352
- };
353
352
  outputSchema?: TSchema;
354
353
  outputRetries?: number;
355
354
  tracer?: import("./trace.js").TracerHook;
@@ -389,7 +388,7 @@ export interface TaskSpec {
389
388
  } | undefined>;
390
389
  signal?: AbortSignal;
391
390
  }
392
- export type TaskStatus = "completed" | "blocked" | "failed" | "timeout" | "suspended" | "needs_review";
391
+ export type TaskStatus = "completed" | "blocked" | "failed" | "suspended" | "needs_review";
393
392
  export interface RemoteEnvFailureNote {
394
393
  op: "suspendVM" | "resumeVM" | "postResumeInit";
395
394
  code: import("./remote-env.js").RemoteExecutionErrorCode;
@@ -446,17 +445,10 @@ export interface TaskResult {
446
445
  costMicroUsd: number;
447
446
  };
448
447
  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
448
  finalVerifyInjected?: true;
458
449
  finalVerifyInjections?: number;
459
450
  attachmentsInjected?: number;
451
+ approachNoticesSent?: number;
460
452
  repetitionCuts?: number;
461
453
  repetitionSpared?: number;
462
454
  repetitionEvents?: Array<{
@@ -568,7 +560,7 @@ export type TaskEvent = ({
568
560
  modelFallback?: true;
569
561
  fallbackReason?: "window";
570
562
  clampedRatio?: number;
571
- clampReason?: "budget" | "walltime" | "tolerance";
563
+ clampReason?: "budget" | "tolerance";
572
564
  phaseDurations?: import("./auto-compaction.js").CompactionPhaseDurations;
573
565
  } & TaskEventIdentity) | ({
574
566
  type: "compaction_outcome";
@@ -577,7 +569,7 @@ export type TaskEvent = ({
577
569
  reason?: string;
578
570
  } & TaskEventIdentity) | ({
579
571
  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";
572
+ 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
573
  preview: string;
582
574
  } & TaskEventIdentity) | ({
583
575
  type: "diagnostics";
@@ -750,6 +742,8 @@ export interface RunnerDeps {
750
742
  workflowRunStore?: import("./workflow-run-store.js").WorkflowRunStore;
751
743
  backgroundAgentStore?: import("./background-agent-store.js").BackgroundAgentStore;
752
744
  mailboxStore?: import("./mailbox-store.js").MailboxStore;
745
+ usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
746
+ usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
753
747
  workflowJournalStore?: import("./workflow-journal-store.js").WorkflowJournalStore;
754
748
  workflowScriptStore?: import("../orchestration/workflow-script-store.js").WorkflowScriptStore;
755
749
  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,7 @@ 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?;
60
53
  private loopTrace?;
61
54
  private resilience?;
62
55
  private maxToolConcurrency?;