@samrahimi/smol-js 0.7.0 → 0.7.2

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.
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ __export(index_exports, {
49
49
  Orchestrator: () => Orchestrator,
50
50
  ProxyTool: () => ProxyTool,
51
51
  ReadFileTool: () => ReadFileTool,
52
+ TerminalAgent: () => TerminalAgent,
52
53
  Tool: () => Tool,
53
54
  ToolUseAgent: () => ToolUseAgent,
54
55
  UserInputTool: () => UserInputTool,
@@ -792,7 +793,7 @@ Total time: ${(duration / 1e3).toFixed(2)}s`);
792
793
  }
793
794
  /** Sleep for a specified duration */
794
795
  sleep(ms) {
795
- return new Promise((resolve6) => setTimeout(resolve6, ms));
796
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
796
797
  }
797
798
  };
798
799
 
@@ -1461,12 +1462,12 @@ var UserInputTool = class extends Tool {
1461
1462
  input: process.stdin,
1462
1463
  output: process.stdout
1463
1464
  });
1464
- return new Promise((resolve6) => {
1465
+ return new Promise((resolve7) => {
1465
1466
  rl.question(`
1466
1467
  [Agent asks]: ${question}
1467
1468
  Your response: `, (answer) => {
1468
1469
  rl.close();
1469
- resolve6(answer);
1470
+ resolve7(answer);
1470
1471
  });
1471
1472
  });
1472
1473
  }
@@ -2108,6 +2109,369 @@ Please try a different approach.`
2108
2109
  }
2109
2110
  };
2110
2111
 
