@sema-agent/core 1.451.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/agents/agent-transcript-tool.d.ts +15 -0
  2. package/dist/agents/agent-transcript-tool.js +122 -0
  3. package/dist/agents/cascade.js +5 -8
  4. package/dist/agents/observer.js +3 -0
  5. package/dist/agents/repair-loop.d.ts +1 -1
  6. package/dist/agents/repair-loop.js +1 -1
  7. package/dist/agents/retain-ledger.d.ts +60 -0
  8. package/dist/agents/retain-ledger.js +225 -0
  9. package/dist/agents/roster-store.d.ts +1 -0
  10. package/dist/agents/roster-store.js +9 -0
  11. package/dist/agents/send-message-tool.d.ts +41 -0
  12. package/dist/agents/send-message-tool.js +469 -0
  13. package/dist/agents/subagent.d.ts +33 -108
  14. package/dist/agents/subagent.js +40 -813
  15. package/dist/agents/teacher.d.ts +0 -1
  16. package/dist/agents/teacher.js +0 -9
  17. package/dist/bin/sema-tb.js +3 -3
  18. package/dist/brain/anthropic.js +15 -40
  19. package/dist/brain/openai.js +15 -40
  20. package/dist/brain/reasoning.d.ts +1 -1
  21. package/dist/brain/stream-engine.js +2 -2
  22. package/dist/brain/stream-shared.d.ts +17 -0
  23. package/dist/brain/stream-shared.js +50 -0
  24. package/dist/brain/timeout.d.ts +1 -1
  25. package/dist/brain/timeout.js +1 -1
  26. package/dist/config/catalog.js +1 -3
  27. package/dist/config/defaults.d.ts +6 -0
  28. package/dist/config/defaults.js +6 -0
  29. package/dist/core/ask-question.d.ts +1 -1
  30. package/dist/core/ask-question.js +5 -5
  31. package/dist/core/background-agent-store.d.ts +2 -0
  32. package/dist/core/background-agent-store.js +5 -1
  33. package/dist/core/background-shell.d.ts +1 -1
  34. package/dist/core/checkpoint-store.d.ts +0 -1
  35. package/dist/core/exec-gate.d.ts +1 -1
  36. package/dist/core/exec-output-tail.d.ts +19 -1
  37. package/dist/core/exec-output-tail.js +44 -5
  38. package/dist/core/file-snapshot-store.d.ts +1 -1
  39. package/dist/core/fs-write-gate-policy.d.ts +1 -1
  40. package/dist/core/git-worktree-env.d.ts +1 -1
  41. package/dist/core/hooks.d.ts +1 -1
  42. package/dist/core/image-downsample.d.ts +44 -0
  43. package/dist/core/image-downsample.js +97 -0
  44. package/dist/core/lsp-session.d.ts +1 -0
  45. package/dist/core/lsp-session.js +24 -6
  46. package/dist/core/lsp.d.ts +11 -1
  47. package/dist/core/lsp.js +63 -6
  48. package/dist/core/mailbox-store.d.ts +3 -2
  49. package/dist/core/mailbox-store.js +19 -4
  50. package/dist/core/mcp.d.ts +3 -45
  51. package/dist/core/mcp.js +2 -97
  52. package/dist/core/media-byte-cap.d.ts +1 -1
  53. package/dist/core/memory-recall.d.ts +0 -12
  54. package/dist/core/memory-recall.js +0 -222
  55. package/dist/core/memory.d.ts +0 -6
  56. package/dist/core/memory.js +0 -88
  57. package/dist/core/message-utils.d.ts +1 -1
  58. package/dist/core/oracle-isolation.d.ts +1 -1
  59. package/dist/core/remote-env.d.ts +1 -1
  60. package/dist/core/roles.d.ts +1 -1
  61. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  62. package/dist/core/runner/assemble-result.d.ts +0 -2
  63. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  64. package/dist/core/runner/prepare-memory.d.ts +16 -0
  65. package/dist/core/runner/prepare-memory.js +191 -0
  66. package/dist/core/runner/prepare-task.d.ts +4 -28
  67. package/dist/core/runner/prepare-task.js +29 -208
  68. package/dist/core/runner/prompt-suggestions.d.ts +1 -1
  69. package/dist/core/runner/runtask.d.ts +2 -3
  70. package/dist/core/runner/runtask.js +337 -529
  71. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  72. package/dist/core/runner/synthetic-tools.d.ts +5 -5
  73. package/dist/core/runner/synthetic-tools.js +4 -4
  74. package/dist/core/runner/tool-disclosure.d.ts +3 -3
  75. package/dist/core/runner/tool-disclosure.js +2 -2
  76. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  77. package/dist/core/runner/tool-output-projection.js +81 -0
  78. package/dist/core/runtime.d.ts +1 -1
  79. package/dist/core/scheduler.d.ts +1 -1
  80. package/dist/core/sensitive-path-policy.d.ts +1 -1
  81. package/dist/core/session-reconcile.d.ts +1 -1
  82. package/dist/core/session-reconcile.js +5 -2
  83. package/dist/core/session-store.d.ts +2 -1
  84. package/dist/core/session-store.js +2 -1
  85. package/dist/core/session.d.ts +0 -1
  86. package/dist/core/task-notification.d.ts +1 -0
  87. package/dist/core/task-registry-agent.d.ts +115 -0
  88. package/dist/core/task-registry-agent.js +1163 -0
  89. package/dist/core/task-registry-monitor.d.ts +13 -0
  90. package/dist/core/task-registry-monitor.js +347 -0
  91. package/dist/core/task-registry-shared.d.ts +294 -0
  92. package/dist/core/task-registry-shared.js +271 -0
  93. package/dist/core/task-registry-workflow.d.ts +5 -0
  94. package/dist/core/task-registry-workflow.js +143 -0
  95. package/dist/core/task-registry.d.ts +54 -213
  96. package/dist/core/task-registry.js +190 -1766
  97. package/dist/core/task-tool-shape.d.ts +32 -0
  98. package/dist/core/task-tool-shape.js +83 -0
  99. package/dist/core/tool-policy.d.ts +1 -2
  100. package/dist/core/tool-result-budget.d.ts +1 -1
  101. package/dist/core/tool-result-store.d.ts +2 -2
  102. package/dist/core/tool-result-store.js +3 -3
  103. package/dist/core/tools.d.ts +7 -3
  104. package/dist/core/tools.js +3 -8
  105. package/dist/core/trace.d.ts +2 -2
  106. package/dist/core/types.d.ts +1 -29
  107. package/dist/core/workflow-journal-store.d.ts +2 -0
  108. package/dist/core/workflow-journal-store.js +14 -0
  109. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  110. package/dist/core/workflow-run-store-contract.js +19 -19
  111. package/dist/engine/execution-env/node-execution-env.d.ts +14 -0
  112. package/dist/engine/execution-env/node-execution-env.js +184 -44
  113. package/dist/engine/lsp/frame-decoder.d.ts +1 -1
  114. package/dist/engine/lsp/frame-decoder.js +1 -1
  115. package/dist/engine/lsp/node-lsp-manager.d.ts +4 -2
  116. package/dist/engine/lsp/node-lsp-manager.js +22 -5
  117. package/dist/engine/lsp/stdio-lsp-transport.d.ts +1 -1
  118. package/dist/engine/lsp/stdio-lsp-transport.js +19 -8
  119. package/dist/index.d.ts +18 -16
  120. package/dist/index.js +15 -13
  121. package/dist/internal/harness-types.d.ts +7 -0
  122. package/dist/internal/harness-types.js +1 -0
  123. package/dist/internal/harness.d.ts +1 -7
  124. package/dist/internal/harness.js +1 -0
  125. package/dist/orchestration/goal.js +2 -2
  126. package/dist/orchestration/run-workflow-tool.d.ts +2 -1
  127. package/dist/orchestration/run-workflow-tool.js +29 -6
  128. package/dist/orchestration/workflow-types.d.ts +193 -0
  129. package/dist/orchestration/workflow-types.js +54 -0
  130. package/dist/orchestration/workflow.d.ts +3 -184
  131. package/dist/orchestration/workflow.js +87 -64
  132. package/dist/prompt-assembly/assemble.d.ts +3 -3
  133. package/dist/prompt-assembly/assemble.js +0 -16
  134. package/dist/prompt-assembly/explain.d.ts +1 -1
  135. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  136. package/dist/prompts/default.d.ts +1 -6
  137. package/dist/prompts/default.js +3 -11
  138. package/dist/scenarios/env.d.ts +1 -1
  139. package/dist/scenarios/full-body.d.ts +0 -3
  140. package/dist/scenarios/full-body.js +0 -2
  141. package/dist/stores/cc/mailbox-store.js +6 -1
  142. package/dist/stores/file/background-agent-store.d.ts +2 -15
  143. package/dist/stores/file/background-agent-store.js +33 -102
  144. package/dist/stores/file/checkpoint-store.d.ts +1 -14
  145. package/dist/stores/file/checkpoint-store.js +47 -119
  146. package/dist/stores/file/file-snapshot-store.d.ts +1 -1
  147. package/dist/stores/file/mailbox-store.d.ts +1 -1
  148. package/dist/stores/file/mailbox-store.js +9 -7
  149. package/dist/stores/file/shared-ledger.d.ts +35 -0
  150. package/dist/stores/file/shared-ledger.js +94 -0
  151. package/dist/stores/file/workflow-run-store.d.ts +1 -14
  152. package/dist/stores/file/workflow-run-store.js +24 -93
  153. package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
  154. package/dist/tools/fs/bash-readonly-classifier.js +166 -0
  155. package/dist/tools/fs/encoding.d.ts +5 -0
  156. package/dist/tools/fs/encoding.js +6 -0
  157. package/dist/tools/fs/fs-bash.d.ts +31 -0
  158. package/dist/tools/fs/fs-bash.js +672 -0
  159. package/dist/tools/fs/fs-pdf.d.ts +22 -0
  160. package/dist/tools/fs/fs-pdf.js +236 -0
  161. package/dist/tools/fs/fs-read.d.ts +8 -0
  162. package/dist/tools/fs/fs-read.js +286 -0
  163. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  164. package/dist/tools/fs/fs-search-tools.js +173 -0
  165. package/dist/tools/fs/fs-shared.d.ts +55 -0
  166. package/dist/tools/fs/fs-shared.js +144 -0
  167. package/dist/tools/fs/fs-write.d.ts +8 -0
  168. package/dist/tools/fs/fs-write.js +423 -0
  169. package/dist/tools/fs/index.d.ts +12 -65
  170. package/dist/tools/fs/index.js +26 -2026
  171. package/dist/tools/fs/notebook.d.ts +43 -0
  172. package/dist/tools/fs/notebook.js +141 -0
  173. package/dist/tools/fs/pdf.d.ts +1 -1
  174. package/dist/tools/fs/repo-map.d.ts +3 -3
  175. package/dist/tools/fs/repo-map.js +3 -3
  176. package/dist/tools/fs/safety.d.ts +1 -1
  177. package/dist/tools/fs/search.d.ts +1 -1
  178. package/dist/tools/fs/search.js +141 -12
  179. package/dist/tools/gitea-issue.js +4 -2
  180. package/dist/tools/monitor.d.ts +1 -1
  181. package/dist/tools/monitor.js +12 -8
  182. package/dist/tools/scheduler-tools.d.ts +1 -1
  183. package/dist/tools/scheduler-tools.js +16 -16
  184. package/dist/tools/task-list.js +34 -12
  185. package/dist/tools/web.d.ts +4 -2
  186. package/dist/tools/web.js +106 -20
  187. package/dist/tools/worktree.d.ts +1 -1
  188. package/dist/tools/worktree.js +14 -14
  189. package/package.json +1 -1
  190. package/dist/core/dynamic-recall.d.ts +0 -32
  191. package/dist/core/dynamic-recall.js +0 -76
