@sema-agent/core 1.452.0 → 2.0.1

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 (167) 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/repair-loop.d.ts +1 -1
  5. package/dist/agents/repair-loop.js +1 -1
  6. package/dist/agents/retain-ledger.d.ts +60 -0
  7. package/dist/agents/retain-ledger.js +225 -0
  8. package/dist/agents/roster-store.d.ts +1 -0
  9. package/dist/agents/roster-store.js +9 -0
  10. package/dist/agents/send-message-tool.d.ts +41 -0
  11. package/dist/agents/send-message-tool.js +469 -0
  12. package/dist/agents/subagent.d.ts +33 -108
  13. package/dist/agents/subagent.js +22 -834
  14. package/dist/agents/teacher.d.ts +0 -1
  15. package/dist/agents/teacher.js +0 -9
  16. package/dist/bin/sema-tb.js +3 -3
  17. package/dist/brain/anthropic.js +15 -40
  18. package/dist/brain/openai.js +15 -40
  19. package/dist/brain/reasoning.d.ts +1 -1
  20. package/dist/brain/stream-engine.js +2 -2
  21. package/dist/brain/stream-shared.d.ts +17 -0
  22. package/dist/brain/stream-shared.js +50 -0
  23. package/dist/brain/timeout.d.ts +1 -1
  24. package/dist/brain/timeout.js +1 -1
  25. package/dist/config/catalog.js +1 -3
  26. package/dist/config/defaults.d.ts +6 -0
  27. package/dist/config/defaults.js +6 -0
  28. package/dist/core/ask-question.d.ts +1 -1
  29. package/dist/core/background-shell.d.ts +1 -1
  30. package/dist/core/checkpoint-store.d.ts +0 -1
  31. package/dist/core/exec-gate.d.ts +1 -1
  32. package/dist/core/exec-output-tail.d.ts +1 -0
  33. package/dist/core/exec-output-tail.js +6 -0
  34. package/dist/core/file-snapshot-store.d.ts +1 -1
  35. package/dist/core/fs-write-gate-policy.d.ts +1 -1
  36. package/dist/core/git-worktree-env.d.ts +1 -1
  37. package/dist/core/hooks.d.ts +1 -1
  38. package/dist/core/image-downsample.d.ts +44 -0
  39. package/dist/core/image-downsample.js +97 -0
  40. package/dist/core/lsp.d.ts +1 -1
  41. package/dist/core/mcp.d.ts +3 -45
  42. package/dist/core/mcp.js +2 -97
  43. package/dist/core/media-byte-cap.d.ts +1 -1
  44. package/dist/core/memory-recall.d.ts +0 -12
  45. package/dist/core/memory-recall.js +0 -222
  46. package/dist/core/memory.d.ts +0 -6
  47. package/dist/core/memory.js +0 -88
  48. package/dist/core/message-utils.d.ts +1 -1
  49. package/dist/core/oracle-isolation.d.ts +1 -1
  50. package/dist/core/remote-env.d.ts +1 -1
  51. package/dist/core/roles.d.ts +1 -1
  52. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  53. package/dist/core/runner/assemble-result.d.ts +0 -2
  54. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  55. package/dist/core/runner/prepare-memory.d.ts +16 -0
  56. package/dist/core/runner/prepare-memory.js +191 -0
  57. package/dist/core/runner/prepare-task.d.ts +4 -28
  58. package/dist/core/runner/prepare-task.js +19 -207
  59. package/dist/core/runner/prompt-suggestions.d.ts +1 -1
  60. package/dist/core/runner/runtask.d.ts +2 -3
  61. package/dist/core/runner/runtask.js +337 -529
  62. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  63. package/dist/core/runner/synthetic-tools.d.ts +5 -5
  64. package/dist/core/runner/synthetic-tools.js +4 -4
  65. package/dist/core/runner/tool-disclosure.d.ts +3 -3
  66. package/dist/core/runner/tool-disclosure.js +2 -2
  67. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  68. package/dist/core/runner/tool-output-projection.js +81 -0
  69. package/dist/core/runtime.d.ts +1 -1
  70. package/dist/core/scheduler.d.ts +1 -1
  71. package/dist/core/sensitive-path-policy.d.ts +1 -1
  72. package/dist/core/session-reconcile.d.ts +1 -1
  73. package/dist/core/session-store.d.ts +2 -1
  74. package/dist/core/session-store.js +2 -1
  75. package/dist/core/session.d.ts +0 -1
  76. package/dist/core/task-registry-agent.d.ts +115 -0
  77. package/dist/core/task-registry-agent.js +1163 -0
  78. package/dist/core/task-registry-monitor.d.ts +13 -0
  79. package/dist/core/task-registry-monitor.js +347 -0
  80. package/dist/core/task-registry-shared.d.ts +294 -0
  81. package/dist/core/task-registry-shared.js +271 -0
  82. package/dist/core/task-registry-workflow.d.ts +5 -0
  83. package/dist/core/task-registry-workflow.js +143 -0
  84. package/dist/core/task-registry.d.ts +51 -216
  85. package/dist/core/task-registry.js +127 -1940
  86. package/dist/core/task-tool-shape.d.ts +32 -0
  87. package/dist/core/task-tool-shape.js +83 -0
  88. package/dist/core/tool-policy.d.ts +1 -2
  89. package/dist/core/tool-result-budget.d.ts +1 -1
  90. package/dist/core/tool-result-store.d.ts +2 -2
  91. package/dist/core/tool-result-store.js +1 -1
  92. package/dist/core/tools.d.ts +2 -3
  93. package/dist/core/tools.js +1 -8
  94. package/dist/core/trace.d.ts +2 -2
  95. package/dist/core/types.d.ts +0 -29
  96. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  97. package/dist/core/workflow-run-store-contract.js +19 -19
  98. package/dist/engine/execution-env/node-execution-env.d.ts +13 -0
  99. package/dist/engine/execution-env/node-execution-env.js +55 -25
  100. package/dist/engine/loop/agent-loop.d.ts +1 -1
  101. package/dist/engine/loop/agent-loop.js +10 -9
  102. package/dist/engine/lsp/frame-decoder.d.ts +1 -1
  103. package/dist/engine/lsp/frame-decoder.js +1 -1
  104. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  105. package/dist/engine/lsp/stdio-lsp-transport.js +2 -2
  106. package/dist/index.d.ts +16 -14
  107. package/dist/index.js +13 -11
  108. package/dist/internal/harness-types.d.ts +7 -0
  109. package/dist/internal/harness-types.js +1 -0
  110. package/dist/internal/harness.d.ts +1 -7
  111. package/dist/internal/harness.js +1 -0
  112. package/dist/orchestration/goal.js +2 -2
  113. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  114. package/dist/orchestration/workflow-types.d.ts +193 -0
  115. package/dist/orchestration/workflow-types.js +54 -0
  116. package/dist/orchestration/workflow.d.ts +3 -193
  117. package/dist/orchestration/workflow.js +7 -59
  118. package/dist/prompt-assembly/assemble.d.ts +3 -3
  119. package/dist/prompt-assembly/assemble.js +0 -16
  120. package/dist/prompt-assembly/explain.d.ts +1 -1
  121. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  122. package/dist/prompts/default.d.ts +1 -6
  123. package/dist/prompts/default.js +3 -11
  124. package/dist/scenarios/env.d.ts +1 -1
  125. package/dist/scenarios/full-body.d.ts +0 -3
  126. package/dist/scenarios/full-body.js +0 -2
  127. package/dist/stores/file/background-agent-store.d.ts +2 -15
  128. package/dist/stores/file/background-agent-store.js +30 -100
  129. package/dist/stores/file/checkpoint-store.d.ts +1 -14
  130. package/dist/stores/file/checkpoint-store.js +47 -119
  131. package/dist/stores/file/file-snapshot-store.d.ts +1 -1
  132. package/dist/stores/file/mailbox-store.js +33 -2
  133. package/dist/stores/file/shared-ledger.d.ts +35 -0
  134. package/dist/stores/file/shared-ledger.js +94 -0
  135. package/dist/stores/file/workflow-run-store.d.ts +1 -14
  136. package/dist/stores/file/workflow-run-store.js +24 -93
  137. package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
  138. package/dist/tools/fs/bash-readonly-classifier.js +166 -0
  139. package/dist/tools/fs/fs-bash.d.ts +31 -0
  140. package/dist/tools/fs/fs-bash.js +672 -0
  141. package/dist/tools/fs/fs-pdf.d.ts +22 -0
  142. package/dist/tools/fs/fs-pdf.js +236 -0
  143. package/dist/tools/fs/fs-read.d.ts +8 -0
  144. package/dist/tools/fs/fs-read.js +286 -0
  145. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  146. package/dist/tools/fs/fs-search-tools.js +175 -0
  147. package/dist/tools/fs/fs-shared.d.ts +56 -0
  148. package/dist/tools/fs/fs-shared.js +148 -0
  149. package/dist/tools/fs/fs-write.d.ts +8 -0
  150. package/dist/tools/fs/fs-write.js +421 -0
  151. package/dist/tools/fs/index.d.ts +12 -65
  152. package/dist/tools/fs/index.js +26 -2090
  153. package/dist/tools/fs/pdf.d.ts +1 -1
  154. package/dist/tools/fs/repo-map.d.ts +3 -3
  155. package/dist/tools/fs/repo-map.js +1 -1
  156. package/dist/tools/fs/safety.d.ts +4 -1
  157. package/dist/tools/fs/safety.js +7 -1
  158. package/dist/tools/fs/search.d.ts +2 -1
  159. package/dist/tools/fs/search.js +21 -3
  160. package/dist/tools/monitor.d.ts +1 -1
  161. package/dist/tools/scheduler-tools.d.ts +1 -1
  162. package/dist/tools/web.d.ts +2 -2
  163. package/dist/tools/web.js +46 -8
  164. package/dist/tools/worktree.d.ts +1 -1
  165. package/package.json +1 -1
  166. package/dist/core/dynamic-recall.d.ts +0 -32
  167. 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,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;