2112
+ // src/agents/TerminalAgent.ts
2113
+ var import_child_process = require("child_process");
2114
+
2115
+ // src/prompts/terminalAgent.ts
2116
+ function generateTerminalAgentSystemPrompt(variables) {
2117
+ const { customInstructions, hasSubAgents, subAgentDescriptions } = variables;
2118
+ let delegationSection = "";
2119
+ if (hasSubAgents) {
2120
+ delegationSection = `
2121
+ ## Delegation to Sub-Agents
2122
+
2123
+ You can delegate tasks to specialized sub-agents when terminal commands alone
2124
+ are not sufficient. Available sub-agents:
2125
+
2126
+ ${subAgentDescriptions}
2127
+
2128
+ To delegate, call the sub-agent tool with a clear task description. Wait for
2129
+ its result before continuing. Sub-agents handle their own tool calls internally.
2130
+ `;
2131
+ }
2132
+ return `You are a terminal operations agent running on macOS. You accomplish tasks
2133
+ by reasoning about what shell commands to run and executing them one or more at
2134
+ a time.
2135
+
2136
+ ## How You Work
2137
+
2138
+ You follow a ReAct (Reasoning + Acting) loop:
2139
+ 1. **Think**: Analyze the task and decide what commands to run next.
2140
+ 2. **Act**: Emit one or more shell commands inside a fenced code block.
2141
+ 3. **Observe**: Review the command output (stdout, stderr, exit code) that the
2142
+ framework feeds back to you.
2143
+ 4. Repeat until the task is complete, then signal your final answer (see below).
2144
+
2145
+ ## Emitting Commands
2146
+
2147
+ Place your shell commands inside a fenced sh code block. You may include
2148
+ multiple commands separated by newlines. Each command is executed
2149
+ sequentially in its own shell invocation on the user's macOS terminal:
2150
+
2151
+ \`\`\`sh
2152
+ echo "hello"
2153
+ \`\`\`
2154
+
2155
+ \`\`\`sh
2156
+ ls -la ~/Documents
2157
+ pwd
2158
+ \`\`\`
2159
+
2160
+ **Important**: Each code block is treated as a batch. Commands within a single
2161
+ block run sequentially. The output of the entire block is returned as one
2162
+ observation. If you need to inspect intermediate output before proceeding,
2163
+ use separate blocks across steps.
2164
+
2165
+ ## Rules
2166
+
2167
+ 1. **Safety first**: Before running anything destructive (rm, mv on important
2168
+ files, format commands, etc.), explain what you are about to do in your
2169
+ reasoning so the user can read it during the 5-second delay and abort with
2170
+ Ctrl+C if needed.
2171
+
2172
+ 2. **Signalling completion**: When the task is done, do NOT emit any more
2173
+ \`\`\`sh blocks. Instead, write your summary on a line that starts with
2174
+ the exact marker:
2175
+
2176
+ FINAL_ANSWER: <your summary here>
2177
+
2178
+ Everything after "FINAL_ANSWER: " (including multiple lines) is captured
2179
+ as your final answer. This is how you tell the framework you are finished.
2180
+
2181
+ 3. **Handle errors**: If a command fails (non-zero exit code or stderr output),
2182
+ analyze what went wrong and try a corrective approach. Do not repeat the
2183
+ exact same command.
2184
+
2185
+ 4. **Be explicit about paths**: Use absolute paths or \`cd\` explicitly. Do not
2186
+ assume the working directory persists between steps (it does not).
2187
+
2188
+ 5. **macOS conventions**: Use macOS / BSD variants of commands (e.g. \`gstat\`
2189
+ may not exist; use \`stat\` with the right flags). Prefer Homebrew paths
2190
+ (\`/opt/homebrew/bin\`) for installed tools. Use \`sw_vers\` for OS info.
2191
+
2192
+ 6. **Prefer streaming-friendly commands**: Avoid commands that buffer all output
2193
+ until completion. Prefer tools that print as they go.
2194
+
2195
+ 7. **No interactive prompts**: Do not run commands that wait for user input
2196
+ (e.g. \`ssh\` without key auth, interactive installers). If a command would
2197
+ prompt, pass flags to make it non-interactive or use \`yes |\` piping.
2198
+ ${delegationSection}
2199
+ ${customInstructions ? `## Additional Instructions
2200
+
2201
+ ${customInstructions}` : ""}
2202
+
2203
+ Begin. Think about the task, then emit your first shell command(s).`;
2204
+ }
2205
+
2206
+ // src/agents/TerminalAgent.ts
2207
+ var SH_BLOCK_REGEX = /```(?:sh|bash|shell|zsh)\n([\s\S]*?)```/g;
2208
+ var THOUGHT_REGEX2 = /(?:Thought|Reasoning|Think):\s*([\s\S]*?)(?=```|FINAL_ANSWER:|$)/i;
2209
+ var FINAL_ANSWER_MARKER = /^FINAL_ANSWER:\s*([\s\S]*)$/m;
2210
+ var NEWLINE = "\n";
2211
+ var TerminalAgent = class extends Agent {
2212
+ commandDelay;
2213
+ maxOutputLength;
2214
+ constructor(config) {
2215
+ super({ ...config, verboseLevel: 2 /* DEBUG */ });
2216
+ this.commandDelay = config.commandDelay ?? 5;
2217
+ this.maxOutputLength = config.maxOutputLength ?? 8e3;
2218
+ const keepTools = /* @__PURE__ */ new Map();
2219
+ for (const [name, tool] of this.tools) {
2220
+ if (tool.constructor.name === "AgentTool") {
2221
+ keepTools.set(name, tool);
2222
+ }
2223
+ }
2224
+ this.tools = keepTools;
2225
+ if (!this.tools.has("final_answer")) {
2226
+ this.tools.set("final_answer", new FinalAnswerTool());
2227
+ }
2228
+ }
2229
+ /**
2230
+ * Build the system prompt. Includes delegation info if sub-agents are present.
2231
+ */
2232
+ initializeSystemPrompt() {
2233
+ const agentTools = Array.from(this.tools.values()).filter(
2234
+ (t) => t.constructor.name === "AgentTool"
2235
+ );
2236
+ const hasSubAgents = agentTools.length > 0;
2237
+ const subAgentDescriptions = agentTools.map((t) => `- **${t.name}**: ${t.description}`).join("\n");
2238
+ return generateTerminalAgentSystemPrompt({
2239
+ customInstructions: this.config.customInstructions,
2240
+ hasSubAgents,
2241
+ subAgentDescriptions
2242
+ });
2243
+ }
2244
+ /**
2245
+ * Execute one step of the ReAct loop:
2246
+ * 1. Send messages to LLM (with tool defs for final_answer / delegation)
2247
+ * 2. Extract reasoning and ```sh blocks from the response
2248
+ * 3. If tool calls present (final_answer or delegation), process them
2249
+ * 4. Otherwise execute shell commands with the pre-execution delay
2250
+ * 5. Feed stdout/stderr/exit-code back as observation
2251
+ */
2252
+ async executeStep(memoryStep) {
2253
+ const messages = this.memory.toMessages();
2254
+ memoryStep.modelInputMessages = [...messages];
2255
+ const actionSteps = this.memory.getActionSteps();
2256
+ const prevStep = actionSteps.length >= 2 ? actionSteps[actionSteps.length - 2] : void 0;
2257
+ if (prevStep?.error) {
2258
+ messages.push({
2259
+ role: "user",
2260
+ content: `Your previous action encountered an error: ${prevStep.error.message}
2261
+ Please try a different approach.`
2262
+ });
2263
+ }
2264
+ const delegationTools = Array.from(this.tools.values()).filter((t) => t.constructor.name === "AgentTool");
2265
+ const toolDefinitions = delegationTools.map((t) => t.toOpenAITool());
2266
+ this.logger.subheader("Agent thinking...");
2267
+ const response = await this.generateResponse(messages, toolDefinitions);
2268
+ memoryStep.modelOutputMessage = response;
2269
+ memoryStep.tokenUsage = response.tokenUsage;
2270
+ const content = response.content ?? "";
2271
+ const thoughtMatch = content.match(THOUGHT_REGEX2);
2272
+ if (thoughtMatch) {
2273
+ this.logger.reasoning(thoughtMatch[1].trim());
2274
+ this.emitEvent("agent_thinking", { step: this.currentStep, content: thoughtMatch[1].trim() });
2275
+ }
2276
+ const finalMatch = content.match(FINAL_ANSWER_MARKER);
2277
+ if (finalMatch) {
2278
+ const answer = finalMatch[1].trim();
2279
+ this.logger.finalAnswer(answer);
2280
+ return { output: answer, isFinalAnswer: true };
2281
+ }
2282
+ if (response.toolCalls && response.toolCalls.length > 0) {
2283
+ memoryStep.toolCalls = response.toolCalls;
2284
+ const results = await this.processToolCalls(response.toolCalls);
2285
+ memoryStep.toolResults = results;
2286
+ for (const result of results) {
2287
+ if (result.error) {
2288
+ this.logger.error(`Tool ${result.toolName} failed: ${result.error}`);
2289
+ } else {
2290
+ const str = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
2291
+ this.logger.output(`[${result.toolName}]: ${str.slice(0, 500)}${str.length > 500 ? "..." : ""}`);
2292
+ }
2293
+ }
2294
+ const obs = results.map((r) => r.error ? `[${r.toolName}] Error: ${r.error}` : `[${r.toolName}] Result: ${typeof r.result === "string" ? r.result : JSON.stringify(r.result)}`).join("\n");
2295
+ memoryStep.observation = `Observation:
2296
+ ${obs}`;
2297
+ return { output: null, isFinalAnswer: false };
2298
+ }
2299
+ const blocks = [];
2300
+ let match;
2301
+ const regex = new RegExp(SH_BLOCK_REGEX.source, "g");
2302
+ while ((match = regex.exec(content)) !== null) {
2303
+ blocks.push(match[1].trim());
2304
+ }
2305
+ if (blocks.length === 0) {
2306
+ this.logger.warn("No shell commands or tool calls in response.");
2307
+ memoryStep.observation = "No shell command block was found in your response. Emit commands inside a ```sh code block, or call final_answer if the task is complete.";
2308
+ return { output: null, isFinalAnswer: false };
2309
+ }
2310
+ const allCommands = blocks.join("\n---\n");
2311
+ this.logger.code(allCommands, "sh");
2312
+ this.emitEvent("agent_observation", {
2313
+ step: this.currentStep,
2314
+ observation: `Pending commands:
2315
+ ${allCommands}`
2316
+ });
2317
+ this.logger.waiting(this.commandDelay);
2318
+ await this.sleep(this.commandDelay * 1e3);
2319
+ const observations = [];
2320
+ let hitError = false;
2321
+ for (let i = 0; i < blocks.length; i++) {
2322
+ const block = blocks[i];
2323
+ const commands = block.split(NEWLINE).filter((l) => l.trim() && !l.trim().startsWith("#"));
2324
+ this.logger.subheader(`Executing command block ${blocks.length > 1 ? `${i + 1}/${blocks.length}` : ""}...`);
2325
+ for (const cmd of commands) {
2326
+ this.logger.info(` $ ${cmd}`);
2327
+ const result = this.runCommand(cmd);
2328
+ if (result.stdout) {
2329
+ this.logger.logs(result.stdout);
2330
+ }
2331
+ if (result.stderr) {
2332
+ this.logger.error(`stderr: ${result.stderr}`);
2333
+ }
2334
+ observations.push(
2335
+ `$ ${cmd}
2336
+ ` + (result.stdout ? `stdout:
2337
+ ${result.stdout}` : "") + (result.stderr ? `stderr:
2338
+ ${result.stderr}` : "") + `exit code: ${result.exitCode}`
2339
+ );
2340
+ if (result.exitCode !== 0) {
2341
+ hitError = true;
2342
+ break;
2343
+ }
2344
+ }
2345
+ if (hitError) break;
2346
+ }
2347
+ const observation = observations.join("\n\n");
2348
+ memoryStep.observation = `Observation:
2349
+ ${observation}`;
2350
+ if (hitError) {
2351
+ memoryStep.error = new Error("Command exited with non-zero status. See observation for details.");
2352
+ }
2353
+ return { output: observation, isFinalAnswer: false };
2354
+ }
2355
+ /**
2356
+ * Run a single shell command, capture stdout/stderr, return structured result.
2357
+ */
2358
+ runCommand(cmd) {
2359
+ try {
2360
+ const stdout = (0, import_child_process.execSync)(cmd, {
2361
+ encoding: "utf8",
2362
+ stdio: ["pipe", "pipe", "pipe"],
2363
+ shell: "/bin/zsh",
2364
+ timeout: 12e4,
2365
+ // 2-minute per-command timeout
2366
+ maxBuffer: 50 * 1024 * 1024
2367
+ // 50 MB
2368
+ });
2369
+ return { stdout: this.truncateOutput(stdout), stderr: "", exitCode: 0 };
2370
+ } catch (err) {
2371
+ const e = err;
2372
+ return {
2373
+ stdout: this.truncateOutput(e.stdout ?? ""),
2374
+ stderr: this.truncateOutput(e.stderr ?? e.message),
2375
+ exitCode: e.status ?? 1
2376
+ };
2377
+ }
2378
+ }
2379
+ /**
2380
+ * Truncate long output, preserving head and tail so context stays useful.
2381
+ */
2382
+ truncateOutput(output) {
2383
+ if (output.length <= this.maxOutputLength) return output;
2384
+ const half = Math.floor(this.maxOutputLength / 2);
2385
+ const head = output.slice(0, half);
2386
+ const tail = output.slice(output.length - half);
2387
+ const omitted = output.length - this.maxOutputLength;
2388
+ return `${head}
2389
+
2390
+ ... [${omitted} characters omitted] ...
2391
+
2392
+ ${tail}`;
2393
+ }
2394
+ /**
2395
+ * Generate response, with streaming if available.
2396
+ */
2397
+ async generateResponse(messages, toolDefinitions) {
2398
+ if (this.config.streamOutputs && this.model.supportsStreaming() && this.model.generateStream) {
2399
+ let fullContent = "";
2400
+ const generator = this.model.generateStream(messages, {
2401
+ toolDefinitions,
2402
+ maxTokens: this.config.maxTokens,
2403
+ temperature: this.config.temperature
2404
+ });
2405
+ for await (const chunk of generator) {
2406
+ this.logger.streamChar(chunk);
2407
+ fullContent += chunk;
2408
+ }
2409
+ this.logger.streamEnd();
2410
+ return { role: "assistant", content: fullContent };
2411
+ }
2412
+ return this.model.generate(messages, {
2413
+ toolDefinitions,
2414
+ maxTokens: this.config.maxTokens,
2415
+ temperature: this.config.temperature
2416
+ });
2417
+ }
2418
+ /**
2419
+ * Process tool calls (final_answer or AgentTool delegation).
2420
+ */
2421
+ async processToolCalls(toolCalls) {
2422
+ const results = [];
2423
+ for (const tc of toolCalls) {
2424
+ const toolName = tc.function.name;
2425
+ const tool = this.tools.get(toolName);
2426
+ if (!tool) {
2427
+ results.push({ toolCallId: tc.id, toolName, result: null, error: `Unknown tool: ${toolName}` });
2428
+ continue;
2429
+ }
2430
+ let args;
2431
+ try {
2432
+ args = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
2433
+ } catch {
2434
+ results.push({ toolCallId: tc.id, toolName, result: null, error: "Failed to parse tool arguments" });
2435
+ continue;
2436
+ }
2437
+ this.logger.info(` Calling tool: ${toolName}(${JSON.stringify(args).slice(0, 100)}...)`);
2438
+ this.emitEvent("agent_tool_call", { step: this.currentStep, toolCallId: tc.id, toolName, arguments: args });
2439
+ try {
2440
+ const result = await tool.call(args);
2441
+ this.emitEvent("agent_tool_result", { step: this.currentStep, toolCallId: tc.id, toolName, result, duration: 0 });
2442
+ results.push({ toolCallId: tc.id, toolName, result });
2443
+ } catch (error) {
2444
+ const msg = `Tool execution error: ${error.message}`;
2445
+ this.emitEvent("agent_tool_result", { step: this.currentStep, toolCallId: tc.id, toolName, result: null, error: msg, duration: 0 });
2446
+ results.push({ toolCallId: tc.id, toolName, result: null, error: msg });
2447
+ }
2448
+ }
2449
+ return results;
2450
+ }
2451
+ /**
2452
+ * Override: force final answer via tool call format when max steps hit.
2453
+ */
2454
+ async provideFinalAnswer(task) {
2455
+ this.logger.subheader("Generating final answer from accumulated context");
2456
+ const messages = this.memory.toMessages();
2457
+ messages.push({
2458
+ role: "user",
2459
+ content: `You have reached the maximum number of steps. Based on your work so far, provide the best answer for the task: "${task}". Call the final_answer tool with your response.`
2460
+ });
2461
+ const toolDefinitions = [new FinalAnswerTool().toOpenAITool()];
2462
+ const response = await this.model.generate(messages, { toolDefinitions, maxTokens: this.config.maxTokens, temperature: this.config.temperature });
2463
+ if (response.toolCalls && response.toolCalls.length > 0) {
2464
+ try {
2465
+ const args = typeof response.toolCalls[0].function.arguments === "string" ? JSON.parse(response.toolCalls[0].function.arguments) : response.toolCalls[0].function.arguments;
2466
+ return args.answer;
2467
+ } catch {
2468
+ return response.content;
2469
+ }
2470
+ }
2471
+ return response.content;
2472
+ }
2473
+ };
2474
+
2111
2475
  // src/models/Model.ts
