@sema-agent/core 5.7.0 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/subagent.d.ts +4 -7
  4. package/dist/agents/subagent.js +10 -19
  5. package/dist/agents/teacher.js +3 -3
  6. package/dist/agents/team.d.ts +1 -1
  7. package/dist/agents/team.js +1 -1
  8. package/dist/agents/verify.js +1 -1
  9. package/dist/brain/anthropic.js +24 -35
  10. package/dist/brain/openai.js +22 -33
  11. package/dist/brain/stream-engine.d.ts +0 -2
  12. package/dist/brain/stream-engine.js +5 -47
  13. package/dist/brain/stream-shared.d.ts +0 -10
  14. package/dist/brain/stream-shared.js +0 -23
  15. package/dist/brain/terminal-cause.d.ts +0 -1
  16. package/dist/brain/terminal-cause.js +0 -3
  17. package/dist/brain/timeout.d.ts +4 -2
  18. package/dist/brain/timeout.js +4 -14
  19. package/dist/config/catalog.d.ts +3 -11
  20. package/dist/config/catalog.js +30 -69
  21. package/dist/config/defaults.d.ts +0 -3
  22. package/dist/config/defaults.js +0 -3
  23. package/dist/core/auto-compaction.d.ts +2 -11
  24. package/dist/core/auto-compaction.js +3 -67
  25. package/dist/core/checkpoint-store.d.ts +10 -8
  26. package/dist/core/checkpoint-store.js +7 -7
  27. package/dist/core/memory-engine/file-backend.js +20 -17
  28. package/dist/core/memory-engine/layout.d.ts +5 -0
  29. package/dist/core/memory-engine/layout.js +72 -47
  30. package/dist/core/runner/assemble-result.d.ts +2 -8
  31. package/dist/core/runner/assemble-result.js +15 -16
  32. package/dist/core/runner/prepare-task.d.ts +26 -6
  33. package/dist/core/runner/prepare-task.js +203 -141
  34. package/dist/core/runner/runtask.d.ts +0 -2
  35. package/dist/core/runner/runtask.js +200 -244
  36. package/dist/core/session-policy-store.d.ts +5 -0
  37. package/dist/core/session-policy-store.js +3 -1
  38. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  39. package/dist/core/task-registry-agent.d.ts +2 -0
  40. package/dist/core/task-registry-agent.js +33 -1
  41. package/dist/core/task-registry.d.ts +2 -0
  42. package/dist/core/task-registry.js +9 -1
  43. package/dist/core/tool-errors.js +10 -2
  44. package/dist/core/trace.d.ts +1 -8
  45. package/dist/core/types.d.ts +24 -30
  46. package/dist/core/usage-window-store.d.ts +39 -0
  47. package/dist/core/usage-window-store.js +115 -0
  48. package/dist/engine/execution-env/node-execution-env.js +0 -22
  49. package/dist/engine/harness/agent-harness.d.ts +0 -7
  50. package/dist/engine/harness/agent-harness.js +13 -53
  51. package/dist/engine/harness/types.d.ts +2 -15
  52. package/dist/engine/llm/types.d.ts +1 -2
  53. package/dist/engine/loop/agent-loop.d.ts +1 -1
  54. package/dist/engine/loop/agent-loop.js +5 -171
  55. package/dist/engine/loop/types.d.ts +0 -18
  56. package/dist/index.d.ts +10 -5
  57. package/dist/index.js +8 -4
  58. package/dist/orchestration/goal.js +1 -5
  59. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  60. package/dist/orchestration/run-workflow-tool.js +1 -2
  61. package/dist/orchestration/workflow-governance.d.ts +6 -6
  62. package/dist/orchestration/workflow-governance.js +33 -47
  63. package/dist/prompt-assembly/event-registry.js +2 -3
  64. package/dist/stores/cc/lockfile.js +9 -11
  65. package/dist/stores/cc/mailbox-store.js +49 -38
  66. package/dist/stores/file/index.d.ts +3 -0
  67. package/dist/stores/file/index.js +4 -0
  68. package/dist/stores/file/usage-window-store.d.ts +9 -0
  69. package/dist/stores/file/usage-window-store.js +86 -0
  70. package/dist/tools/fs/fs-bash.d.ts +1 -10
  71. package/dist/tools/fs/fs-bash.js +20 -60
  72. package/dist/tools/fs/index.d.ts +0 -2
  73. package/dist/tools/fs/index.js +1 -2
  74. package/package.json +1 -4
  75. package/dist/bin/sema-tb.d.ts +0 -31
  76. package/dist/bin/sema-tb.js +0 -448
  77. package/dist/bin/tb-env.d.ts +0 -2
  78. package/dist/bin/tb-env.js +0 -17
  79. package/dist/brain/walltime.d.ts +0 -1
  80. package/dist/brain/walltime.js +0 -1
  81. package/dist/core/runner/call-cap.d.ts +0 -67
  82. package/dist/core/runner/call-cap.js +0 -145
  83. package/dist/core/runner/cut-kill.d.ts +0 -10
  84. package/dist/core/runner/cut-kill.js +0 -37