@@ -52,7 +52,6 @@ export interface TeacherRunResult extends TaskResult {
52
52
  teacherStats: {
53
53
  tokens: number;
54
54
  tasks: number;
55
- costUsd?: number;
56
55
  costMicroUsd?: number;
57
56
  humanReview?: {
58
57
  count: number;
@@ -82,7 +82,6 @@ async function runTeacherCore(runner, studentSpec, teacher) {
82
82
  const escalations = [];
83
83
  let studentTokens = 0;
84
84
  let studentTurns = 0;
85
- let studentCost = 0;
86
85
  let studentCostMicro = 0;
87
86
  let studentPrompt = 0;
88
87
  let studentCached = 0;
@@ -98,10 +97,8 @@ async function runTeacherCore(runner, studentSpec, teacher) {
98
97
  let studentNestedTurns = 0;
99
98
  let studentNestedTasks = 0;
100
99
  let studentNestedCostMicro = 0;
101
- let studentNestedCost = 0;
102
100
  let teacherTokens = 0;
103
101
  let teacherTasks = 0;
104
- let teacherCost = 0;
105
102
  let teacherCostMicro = 0;
106
103
  let teacherHrCount = 0;
107
104
  let teacherHrWaitMs = 0;
@@ -175,7 +172,6 @@ async function runTeacherCore(runner, studentSpec, teacher) {
175
172
  const s = r.stats;
176
173
  studentTokens += s.tokens;
177
174
  studentTurns += s.turns;
178
- studentCost += s.costUsd ?? 0;
179
175
  studentCostMicro += s.costMicroUsd ?? 0;
180
176
  studentPrompt += s.promptTokens ?? 0;
181
177
  studentCached += s.cachedTokens ?? 0;
@@ -196,13 +192,11 @@ async function runTeacherCore(runner, studentSpec, teacher) {
196
192
  studentNestedTurns += s.nested.turns;
197
193
  studentNestedTasks += s.nested.tasks;
198
194
  studentNestedCostMicro += s.nested.costMicroUsd ?? 0;
199
- studentNestedCost += s.nested.costUsd ?? 0;
200
195
  }
201
196
  };
202
197
  const addTeacher = (r) => {
203
198
  teacherTokens += r.stats.tokens + (r.stats.nested?.tokens ?? 0);
204
199
  teacherTasks += 1;
205
- teacherCost += (r.stats.costUsd ?? 0) + (r.stats.nested?.costUsd ?? 0);
206
200
  teacherCostMicro += (r.stats.costMicroUsd ?? 0) + (r.stats.nested?.costMicroUsd ?? 0);
207
201
  if (r.stats.humanReview) {
208
202
  teacherHrCount += r.stats.humanReview.count;
@@ -422,7 +416,6 @@ async function runTeacherCore(runner, studentSpec, teacher) {
422
416
  ...result.stats,
423
417
  tokens: studentTokens,
424
418
  turns: studentTurns,
425
- costUsd: studentCost || undefined,
426
419
  costMicroUsd: studentCostMicro || undefined,
427
420
  promptTokens: studentPrompt || undefined,
428
421
  totalInputTokens: studentPrompt || undefined,
@@ -437,7 +430,6 @@ async function runTeacherCore(runner, studentSpec, teacher) {
437
430
  turns: studentNestedTurns,
438
431
  tasks: studentNestedTasks,
439
432
  costMicroUsd: studentNestedCostMicro || undefined,
440
- costUsd: studentNestedCost || undefined,
441
433
  }
442
434
  : undefined,
443
435
  costBreakdown: {
@@ -452,7 +444,6 @@ async function runTeacherCore(runner, studentSpec, teacher) {
452
444
  teacherStats: {
453
445
  tokens: teacherTokens,
454
446
  tasks: teacherTasks,
455
- costUsd: teacherCost || undefined,
456
447
  costMicroUsd: teacherCostMicro || undefined,
457
448
  humanReview: teacherHrCount > 0 ? { count: teacherHrCount, totalWaitMs: teacherHrWaitMs, gates: teacherHrGates } : undefined,
458
449
  },
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync, realpathSync, createWriteStream } from "node:fs";
3
3
  import { pathToFileURL } from "node:url";
4
- import { Runner, NodeExecutionEnv, createOpenAIBrain, createAnthropicBrain, CODE_SYSTEM_PROMPT, assembleCodeTools, makeWebFetchSummarizer, } from "../index.js";
4
+ import { Runner, NodeExecutionEnv, createOpenAIBrain, createAnthropicBrain, CODE_SYSTEM_PROMPT, assembleCodeTools, createWebFetchSummarizer, } from "../index.js";
5
5
  import { positiveIntFromEnv, tbModelInput } from "./tb-env.js";
6
6
  import { inferMaxTokensField } from "../brain/openai.js";
7
7
  function usage(code = 2) {
@@ -353,7 +353,7 @@ async function main() {
353
353
  ? {
354
354
  systemPrompt: CODE_SYSTEM_PROMPT,
355
355
  tools: assembleCodeTools({
356
- ...(process.env.TB_SUMMARIZE === "0" ? {} : { webFetch: { summarize: makeWebFetchSummarizer(brain, model) } }),
356
+ ...(process.env.TB_SUMMARIZE === "0" ? {} : { webFetch: { summarize: createWebFetchSummarizer(brain, model) } }),
357
357
  ...(process.env.TB_TASKLIST === "0" || process.env.TB_TASKLIST === "false" ? { taskList: false } : {}),
358
358
  }),
359
359
  }
@@ -426,7 +426,7 @@ async function main() {
426
426
  const r = await stream.result();
427
427
  const mech = r.stats.mechanisms;
428
428
  line(`[done] status=${r.status} turns=${r.stats.turns} toolCalls=${r.stats.toolCalls ?? 0} ` +
429
- `tokens=${r.stats.tokens} costUsd=${((r.stats.costMicroUsd ?? 0) / 1e6).toFixed(6)} costMicroUsd=${r.stats.costMicroUsd ?? 0}` +
429
+ `tokens=${r.stats.tokens} costMicroUsd=${r.stats.costMicroUsd ?? 0}` +
430
430
  ` model=${lastServedModel ?? model.id} degraded=${degradedTo ?? "no"}` +
431
431
  (r.stats.cacheHitRate !== undefined ? ` cacheHitRate=${r.stats.cacheHitRate.toFixed(3)}` : "") +
432
432
  (mech ? ` mech=${mech.finalizeInjected ? "finalize" : ""}${mech.nudgesSent ? `+nudges:${mech.nudgesSent}` : ""}${mech.capShrinks ? `+capShrinks:${mech.capShrinks}` : ""}${mech.callCutoffs ? `+callCutoffs:${mech.callCutoffs}` : ""}${mech.toolClamps ? `+toolClamps:${mech.toolClamps}` : ""}` : "") +
@@ -1,6 +1,7 @@
1
1
  import {} from "../internal/llm.js";
2
2
  import { BrainError } from "./errors.js";
3
- import { DEGENERATE_MESSAGE, inspectDegenerate, trimDegenerateTail } from "./repetition.js";
3
+ import { DEGENERATE_MESSAGE, trimDegenerateTail } from "./repetition.js";
4
+ import { createRepetitionPoll, createWalltimeGate } from "./stream-shared.js";
4
5
  import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
5
6
  import { emitBrainTelemetry } from "./status-sink.js";
6
7
  import { ANTHROPIC_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
@@ -340,24 +341,8 @@ export function createAnthropicBrain(config = {}) {
340
341
  let nextContentIndex = 0;
341
342
  let degenerate = false;
342
343
  let degenTextBlock;
343
- let repCut;
344
- const repSpared = new Map();
345
- const pollRepetition = (text) => {
346
- const insp = inspectDegenerate(text);
347
- for (const s of insp.spared) {
348
- const k = `${s.rule} ${s.segment}`;
349
- const prev = repSpared.get(k);
350
- if (prev === undefined)
351
- repSpared.set(k, s);
352
- else if (s.reps > prev.reps)
353
- prev.reps = s.reps;
354
- }
355
- if (insp.degenerate)
356
- repCut = insp.cut;
357
- return insp.degenerate;
358
- };
359
- let walltimeCut = false;
360
- const callDeadlineMs = options?.callDeadlineMs;
344
+ const rep = createRepetitionPoll();
345
+ const walltime = createWalltimeGate({ callDeadlineMs: options?.callDeadlineMs, isDegenerate: () => degenerate, cancel: () => ctrl.cancel() });
361
346
  let lastDegenCheck = 0;
362
347
  let lastDegenCheckThinking = 0;
363
348
  const detectRep = config.detectRepetition !== false;
@@ -416,7 +401,7 @@ export function createAnthropicBrain(config = {}) {
416
401
  out.push({ type: "text_delta", contentIndex: idx, delta: d.text, partial: { ...partial } });
417
402
  if (detectRep && !degenerate && acc.text.length - lastDegenCheck >= 64) {
418
403
  lastDegenCheck = acc.text.length;
419
- if (pollRepetition(acc.text)) {
404
+ if (rep.poll(acc.text)) {
420
405
  degenerate = true;
421
406
  degenTextBlock = acc;
422
407
  ctrl.cancel();
@@ -430,7 +415,7 @@ export function createAnthropicBrain(config = {}) {
430
415
  out.push({ type: "thinking_delta", contentIndex: idx, delta: d.thinking, partial: { ...partial } });
431
416
  if (detectRep && !degenerate && acc.text.length - lastDegenCheckThinking >= 64) {
432
417
  lastDegenCheckThinking = acc.text.length;
433
- if (pollRepetition(acc.text)) {
418
+ if (rep.poll(acc.text)) {
434
419
  degenerate = true;
435
420
  ctrl.cancel();
436
421
  }
@@ -512,19 +497,9 @@ export function createAnthropicBrain(config = {}) {
512
497
  }
513
498
  }
514
499
  },
515
- onDeadline() {
516
- if (degenerate)
517
- return false;
518
- walltimeCut = true;
519
- return true;
520
- },
500
+ onDeadline: () => walltime.onDeadline(),
521
501
  onLine(rawLine) {
522
- if (callDeadlineMs !== undefined && !walltimeCut && !degenerate && Date.now() >= callDeadlineMs) {
523
- walltimeCut = true;
524
- ctrl.cancel();
525
- return;
526
- }
527
- if (walltimeCut)
502
+ if (walltime.shouldDropLine())
528
503
  return;
529
504
  const line = rawLine.trim();
530
505
  if (!line.startsWith("data:"))
@@ -559,7 +534,7 @@ export function createAnthropicBrain(config = {}) {
559
534
  finalContent.push(block);
560
535
  }
561
536
  else if (acc.type === "text") {
562
- const text = acc === degenTextBlock && repCut !== undefined ? trimDegenerateTail(acc.text, repCut) : acc.text;
537
+ const text = acc === degenTextBlock && rep.cut !== undefined ? trimDegenerateTail(acc.text, rep.cut) : acc.text;
563
538
  if (text) {
564
539
  finalContent.push({ type: "text", text });
565
540
  if (text.trim())
@@ -593,9 +568,9 @@ export function createAnthropicBrain(config = {}) {
593
568
  const staticReasoningCut = truncatedEmpty && !dynamicCut && reasoningSeen && options?.staticReasoningCutDowngrade === true;
594
569
  const safetyCut = stopReason === "refusal";
595
570
  const contextWindowExceeded = stopReason === "model_context_window_exceeded";
596
- const emptyNoFinish = noUsableContent && stopReason == null && !degenerate && !walltimeCut;
571
+ const emptyNoFinish = noUsableContent && stopReason == null && !degenerate && !walltime.cut;
597
572
  const maxTokensNote = `effective max_tokens=${sentMaxTokens ?? "?"} (from ${sentMaxTokensLane === "options" ? "options.maxTokens" : sentMaxTokensLane === "model" ? "model.maxTokens" : "config default"})`;
598
- const errorMessage = walltimeCut
573
+ const errorMessage = walltime.cut
599
574
  ? WALLTIME_CUTOFF_MESSAGE
600
575
  : degenerate
601
576
  ? DEGENERATE_MESSAGE
@@ -613,14 +588,14 @@ export function createAnthropicBrain(config = {}) {
613
588
  : emptyNoFinish
614
589
  ? `[stream_torn] model stream ended with no content and no stop_reason — the response was lost (a torn stream or an in-band provider error frame)`
615
590
  : undefined));
616
- const errored = walltimeCut ||
591
+ const errored = walltime.cut ||
617
592
  degenerate ||
618
593
  safetyCut ||
619
594
  contextWindowExceeded ||
620
595
  emptyNoFinish ||
621
596
  (toolError !== undefined && noUsableContent) ||
622
597
  (truncatedEmpty && !dynamicCut && !staticReasoningCut);
623
- const errorKind = !walltimeCut && !degenerate && !safetyCut && toolError === undefined && truncatedEmpty && !dynamicCut && !staticReasoningCut
598
+ const errorKind = !walltime.cut && !degenerate && !safetyCut && toolError === undefined && truncatedEmpty && !dynamicCut && !staticReasoningCut
624
599
  ? "length_empty"
625
600
  : undefined;
626
601
  const finalMessage = {
@@ -632,8 +607,8 @@ export function createAnthropicBrain(config = {}) {
632
607
  timestamp: Date.now(),
633
608
  ...(errorMessage ? { errorMessage } : {}),
634
609
  ...(errorKind ? { errorKind } : {}),
635
- ...(repCut !== undefined || repSpared.size > 0
636
- ? { repetition: { ...(repCut !== undefined ? { cut: repCut } : {}), ...(repSpared.size > 0 ? { spared: [...repSpared.values()] } : {}) } }
610
+ ...(rep.cut !== undefined || rep.spared.length > 0
611
+ ? { repetition: { ...(rep.cut !== undefined ? { cut: rep.cut } : {}), ...(rep.spared.length > 0 ? { spared: rep.spared } : {}) } }
637
612
  : {}),
638
613
  ...(staticReasoningCut ? { staticReasoningCut: true } : {}),
639
614
  ...(stopDetails
@@ -1,6 +1,7 @@
1
1
  import {} from "../internal/llm.js";
2
2
  import { repairTextToolCalls } from "./tool-call-repair.js";
3
- import { DEGENERATE_MESSAGE, inspectDegenerate, trimDegenerateTail } from "./repetition.js";
3
+ import { DEGENERATE_MESSAGE, trimDegenerateTail } from "./repetition.js";
4
+ import { createRepetitionPoll, createWalltimeGate } from "./stream-shared.js";
4
5
  import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
5
6
  import { emitBrainTelemetry } from "./status-sink.js";
6
7
  import { OPENAI_RESERVED, applyExtraBody, stripAuthHeaders } from "./request-params.js";
@@ -343,24 +344,8 @@ export function createOpenAIBrain(config = {}) {
343
344
  let accumRefusal = "";
344
345
  let degenerate = false;
345
346
  let degenFace;
346
- let repCut;
347
- const repSpared = new Map();
348
- const pollRepetition = (text) => {
349
- const insp = inspectDegenerate(text);
350
- for (const s of insp.spared) {
351
- const k = `${s.rule} ${s.segment}`;
352
- const prev = repSpared.get(k);
353
- if (prev === undefined)
354
- repSpared.set(k, s);
355
- else if (s.reps > prev.reps)
356
- prev.reps = s.reps;
357
- }
358
- if (insp.degenerate)
359
- repCut = insp.cut;
360
- return insp.degenerate;
361
- };
362
- let walltimeCut = false;
363
- const callDeadlineMs = options?.callDeadlineMs;
347
+ const rep = createRepetitionPoll();
348
+ const walltime = createWalltimeGate({ callDeadlineMs: options?.callDeadlineMs, isDegenerate: () => degenerate, cancel: () => ctrl.cancel() });
364
349
  let lastDegenCheck = 0;
365
350
  let lastDegenCheckReasoning = 0;
366
351
  const detectRep = config.detectRepetition !== false;
@@ -389,7 +374,7 @@ export function createOpenAIBrain(config = {}) {
389
374
  out.push({ type: "thinking_delta", contentIndex: 0, delta, partial: { ...partial } });
390
375
  if (detectRep && !degenerate && accumReasoning.length - lastDegenCheckReasoning >= 64) {
391
376
  lastDegenCheckReasoning = accumReasoning.length;
392
- if (pollRepetition(accumReasoning)) {
377
+ if (rep.poll(accumReasoning)) {
393
378
  degenerate = true;
394
379
  degenFace = "reasoning";
395
380
  ctrl.cancel();
@@ -418,7 +403,7 @@ export function createOpenAIBrain(config = {}) {
418
403
  out.push({ type: "text_delta", contentIndex: textIndex, delta, partial: { ...partial } });
419
404
  if (detectRep && !degenerate && accumText.length - lastDegenCheck >= 64) {
420
405
  lastDegenCheck = accumText.length;
421
- if (pollRepetition(accumText)) {
406
+ if (rep.poll(accumText)) {
422
407
  degenerate = true;
423
408
  degenFace = "text";
424
409
  ctrl.cancel();
@@ -438,19 +423,9 @@ export function createOpenAIBrain(config = {}) {
438
423
  sealForRetry() {
439
424
  closeThinking();
440
425
  },
441
- onDeadline() {
442
- if (degenerate)
443
- return false;
444
- walltimeCut = true;
445
- return true;
446
- },
426
+ onDeadline: () => walltime.onDeadline(),
447
427
  onLine(rawLine) {
448
- if (callDeadlineMs !== undefined && !walltimeCut && !degenerate && Date.now() >= callDeadlineMs) {
449
- walltimeCut = true;
450
- ctrl.cancel();
451
- return;
452
- }
453
- if (walltimeCut)
428
+ if (walltime.shouldDropLine())
454
429
  return;
455
430
  const line = rawLine.trim();
456
431
  if (!line.startsWith("data:"))
@@ -518,7 +493,7 @@ export function createOpenAIBrain(config = {}) {
518
493
  const finalContent = [];
519
494
  if (accumReasoning)
520
495
  finalContent.push({ type: "thinking", thinking: accumReasoning });
521
- const textFace = degenerate && degenFace === "text" && repCut !== undefined ? trimDegenerateTail(accumText, repCut) : accumText;
496
+ const textFace = degenerate && degenFace === "text" && rep.cut !== undefined ? trimDegenerateTail(accumText, rep.cut) : accumText;
522
497
  if (textFace)
523
498
  finalContent.push({ type: "text", text: textFace });
524
499
  const toolCalls = [];
@@ -564,11 +539,11 @@ export function createOpenAIBrain(config = {}) {
564
539
  const dynamicCut = truncatedEmpty && options?.maxTokensDynamic === true;
565
540
  const safetyCut = finishReason === "content_filter";
566
541
  const refused = accumRefusal.trim().length > 0;
567
- const emptyNoFinish = noUsableContent && finishReason == null && !degenerate && !walltimeCut;
542
+ const emptyNoFinish = noUsableContent && finishReason == null && !degenerate && !walltime.cut;
568
543
  const maxTokensNote = sentMaxTokens !== undefined
569
544
  ? `effective max_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options" ? "options.maxTokens" : "model.maxTokens"})`
570
545
  : "max_tokens not set (neither options.maxTokens nor model.maxTokens — the provider's default cap applied)";
571
- const errorMessage = walltimeCut
546
+ const errorMessage = walltime.cut
572
547
  ? WALLTIME_CUTOFF_MESSAGE
573
548
  : degenerate
574
549
  ? DEGENERATE_MESSAGE
@@ -584,14 +559,14 @@ export function createOpenAIBrain(config = {}) {
584
559
  : emptyNoFinish
585
560
  ? `[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)`
586
561
  : undefined));
587
- const errored = walltimeCut ||
562
+ const errored = walltime.cut ||
588
563
  degenerate ||
589
564
  safetyCut ||
590
565
  refused ||
591
566
  emptyNoFinish ||
592
567
  (toolError !== undefined && noUsableContent) ||
593
568
  (truncatedEmpty && !dynamicCut);
594
- const errorKind = !walltimeCut && !degenerate && !safetyCut && !refused && toolError === undefined && truncatedEmpty && !dynamicCut
569
+ const errorKind = !walltime.cut && !degenerate && !safetyCut && !refused && toolError === undefined && truncatedEmpty && !dynamicCut
595
570
  ? "length_empty"
596
571
  : undefined;
597
572
  const finalMessage = {
@@ -603,8 +578,8 @@ export function createOpenAIBrain(config = {}) {
603
578
  timestamp: Date.now(),
604
579
  ...(errorMessage ? { errorMessage } : {}),
605
580
  ...(errorKind ? { errorKind } : {}),
606
- ...(repCut !== undefined || repSpared.size > 0
607
- ? { repetition: { ...(repCut !== undefined ? { cut: repCut } : {}), ...(repSpared.size > 0 ? { spared: [...repSpared.values()] } : {}) } }
581
+ ...(rep.cut !== undefined || rep.spared.length > 0
582
+ ? { repetition: { ...(rep.cut !== undefined ? { cut: rep.cut } : {}), ...(rep.spared.length > 0 ? { spared: rep.spared } : {}) } }
608
583
  : {}),
609
584
  };
610
585
  if (errored) {
@@ -1,4 +1,4 @@
1
- import type { ThinkingLevel } from "../internal/harness.js";
1
+ import type { ThinkingLevel } from "../internal/harness-types.js";
2
2
  export type ReasoningIntensity = ThinkingLevel;
3
3
  export declare function isThinkingLevel(v: unknown): v is ThinkingLevel;
4
4
  export declare function rankOf(level: ThinkingLevel): number;
@@ -2,7 +2,7 @@ import { createAssistantMessageEventStream, } from "../internal/llm.js";
2
2
  import { BrainError, classifyHttp } from "./errors.js";
3
3
  import { retryBackoffMs } from "./retry.js";
4
4
  import { emitBrainStatus, emitBrainTelemetry } from "./status-sink.js";
5
- import { makeConnectController } from "./timeout.js";
5
+ import { createConnectController } from "./timeout.js";
6
6
  import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
7
7
  const EMPTY_USAGE = {
8
8
  input: 0,
@@ -80,7 +80,7 @@ export function runStreamingBrain(args) {
80
80
  if (callDeadlineMs !== undefined && Date.now() >= callDeadlineMs) {
81
81
  throw new Error(WALLTIME_CUTOFF_MESSAGE);
82
82
  }
83
- const cc = makeConnectController(connectTimeoutMs, signal, callDeadlineMs);
83
+ const cc = createConnectController(connectTimeoutMs, signal, callDeadlineMs);
84
84
  let r;
85
85
  let netErr;
86
86
  try {
@@ -0,0 +1,17 @@
1
+ import { type RepetitionEvent } from "./repetition.js";
2
+ export interface RepetitionPoll {
3
+ poll(text: string): boolean;
4
+ readonly cut: RepetitionEvent | undefined;
5
+ readonly spared: RepetitionEvent[];
6
+ }
7
+ export declare function createRepetitionPoll(): RepetitionPoll;
8
+ export interface WalltimeGate {
9
+ onDeadline(): boolean;
10
+ shouldDropLine(): boolean;
11
+ readonly cut: boolean;
12
+ }
13
+ export declare function createWalltimeGate(deps: {
14
+ callDeadlineMs: number | undefined;
15
+ isDegenerate: () => boolean;
16
+ cancel: () => void;
17
+ }): WalltimeGate;
@@ -0,0 +1,50 @@
1
+ import { inspectDegenerate } from "./repetition.js";
2
+ export function createRepetitionPoll() {
3
+ let repCut;
4
+ const repSpared = new Map();
5
+ return {
6
+ get cut() {
7
+ return repCut;
8
+ },
9
+ get spared() {
10
+ return [...repSpared.values()];
11
+ },
12
+ poll(text) {
13
+ const insp = inspectDegenerate(text);
14
+ for (const s of insp.spared) {
15
+ const k = `${s.rule} ${s.segment}`;
16
+ const prev = repSpared.get(k);
17
+ if (prev === undefined)
18
+ repSpared.set(k, s);
19
+ else if (s.reps > prev.reps)
20
+ prev.reps = s.reps;
21
+ }
22
+ if (insp.degenerate)
23
+ repCut = insp.cut;
24
+ return insp.degenerate;
25
+ },
26
+ };
27
+ }
28
+ export function createWalltimeGate(deps) {
29
+ const { callDeadlineMs, isDegenerate, cancel } = deps;
30
+ let walltimeCut = false;
31
+ return {
32
+ get cut() {
33
+ return walltimeCut;
34
+ },
35
+ onDeadline() {
36
+ if (isDegenerate())
37
+ return false;
38
+ walltimeCut = true;
39
+ return true;
40
+ },
41
+ shouldDropLine() {
42
+ if (callDeadlineMs !== undefined && !walltimeCut && !isDegenerate() && Date.now() >= callDeadlineMs) {
43
+ walltimeCut = true;
44
+ cancel();
45
+ return true;
46
+ }
47
+ return walltimeCut;
48
+ },
49
+ };
50
+ }
@@ -3,7 +3,7 @@ export interface BrainTimeoutConfig {
3
3
  firstTokenTimeoutMs?: number;
4
4
  idleTimeoutMs?: number;
5
5
  }
6
- export declare function makeConnectController(connectTimeoutMs: number | undefined, outerSignal: AbortSignal | undefined, deadlineAtMs?: number): {
6
+ export declare function createConnectController(connectTimeoutMs: number | undefined, outerSignal: AbortSignal | undefined, deadlineAtMs?: number): {
7
7
  signal: AbortSignal;
8
8
  timedOut: () => boolean;
9
9
  deadlineHit: () => boolean;
@@ -1,4 +1,4 @@
1
- export function makeConnectController(connectTimeoutMs, outerSignal, deadlineAtMs) {
1
+ export function createConnectController(connectTimeoutMs, outerSignal, deadlineAtMs) {
2
2
  const ac = new AbortController();
3
3
  let timedOut = false;
4
4
  let deadlineHit = false;
@@ -1,6 +1,4 @@
1
- import { DEFAULT_MAX_TURNS } from "../core/runner/runtask.js";
2
- import { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../agents/subagent.js";
3
- import { SESSION_DEFAULT_TTL_DAYS } from "../core/session-store.js";
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";
4
2
  export const CONFIG_CATALOG_VERSION = "sema-config@1";
5
3
  export function describeConfigCatalog() {
6
4
  const knobs = [
@@ -0,0 +1,6 @@
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
+ export declare const RETAIN_DEFAULT_TTL_MS: number;
5
+ export declare const RETAIN_DEFAULT_MAX = 16;
6
+ export declare const SESSION_DEFAULT_TTL_DAYS = 7;
@@ -0,0 +1,6 @@
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
+ export const RETAIN_DEFAULT_TTL_MS = 30 * 60 * 1000;
5
+ export const RETAIN_DEFAULT_MAX = 16;
6
+ export const SESSION_DEFAULT_TTL_DAYS = 7;
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "../internal/harness.js";
1
+ import type { AgentTool } from "../internal/harness-types.js";
2
2
  import type { ToolPolicy } from "./tool-policy.js";
3
3
  export declare const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
4
4
  export interface AskQuestionOption {
@@ -1,5 +1,5 @@
1
1
  import { Type } from "typebox";
2
- import { defineTool } from "./tools.js";
2
+ import { defineTool, errorResult } from "./tools.js";
3
3
  import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
4
4
  export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
5
5
  const NO_HUMAN = "No human is available to answer right now. Proceed with your best judgment: pick the most reasonable " +
@@ -66,23 +66,23 @@ export function createAskUserQuestionTool(onQuestion, source) {
66
66
  execute: async (args, ctx) => {
67
67
  const { questions } = args;
68
68
  if (!Array.isArray(questions) || questions.length < 1 || questions.length > 4) {
69
- return "Error (AskUserQuestion): provide between 1 and 4 questions.";
69
+ return errorResult("Error (AskUserQuestion): provide between 1 and 4 questions.");
70
70
  }
71
71
  const seenHeaders = new Set();
72
72
  for (const q of questions) {
73
73
  if (!Array.isArray(q.options) || q.options.length < 2 || q.options.length > 4) {
74
- return `Error (AskUserQuestion): the question "${q.header || q.question}" must have between 2 and 4 options.`;
74
+ return errorResult(`Error (AskUserQuestion): the question "${q.header || q.question}" must have between 2 and 4 options.`);
75
75
  }
76
76
  const key = typeof q.header === "string" ? q.header.trim() : "";
77
77
  if (key === "" || seenHeaders.has(key)) {
78
- return `Error (AskUserQuestion): each question needs a distinct, non-empty header (got a duplicate or empty one).`;
78
+ return errorResult(`Error (AskUserQuestion): each question needs a distinct, non-empty header (got a duplicate or empty one).`);
79
79
  }
80
80
  seenHeaders.add(key);
81
81
  const seenLabels = new Set();
82
82
  for (const o of q.options) {
83
83
  const labelKey = typeof o?.label === "string" ? o.label.trim() : "";
84
84
  if (seenLabels.has(labelKey)) {
85
- return `Error (AskUserQuestion): the question "${q.header || q.question}" has duplicate option labels — option labels must be unique within each question.`;
85
+ return errorResult(`Error (AskUserQuestion): the question "${q.header || q.question}" has duplicate option labels — option labels must be unique within each question.`);
86
86
  }
87
87
  seenLabels.add(labelKey);
88
88
  }
@@ -28,6 +28,7 @@ export interface BackgroundAgentRecord {
28
28
  settledAt?: number;
29
29
  status: "running" | "parked" | "completed" | "failed" | "killed";
30
30
  stoppedBy?: string;
31
+ completionId?: string;
31
32
  seq?: number;
32
33
  parkedCheckpointToken?: string;
33
34
  parkedAt?: number;
@@ -66,6 +67,7 @@ export declare function canAccessAgentRecord(record: Pick<BackgroundAgentRecord,
66
67
  scope?: string;
67
68
  sessionId?: string;
68
69
  }): boolean;
70
+ export declare const STALE_RUNNING_REAP_ATTRIBUTION = "the host process was interrupted while this agent was running \u2014 its in-process state was lost. Check its worktree / output file for partial work before assuming the task landed (stale running row reaped).";
69
71
  export interface BackgroundAgentStore {
70
72
  put(record: BackgroundAgentRecord): Promise<void>;
71
73
  get(handle: string, scope: string): Promise<BackgroundAgentRecord | null>;
@@ -1,3 +1,4 @@
1
+ import { uuidv7 } from "../internal/harness.js";
1
2
  export class BackgroundAgentStoreError extends Error {
2
3
  code;
3
4
  constructor(code, message) {
@@ -20,6 +21,7 @@ export function canAccessAgentRecord(record, access) {
20
21
  }
21
22
  return false;
22
23
  }
24
+ export const STALE_RUNNING_REAP_ATTRIBUTION = "the host process was interrupted while this agent was running — its in-process state was lost. Check its worktree / output file for partial work before assuming the task landed (stale running row reaped).";
23
25
  export async function reconcileParkedAgents(stores, scope, now, opts) {
24
26
  const out = { failed: 0, rolledBack: 0 };
25
27
  const excluded = (row) => (opts?.excludeHandles?.has(row.handle) ?? false) ||
@@ -31,6 +33,7 @@ export async function reconcileParkedAgents(stores, scope, now, opts) {
31
33
  next.stoppedBy = "system";
32
34
  next.settledAt = now;
33
35
  next.updatedAt = now;
36
+ next.completionId ??= uuidv7();
34
37
  delete next.parkedCheckpointToken;
35
38
  delete next.parkClaimId;
36
39
  delete next.parkedAt;
@@ -248,7 +251,8 @@ export class InMemoryBackgroundAgentStore {
248
251
  const flipped = structuredClone(r);
249
252
  flipped.status = "failed";
250
253
  flipped.stoppedBy = "system";
251
- flipped.summary = flipped.summary ?? "host process interrupted (stale running row reaped)";
254
+ flipped.summary = flipped.summary ?? STALE_RUNNING_REAP_ATTRIBUTION;
255
+ flipped.error = flipped.error ?? STALE_RUNNING_REAP_ATTRIBUTION;
252
256
  flipped.settledAt = now;
253
257
  flipped.updatedAt = now;
254
258
  flipped.rev = r.rev + 1;
@@ -1,4 +1,4 @@
1
- import type { ExecutionEnv, ExecutionEnvExecOptions, Result } from "../internal/harness.js";
1
+ import type { ExecutionEnv, ExecutionEnvExecOptions, Result } from "../internal/harness-types.js";
2
2
  export type BackgroundShellId = string & {
3
3
  readonly __brand: "BackgroundShellId";
4
4
  };
@@ -115,7 +115,6 @@ export interface CheckpointState {
115
115
  tokens: number;
116
116
  turns: number;
117
117
  tasks: number;
118
- costUsd: number;
119
118
  costMicroUsd: number;
120
119
  };
121
120
  consolidationNotes?: ConsolidationNote[];
@@ -1,4 +1,4 @@
1
- import type { ExecutionEnv } from "../internal/harness.js";
1
+ import type { ExecutionEnv } from "../internal/harness-types.js";
2
2
  export interface ExecStep {
3
3
  command: string;
4
4
  label?: string;
@@ -1,9 +1,11 @@
1
+ import { StringDecoder } from "node:string_decoder";
1
2
  export declare const MAX_EXEC_OUTPUT_BYTES: number;
2
3
  export declare class RollingTailBuffer {
3
4
  private readonly maxBytes;
4
5
  private readonly chunks;
5
6
  private size;
6
- private dropped;
7
+ private headDropped;
8
+ private sourceSkipped;
7
9
  constructor(maxBytes?: number);
8
10
  push(chunk: Buffer): void;
9
11
  recordSkippedBytes(n: number): void;
@@ -11,5 +13,21 @@ export declare class RollingTailBuffer {
11
13
  text: string;
12
14
  droppedBytes: number;
13
15
  };
16
+ bytes(): Buffer;
17
+ headDroppedBytes(): number;
18
+ sourceSkippedBytes(): number;
14
19
  }
20
+ export interface StreamCursorState {
21
+ tail: RollingTailBuffer;
22
+ cursorBytes: number;
23
+ acceptedBytes: number;
24
+ disclosedSkippedBytes: number;
25
+ decoder: StringDecoder;
26
+ }
27
+ export declare function newStreamCursorState(tail?: RollingTailBuffer): StreamCursorState;
28
+ export declare function sliceStreamIncrement(s: StreamCursorState, terminal: boolean): {
29
+ inc: string;
30
+ droppedBeforeCursor: number;
31
+ };
15
32
  export declare function markTruncated(text: string, droppedBytes: number): string;
33
+ export declare function markLiveSpoolRotations(text: string, liveRotations: number): string;