@@ -30,3 +30,4 @@ export declare function sliceStreamIncrement(s: StreamCursorState, terminal: boo
30
30
  droppedBeforeCursor: number;
31
31
  };
32
32
  export declare function markTruncated(text: string, droppedBytes: number): string;
33
+ export declare function markLiveSpoolRotations(text: string, liveRotations: number): string;
@@ -73,3 +73,9 @@ export function markTruncated(text, droppedBytes) {
73
73
  ? `[... ${droppedBytes} earlier byte(s) truncated; showing last ${Buffer.byteLength(text, "utf8")} ...]\n${text}`
74
74
  : text;
75
75
  }
76
+ export function markLiveSpoolRotations(text, liveRotations) {
77
+ if (liveRotations <= 0)
78
+ return text;
79
+ const note = `[spool reclaimed ${liveRotations} time(s) while the process was still writing; bytes appended during those windows may be missing beyond any counted truncation]`;
80
+ return text === "" ? note : `${text}${text.endsWith("\n") ? "" : "\n"}${note}`;
81
+ }
@@ -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 FileSnapshotError {
3
3
  code: "too_large" | "enumerate_failed" | "read_failed" | "restore_failed" | "not_found" | "aborted";
4
4
  message: string;
@@ -1,4 +1,4 @@
1
- import type { ExecutionEnv } from "../internal/harness.js";
1
+ import type { ExecutionEnv } from "../internal/harness-types.js";
2
2
  import type { ToolPolicy } from "./tool-policy.js";
3
3
  export interface FsWriteGatePolicyOptions {
4
4
  env: ExecutionEnv;
@@ -1,4 +1,4 @@
1
- import type { ExecutionEnv } from "../internal/harness.js";
1
+ import type { ExecutionEnv } from "../internal/harness-types.js";
2
2
  export declare const WORKTREE_PARENT = ".sema-worktrees";
3
3
  export interface AddWorktreeOptions {
4
4
  repoRoot: string;
@@ -1,5 +1,5 @@
1
1
  import type { DocumentContent, ImageContent, TextContent } from "../internal/llm.js";
2
- import type { SessionTreeEntry } from "../internal/harness.js";
2
+ import type { SessionTreeEntry } from "../internal/harness-types.js";
3
3
  import type { PermissionResult, ResolvedAsk, ToolCallRequest } from "./tool-policy.js";
4
4
  export interface Hooks {
5
5
  preToolUse?(toolName: string, input: unknown, ctx: HookToolContext): PreToolUseResult | undefined | Promise<PreToolUseResult | undefined>;
@@ -0,0 +1,44 @@
1
+ export declare const MCP_IMAGE_MAX_BASE64: number;
2
+ export declare const IMAGE_TARGET_RAW_SIZE: number;
3
+ export declare const IMAGE_MAX_WIDTH = 2000;
4
+ export declare const IMAGE_MAX_HEIGHT = 2000;
5
+ export type McpImageResizer = (base64: string, mimeType: string) => Promise<{
6
+ base64: string;
7
+ mimeType: string;
8
+ } | undefined>;
9
+ export interface ImageDimensions {
10
+ originalWidth: number;
11
+ originalHeight: number;
12
+ displayWidth: number;
13
+ displayHeight: number;
14
+ }
15
+ export interface DownsampledImage {
16
+ base64: string;
17
+ mimeType: string;
18
+ dimensions?: ImageDimensions;
19
+ }
20
+ export type ImageDownsampler = (input: Buffer, mimeType: string) => Promise<DownsampledImage | undefined>;
21
+ type SharpFactory = (input: Buffer) => SharpOps;
22
+ interface SharpOps {
23
+ metadata: () => Promise<{
24
+ width?: number;
25
+ height?: number;
26
+ format?: string;
27
+ }>;
28
+ resize: (w: number, h: number, o: {
29
+ fit: "inside";
30
+ withoutEnlargement: boolean;
31
+ }) => SharpOps;
32
+ jpeg: (o: {
33
+ quality: number;
34
+ }) => SharpOps;
35
+ png: (o: {
36
+ compressionLevel: number;
37
+ palette: boolean;
38
+ }) => SharpOps;
39
+ toBuffer: () => Promise<Buffer>;
40
+ }
41
+ export declare function createImageDownsampler(sharp: SharpFactory): ImageDownsampler;
42
+ export declare function sharpImageDownsampler(): Promise<ImageDownsampler | undefined>;
43
+ export declare function sharpImageResizer(): Promise<McpImageResizer | undefined>;
44
+ export {};