@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
@@ -1,9 +1,8 @@
1
1
  import {} from "../internal/llm.js";
2
2
  import { repairTextToolCalls } from "./tool-call-repair.js";
3
3
  import { DEGENERATE_MESSAGE, trimDegenerateTail } from "./repetition.js";
4
- import { createRepetitionPoll, createWalltimeGate, parseStreamedToolArgs } from "./stream-shared.js";
4
+ import { createRepetitionPoll, parseStreamedToolArgs } from "./stream-shared.js";
5
5
  import { mintFallbackToolCallId } from "./tool-call-id.js";
6
- import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
7
6
  import { emitBrainTelemetry } from "./status-sink.js";
8
7
  import { errorResultMediaNote, IMAGE_OMITTED_NO_VISION, imagesOmittedNoVisionNote, modelSupportsVision, sendableImages } from "./media-degrade.js";
9
8
  import { OPENAI_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
@@ -275,7 +274,6 @@ export function createOpenAIBrain(config = {}) {
275
274
  signal: options?.signal,
276
275
  config,
277
276
  httpLabel: "gateway",
278
- callDeadlineMs: options?.callDeadlineMs,
279
277
  stallTimeouts: options?.stallTimeouts,
280
278
  buildRequest: () => {
281
279
  const apiKey = options?.apiKey ?? config.apiKey;
@@ -330,7 +328,6 @@ export function createOpenAIBrain(config = {}) {
330
328
  let degenerate = false;
331
329
  let degenFace;
332
330
  const rep = createRepetitionPoll();
333
- const walltime = createWalltimeGate({ callDeadlineMs: options?.callDeadlineMs, isDegenerate: () => degenerate, cancel: () => ctrl.cancel() });
334
331
  let lastDegenCheck = 0;
335
332
  let lastDegenCheckReasoning = 0;
336
333
  const detectRep = config.detectRepetition !== false;
@@ -410,10 +407,7 @@ export function createOpenAIBrain(config = {}) {
410
407
  sealForRetry() {
411
408
  closeThinking();
412
409
  },
413
- onDeadline: () => walltime.onDeadline(),
414
410
  onLine(rawLine) {
415
- if (walltime.shouldDropLine())
416
- return;
417
411
  const line = rawLine.trim();
418
412
  if (!line.startsWith("data:"))
419
413
  return;
@@ -534,40 +528,35 @@ export function createOpenAIBrain(config = {}) {
534
528
  const dynamicCut = truncatedEmpty && options?.maxTokensDynamic === true;
535
529
  const safetyCut = finishReason === "content_filter";
536
530
  const refused = accumRefusal.trim().length > 0;
537
- const emptyNoFinish = noUsableContent && finishReason == null && !degenerate && !walltime.cut;
531
+ const emptyNoFinish = noUsableContent && finishReason == null && !degenerate;
538
532
  const maxTokensNote = sentMaxTokens !== undefined
539
533
  ? `effective max_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options" ? "options.maxTokens" : "model.maxTokens"})`
540
534
  : "max_tokens not set (neither options.maxTokens nor model.maxTokens — the provider's default cap applied)";
541
- const errorMessage = walltime.cut
542
- ? WALLTIME_CUTOFF_MESSAGE
543
- : degenerate
544
- ? DEGENERATE_MESSAGE
545
- : safetyCut
546
- ? `[refusal] response cut by the provider content filter (finish_reason="content_filter") — the output was censored/truncated for policy and is unreliable`
547
- : refused
548
- ? `[refusal] response refused by the model for policy reasons: ${accumRefusal.trim()}`
549
- : (toolError ??
550
- (truncatedEmpty && !dynamicCut
551
- ? accumReasoning.trim()
552
- ? `[length_empty] response truncated at max_tokens (finish_reason="length", ${maxTokensNote}) with no answer text — the model spent the whole budget on reasoning (${accumReasoning.length} reasoning chars). Raise max_tokens, or disable thinking for this task.`
553
- : `[length_empty] response truncated at max_tokens (finish_reason="length", ${maxTokensNote}) with no answer text raise max_tokens`
554
- : emptyNoFinish
555
- ? `[stream_torn] model stream ended with no content and no finish_reason — the response was lost (a torn stream or an in-band provider error frame)${malformedFrames > 0 ? ` — ${malformedFrames} unparseable data frame(s) were dropped on this stream (first sample: ${JSON.stringify(malformedSample)})` : ""}`
556
- : undefined));
557
- const errored = walltime.cut ||
558
- degenerate ||
535
+ const errorMessage = degenerate
536
+ ? DEGENERATE_MESSAGE
537
+ : safetyCut
538
+ ? `[refusal] response cut by the provider content filter (finish_reason="content_filter") — the output was censored/truncated for policy and is unreliable`
539
+ : refused
540
+ ? `[refusal] response refused by the model for policy reasons: ${accumRefusal.trim()}`
541
+ : (toolError ??
542
+ (truncatedEmpty && !dynamicCut
543
+ ? accumReasoning.trim()
544
+ ? `[length_empty] response truncated at max_tokens (finish_reason="length", ${maxTokensNote}) with no answer text — the model spent the whole budget on reasoning (${accumReasoning.length} reasoning chars). Raise max_tokens, or disable thinking for this task.`
545
+ : `[length_empty] response truncated at max_tokens (finish_reason="length", ${maxTokensNote}) with no answer text — raise max_tokens`
546
+ : emptyNoFinish
547
+ ? `[stream_torn] model stream ended with no content and no finish_reason — the response was lost (a torn stream or an in-band provider error frame)${malformedFrames > 0 ? ` — ${malformedFrames} unparseable data frame(s) were dropped on this stream (first sample: ${JSON.stringify(malformedSample)})` : ""}`
548
+ : undefined));
549
+ const errored = degenerate ||
559
550
  safetyCut ||
560
551
  refused ||
561
552
  emptyNoFinish ||
562
553
  (toolError !== undefined && noUsableContent) ||
563
554
  (truncatedEmpty && !dynamicCut);
564
- const errorKind = walltime.cut
565
- ? "walltime_cutoff"
566
- : degenerate
567
- ? "degenerate"
568
- : !safetyCut && !refused && toolError === undefined && truncatedEmpty && !dynamicCut
569
- ? "length_empty"
570
- : undefined;
555
+ const errorKind = degenerate
556
+ ? "degenerate"
557
+ : !safetyCut && !refused && toolError === undefined && truncatedEmpty && !dynamicCut
558
+ ? "length_empty"
559
+ : undefined;
571
560
  const finalMessage = {
572
561
  ...partial,
573
562
  content: finalContent.length > 0 ? finalContent : [{ type: "text", text: "" }],
@@ -1,5 +1,6 @@
1
1
  export declare const OPENAI_RESERVED: ReadonlySet<string>;
2
2
  export declare const ANTHROPIC_RESERVED: ReadonlySet<string>;
3
+ export declare const RESPONSES_RESERVED: ReadonlySet<string>;
3
4
  export declare function reservedFor(api: string): ReadonlySet<string>;
4
5
  export declare function applyExtraBody(body: Record<string, unknown>, extraBody: Record<string, unknown> | undefined, reserved: ReadonlySet<string>): Record<string, unknown>;
5
6
  export declare function stripAuthHeaders(headers: Record<string, string>): void;
@@ -25,7 +25,23 @@ export const ANTHROPIC_RESERVED = new Set([
25
25
  "output_config",
26
26
  "context_management",
27
27
  ]);
28
+ export const RESPONSES_RESERVED = new Set([
29
+ "model",
30
+ "input",
31
+ "stream",
32
+ "instructions",
33
+ "tools",
34
+ "temperature",
35
+ "max_output_tokens",
36
+ "reasoning",
37
+ "store",
38
+ "previous_response_id",
39
+ "conversation",
40
+ ]);
41
+ const RESPONSES_APIS = new Set(["openai-responses", "azure-openai-responses", "openai-chatgpt-responses"]);
28
42
  export function reservedFor(api) {
43
+ if (RESPONSES_APIS.has(api))
44
+ return RESPONSES_RESERVED;
29
45
  return api === "anthropic-messages" ? ANTHROPIC_RESERVED : OPENAI_RESERVED;
30
46
  }
31
47
  export function applyExtraBody(body, extraBody, reserved) {
@@ -18,7 +18,6 @@ export interface StreamControls {
18
18
  }
19
19
  export interface StreamParser {
20
20
  onLine(line: string): void;
21
- onDeadline?(): boolean;
22
21
  finalize(): void;
23
22
  snapshot(): {
24
23
  hasSubstantiveText: boolean;
@@ -35,7 +34,6 @@ export declare function runStreamingBrain(args: {
35
34
  httpLabel: string;
36
35
  buildRequest: () => SSERequest;
37
36
  makeParser: (ctrl: StreamControls) => StreamParser;
38
- callDeadlineMs?: number;
39
37
  stallTimeouts?: {
40
38
  connectMs?: number;
41
39
  firstTokenMs?: number;
@@ -3,7 +3,6 @@ import { BrainError, classifyHttp, describeNetworkError } from "./errors.js";
3
3
  import { retryBackoffMs } from "./retry.js";
4
4
  import { emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
5
5
  import { createConnectController, resolveStallTimeoutMs } from "./timeout.js";
6
- import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
7
6
  const DEFAULT_MAX_RETRIES = 10;
8
7
  const MAX_RETRIES_ENV_CEILING = 15;
9
8
  const MAX_RETRIES_ENV = "SEMA_MAX_RETRIES";
@@ -43,9 +42,6 @@ function emptyAssistant(model) {
43
42
  function isAbortError(err) {
44
43
  return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
45
44
  }
46
- function isWalltimeCutoff(err) {
47
- return err instanceof Error && err.message === WALLTIME_CUTOFF_MESSAGE;
48
- }
49
45
  function sleep(ms, signal) {
50
46
  return new Promise((resolve) => {
51
47
  if (signal?.aborted)
@@ -78,7 +74,7 @@ async function sleepAnnouncingRetry(totalMs, signal, frame) {
78
74
  }
79
75
  }
80
76
  export function runStreamingBrain(args) {
81
- const { model, doFetch, signal, config, httpLabel, buildRequest, makeParser, callDeadlineMs, stallTimeouts } = args;
77
+ const { model, doFetch, signal, config, httpLabel, buildRequest, makeParser, stallTimeouts } = args;
82
78
  const out = createAssistantMessageEventStream();
83
79
  let cleanup;
84
80
  let announcedRetry = false;
@@ -88,13 +84,10 @@ export function runStreamingBrain(args) {
88
84
  .catch((err) => {
89
85
  const aborted = signal?.aborted === true || isAbortError(err);
90
86
  terminalRetryPhase = "gave_up";
91
- const walltimeCut = isWalltimeCutoff(err);
92
- terminalRetryDetail = aborted ? "cancelled while retrying" : walltimeCut ? "wall-clock deadline reached while retrying" : "retries exhausted";
87
+ terminalRetryDetail = aborted ? "cancelled while retrying" : "retries exhausted";
93
88
  const errorMsg = emptyAssistant(model);
94
89
  errorMsg.stopReason = aborted ? "aborted" : "error";
95
90
  errorMsg.errorMessage = err instanceof Error ? err.message : String(err);
96
- if (walltimeCut)
97
- errorMsg.errorKind = "walltime_cutoff";
98
91
  errorMsg.usageMissing = true;
99
92
  out.push({ type: "error", reason: aborted ? "aborted" : "error", error: errorMsg });
100
93
  })
@@ -128,20 +121,13 @@ export function runStreamingBrain(args) {
128
121
  e.name = "AbortError";
129
122
  throw e;
130
123
  }
131
- if (callDeadlineMs !== undefined && Date.now() >= callDeadlineMs) {
132
- throw new Error(WALLTIME_CUTOFF_MESSAGE);
133
- }
134
- const cc = createConnectController(connectTimeoutMs, signal, callDeadlineMs);
124
+ const cc = createConnectController(connectTimeoutMs, signal);
135
125
  let r;
136
126
  let netErr;
137
127
  try {
138
128
  r = await doFetch(req.url, { method: "POST", headers: req.headers, body: req.body, signal: cc.signal });
139
129
  }
140
130
  catch (e) {
141
- if (cc.deadlineHit()) {
142
- cc.dispose();
143
- throw new Error(WALLTIME_CUTOFF_MESSAGE);
144
- }
145
131
  if (cc.timedOut()) {
146
132
  netErr = new Error(`connect timeout after ${connectTimeoutMs}ms`);
147
133
  }
@@ -153,10 +139,6 @@ export function runStreamingBrain(args) {
153
139
  netErr = e;
154
140
  }
155
141
  }
156
- if (r && cc.deadlineHit()) {
157
- cc.dispose();
158
- throw new Error(WALLTIME_CUTOFF_MESSAGE);
159
- }
160
142
  if (r && r.ok && r.body) {
161
143
  cc.clearTimer();
162
144
  connect = cc;
@@ -168,13 +150,7 @@ export function runStreamingBrain(args) {
168
150
  const retryable = netErr !== undefined ||
169
151
  (r !== undefined && (r.status >= 500 || r.status === 429 || r.status === 408 || r.status === 409));
170
152
  if (retryable && attempt < maxRetries) {
171
- if (callDeadlineMs !== undefined && Date.now() >= callDeadlineMs) {
172
- throw new Error(WALLTIME_CUTOFF_MESSAGE);
173
- }
174
153
  const delayMs = retryBackoffMs(baseDelay, attempt, r);
175
- if (callDeadlineMs !== undefined && Date.now() + delayMs >= callDeadlineMs) {
176
- throw new Error(WALLTIME_CUTOFF_MESSAGE);
177
- }
178
154
  const statusPhase = r?.status === 429 ? "rate_limited" : netErr !== undefined ? "reconnecting" : "retrying";
179
155
  emitBrainTelemetry({ kind: "retry", attempt: attempt + 1, phase: "connect" });
180
156
  announcedRetry = true;
@@ -228,8 +204,6 @@ export function runStreamingBrain(args) {
228
204
  void reader.cancel().catch(() => undefined);
229
205
  }, idleTimeoutMs);
230
206
  };
231
- let dlTimer;
232
- let deadlineCut = false;
233
207
  const sawContentToken = () => {
234
208
  bumpIdle();
235
209
  if (firstTokenSeen)
@@ -250,10 +224,6 @@ export function runStreamingBrain(args) {
250
224
  clearTimeout(idleTimer);
251
225
  idleTimer = undefined;
252
226
  }
253
- if (dlTimer) {
254
- clearTimeout(dlTimer);
255
- dlTimer = undefined;
256
- }
257
227
  connect?.dispose();
258
228
  void reader.cancel().catch(() => undefined);
259
229
  };
@@ -273,14 +243,6 @@ export function runStreamingBrain(args) {
273
243
  sawContentToken,
274
244
  cancel: () => void reader.cancel().catch(() => undefined),
275
245
  });
276
- if (callDeadlineMs !== undefined && parser.onDeadline) {
277
- dlTimer = setTimeout(() => {
278
- dlTimer = undefined;
279
- deadlineCut = parser.onDeadline();
280
- void reader.cancel().catch(() => undefined);
281
- }, Math.max(0, callDeadlineMs - Date.now()));
282
- dlTimer.unref?.();
283
- }
284
246
  const INBAND_OVERLOAD_WARNING = "API overloaded mid-response. The response above may be incomplete.";
285
247
  const onLineOverloadTierA = (line) => {
286
248
  try {
@@ -334,14 +296,10 @@ export function runStreamingBrain(args) {
334
296
  clearTimeout(idleTimer);
335
297
  idleTimer = undefined;
336
298
  }
337
- if (dlTimer) {
338
- clearTimeout(dlTimer);
339
- dlTimer = undefined;
340
- }
341
- if (!failure && firstTokenTimedOut && !deadlineCut) {
299
+ if (!failure && firstTokenTimedOut) {
342
300
  failure = { kind: "stalled", err: new BrainError("network", `first-token timeout after ${firstTokenTimeoutMs}ms`) };
343
301
  }
344
- if (!failure && idleTimedOut && !deadlineCut) {
302
+ if (!failure && idleTimedOut) {
345
303
  failure = { kind: "stalled", err: new BrainError("network", `stream idle timeout after ${idleTimeoutMs}ms`) };
346
304
  }
347
305
  if (!failure) {
@@ -6,13 +6,3 @@ export interface RepetitionPoll {
6
6
  readonly spared: RepetitionEvent[];
7
7
  }
8
8
  export declare function createRepetitionPoll(): RepetitionPoll;
9
- export interface WalltimeGate {
10
- onDeadline(): boolean;
11
- shouldDropLine(): boolean;
12
- readonly cut: boolean;
13
- }
14
- export declare function createWalltimeGate(deps: {
15
- callDeadlineMs: number | undefined;
16
- isDegenerate: () => boolean;
17
- cancel: () => void;
18
- }): WalltimeGate;
@@ -39,26 +39,3 @@ export function createRepetitionPoll() {
39
39
  },
40
40
  };
41
41
  }
42
- export function createWalltimeGate(deps) {
43
- const { callDeadlineMs, isDegenerate, cancel } = deps;
44
- let walltimeCut = false;
45
- return {
46
- get cut() {
47
- return walltimeCut;
48
- },
49
- onDeadline() {
50
- if (isDegenerate())
51
- return false;
52
- walltimeCut = true;
53
- return true;
54
- },
55
- shouldDropLine() {
56
- if (callDeadlineMs !== undefined && !walltimeCut && !isDegenerate() && Date.now() >= callDeadlineMs) {
57
- walltimeCut = true;
58
- cancel();
59
- return true;
60
- }
61
- return walltimeCut;
62
- },
63
- };
64
- }
@@ -1,4 +1,3 @@
1
1
  import type { AssistantMessage } from "../internal/llm.js";
2
2
  export type TerminalCauseCarrier = Pick<AssistantMessage, "errorKind">;
3
3
  export declare function isDegenerateCutMessage(message: TerminalCauseCarrier): boolean;
4
- export declare function isWalltimeCutMessage(message: TerminalCauseCarrier): boolean;
@@ -1,6 +1,3 @@
1
1
  export function isDegenerateCutMessage(message) {
2
2
  return message.errorKind === "degenerate";
3
3
  }
4
- export function isWalltimeCutMessage(message) {
5
- return message.errorKind === "walltime_cutoff";
6
- }
@@ -1,14 +1,16 @@
1
1
  import type { StreamFn } from "../internal/llm.js";
2
2
  export declare function resolveStallTimeoutMs(value: number | undefined, knob: string): number | undefined;
3
+ export declare const STALL_CONNECT_MS = 30000;
4
+ export declare const STALL_FIRST_TOKEN_MS = 120000;
5
+ export declare const STALL_IDLE_MS = 90000;
3
6
  export interface BrainTimeoutConfig {
4
7
  connectTimeoutMs?: number;
5
8
  firstTokenTimeoutMs?: number;
6
9
  idleTimeoutMs?: number;
7
10
  }
8
- export declare function createConnectController(connectTimeoutMs: number | undefined, outerSignal: AbortSignal | undefined, deadlineAtMs?: number): {
11
+ export declare function createConnectController(connectTimeoutMs: number | undefined, outerSignal: AbortSignal | undefined): {
9
12
  signal: AbortSignal;
10
13
  timedOut: () => boolean;
11
- deadlineHit: () => boolean;
12
14
  clearTimer: () => void;
13
15
  dispose: () => void;
14
16
  };
@@ -8,10 +8,12 @@ export function resolveStallTimeoutMs(value, knob) {
8
8
  }
9
9
  return value;
10
10
  }
11
- export function createConnectController(connectTimeoutMs, outerSignal, deadlineAtMs) {
11
+ export const STALL_CONNECT_MS = 30_000;
12
+ export const STALL_FIRST_TOKEN_MS = 120_000;
13
+ export const STALL_IDLE_MS = 90_000;
14
+ export function createConnectController(connectTimeoutMs, outerSignal) {
12
15
  const ac = new AbortController();
13
16
  let timedOut = false;
14
- let deadlineHit = false;
15
17
  const onAbort = () => ac.abort(outerSignal?.reason);
16
18
  if (outerSignal) {
17
19
  if (outerSignal.aborted)
@@ -26,26 +28,14 @@ export function createConnectController(connectTimeoutMs, outerSignal, deadlineA
26
28
  ac.abort();
27
29
  }, connectTimeoutMs);
28
30
  }
29
- let deadlineTimer;
30
- if (deadlineAtMs !== undefined) {
31
- deadlineTimer = setTimeout(() => {
32
- deadlineHit = true;
33
- ac.abort();
34
- }, Math.max(0, deadlineAtMs - Date.now()));
35
- deadlineTimer.unref?.();
36
- }
37
31
  const clearTimer = () => {
38
32
  if (timer)
39
33
  clearTimeout(timer);
40
34
  timer = undefined;
41
- if (deadlineTimer)
42
- clearTimeout(deadlineTimer);
43
- deadlineTimer = undefined;
44
35
  };
45
36
  return {
46
37
  signal: ac.signal,
47
38
  timedOut: () => timedOut,
48
- deadlineHit: () => deadlineHit,
49
39
  clearTimer,
50
40
  dispose: () => {
51
41
  clearTimer();
@@ -1,3 +1,4 @@
1
+ import type { TaskLimits } from "../core/types.js";
1
2
  export declare const CONFIG_CATALOG_VERSION = "sema-config@1";
2
3
  export interface ConfigKnob {
3
4
  key: string;
@@ -24,18 +25,9 @@ export interface EffectiveConfigField {
24
25
  }
25
26
  export declare const MAX_OVERRIDE_DECLARATIONS = 32;
26
27
  export declare function resolveEffectiveConfig(spec: {
27
- limits?: {
28
- maxTurns?: number;
29
- timeoutSec?: number;
30
- maxOutputTokens?: number;
31
- deadlineNudge?: false | object;
32
- callCapByDeadline?: false;
33
- gracefulFinalize?: false;
34
- };
35
- maxCostUsd?: number;
36
- maxTokens?: number;
28
+ limits?: Pick<TaskLimits, "maxTurns" | "maxWalltimeMs" | "maxOutputTokens" | "maxCostUsd" | "maxTokens">;
37
29
  resourceSuspend?: {
38
- totalWalltimeSec?: number;
30
+ totalTokens?: number;
39
31
  totalBudgetUsd?: number;
40
32
  };
41
33
  configOverrides?: readonly ConfigOverrideDeclaration[];
@@ -1,22 +1,22 @@
1
- import { DEFAULT_MAX_TURNS, FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, SESSION_DEFAULT_TTL_DAYS } from "./defaults.js";
1
+ import { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX, SESSION_DEFAULT_TTL_DAYS } from "./defaults.js";
2
2
  export const CONFIG_CATALOG_VERSION = "sema-config@1";
3
3
  export function describeConfigCatalog() {
4
4
  const knobs = [
5
5
  {
6
6
  key: "limits.maxTurns",
7
- recommended: DEFAULT_MAX_TURNS,
7
+ recommended: null,
8
8
  unit: "turns",
9
- kind: "safety-net",
10
- summary: "Runaway net, NOT the long-run knob: catches a wedged tool-call loop before it burns budget. A genuinely long task sets its own explicit bound. 0 = unbounded.",
11
- precedence: "spec.limits.maxTurns > engine default",
9
+ kind: "budget",
10
+ summary: "Per-slice turn ceiling. Unset = unbounded (design/164 retired the engine-side net): a task that needs a turn bound states one. 0 = the explicit unbounded sentinel.",
11
+ precedence: "spec.limits.maxTurns > unset",
12
12
  },
13
13
  {
14
- key: "limits.timeoutSec",
14
+ key: "limits.maxWalltimeMs",
15
15
  recommended: null,
16
- unit: "seconds",
16
+ unit: "ms",
17
17
  kind: "budget",
18
- summary: "Hard wall-clock budget for the task. Unset = unbounded. Setting it arms the deadline family (nudges, call caps, graceful finalize) by default.",
19
- precedence: "spec.limits.timeoutSec > unset",
18
+ summary: "Per-slice ACTIVE wall-clock window for the task (suspended time does not count; a resume opens a fresh window). Unset = unbounded, which is the recommendation bound work by tokens, not by time.",
19
+ precedence: "spec.limits.maxWalltimeMs > unset",
20
20
  },
21
21
  {
22
22
  key: "limits.maxOutputTokens",
@@ -27,65 +27,35 @@ export function describeConfigCatalog() {
27
27
  precedence: "spec.limits.maxOutputTokens > model.maxTokens > provider default",
28
28
  },
29
29
  {
30
- key: "limits.deadlineNudge",
31
- recommended: true,
32
- kind: "behavior",
33
- summary: "Two one-shot convergence reminders (~80%/~95% of timeoutSec). Auto-ON when timeoutSec is set; no effect without it.",
34
- precedence: "spec.limits.deadlineNudge > derived(timeoutSec set)",
35
- },
36
- {
37
- key: "limits.callCapByDeadline",
38
- recommended: true,
39
- kind: "behavior",
40
- summary: "Shrink each call's max_tokens to what remaining wall-clock can absorb (shrink-only). Auto-ON when timeoutSec is set.",
41
- precedence: "spec.limits.callCapByDeadline > derived(timeoutSec set)",
42
- },
43
- {
44
- key: "limits.gracefulFinalize",
45
- recommended: true,
46
- kind: "behavior",
47
- summary: "One-shot finalize turn + write-out cushion + soft execution deadline when remaining time no longer fits a full cycle. Auto-ON when timeoutSec is set.",
48
- precedence: "spec.limits.gracefulFinalize > derived(timeoutSec set)",
49
- },
50
- {
51
- key: "budget.maxCostUsd",
30
+ key: "limits.maxCostUsd",
52
31
  recommended: null,
53
32
  unit: "usd",
54
33
  kind: "budget",
55
- summary: "Cumulative spend cap for the task (hard-cancels the in-flight stream by default when crossed). Unset = unbounded.",
56
- precedence: "spec.maxCostUsd > unset",
34
+ summary: "Per-slice spend ceiling (hard-cancels the in-flight stream by default when crossed). Unset = unbounded.",
35
+ precedence: "spec.limits.maxCostUsd > unset",
57
36
  },
58
37
  {
59
- key: "budget.maxTokens",
38
+ key: "limits.maxTokens",
60
39
  recommended: null,
61
40
  unit: "tokens",
62
41
  kind: "budget",
63
- summary: "Cumulative token budget for the task. Unset = unbounded.",
64
- precedence: "spec.maxTokens > unset",
42
+ summary: "Per-slice token budget the PRIMARY axis. Unset = unbounded.",
43
+ precedence: "spec.limits.maxTokens > unset",
65
44
  },
66
45
  {
67
- key: "budget.totalWalltimeSec",
46
+ key: "limits.approachNotice",
68
47
  recommended: null,
69
- unit: "seconds",
70
- kind: "budget",
71
- summary: "Cross-slice wall-clock allocation for resource-suspend tasks (RB-21). First slice only; later slices read the frozen ledger. Unset = per-slice budgets only.",
72
- precedence: "spec.resources.totalWalltimeSec (first slice) > unset",
73
- },
74
- {
75
- key: "delegation.forkMaxTurns",
76
- recommended: FORK_DEFAULT_MAX_TURNS,
77
- unit: "turns",
78
- kind: "safety-net",
79
- summary: "Default turn cap for the Agent fork arm (child inherits the parent's world; tighter net than the global default).",
80
- precedence: "agent definition limits > fork default",
48
+ kind: "behavior",
49
+ summary: "Advisory limit-approach reminders (one at ~80% of the tightest armed axis, one at ~95%). Unset = ON whenever an axis is armed; false = off; { at } overrides the thresholds.",
50
+ precedence: "spec.limits.approachNotice > on-when-any-axis-armed",
81
51
  },
82
52
  {
83
- key: "delegation.sessionBgTimeoutSec",
84
- recommended: SESSION_BG_DEFAULT_TIMEOUT_SEC,
85
- unit: "seconds",
86
- kind: "safety-net",
87
- summary: "Default wall-clock budget for a session-scoped background child.",
88
- precedence: "spawn args > default",
53
+ key: "budget.totalTokens",
54
+ recommended: null,
55
+ unit: "tokens",
56
+ kind: "budget",
57
+ summary: "Cross-slice token allocation for resource-suspend tasks. First slice only; later slices read the frozen ledger. Unset = per-slice windows only.",
58
+ precedence: "spec.resourceSuspend.totalTokens (first slice) > unset",
89
59
  },
90
60
  {
91
61
  key: "delegation.retainTtlMs",
@@ -119,28 +89,19 @@ const MAX_DECLARATION_CHARS = 200;
119
89
  const sanitizeDeclarationText = (s) => s.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/g, "").slice(0, MAX_DECLARATION_CHARS);
120
90
  export function resolveEffectiveConfig(spec, ctx) {
121
91
  const l = spec.limits;
122
- const hasDeadline = l?.timeoutSec !== undefined;
123
92
  const pick = (key, specVal, dflt) => specVal !== undefined ? { key, value: specVal, provenance: "spec" } : { key, value: dflt, provenance: "default" };
124
- const deadlineSwitch = (key, v) => v === false
125
- ? { key, value: false, provenance: "spec" }
126
- : v !== undefined
127
- ? { key, value: hasDeadline, provenance: "spec" }
128
- : { key, value: hasDeadline, provenance: "derived" };
129
93
  const maxOutputTokens = l?.maxOutputTokens !== undefined
130
94
  ? { key: "limits.maxOutputTokens", value: l.maxOutputTokens, provenance: "spec" }
131
95
  : ctx?.modelMaxTokens !== undefined
132
96
  ? { key: "limits.maxOutputTokens", value: ctx.modelMaxTokens, provenance: "derived" }
133
97
  : { key: "limits.maxOutputTokens", value: null, provenance: "default" };
134
98
  const fields = [
135
- pick("limits.maxTurns", l?.maxTurns, DEFAULT_MAX_TURNS),
136
- pick("limits.timeoutSec", l?.timeoutSec, null),
99
+ pick("limits.maxTurns", l?.maxTurns, null),
100
+ pick("limits.maxWalltimeMs", l?.maxWalltimeMs, null),
137
101
  maxOutputTokens,
138
- deadlineSwitch("limits.deadlineNudge", l?.deadlineNudge),
139
- deadlineSwitch("limits.callCapByDeadline", l?.callCapByDeadline),
140
- deadlineSwitch("limits.gracefulFinalize", l?.gracefulFinalize),
141
- pick("budget.maxCostUsd", spec.maxCostUsd, null),
142
- pick("budget.maxTokens", spec.maxTokens, null),
143
- pick("budget.totalWalltimeSec", spec.resourceSuspend?.totalWalltimeSec, null),
102
+ pick("limits.maxCostUsd", l?.maxCostUsd, null),
103
+ pick("limits.maxTokens", l?.maxTokens, null),
104
+ pick("budget.totalTokens", spec.resourceSuspend?.totalTokens, null),
144
105
  pick("budget.totalBudgetUsd", spec.resourceSuspend?.totalBudgetUsd, null),
145
106
  ];
146
107
  for (const d of (spec.configOverrides ?? []).slice(0, MAX_OVERRIDE_DECLARATIONS)) {
@@ -1,6 +1,3 @@
1
- export declare const DEFAULT_MAX_TURNS = 1000;
2
- export declare const FORK_DEFAULT_MAX_TURNS = 200;
3
- export declare const SESSION_BG_DEFAULT_TIMEOUT_SEC: number;
4
1
  export declare const RETAIN_DEFAULT_TTL_MS: number;
5
2
  export declare const RETAIN_DEFAULT_MAX = 16;
6
3
  export declare const SESSION_DEFAULT_TTL_DAYS = 7;
@@ -1,6 +1,3 @@
1
- export const DEFAULT_MAX_TURNS = 1000;
2
- export const FORK_DEFAULT_MAX_TURNS = 200;
3
- export const SESSION_BG_DEFAULT_TIMEOUT_SEC = 30 * 60;
4
1
  export const RETAIN_DEFAULT_TTL_MS = 30 * 60 * 1000;
5
2
  export const RETAIN_DEFAULT_MAX = 16;
6
3
  export const SESSION_DEFAULT_TTL_DAYS = 7;
@@ -6,16 +6,14 @@ import type { Brain } from "./types.js";
6
6
  export declare const DEFAULT_COMPACTION_INSTRUCTIONS: string;
7
7
  export declare const COMPACTION_INSTRUCTIONS_MAX_CHARS = 2048;
8
8
  export declare function sanitizeCompactionInstructions(text: string): string;
9
- export declare const COMPACTION_THROUGHPUT_PRIOR_TOKENS_PER_MS = 3;
10
9
  export type CompactionPhaseDurations = Omit<Extract<TraceEvent, {
11
10
  kind: "compaction.phase_timings";
12
11
  }>, "kind" | "version" | "taskId" | "ts" | "durationMs">;
13
- export declare function isCompactionWalltimeAbort(err: unknown): boolean;
14
12
  export declare function isCompactionManualCancel(err: unknown): boolean;
15
13
  export interface CompactionWindowSafetyInfo {
16
14
  kind: "fallback" | "clamp_disclosure";
17
15
  truncationRatio: number;
18
- reason?: "budget" | "walltime" | "tolerance";
16
+ reason?: "budget" | "tolerance";
19
17
  contentTokens: number;
20
18
  headroomTokens?: number;
21
19
  estCostMicroUsd?: number;
@@ -51,13 +49,6 @@ export interface MaybeCompactOptions {
51
49
  capMicroUsd: number;
52
50
  mainInputPer1M?: number;
53
51
  };
54
- walltime?: {
55
- deadlineMs: number;
56
- cushionMs: () => number;
57
- throughputRef: {
58
- ewmaTokensPerMs?: number;
59
- };
60
- };
61
52
  onWindowSafety?: (info: CompactionWindowSafetyInfo) => void;
62
53
  minTokens?: number;
63
54
  force?: boolean;
@@ -131,7 +122,7 @@ export declare function maybeCompact(opts: MaybeCompactOptions): Promise<{
131
122
  modelFallback?: true;
132
123
  fallbackReason?: "window";
133
124
  clampedRatio?: number;
134
- clampReason?: "budget" | "walltime" | "tolerance";
125
+ clampReason?: "budget" | "tolerance";
135
126
  }>;
136
127
  export declare function nextTrimForceBackoff(prev: boolean, comp: {
137
128
  compacted: boolean;