2112
2476
  var Model = class {
2113
2477
  /**
@@ -2795,7 +3159,7 @@ var ExaResearchTool = class extends Tool {
2795
3159
  while (Date.now() - startTime < this.maxPollTime) {
2796
3160
  attempts++;
2797
3161
  if (attempts > 1) {
2798
- await new Promise((resolve6) => setTimeout(resolve6, this.pollInterval));
3162
+ await new Promise((resolve7) => setTimeout(resolve7, this.pollInterval));
2799
3163
  }
2800
3164
  const statusResponse = await fetch(`https://api.exa.ai/research/v1/${researchId}`, {
2801
3165
  method: "GET",
@@ -2847,10 +3211,11 @@ var ExaResearchTool = class extends Tool {
2847
3211
  };
2848
3212
 
2849
3213
  // src/tools/ProxyTool.ts
2850
- var import_child_process2 = require("child_process");
3214
+ var import_child_process3 = require("child_process");
3215
+ var path6 = __toESM(require("path"));
2851
3216
 
2852
3217
  // src/utils/bunInstaller.ts
2853
- var import_child_process = require("child_process");
3218
+ var import_child_process2 = require("child_process");
2854
3219
  var path5 = __toESM(require("path"));
2855
3220
  var fs5 = __toESM(require("fs"));
2856
3221
  var os3 = __toESM(require("os"));
@@ -2871,7 +3236,7 @@ async function ensureBunAvailable() {
2871
3236
  "\n[smol-js] Bun is required to run custom tools but was not found. Installing Bun automatically...\n"
2872
3237
  );
2873
3238
  try {
2874
- (0, import_child_process.execSync)("curl --proto =https --tlsv1.2 -sSf https://bun.sh | bash", {
3239
+ (0, import_child_process2.execSync)("curl --proto =https --tlsv1.2 -sSf https://bun.sh | bash", {
2875
3240
  stdio: "inherit",
2876
3241
  shell: "/bin/bash",
2877
3242
  env: { ...process.env, HOME: os3.homedir() }
@@ -2896,7 +3261,7 @@ Details: ${err.message}`
2896
3261
  function whichBun() {
2897
3262
  try {
2898
3263
  const cmd = process.platform === "win32" ? "where bun" : "which bun";
2899
- const result = (0, import_child_process.execSync)(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
3264
+ const result = (0, import_child_process2.execSync)(cmd, { encoding: "utf8", stdio: "pipe" }).trim();
2900
3265
  const first = result.split("\n")[0]?.trim();
2901
3266
  if (first && fs5.existsSync(first)) return first;
2902
3267
  return null;
@@ -2906,10 +3271,12 @@ function whichBun() {
2906
3271
  }
2907
3272
 
2908
3273
  // src/tools/ProxyTool.ts
2909
- var TOOL_OUTPUT_PREFIX = "[TOOL_OUTPUT]";
2910
3274
  var TOOL_RESULT_PREFIX = "[TOOL_RESULT]";
2911
3275
  var TOOL_ERROR_PREFIX = "[TOOL_ERROR]";
2912
3276
  var DEFAULT_TOOL_TIMEOUT_MS = 6e4;
3277
+ function resolveHarnessPath() {
3278
+ return path6.resolve(__dirname, "..", "toolHarness.ts");
3279
+ }
2913
3280
  var ProxyTool = class extends Tool {
2914
3281
  name;
2915
3282
  description;
@@ -2918,6 +3285,7 @@ var ProxyTool = class extends Tool {
2918
3285
  toolPath;
2919
3286
  timeout;
2920
3287
  bunPath = null;
3288
+ harnessPath = null;
2921
3289
  constructor(config) {
2922
3290
  super();
2923
3291
  this.name = config.name;
@@ -2928,23 +3296,25 @@ var ProxyTool = class extends Tool {
2928
3296
  this.timeout = config.timeout ?? DEFAULT_TOOL_TIMEOUT_MS;
2929
3297
  }
2930
3298
  /**
2931
- * Ensure Bun is available before first invocation.
3299
+ * Ensure Bun is available and locate the harness before first invocation.
2932
3300
  */
2933
3301
  async setup() {
2934
3302
  this.bunPath = await ensureBunAvailable();
3303
+ this.harnessPath = resolveHarnessPath();
2935
3304
  this.isSetup = true;
2936
3305
  }
2937
3306
  /**
2938
- * Spawn the tool in a Bun child process, pass serialized args via CLI,
2939
- * stream stdout back as log lines, and parse the final result.
3307
+ * Spawn the harness in a Bun child process. The harness imports the tool,
3308
+ * calls execute(args), and writes the protocol lines. Any console.log from
3309
+ * the tool flows through stdout as plain lines.
2940
3310
  */
2941
3311
  async execute(args) {
2942
- if (!this.bunPath) {
3312
+ if (!this.bunPath || !this.harnessPath) {
2943
3313
  await this.setup();
2944
3314
  }
2945
3315
  const serializedArgs = JSON.stringify(args);
2946
- return new Promise((resolve6, reject) => {
2947
- const child = (0, import_child_process2.spawn)(this.bunPath, ["run", this.toolPath, serializedArgs], {
3316
+ return new Promise((resolve7, reject) => {
3317
+ const child = (0, import_child_process3.spawn)(this.bunPath, ["run", this.harnessPath, this.toolPath, serializedArgs], {
2948
3318
  stdio: ["pipe", "pipe", "pipe"],
2949
3319
  env: { ...process.env }
2950
3320
  });
@@ -3009,12 +3379,12 @@ ${logBuffer.join("\n")}
3009
3379
  [Tool result]
3010
3380
  `;
3011
3381
  if (typeof result === "string") {
3012
- resolve6(logPrefix + result);
3382
+ resolve7(logPrefix + result);
3013
3383
  } else {
3014
- resolve6({ logs: logBuffer.join("\n"), result });
3384
+ resolve7({ logs: logBuffer.join("\n"), result });
3015
3385
  }
3016
3386
  } else {
3017
- resolve6(result);
3387
+ resolve7(result);
3018
3388
  }
3019
3389
  return;
3020
3390
  }
@@ -3024,7 +3394,7 @@ ${logBuffer.join("\n")}
3024
3394
  `Custom tool "${this.name}" exited with code ${code}. Output: ${combined || "(none)"}`
3025
3395
  ));
3026
3396
  } else {
3027
- resolve6(combined || `Tool "${this.name}" produced no output.`);
3397
+ resolve7(combined || `Tool "${this.name}" produced no output.`);
3028
3398
  }
3029
3399
  });
3030
3400
  child.on("error", (err) => {
@@ -3035,7 +3405,7 @@ ${logBuffer.join("\n")}
3035
3405
  });
3036
3406
  });
3037
3407
  }
3038
- // --- internal line parser ---
3408
+ // --- line parser: protocol is spoken by harness, interpreted here ---
3039
3409
  processLine(line, handlers) {
3040
3410
  const trimmed = line.trimEnd();
3041
3411
  if (!trimmed) return;
@@ -3048,8 +3418,6 @@ ${logBuffer.join("\n")}
3048
3418
  }
3049
3419
  } else if (trimmed.startsWith(TOOL_ERROR_PREFIX)) {
3050
3420
  handlers.onError(trimmed.slice(TOOL_ERROR_PREFIX.length).trim());
3051
- } else if (trimmed.startsWith(TOOL_OUTPUT_PREFIX)) {
3052
- handlers.onOutput(trimmed.slice(TOOL_OUTPUT_PREFIX.length).trim());
3053
3421
  } else {
3054
3422
  handlers.onOutput(trimmed);
3055
3423
  }
@@ -3058,7 +3426,7 @@ ${logBuffer.join("\n")}
3058
3426
 
3059
3427
  // src/tools/CustomToolScanner.ts
3060
3428
  var fs6 = __toESM(require("fs"));
3061
- var path6 = __toESM(require("path"));
3429
+ var path7 = __toESM(require("path"));
3062
3430
  var METADATA_REGEX = /export\s+const\s+TOOL_METADATA\s*=\s*(\{[\s\S]*?\});\s*$/m;
3063
3431
  function scanCustomTools(folderPath) {
3064
3432
  if (!fs6.existsSync(folderPath)) {
@@ -3070,10 +3438,10 @@ function scanCustomTools(folderPath) {
3070
3438
  const discovered = [];
3071
3439
  for (const entry of entries) {
3072
3440
  if (entry.isDirectory()) continue;
3073
- const ext = path6.extname(entry.name).toLowerCase();
3441
+ const ext = path7.extname(entry.name).toLowerCase();
3074
3442
  if (ext !== ".ts" && ext !== ".js") continue;
3075
- const filePath = path6.resolve(folderPath, entry.name);
3076
- const baseName = path6.basename(entry.name, ext);
3443
+ const filePath = path7.resolve(folderPath, entry.name);
3444
+ const baseName = path7.basename(entry.name, ext);
3077
3445
  let metadata;
3078
3446
  try {
3079
3447
  metadata = extractMetadata(filePath);
@@ -3141,7 +3509,7 @@ function loadCustomTools(folderPath) {
3141
3509
 
3142
3510
  // src/orchestrator/YAMLLoader.ts
3143
3511
  var fs7 = __toESM(require("fs"));
3144
- var path7 = __toESM(require("path"));
3512
+ var path8 = __toESM(require("path"));
3145
3513
  var import_yaml = __toESM(require("yaml"));
3146
3514
  var TOOL_REGISTRY = {
3147
3515
  read_file: ReadFileTool,
@@ -3173,7 +3541,7 @@ var YAMLLoader = class {
3173
3541
  * Load a workflow from a YAML file path.
3174
3542
  */
3175
3543
  loadFromFile(filePath) {
3176
- const absolutePath = path7.isAbsolute(filePath) ? filePath : path7.resolve(process.cwd(), filePath);
3544
+ const absolutePath = path8.isAbsolute(filePath) ? filePath : path8.resolve(process.cwd(), filePath);
3177
3545
  if (!fs7.existsSync(absolutePath)) {
3178
3546
  throw new Error(`Workflow file not found: ${absolutePath}`);
3179
3547
  }
@@ -3317,32 +3685,24 @@ var YAMLLoader = class {
3317
3685
  }
3318
3686
  }
3319
3687
  const maxContextLength = definition.maxContextLength ?? globalMaxContextLength;
3688
+ const sharedConfig = {
3689
+ model,
3690
+ tools: agentTools,
3691
+ maxSteps: definition.maxSteps,
3692
+ customInstructions: definition.customInstructions,
3693
+ persistent: definition.persistent,
3694
+ maxContextLength,
3695
+ memoryStrategy: definition.memoryStrategy,
3696
+ maxTokens: definition.maxTokens,
3697
+ temperature: definition.temperature,
3698
+ name
3699
+ };
3320
3700
  if (definition.type === "CodeAgent") {
3321
- return new CodeAgent({
3322
- model,
3323
- tools: agentTools,
3324
- maxSteps: definition.maxSteps,
3325
- customInstructions: definition.customInstructions,
3326
- persistent: definition.persistent,
3327
- maxContextLength,
3328
- memoryStrategy: definition.memoryStrategy,
3329
- maxTokens: definition.maxTokens,
3330
- temperature: definition.temperature,
3331
- name
3332
- });
3701
+ return new CodeAgent(sharedConfig);
3702
+ } else if (definition.type === "TerminalAgent") {
3703
+ return new TerminalAgent(sharedConfig);
3333
3704
  } else {
3334
- return new ToolUseAgent({
3335
- model,
3336
- tools: agentTools,
3337
- maxSteps: definition.maxSteps,
3338
- customInstructions: definition.customInstructions,
3339
- persistent: definition.persistent,
3340
- maxContextLength,
3341
- memoryStrategy: definition.memoryStrategy,
3342
- maxTokens: definition.maxTokens,
3343
- temperature: definition.temperature,
3344
- name
3345
- });
3705
+ return new ToolUseAgent(sharedConfig);
3346
3706
  }
3347
3707
  }
3348
3708
  };
@@ -3742,6 +4102,7 @@ var Orchestrator = class {
3742
4102
  Orchestrator,
3743
4103
  ProxyTool,
3744
4104
  ReadFileTool,
4105
+ TerminalAgent,
3745
4106
  Tool,
3746
4107
  ToolUseAgent,
3747
4108
  UserInputTool,