@@ -11,7 +11,6 @@ export * from "./fs-search-tools.js";
11
11
  export * from "./bash-readonly-classifier.js";
12
12
  export * from "./fs-bash.js";
13
13
  import { type CwdRef, type ReadImageDownsamplerOption } from "./fs-shared.js";
14
- import { type ExecClampOption } from "./fs-bash.js";
15
14
  export interface HandsToolkitOptions {
16
15
  additionalRoots?: readonly string[];
17
16
  includeShell?: boolean;
@@ -28,7 +27,6 @@ export interface HandsToolkitOptions {
28
27
  priority?: "now" | "next" | "later";
29
28
  }) => void;
30
29
  detachHub?: import("../../core/tool-detach.js").ToolDetachHub;
31
- execClamp?: ExecClampOption;
32
30
  bashDefaultTimeoutMs?: number;
33
31
  bashMaxTimeoutMs?: number;
34
32
  oneShot?: boolean;
@@ -35,7 +35,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
35
35
  tools.push(createGrepTool(env, rootCanonical, additionalRoots), createGlobTool(env, rootCanonical, additionalRoots), createRepoMapTool(env, rootCanonical, additionalRoots));
36
36
  if (includeShell) {
37
37
  tools.push(readOnly
38
- ? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), opts.execClamp, {
38
+ ? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), {
39
39
  ...(opts.bashDefaultTimeoutMs !== undefined ? { bashDefaultTimeoutMs: opts.bashDefaultTimeoutMs } : {}),
40
40
  ...(opts.bashMaxTimeoutMs !== undefined ? { bashMaxTimeoutMs: opts.bashMaxTimeoutMs } : {}),
41
41
  ...(additionalRoots !== undefined ? { additionalRoots } : {}),
@@ -47,7 +47,6 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
47
47
  ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
48
48
  onTaskNotification: opts.taskNotification,
49
49
  detachHub: opts.detachHub,
50
- execClamp: opts.execClamp,
51
50
  ...(opts.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: opts.autoBackgroundOnTimeout } : {}),
52
51
  ...(opts.oneShot !== undefined ? { oneShot: opts.oneShot } : {}),
53
52
  ...(additionalRoots !== undefined ? { additionalRoots } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.7.0",
3
+ "version": "5.8.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -20,9 +20,6 @@
20
20
  ],
21
21
  "main": "./dist/index.js",
22
22
  "types": "./dist/index.d.ts",
23
- "bin": {
24
- "sema-tb": "dist/bin/sema-tb.js"
25
- },
26
23
  "exports": {
27
24
  ".": {
28
25
  "types": "./dist/index.d.ts",
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
- import { type Model } from "../index.js";
3
- interface Args {
4
- model: string;
5
- objectiveFile: string | undefined;
6
- jsonl: string | undefined;
7
- }
8
- export type ParsedArgs = {
9
- args: Args;
10
- } | {
11
- help: true;
12
- } | {
13
- usageError: string;
14
- };
15
- export declare function parseArgs(argv: string[], env?: Record<string, string | undefined>): ParsedArgs;
16
- export type InstructionResolution = {
17
- instruction: string;
18
- } | {
19
- error: string;
20
- };
21
- export declare function resolveInstruction(args: Pick<Args, "objectiveFile">, readInput: (source: string | 0) => string): InstructionResolution;
22
- type TbCost = Model["cost"];
23
- export type TbCostResolution = {
24
- cost: TbCost;
25
- source: "tb-cost-env" | "model-family" | "provider-default" | "disabled";
26
- warning?: string;
27
- } | {
28
- error: string;
29
- };
30
- export declare function resolveTbCost(modelId: string, provider: string, baseUrlOverridden: boolean, tbCostEnv: string | undefined): TbCostResolution;
31
- export {};
@@ -1,448 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFileSync, realpathSync, createWriteStream } from "node:fs";
3
- import { pathToFileURL } from "node:url";
4
- import { Runner, NodeExecutionEnv, createOpenAIBrain, createAnthropicBrain, CODE_SYSTEM_PROMPT, assembleCodeTools, createWebFetchSummarizer, } from "../index.js";
5
- import { positiveIntFromEnv, tbModelInput } from "./tb-env.js";
6
- import { inferMaxTokensField } from "../brain/openai.js";
7
- function usage(code = 2) {
8
- const out = code === 0 ? process.stdout : process.stderr;
9
- out.write("Usage: sema-tb --model <provider/model> [--objective-file <path>] [--jsonl <path>]\n" +
10
- " Instruction legs: --objective-file <path> (preferred for real task text) | stdin (default when the flag is absent).\n" +
11
- " WARNING: argv is visible to every process in the sandbox (ps / pgrep -f / /proc/*/cmdline), so instructions ride\n" +
12
- " --objective-file or stdin; the retired --message argv leg is refused (process-list exposure).\n" +
13
- "Env: <PROVIDER>_API_KEY / <PROVIDER>_BASE_URL, TB_MAX_TURNS, TB_TIMEOUT_SEC, TB_FULLBODY, TB_TODOREMIND, TB_BGTASKS, TB_SUMMARIZE,\n" +
14
- " TB_FINALVERIFY, TB_CALLCAP, TB_FINALIZE, TB_REASONING, TB_JSONL, TB_CONTEXT_WINDOW, TB_MAX_OUTPUT_TOKENS, TB_TASKLIST, TB_VISION\n" +
15
- ' TB_COST="input,output[,cacheRead[,cacheWrite]]" — USD per MTok; explicit pricing override (omitted\n' +
16
- " trailing segments default to 0). Without it, pricing resolves by MODEL FAMILY (deepseek/claude/gpt-o*)\n" +
17
- " first, then the wire-prefix table; unknown model on an overridden BASE_URL ⇒ cost accounting disabled.\n");
18
- process.exit(code);
19
- }
20
- export function parseArgs(argv, env = process.env) {
21
- let model;
22
- let objectiveFile;
23
- let jsonl;
24
- for (let i = 0; i < argv.length; i++) {
25
- const a = argv[i];
26
- if (a === "--message") {
27
- return { usageError: "--message is retired (argv is visible to every sandbox process — the exposure once made a model's pgrep cleanup kill its own agent). Use --objective-file <path> or pipe the instruction on stdin." };
28
- }
29
- if (a === "--model" || a === "--objective-file" || a === "--jsonl") {
30
- const v = argv[++i];
31
- if (v === undefined)
32
- return { usageError: `missing value for ${a}` };
33
- if (a === "--model")
34
- model = v;
35
- else if (a === "--objective-file")
36
- objectiveFile = v;
37
- else
38
- jsonl = v;
39
- }
40
- else if (a === "--help" || a === "-h")
41
- return { help: true };
42
- else
43
- return { usageError: `unknown argument: ${a}` };
44
- }
45
- if (!model || !model.includes("/")) {
46
- return { usageError: "--model must be <provider/model> (e.g. deepseek/deepseek-v4-flash)" };
47
- }
48
- return { args: { model, objectiveFile, jsonl: jsonl ?? env.TB_JSONL } };
49
- }
50
- export function resolveInstruction(args, readInput) {
51
- const leg = args.objectiveFile !== undefined ? `--objective-file ${args.objectiveFile}` : "stdin (no --objective-file)";
52
- let raw;
53
- try {
54
- raw = readInput(args.objectiveFile ?? 0);
55
- }
56
- catch (e) {
57
- return { error: `cannot read instruction from ${leg}: ${e instanceof Error ? e.message : String(e)}` };
58
- }
59
- const instruction = raw.trim();
60
- if (!instruction)
61
- return { error: `empty instruction (${leg})` };
62
- return { instruction };
63
- }
64
- const envPrefix = (provider) => provider.toUpperCase().replace(/-/g, "_");
65
- const PROVIDER_DEFAULTS = {
66
- deepseek: {
67
- baseUrl: "https://api.deepseek.com",
68
- contextWindow: 200_000,
69
- maxTokens: 32_000,
70
- charsPerToken: 3,
71
- reasoning: true,
72
- cost: { input: 0.28, output: 0.42, cacheRead: 0.028, cacheWrite: 0 },
73
- },
74
- openai: {
75
- baseUrl: "https://api.openai.com/v1",
76
- contextWindow: 200_000,
77
- maxTokens: 16_384,
78
- charsPerToken: 3,
79
- reasoning: false,
80
- cost: { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
81
- },
82
- anthropic: {
83
- baseUrl: "https://api.anthropic.com",
84
- contextWindow: 200_000,
85
- maxTokens: 32_000,
86
- charsPerToken: 3,
87
- reasoning: true,
88
- cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
89
- },
90
- };
91
- const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
92
- function parseTbCostEnv(raw) {
93
- const parts = raw.split(",").map((p) => p.trim());
94
- if (parts.length > 4)
95
- return { error: `expected at most 4 comma-separated values, got ${parts.length}` };
96
- const nums = [];
97
- for (const p of parts) {
98
- if (!/^(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/.test(p)) {
99
- return { error: `${JSON.stringify(p)} is not a non-negative number` };
100
- }
101
- const n = Number(p);
102
- if (!Number.isFinite(n))
103
- return { error: `${JSON.stringify(p)} is not finite` };
104
- nums.push(n);
105
- }
106
- return { input: nums[0] ?? 0, output: nums[1] ?? 0, cacheRead: nums[2] ?? 0, cacheWrite: nums[3] ?? 0 };
107
- }
108
- export function resolveTbCost(modelId, provider, baseUrlOverridden, tbCostEnv) {
109
- const raw = tbCostEnv?.trim();
110
- if (raw) {
111
- const parsed = parseTbCostEnv(raw);
112
- if ("error" in parsed) {
113
- return {
114
- error: `sema-tb: invalid TB_COST ${JSON.stringify(tbCostEnv)}: ${parsed.error} ` +
115
- `(expected "input,output[,cacheRead[,cacheWrite]]" in USD per MTok)`,
116
- };
117
- }
118
- return { cost: parsed, source: "tb-cost-env" };
119
- }
120
- const id = modelId.toLowerCase();
121
- const family = id.includes("deepseek")
122
- ? "deepseek"
123
- : id.startsWith("claude")
124
- ? "anthropic"
125
- : id.startsWith("gpt") || id.startsWith("o1") || id.startsWith("o3") || id.startsWith("o4")
126
- ? "openai"
127
- : undefined;
128
- if (family)
129
- return { cost: { ...PROVIDER_DEFAULTS[family].cost }, source: "model-family" };
130
- if (!baseUrlOverridden) {
131
- const d = PROVIDER_DEFAULTS[provider];
132
- return { cost: d ? { ...d.cost } : { ...ZERO_COST }, source: "provider-default" };
133
- }
134
- return {
135
- cost: { ...ZERO_COST },
136
- source: "disabled",
137
- warning: `sema-tb: unknown pricing for model ${modelId} on overridden endpoint — ` +
138
- `cost accounting disabled; set TB_COST to enable\n`,
139
- };
140
- }
141
- function buildModelAndBrain(providerModel) {
142
- const [provider, ...rest] = providerModel.split("/");
143
- const modelId = rest.join("/");
144
- const prefix = envPrefix(provider);
145
- const apiKeyEnv = `${prefix}_API_KEY`;
146
- const apiKey = process.env[apiKeyEnv] ??
147
- (provider === "anthropic" ? process.env.ANTHROPIC_AUTH_TOKEN : undefined);
148
- const baseUrlOverride = process.env[`${prefix}_BASE_URL`]?.trim();
149
- const d = PROVIDER_DEFAULTS[provider];
150
- if (!d && !baseUrlOverride) {
151
- process.stderr.write(`sema-tb: unknown provider "${provider}" and no ${prefix}_BASE_URL set — cannot resolve an endpoint.\n`);
152
- process.exit(2);
153
- }
154
- if (!apiKey) {
155
- process.stderr.write(`sema-tb: missing ${apiKeyEnv} in the environment.\n`);
156
- process.exit(2);
157
- }
158
- const costRes = resolveTbCost(modelId, provider, Boolean(baseUrlOverride), process.env.TB_COST);
159
- if ("error" in costRes) {
160
- process.stderr.write(`${costRes.error}\n`);
161
- process.exit(2);
162
- }
163
- if (costRes.warning)
164
- process.stderr.write(costRes.warning);
165
- const isAnthropic = provider === "anthropic";
166
- const contextWindowOverride = positiveIntFromEnv("TB_CONTEXT_WINDOW");
167
- const maxOutputTokensOverride = positiveIntFromEnv("TB_MAX_OUTPUT_TOKENS");
168
- const model = {
169
- id: modelId,
170
- name: providerModel,
171
- api: isAnthropic ? "anthropic-messages" : "openai-completions",
172
- provider,
173
- baseUrl: baseUrlOverride || d.baseUrl,
174
- reasoning: process.env.TB_REASONING === "0" ? false : process.env.TB_REASONING === "1" ? true : (d?.reasoning ?? false),
175
- input: tbModelInput(),
176
- cost: costRes.cost,
177
- contextWindow: contextWindowOverride ?? d?.contextWindow ?? 200_000,
178
- maxTokens: maxOutputTokensOverride ?? d?.maxTokens ?? 32_000,
179
- charsPerToken: d?.charsPerToken ?? 3,
180
- ...(isAnthropic ? {} : { compat: { maxTokensField: inferMaxTokensField(modelId) } }),
181
- };
182
- const brain = isAnthropic ? createAnthropicBrain({ apiKey }) : createOpenAIBrain({ apiKey });
183
- return { model, brain, apiKeyEnv };
184
- }
185
- const T0 = Date.now();
186
- const elapsed = () => `[+${((Date.now() - T0) / 1000).toFixed(1)}s]`;
187
- const line = (s) => void process.stdout.write(`${elapsed()} ${s}\n`);
188
- function clipLine(s, cap = 200) {
189
- const one = s.replace(/\s*\n\s*/g, " ⏎ ");
190
- return one.length > cap ? one.slice(0, cap) + `…(+${one.length - cap})` : one;
191
- }
192
- function summarizeArgs(args, cap = 220) {
193
- let s;
194
- try {
195
- s = JSON.stringify(args) ?? "";
196
- }
197
- catch {
198
- s = String(args);
199
- }
200
- s = s.replace(/\s*\n\s*/g, " ");
201
- return s.length > cap ? s.slice(0, cap) + `…(+${s.length - cap})` : s;
202
- }
203
- class TextLineBuffer {
204
- prefix;
205
- buf = "";
206
- constructor(prefix = "[text]") {
207
- this.prefix = prefix;
208
- }
209
- push(delta) {
210
- this.buf += delta;
211
- let nl;
212
- while ((nl = this.buf.indexOf("\n")) >= 0) {
213
- const row = this.buf.slice(0, nl);
214
- this.buf = this.buf.slice(nl + 1);
215
- if (row.trim().length > 0)
216
- line(`${this.prefix} ${row}`);
217
- }
218
- }
219
- flush() {
220
- if (this.buf.trim().length > 0)
221
- line(`${this.prefix} ${this.buf}`);
222
- this.buf = "";
223
- }
224
- }
225
- function intFromEnv(name, fallback) {
226
- const raw = process.env[name];
227
- if (!raw)
228
- return fallback;
229
- const n = Number.parseInt(raw, 10);
230
- return Number.isFinite(n) && n > 0 ? n : fallback;
231
- }
232
- async function main() {
233
- process.title = "sema-tb-agent";
234
- const parsed = parseArgs(process.argv.slice(2));
235
- if ("help" in parsed)
236
- usage(0);
237
- if ("usageError" in parsed) {
238
- process.stderr.write(`sema-tb: ${parsed.usageError}\n`);
239
- usage();
240
- }
241
- const args = parsed.args;
242
- const resolved = resolveInstruction(args, (source) => readFileSync(source, "utf8"));
243
- if ("error" in resolved) {
244
- process.stderr.write(`sema-tb: ${resolved.error}\n`);
245
- process.exit(2);
246
- }
247
- const instruction = resolved.instruction;
248
- const { model, brain } = buildModelAndBrain(args.model);
249
- const cwd = process.cwd();
250
- const maxTurns = intFromEnv("TB_MAX_TURNS", 500);
251
- const timeoutSec = intFromEnv("TB_TIMEOUT_SEC", 3000);
252
- let jsonl;
253
- if (args.jsonl) {
254
- try {
255
- jsonl = createWriteStream(args.jsonl, { flags: "w" });
256
- jsonl.on("error", () => void (jsonl = undefined));
257
- }
258
- catch {
259
- jsonl = undefined;
260
- }
261
- }
262
- const sink = (ch, payload) => {
263
- if (!jsonl)
264
- return;
265
- try {
266
- jsonl.write(`${JSON.stringify({ ch, tMs: Date.now() - T0, ...payload })}\n`);
267
- }
268
- catch {
269
- }
270
- };
271
- line(`[init] model=${args.model} baseUrl=${model.baseUrl} cwd=${cwd} maxTurns=${maxTurns} timeoutSec=${timeoutSec} fullBody=${process.env.TB_FULLBODY !== "0"}${args.jsonl ? ` jsonl=${args.jsonl}` : ""}`);
272
- let degradedTo;
273
- let lastServedModel;
274
- const tracer = (e) => {
275
- sink("trace", e);
276
- switch (e.kind) {
277
- case "brain.call":
278
- lastServedModel = e.model;
279
- line(`[call] model=${e.model} in=${e.promptTokens} tot=${e.totalInputTokens} out=${e.completionTokens} cache=${e.cacheRead} latMs=${e.latencyMs}` +
280
- (e.firstTokenMs !== undefined ? ` ttftMs=${e.firstTokenMs}` : "") +
281
- (e.stopReason !== undefined ? ` stop=${e.stopReason}` : "") +
282
- (e.callCap !== undefined ? ` cap=${e.callCap}${e.capThinkingSkipped ? "(think-skip)" : ""}` : "") +
283
- (e.costMicroUsd !== undefined ? ` costMicroUsd=${e.costMicroUsd}` : ""));
284
- break;
285
- case "task.degraded":
286
- degradedTo = e.to ?? "unknown";
287
- line(`[degraded] ${summarizeArgs(e)}`);
288
- break;
289
- case "prompt.assembled":
290
- line(`[prompt] constitution=${e.constitution} blocks=${e.blocks.map((b) => b.id).join(",")} totalChars=${e.totalChars}`);
291
- break;
292
- case "brain.failover":
293
- line(`[failover] servedIndex=${e.servedIndex}/${e.total - 1}${e.errorCode !== undefined ? ` errorCode=${e.errorCode}` : ""}`);
294
- break;
295
- case "breaker.transition":
296
- line(`[breaker] key=${e.key} phase=${e.phase} failures=${e.failures}`);
297
- break;
298
- case "brain.retry":
299
- line(`[retry] attempt=${e.attempt} phase=${e.phase}`);
300
- break;
301
- case "loop.recovery":
302
- line(`[recovery] ${e.reason}`);
303
- break;
304
- case "compaction.suppressed":
305
- line(`[compaction-suppressed] est=${e.estTokens} floor=${e.floor}`);
306
- break;
307
- default:
308
- break;
309
- }
310
- };
311
- const runner = new Runner({
312
- brain,
313
- executionEnv: new NodeExecutionEnv({ cwd, retainBackgroundProcesses: true }),
314
- rootPath: cwd,
315
- tracer,
316
- });
317
- const fullBody = process.env.TB_FULLBODY !== "0";
318
- const stream = runner.runTaskStream({
319
- objective: instruction,
320
- model,
321
- onAsk: "allow",
322
- streamingToolExecution: true,
323
- limits: {
324
- maxTurns,
325
- timeoutSec,
326
- ...(process.env.TB_CALLCAP === "0" ? { callCapByDeadline: false } : {}),
327
- ...(process.env.TB_FINALIZE === "0" ? { gracefulFinalize: false } : {}),
328
- },
329
- ...(process.env.TB_FINALVERIFY === "0" ? {} : { finalVerification: true }),
330
- ...(process.env.TB_TODOREMIND === "0" && process.env.TB_BGTASKS !== "1"
331
- ? {}
332
- : {
333
- attachments: {
334
- ...(process.env.TB_TODOREMIND === "0" ? {} : { todoReminder: true }),
335
- ...(process.env.TB_TODOREMIND === "off" ? { todoReminderMode: "off" } : {}),
336
- ...(process.env.TB_BGTASKS === "1" ? { backgroundTasks: true } : {}),
337
- },
338
- }),
339
- ...(fullBody
340
- ? {
341
- systemPrompt: CODE_SYSTEM_PROMPT,
342
- tools: assembleCodeTools({
343
- ...(process.env.TB_SUMMARIZE === "0" ? {} : { webFetch: { summarize: createWebFetchSummarizer(brain, model) } }),
344
- ...(process.env.TB_TASKLIST === "0" || process.env.TB_TASKLIST === "false" ? { taskList: false } : {}),
345
- }),
346
- }
347
- : {}),
348
- });
349
- const text = new TextLineBuffer();
350
- const reason = new TextLineBuffer("[reason]");
351
- let turns = 0;
352
- try {
353
- for await (const ev of stream) {
354
- sink("event", ev);
355
- switch (ev.type) {
356
- case "text_delta":
357
- text.push(ev.delta);
358
- break;
359
- case "reasoning_delta":
360
- reason.push(ev.delta);
361
- break;
362
- case "tool_start":
363
- text.flush();
364
- reason.flush();
365
- line(`[tool] ${ev.toolName} ${summarizeArgs(ev.args)}`);
366
- break;
367
- case "tool_end": {
368
- const st = ev.structured;
369
- let detail = "";
370
- if (st?.type === "bash" && typeof st.exitCode === "number") {
371
- detail = ` exit=${st.exitCode}`;
372
- if (st.exitCode !== 0 && typeof st.stderr === "string" && st.stderr.trim()) {
373
- detail += ` stderr=${JSON.stringify(clipLine(st.stderr, 160))}`;
374
- }
375
- }
376
- else if (ev.isError && ev.output !== undefined) {
377
- detail = ` err=${JSON.stringify(clipLine(typeof ev.output === "string" ? ev.output : JSON.stringify(ev.output), 160))}`;
378
- }
379
- line(`[tool_end] ${ev.toolName} ${ev.isError ? "ERROR" : "ok"}${detail}`);
380
- break;
381
- }
382
- case "steering_injected":
383
- line(`[steer] source=${ev.source} ${JSON.stringify(clipLine(ev.preview, 160))}`);
384
- break;
385
- case "status":
386
- line(`[status] ${summarizeArgs(ev, 160)}`);
387
- break;
388
- case "turn_end": {
389
- text.flush();
390
- reason.flush();
391
- turns++;
392
- const u = ev.usage;
393
- line(`[turn] ${turns}${u ? ` in=${u.inputTokens} tot=${u.totalInputTokens} out=${u.outputTokens} cache=${u.cacheReadTokens}` : ""}` +
394
- (ev.usageMissing ? " usageMissing=1" : "") +
395
- (ev.stopReason !== undefined ? ` stop=${ev.stopReason}` : ""));
396
- break;
397
- }
398
- case "compacted":
399
- line(`[compacted] trigger=${ev.trigger} tokensBefore=${ev.tokensBefore}` +
400
- (ev.tokensAfter !== undefined ? ` tokensAfter=${ev.tokensAfter}` : "") +
401
- (ev.durationMs !== undefined ? ` durationMs=${ev.durationMs}` : ""));
402
- break;
403
- default:
404
- break;
405
- }
406
- }
407
- }
408
- catch (e) {
409
- process.stderr.write(`sema-tb: event stream error: ${e instanceof Error ? e.message : String(e)}\n`);
410
- }
411
- text.flush();
412
- reason.flush();
413
- const r = await stream.result();
414
- const mech = r.stats.mechanisms;
415
- line(`[done] status=${r.status} turns=${r.stats.turns} toolCalls=${r.stats.toolCalls ?? 0} ` +
416
- `tokens=${r.stats.tokens} costMicroUsd=${r.stats.costMicroUsd ?? 0}` +
417
- ` model=${lastServedModel ?? model.id} degraded=${degradedTo ?? "no"}` +
418
- (r.stats.cacheHitRate !== undefined ? ` cacheHitRate=${r.stats.cacheHitRate.toFixed(3)}` : "") +
419
- (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}` : ""}` : "") +
420
- (r.errorCode ? ` errorCode=${r.errorCode}` : "") +
421
- (r.errorMessage ? ` error=${JSON.stringify(r.errorMessage.slice(0, 300))}` : "") +
422
- (r.blockedReason ? ` blocked=${JSON.stringify(r.blockedReason.slice(0, 300))}` : ""));
423
- if (r.result)
424
- for (const rl of r.result.split("\n"))
425
- line(`[result] ${rl}`);
426
- sink("event", { type: "done", status: r.status, stats: r.stats, errorCode: r.errorCode });
427
- if (jsonl)
428
- await new Promise((res) => jsonl.end(res));
429
- runner.sessions.dispose();
430
- process.exit(0);
431
- }
432
- function invokedAsScript() {
433
- const argv1 = process.argv[1];
434
- if (!argv1)
435
- return true;
436
- try {
437
- return pathToFileURL(realpathSync(argv1)).href === import.meta.url;
438
- }
439
- catch {
440
- return true;
441
- }
442
- }
443
- if (invokedAsScript()) {
444
- main().catch((e) => {
445
- process.stderr.write(`sema-tb: fatal: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}\n`);
446
- process.exit(1);
447
- });
448
- }
@@ -1,2 +0,0 @@
1
- export declare function positiveIntFromEnv(name: string, env?: NodeJS.ProcessEnv): number | undefined;
2
- export declare function tbModelInput(env?: NodeJS.ProcessEnv): ("text" | "image")[];
@@ -1,17 +0,0 @@
1
- export function positiveIntFromEnv(name, env = process.env) {
2
- const raw = env[name];
3
- if (raw === undefined)
4
- return undefined;
5
- const s = raw.trim();
6
- if (s === "")
7
- return undefined;
8
- const n = /^[0-9]+$/.test(s) ? Number(s) : NaN;
9
- if (!Number.isSafeInteger(n) || n <= 0) {
10
- process.stderr.write(`sema-tb: ignoring ${name}=${JSON.stringify(raw)} — expected a positive integer.\n`);
11
- return undefined;
12
- }
13
- return n;
14
- }
15
- export function tbModelInput(env = process.env) {
16
- return env.TB_VISION === "1" ? ["text", "image"] : ["text"];
17
- }
@@ -1 +0,0 @@
1
- export declare const WALLTIME_CUTOFF_MESSAGE = "walltime cutoff: the response crossed the task's soft wall-clock deadline and was cut off to preserve a write-out window";
@@ -1 +0,0 @@
1
- export const WALLTIME_CUTOFF_MESSAGE = "walltime cutoff: the response crossed the task's soft wall-clock deadline and was cut off to preserve a write-out window";
@@ -1,67 +0,0 @@
1
- export declare const CALL_CAP_PRIOR_TOK_PER_SEC = 40;
2
- export declare const CALL_CAP_SAFETY = 0.5;
3
- export declare const CALL_CAP_MIN_FLOOR = 1024;
4
- export declare const CALL_CAP_THINKING_MIN = 2048;
5
- export declare const WRITEOUT_CUSHION_MAX_MS = 75000;
6
- export declare const WRITEOUT_CUSHION_MIN_MS = 10000;
7
- export declare const CUSHION_LATENCY_FACTOR = 2;
8
- export declare const CUSHION_BUDGET_FRACTION = 0.4;
9
- export declare const FINAL_WRITEOUT_MARGIN_MS = 5000;
10
- export declare const TTFT_WINDOW_FACTOR = 2;
11
- export declare const TTFT_PRIOR_MS = 5000;
12
- export declare const WRITEOUT_WINDOW_NO_SAMPLE_FLOOR_MS = 1000;
13
- export declare const TOOL_CUT_WRITEOUT_RESERVE_MS = 5000;
14
- export declare const TOOL_CUT_DEADLINE_LAG_MS = 2500;
15
- export declare const TOOL_CUT_MIN_WINDOW_MS = 10000;
16
- export declare const TOOL_CUT_FLOOR_GRACE_MS = 2000;
17
- export declare const WALLTIME_STALL_CONNECT_MS = 30000;
18
- export declare const WALLTIME_STALL_FIRST_TOKEN_MS = 120000;
19
- export declare const WALLTIME_STALL_IDLE_MS = 90000;
20
- export declare function writeoutCushionMs(budgetMs: number): number;
21
- export interface CallCapState {
22
- emaTokPerSec?: number;
23
- emaCallMs?: number;
24
- emaToolMs?: number;
25
- peakCallMs?: number;
26
- peakToolMs?: number;
27
- recentCallMs?: number[];
28
- recentToolMs?: number[];
29
- ttftEwma?: number;
30
- last?: {
31
- cap: number;
32
- remainingMs: number;
33
- tokPerSec: number;
34
- thinkingSkipped: boolean;
35
- };
36
- }
37
- export interface CallCapRef {
38
- state: CallCapState;
39
- deadlineMs?: number;
40
- finalizeMode?: boolean;
41
- staticCapTokens?: number;
42
- shrinks?: number;
43
- cushionMs?: number;
44
- budgetMs?: number;
45
- toolClamps?: number;
46
- toolCuts?: number;
47
- pendingCutTools?: number;
48
- reasoningObserved?: boolean;
49
- }
50
- export declare function effectiveCushionMs(ref: CallCapRef): number | undefined;
51
- export declare function minLiveWriteoutWindowMs(state: CallCapState): number;
52
- export declare function softExecDeadlineMs(ref: CallCapRef): number | undefined;
53
- export declare function createCallCapRef(): CallCapRef;
54
- export declare function toolCutDeadlineMs(ref: CallCapRef, nowMs: number): number | undefined;
55
- export declare function hasRealContent(content: ReadonlyArray<{
56
- type?: unknown;
57
- text?: unknown;
58
- thinking?: unknown;
59
- }> | undefined): boolean;
60
- export declare function recordCallSample(state: CallCapState, outputTokens: number, latencyMs: number, opts?: {
61
- contentValid?: boolean;
62
- }): void;
63
- export declare function recordTtftSample(state: CallCapState, ttftMs: number): void;
64
- export declare function recordToolSample(state: CallCapState, durationMs: number): void;
65
- export declare function recordToolCycleSample(state: CallCapState, wallSpanMs: number): void;
66
- export declare function computeCallCap(ref: CallCapRef, nowMs: number, thinkingActive: boolean): number | undefined;
67
- export declare function estimateCycleMs(state: CallCapState): number;