@kimbho/kimbho-cli 0.1.4 → 0.1.5

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.cjs CHANGED
@@ -1147,7 +1147,7 @@ var require_command = __commonJS({
1147
1147
  "../../node_modules/commander/lib/command.js"(exports2) {
1148
1148
  var EventEmitter = require("node:events").EventEmitter;
1149
1149
  var childProcess = require("node:child_process");
1150
- var path6 = require("node:path");
1150
+ var path7 = require("node:path");
1151
1151
  var fs = require("node:fs");
1152
1152
  var process12 = require("node:process");
1153
1153
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -2147,9 +2147,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2147
2147
  let launchWithNode = false;
2148
2148
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2149
2149
  function findFile(baseDir, baseName) {
2150
- const localBin = path6.resolve(baseDir, baseName);
2150
+ const localBin = path7.resolve(baseDir, baseName);
2151
2151
  if (fs.existsSync(localBin)) return localBin;
2152
- if (sourceExt.includes(path6.extname(baseName))) return void 0;
2152
+ if (sourceExt.includes(path7.extname(baseName))) return void 0;
2153
2153
  const foundExt = sourceExt.find(
2154
2154
  (ext) => fs.existsSync(`${localBin}${ext}`)
2155
2155
  );
@@ -2167,17 +2167,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
2167
2167
  } catch {
2168
2168
  resolvedScriptPath = this._scriptPath;
2169
2169
  }
2170
- executableDir = path6.resolve(
2171
- path6.dirname(resolvedScriptPath),
2170
+ executableDir = path7.resolve(
2171
+ path7.dirname(resolvedScriptPath),
2172
2172
  executableDir
2173
2173
  );
2174
2174
  }
2175
2175
  if (executableDir) {
2176
2176
  let localFile = findFile(executableDir, executableFile);
2177
2177
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2178
- const legacyName = path6.basename(
2178
+ const legacyName = path7.basename(
2179
2179
  this._scriptPath,
2180
- path6.extname(this._scriptPath)
2180
+ path7.extname(this._scriptPath)
2181
2181
  );
2182
2182
  if (legacyName !== this._name) {
2183
2183
  localFile = findFile(
@@ -2188,7 +2188,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2188
2188
  }
2189
2189
  executableFile = localFile || executableFile;
2190
2190
  }
2191
- launchWithNode = sourceExt.includes(path6.extname(executableFile));
2191
+ launchWithNode = sourceExt.includes(path7.extname(executableFile));
2192
2192
  let proc;
2193
2193
  if (process12.platform !== "win32") {
2194
2194
  if (launchWithNode) {
@@ -3035,7 +3035,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3035
3035
  * @return {Command}
3036
3036
  */
3037
3037
  nameFromFilename(filename) {
3038
- this._name = path6.basename(filename, path6.extname(filename));
3038
+ this._name = path7.basename(filename, path7.extname(filename));
3039
3039
  return this;
3040
3040
  }
3041
3041
  /**
@@ -3049,9 +3049,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3049
3049
  * @param {string} [path]
3050
3050
  * @return {(string|null|Command)}
3051
3051
  */
3052
- executableDir(path7) {
3053
- if (path7 === void 0) return this._executableDir;
3054
- this._executableDir = path7;
3052
+ executableDir(path8) {
3053
+ if (path8 === void 0) return this._executableDir;
3054
+ this._executableDir = path8;
3055
3055
  return this;
3056
3056
  }
3057
3057
  /**
@@ -3346,7 +3346,7 @@ var {
3346
3346
  // package.json
3347
3347
  var package_default = {
3348
3348
  name: "@kimbho/kimbho-cli",
3349
- version: "0.1.4",
3349
+ version: "0.1.5",
3350
3350
  description: "Kimbho CLI is a terminal-native coding agent for planning, execution, and verification.",
3351
3351
  type: "module",
3352
3352
  engines: {
@@ -3414,9 +3414,12 @@ var AGENT_CATALOG = {
3414
3414
  brainRole: "fast",
3415
3415
  purpose: "Map the workspace, commands, conventions, and likely risk areas before implementation.",
3416
3416
  defaultTools: [
3417
+ "file.list",
3418
+ "file.search",
3417
3419
  "file.read",
3418
3420
  "shell.exec",
3419
- "git.status"
3421
+ "git.status",
3422
+ "git.diff"
3420
3423
  ],
3421
3424
  escalationTriggers: [
3422
3425
  "Unknown build entry points",
@@ -3429,6 +3432,8 @@ var AGENT_CATALOG = {
3429
3432
  brainRole: "planner",
3430
3433
  purpose: "Generate machine-readable plans, milestone graphs, and re-plan when reality changes.",
3431
3434
  defaultTools: [
3435
+ "file.list",
3436
+ "file.search",
3432
3437
  "file.read"
3433
3438
  ],
3434
3439
  escalationTriggers: [
@@ -3442,9 +3447,12 @@ var AGENT_CATALOG = {
3442
3447
  brainRole: "fast",
3443
3448
  purpose: "Select the next ready tasks, assign them to specialists, and supervise completion.",
3444
3449
  defaultTools: [
3450
+ "file.list",
3451
+ "file.search",
3445
3452
  "file.read",
3446
3453
  "shell.exec",
3447
- "tests.run"
3454
+ "tests.run",
3455
+ "git.diff"
3448
3456
  ],
3449
3457
  escalationTriggers: [
3450
3458
  "Blocked dependency chains",
@@ -3457,8 +3465,12 @@ var AGENT_CATALOG = {
3457
3465
  brainRole: "coder",
3458
3466
  purpose: "Implement and refine user-facing flows and integration points.",
3459
3467
  defaultTools: [
3468
+ "file.list",
3469
+ "file.search",
3460
3470
  "file.read",
3461
- "file.patch"
3471
+ "file.patch",
3472
+ "git.diff",
3473
+ "tests.run"
3462
3474
  ],
3463
3475
  escalationTriggers: [
3464
3476
  "Design-system mismatch",
@@ -3471,9 +3483,13 @@ var AGENT_CATALOG = {
3471
3483
  brainRole: "coder",
3472
3484
  purpose: "Implement backend behavior, contracts, and domain logic.",
3473
3485
  defaultTools: [
3486
+ "file.list",
3487
+ "file.search",
3474
3488
  "file.read",
3475
3489
  "file.patch",
3476
- "shell.exec"
3490
+ "shell.exec",
3491
+ "git.diff",
3492
+ "tests.run"
3477
3493
  ],
3478
3494
  escalationTriggers: [
3479
3495
  "Schema drift",
@@ -3486,9 +3502,13 @@ var AGENT_CATALOG = {
3486
3502
  brainRole: "coder",
3487
3503
  purpose: "Own schema design, migrations, and persistence safety.",
3488
3504
  defaultTools: [
3505
+ "file.list",
3506
+ "file.search",
3489
3507
  "file.read",
3490
3508
  "file.patch",
3491
- "shell.exec"
3509
+ "shell.exec",
3510
+ "git.diff",
3511
+ "tests.run"
3492
3512
  ],
3493
3513
  escalationTriggers: [
3494
3514
  "Data-destructive migration",
@@ -3501,9 +3521,13 @@ var AGENT_CATALOG = {
3501
3521
  brainRole: "coder",
3502
3522
  purpose: "Own local toolchain, packaging, CI, and environment plumbing.",
3503
3523
  defaultTools: [
3524
+ "file.list",
3525
+ "file.search",
3504
3526
  "file.read",
3505
3527
  "file.patch",
3506
- "shell.exec"
3528
+ "shell.exec",
3529
+ "git.diff",
3530
+ "tests.run"
3507
3531
  ],
3508
3532
  escalationTriggers: [
3509
3533
  "Dependency installation",
@@ -3516,9 +3540,12 @@ var AGENT_CATALOG = {
3516
3540
  brainRole: "coder",
3517
3541
  purpose: "Run verification, interpret failures, and produce targeted repair loops.",
3518
3542
  defaultTools: [
3543
+ "file.list",
3544
+ "file.search",
3519
3545
  "file.read",
3520
3546
  "shell.exec",
3521
- "tests.run"
3547
+ "tests.run",
3548
+ "git.diff"
3522
3549
  ],
3523
3550
  escalationTriggers: [
3524
3551
  "Flaky verification",
@@ -3531,7 +3558,10 @@ var AGENT_CATALOG = {
3531
3558
  brainRole: "reviewer",
3532
3559
  purpose: "Inspect diffs for correctness, risk, and missing coverage before integration.",
3533
3560
  defaultTools: [
3561
+ "file.list",
3562
+ "file.search",
3534
3563
  "file.read",
3564
+ "git.diff",
3535
3565
  "git.status",
3536
3566
  "tests.run"
3537
3567
  ],
@@ -3546,8 +3576,11 @@ var AGENT_CATALOG = {
3546
3576
  brainRole: "coder",
3547
3577
  purpose: "Combine accepted work, preserve repo consistency, and finalize the handoff state.",
3548
3578
  defaultTools: [
3579
+ "file.list",
3580
+ "file.search",
3549
3581
  "file.read",
3550
3582
  "file.patch",
3583
+ "git.diff",
3551
3584
  "git.status",
3552
3585
  "tests.run"
3553
3586
  ],
@@ -3562,7 +3595,7 @@ function listAgentProfiles() {
3562
3595
  return Object.values(AGENT_CATALOG).sort((left, right) => left.role.localeCompare(right.role));
3563
3596
  }
3564
3597
 
3565
- // ../agent-runtime/dist/orchestrator.js
3598
+ // ../agent-runtime/dist/autonomous.js
3566
3599
  var import_promises4 = require("node:fs/promises");
3567
3600
  var import_node_path4 = __toESM(require("node:path"), 1);
3568
3601
 
@@ -4044,8 +4077,8 @@ function getErrorMap() {
4044
4077
 
4045
4078
  // ../../node_modules/zod/v3/helpers/parseUtil.js
4046
4079
  var makeIssue = (params) => {
4047
- const { data, path: path6, errorMaps, issueData } = params;
4048
- const fullPath = [...path6, ...issueData.path || []];
4080
+ const { data, path: path7, errorMaps, issueData } = params;
4081
+ const fullPath = [...path7, ...issueData.path || []];
4049
4082
  const fullIssue = {
4050
4083
  ...issueData,
4051
4084
  path: fullPath
@@ -4161,11 +4194,11 @@ var errorUtil;
4161
4194
 
4162
4195
  // ../../node_modules/zod/v3/types.js
4163
4196
  var ParseInputLazyPath = class {
4164
- constructor(parent, value, path6, key) {
4197
+ constructor(parent, value, path7, key) {
4165
4198
  this._cachedPath = [];
4166
4199
  this.parent = parent;
4167
4200
  this.data = value;
4168
- this._path = path6;
4201
+ this._path = path7;
4169
4202
  this._key = key;
4170
4203
  }
4171
4204
  get path() {
@@ -7837,6 +7870,8 @@ var SessionEventTypeSchema = external_exports.enum([
7837
7870
  "task-started",
7838
7871
  "task-completed",
7839
7872
  "task-blocked",
7873
+ "task-handed-off",
7874
+ "task-paused",
7840
7875
  "note"
7841
7876
  ]);
7842
7877
  var SessionEventSchema = external_exports.object({
@@ -8164,1294 +8199,2137 @@ async function loadLatestSession(cwd = process.cwd()) {
8164
8199
  return loadSession(import_node_path2.default.join(sessionsDir, latest));
8165
8200
  }
8166
8201
 
8167
- // ../tools/dist/types.js
8168
- var ToolPermissionSchema = external_exports.enum([
8169
- "safe",
8170
- "approval-required",
8171
- "destructive"
8172
- ]);
8173
- var ToolDescriptorSchema = external_exports.object({
8174
- id: external_exports.string().min(1),
8175
- description: external_exports.string().min(1),
8176
- permission: ToolPermissionSchema.default("safe"),
8177
- timeoutMs: external_exports.number().int().positive(),
8178
- retryable: external_exports.boolean().default(false),
8179
- producesArtifacts: external_exports.boolean().default(false),
8180
- allowedRoles: external_exports.array(AgentRoleSchema).default([])
8181
- });
8182
-
8183
- // ../tools/dist/registry.js
8184
- var BUILTIN_TOOLS = [
8202
+ // ../brains/dist/templates.js
8203
+ var BUILTIN_PROVIDER_TEMPLATES = [
8185
8204
  {
8186
- id: "file.read",
8187
- description: "Read a file from the workspace.",
8188
- permission: "safe",
8189
- timeoutMs: 5e3,
8190
- retryable: false,
8191
- producesArtifacts: false,
8192
- allowedRoles: [
8193
- "session-orchestrator",
8194
- "repo-analyst",
8195
- "planner",
8196
- "execution-manager",
8197
- "frontend-specialist",
8198
- "backend-specialist",
8199
- "database-specialist",
8200
- "infra-specialist",
8201
- "test-debugger",
8202
- "reviewer",
8203
- "integrator"
8204
- ]
8205
+ id: "openai",
8206
+ label: "OpenAI",
8207
+ driver: "openai-responses",
8208
+ defaultBaseUrl: "https://api.openai.com/v1",
8209
+ defaultApiKeyEnv: "OPENAI_API_KEY",
8210
+ defaultModel: "gpt-5",
8211
+ notes: "Uses the OpenAI Responses API."
8205
8212
  },
8206
8213
  {
8207
- id: "file.patch",
8208
- description: "Apply a structured patch to workspace files.",
8209
- permission: "approval-required",
8210
- timeoutMs: 1e4,
8211
- retryable: true,
8212
- producesArtifacts: true,
8213
- allowedRoles: [
8214
- "frontend-specialist",
8215
- "backend-specialist",
8216
- "database-specialist",
8217
- "infra-specialist",
8218
- "test-debugger",
8219
- "integrator"
8220
- ]
8214
+ id: "anthropic",
8215
+ label: "Anthropic",
8216
+ driver: "anthropic-messages",
8217
+ defaultBaseUrl: "https://api.anthropic.com/v1/messages",
8218
+ defaultApiKeyEnv: "ANTHROPIC_API_KEY",
8219
+ notes: "Uses the Anthropic Messages API."
8221
8220
  },
8222
8221
  {
8223
- id: "shell.exec",
8224
- description: "Run a shell command inside the workspace.",
8225
- permission: "approval-required",
8226
- timeoutMs: 12e4,
8227
- retryable: true,
8228
- producesArtifacts: true,
8229
- allowedRoles: [
8230
- "repo-analyst",
8231
- "execution-manager",
8232
- "backend-specialist",
8233
- "infra-specialist",
8234
- "test-debugger",
8235
- "reviewer",
8236
- "integrator"
8237
- ]
8222
+ id: "openrouter",
8223
+ label: "OpenRouter",
8224
+ driver: "openai-compatible",
8225
+ defaultBaseUrl: "https://openrouter.ai/api/v1",
8226
+ defaultApiKeyEnv: "OPENROUTER_API_KEY",
8227
+ notes: "Routes through the OpenAI-compatible OpenRouter API."
8238
8228
  },
8239
8229
  {
8240
- id: "git.status",
8241
- description: "Inspect the current git working tree and diff state.",
8242
- permission: "safe",
8243
- timeoutMs: 1e4,
8244
- retryable: true,
8245
- producesArtifacts: false,
8246
- allowedRoles: [
8247
- "session-orchestrator",
8248
- "repo-analyst",
8249
- "execution-manager",
8250
- "reviewer",
8251
- "integrator"
8252
- ]
8230
+ id: "ollama",
8231
+ label: "Ollama",
8232
+ driver: "ollama",
8233
+ defaultBaseUrl: "http://localhost:11434",
8234
+ defaultModel: "qwen2.5-coder:7b",
8235
+ notes: "Runs against a local Ollama server."
8253
8236
  },
8254
8237
  {
8255
- id: "tests.run",
8256
- description: "Execute verification commands such as tests, linting, and builds.",
8257
- permission: "approval-required",
8258
- timeoutMs: 3e5,
8259
- retryable: true,
8260
- producesArtifacts: true,
8261
- allowedRoles: [
8262
- "execution-manager",
8263
- "test-debugger",
8264
- "reviewer",
8265
- "integrator"
8266
- ]
8238
+ id: "lmstudio",
8239
+ label: "LM Studio",
8240
+ driver: "openai-compatible",
8241
+ defaultBaseUrl: "http://localhost:1234/v1",
8242
+ notes: "Works with LM Studio's local OpenAI-compatible server."
8267
8243
  }
8268
8244
  ];
8269
- var ToolRegistry = class {
8270
- tools = /* @__PURE__ */ new Map();
8271
- constructor(seed = BUILTIN_TOOLS) {
8272
- for (const descriptor of seed) {
8273
- const normalized = ToolDescriptorSchema.parse(descriptor);
8274
- this.tools.set(normalized.id, normalized);
8275
- }
8245
+ function listProviderTemplates() {
8246
+ return BUILTIN_PROVIDER_TEMPLATES.slice().sort((left, right) => left.id.localeCompare(right.id));
8247
+ }
8248
+ function findProviderTemplate(templateId) {
8249
+ return BUILTIN_PROVIDER_TEMPLATES.find((template) => template.id === templateId);
8250
+ }
8251
+ function trimTrailingSlash(value) {
8252
+ return value.replace(/\/+$/, "");
8253
+ }
8254
+ function normalizeTemplateBaseUrl(templateId, baseUrl) {
8255
+ if (!baseUrl) {
8256
+ return void 0;
8276
8257
  }
8277
- list() {
8278
- return Array.from(this.tools.values()).sort((left, right) => left.id.localeCompare(right.id));
8258
+ const normalized = trimTrailingSlash(baseUrl);
8259
+ if (templateId !== "lmstudio") {
8260
+ return normalized;
8279
8261
  }
8280
- get(id) {
8281
- return this.tools.get(id);
8262
+ try {
8263
+ const parsed = new URL(normalized);
8264
+ if (!parsed.pathname || parsed.pathname === "/") {
8265
+ parsed.pathname = "/v1";
8266
+ return trimTrailingSlash(parsed.toString());
8267
+ }
8268
+ } catch {
8269
+ return normalized;
8282
8270
  }
8283
- byRole(role) {
8284
- return this.list().filter((tool) => tool.allowedRoles.includes(role));
8271
+ return normalized;
8272
+ }
8273
+ function buildProviderFromTemplate(templateId, options = {}) {
8274
+ const template = findProviderTemplate(templateId);
8275
+ if (!template) {
8276
+ throw new Error(`Unknown provider template "${templateId}".`);
8285
8277
  }
8286
- };
8287
- function createDefaultToolRegistry() {
8288
- return new ToolRegistry(BUILTIN_TOOLS);
8278
+ return ProviderDefinitionSchema.parse({
8279
+ id: options.providerId ?? template.id,
8280
+ label: options.label ?? template.label,
8281
+ driver: template.driver,
8282
+ ...normalizeTemplateBaseUrl(templateId, options.baseUrl) ?? template.defaultBaseUrl ? {
8283
+ baseUrl: normalizeTemplateBaseUrl(templateId, options.baseUrl) ?? template.defaultBaseUrl
8284
+ } : {},
8285
+ ...options.apiKeyEnv ?? template.defaultApiKeyEnv ? {
8286
+ apiKeyEnv: options.apiKeyEnv ?? template.defaultApiKeyEnv
8287
+ } : {},
8288
+ ...options.model ?? template.defaultModel ? {
8289
+ defaultModel: options.model ?? template.defaultModel
8290
+ } : {},
8291
+ ...options.models && options.models.length > 0 ? {
8292
+ models: options.models
8293
+ } : {}
8294
+ });
8289
8295
  }
8290
8296
 
8291
- // ../tools/dist/runtime.js
8297
+ // ../brains/dist/registry.js
8292
8298
  var import_promises3 = require("node:fs/promises");
8293
8299
  var import_node_path3 = __toESM(require("node:path"), 1);
8294
- var import_node_process = __toESM(require("node:process"), 1);
8295
- var import_node_child_process = require("node:child_process");
8296
- var import_node_os = require("node:os");
8297
- var DEFAULT_CAPTURE_LIMIT = 16e3;
8298
- function truncateOutput(value) {
8299
- if (!value) {
8300
- return value;
8301
- }
8302
- if (value.length <= DEFAULT_CAPTURE_LIMIT) {
8303
- return value;
8300
+ var import_node_url = require("node:url");
8301
+ function resolveApiKey(definition) {
8302
+ if (!definition.apiKeyEnv) {
8303
+ return null;
8304
8304
  }
8305
- const omitted = value.length - DEFAULT_CAPTURE_LIMIT;
8306
- return `${value.slice(0, DEFAULT_CAPTURE_LIMIT)}
8307
- ... [truncated ${omitted} chars]`;
8305
+ return process.env[definition.apiKeyEnv] ?? null;
8308
8306
  }
8309
- function resolveWorkspacePath(cwd, filePath) {
8310
- const resolved = import_node_path3.default.resolve(cwd, filePath);
8311
- const relative = import_node_path3.default.relative(cwd, resolved);
8312
- if (relative.startsWith("..") || import_node_path3.default.isAbsolute(relative)) {
8313
- throw new Error(`Path "${filePath}" escapes the workspace.`);
8307
+ function makeAbortSignal(timeoutMs) {
8308
+ const controller = new AbortController();
8309
+ setTimeout(() => controller.abort(), timeoutMs).unref?.();
8310
+ return controller.signal;
8311
+ }
8312
+ var GENERATION_TIMEOUT_MS = 12e4;
8313
+ async function requestJson(url, init, timeoutMs = 3e4) {
8314
+ const response = await fetch(url, {
8315
+ ...init,
8316
+ signal: makeAbortSignal(timeoutMs)
8317
+ });
8318
+ if (!response.ok) {
8319
+ throw new Error(`Request failed with ${response.status} ${response.statusText}`);
8314
8320
  }
8315
- return resolved;
8321
+ return response.json();
8316
8322
  }
8317
- async function runSpawn(command, args, cwd, timeoutMs) {
8318
- return new Promise((resolve, reject) => {
8319
- const child = (0, import_node_child_process.spawn)(command, args, {
8320
- cwd,
8321
- env: import_node_process.default.env,
8322
- stdio: [
8323
- "ignore",
8324
- "pipe",
8325
- "pipe"
8326
- ]
8327
- });
8328
- const stdout = [];
8329
- const stderr = [];
8330
- let settled = false;
8331
- let timedOut = false;
8332
- const timer = setTimeout(() => {
8333
- timedOut = true;
8334
- child.kill("SIGTERM");
8335
- setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
8336
- }, timeoutMs);
8337
- child.stdout.on("data", (chunk) => {
8338
- stdout.push(String(chunk));
8339
- });
8340
- child.stderr.on("data", (chunk) => {
8341
- stderr.push(String(chunk));
8342
- });
8343
- child.on("error", (error) => {
8344
- if (settled) {
8345
- return;
8346
- }
8347
- settled = true;
8348
- clearTimeout(timer);
8349
- reject(error);
8350
- });
8351
- child.on("close", (code) => {
8352
- if (settled) {
8353
- return;
8354
- }
8355
- settled = true;
8356
- clearTimeout(timer);
8357
- resolve({
8358
- code,
8359
- stdout: stdout.join(""),
8360
- stderr: stderr.join(""),
8361
- timedOut
8362
- });
8363
- });
8364
- });
8323
+ function trimTrailingSlash2(value) {
8324
+ return value.replace(/\/+$/, "");
8365
8325
  }
8366
- async function runShellCommand(toolId, command, cwd, timeoutMs) {
8367
- const shell = import_node_process.default.env.SHELL ?? "/bin/sh";
8368
- const result = await runSpawn(shell, [
8369
- "-lc",
8370
- command
8371
- ], cwd, timeoutMs);
8372
- const success = !result.timedOut && result.code === 0;
8373
- const summary = result.timedOut ? `Command timed out after ${timeoutMs}ms.` : success ? `Command completed successfully.` : `Command exited with code ${result.code ?? "unknown"}.`;
8374
- return ToolResultSchema.parse({
8375
- toolId,
8376
- success,
8377
- summary,
8378
- stdout: truncateOutput(result.stdout),
8379
- stderr: truncateOutput(result.stderr)
8380
- });
8326
+ function joinUrl(baseUrl, suffix) {
8327
+ const normalizedBase = trimTrailingSlash2(baseUrl);
8328
+ const normalizedSuffix = suffix.startsWith("/") ? suffix : `/${suffix}`;
8329
+ return `${normalizedBase}${normalizedSuffix}`;
8381
8330
  }
8382
- async function executeFileRead(input, context) {
8383
- const rawPath = typeof input.path === "string" ? input.path : null;
8384
- if (!rawPath) {
8385
- throw new Error("file.read requires a string path.");
8386
- }
8387
- const targetPath = resolveWorkspacePath(context.cwd, rawPath);
8388
- const contents = await (0, import_promises3.readFile)(targetPath, "utf8");
8389
- return ToolResultSchema.parse({
8390
- toolId: "file.read",
8391
- success: true,
8392
- summary: `Read ${import_node_path3.default.relative(context.cwd, targetPath) || import_node_path3.default.basename(targetPath)}.`,
8393
- stdout: truncateOutput(contents),
8394
- artifacts: [
8395
- targetPath
8396
- ]
8397
- });
8331
+ function isOpenRouter(definition) {
8332
+ return definition.baseUrl?.includes("openrouter.ai") ?? false;
8398
8333
  }
8399
- async function executeShell(input, context, timeoutMs) {
8400
- const command = typeof input.command === "string" ? input.command : null;
8401
- if (!command) {
8402
- throw new Error("shell.exec requires a command string.");
8334
+ function buildProviderHeaders(definition, apiKey, includeJsonContentType = true) {
8335
+ const headers = {
8336
+ ...definition.headers
8337
+ };
8338
+ if (includeJsonContentType) {
8339
+ headers["content-type"] = "application/json";
8403
8340
  }
8404
- return runShellCommand("shell.exec", command, context.cwd, timeoutMs);
8341
+ if (apiKey) {
8342
+ headers.authorization = `Bearer ${apiKey}`;
8343
+ }
8344
+ if (isOpenRouter(definition)) {
8345
+ const siteUrl = process.env.OPENROUTER_SITE_URL;
8346
+ const appName = process.env.OPENROUTER_APP_NAME ?? "Kimbho CLI";
8347
+ if (siteUrl) {
8348
+ headers["HTTP-Referer"] = siteUrl;
8349
+ }
8350
+ headers["X-Title"] = appName;
8351
+ }
8352
+ return headers;
8405
8353
  }
8406
- async function executeGitStatus(_input, context, timeoutMs) {
8407
- return runShellCommand("git.status", "git status --short --branch", context.cwd, timeoutMs);
8354
+ function filterModels(models, input = {}) {
8355
+ const normalized = input.search?.trim().toLowerCase();
8356
+ const filtered = normalized ? models.filter((model) => [model.id, model.name].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes(normalized))) : models;
8357
+ return typeof input.limit === "number" ? filtered.slice(0, input.limit) : filtered;
8408
8358
  }
8409
- async function executeTests(input, context, timeoutMs) {
8410
- const command = typeof input.command === "string" && input.command.trim().length > 0 ? input.command : "npm test";
8411
- const result = await runShellCommand("tests.run", command, context.cwd, timeoutMs);
8412
- return ToolResultSchema.parse({
8413
- ...result,
8414
- summary: result.success ? `Verification command passed: ${command}` : `Verification command failed: ${command}`
8359
+ function mapOpenAIStyleModels(providerId, payload, input) {
8360
+ const response = payload;
8361
+ const data = Array.isArray(response.data) ? response.data : [];
8362
+ const models = data.flatMap((item) => {
8363
+ if (typeof item !== "object" || item === null) {
8364
+ return [];
8365
+ }
8366
+ const record = item;
8367
+ const pricing = typeof record.pricing === "object" && record.pricing !== null ? record.pricing : null;
8368
+ const architecture = typeof record.architecture === "object" && record.architecture !== null ? record.architecture : null;
8369
+ return [
8370
+ ProviderModelSchema.parse({
8371
+ id: String(record.id ?? ""),
8372
+ ...typeof record.name === "string" ? {
8373
+ name: record.name
8374
+ } : {},
8375
+ ...typeof record.description === "string" ? {
8376
+ description: record.description
8377
+ } : {},
8378
+ ...typeof record.context_length === "number" ? {
8379
+ contextLength: record.context_length
8380
+ } : {},
8381
+ ...pricing && typeof pricing.prompt === "string" ? {
8382
+ promptPrice: pricing.prompt
8383
+ } : {},
8384
+ ...pricing && typeof pricing.completion === "string" ? {
8385
+ completionPrice: pricing.completion
8386
+ } : {},
8387
+ ...architecture && typeof architecture.modality === "string" ? {
8388
+ modality: architecture.modality
8389
+ } : {},
8390
+ providerId
8391
+ })
8392
+ ];
8415
8393
  });
8394
+ return filterModels(models, input);
8416
8395
  }
8417
- async function executeFilePatch(input, context, timeoutMs) {
8418
- const patch = typeof input.patch === "string" ? input.patch : null;
8419
- if (!patch) {
8420
- throw new Error("file.patch requires a unified diff in the patch field.");
8396
+ function mapAnthropicModels(providerId, payload, input) {
8397
+ const response = payload;
8398
+ const data = Array.isArray(response.data) ? response.data : [];
8399
+ const models = data.flatMap((item) => {
8400
+ if (typeof item !== "object" || item === null) {
8401
+ return [];
8402
+ }
8403
+ const record = item;
8404
+ return [
8405
+ ProviderModelSchema.parse({
8406
+ id: String(record.id ?? ""),
8407
+ ...typeof record.display_name === "string" ? {
8408
+ name: record.display_name
8409
+ } : {},
8410
+ providerId
8411
+ })
8412
+ ];
8413
+ });
8414
+ return filterModels(models, input);
8415
+ }
8416
+ function mapOllamaModels(providerId, payload, input) {
8417
+ const response = payload;
8418
+ const data = Array.isArray(response.models) ? response.models : [];
8419
+ const models = data.flatMap((item) => {
8420
+ if (typeof item !== "object" || item === null) {
8421
+ return [];
8422
+ }
8423
+ const record = item;
8424
+ const details = typeof record.details === "object" && record.details !== null ? record.details : null;
8425
+ const id = typeof record.model === "string" ? record.model : typeof record.name === "string" ? record.name : "";
8426
+ return [
8427
+ ProviderModelSchema.parse({
8428
+ id,
8429
+ ...typeof record.name === "string" ? {
8430
+ name: record.name
8431
+ } : {},
8432
+ ...details && typeof details.family === "string" ? {
8433
+ modality: details.family
8434
+ } : {},
8435
+ ...details && typeof details.parameter_size === "string" ? {
8436
+ description: details.parameter_size
8437
+ } : {},
8438
+ providerId
8439
+ })
8440
+ ];
8441
+ });
8442
+ return filterModels(models, input);
8443
+ }
8444
+ function toChatMessages(input) {
8445
+ const messages = [];
8446
+ if (input.systemPrompt) {
8447
+ messages.push({
8448
+ role: "system",
8449
+ content: input.systemPrompt
8450
+ });
8421
8451
  }
8422
- const tempDir = await (0, import_promises3.mkdtemp)(import_node_path3.default.join((0, import_node_os.tmpdir)(), "kimbho-patch-"));
8423
- const patchPath = import_node_path3.default.join(tempDir, "change.patch");
8424
- try {
8425
- await (0, import_promises3.writeFile)(patchPath, patch, "utf8");
8426
- const result = await runSpawn("git", [
8427
- "apply",
8428
- "--recount",
8429
- "--whitespace=nowarn",
8430
- patchPath
8431
- ], context.cwd, timeoutMs);
8432
- const success = !result.timedOut && result.code === 0;
8433
- return ToolResultSchema.parse({
8434
- toolId: "file.patch",
8435
- success,
8436
- summary: success ? "Patch applied successfully." : result.timedOut ? `Patch timed out after ${timeoutMs}ms.` : `Patch failed with code ${result.code ?? "unknown"}.`,
8437
- stdout: truncateOutput(result.stdout),
8438
- stderr: truncateOutput(result.stderr),
8439
- artifacts: []
8452
+ if (input.messages && input.messages.length > 0) {
8453
+ messages.push(...input.messages);
8454
+ } else if (input.userPrompt) {
8455
+ messages.push({
8456
+ role: "user",
8457
+ content: input.userPrompt
8440
8458
  });
8441
- } finally {
8442
- await (0, import_promises3.rm)(tempDir, { recursive: true, force: true });
8443
8459
  }
8460
+ return messages;
8444
8461
  }
8445
- var ToolRuntime = class {
8446
- registry;
8447
- executors;
8448
- constructor(registry = createDefaultToolRegistry()) {
8449
- this.registry = registry;
8450
- this.executors = /* @__PURE__ */ new Map([
8451
- [
8452
- "file.read",
8453
- (input, context) => executeFileRead(input, context)
8454
- ],
8455
- [
8456
- "file.patch",
8457
- executeFilePatch
8458
- ],
8459
- [
8460
- "shell.exec",
8461
- executeShell
8462
- ],
8463
- [
8464
- "git.status",
8465
- executeGitStatus
8466
- ],
8467
- [
8468
- "tests.run",
8469
- executeTests
8470
- ]
8471
- ]);
8462
+ function toPromptText(input) {
8463
+ const messages = toChatMessages(input);
8464
+ return messages.map((message) => `${message.role.toUpperCase()}: ${message.content}`).join("\n\n");
8465
+ }
8466
+ function requireModel(definition, input) {
8467
+ const model = input.model ?? definition.defaultModel;
8468
+ if (!model) {
8469
+ throw new Error(`Provider "${definition.id}" does not have a resolved model.`);
8472
8470
  }
8473
- async run(toolId, input, context) {
8474
- const descriptor = this.registry.get(toolId);
8475
- if (!descriptor) {
8476
- throw new Error(`Unknown tool "${toolId}".`);
8477
- }
8478
- const executor = this.executors.get(toolId);
8479
- if (!executor) {
8480
- throw new Error(`No executor registered for "${toolId}".`);
8481
- }
8482
- return executor(input, context, descriptor.timeoutMs);
8483
- }
8484
- };
8485
-
8486
- // ../agent-runtime/dist/orchestrator.js
8487
- function createSessionId() {
8488
- return `session-${Date.now()}`;
8489
- }
8490
- function createEventId(type, taskId) {
8491
- return `${type}-${taskId ?? "session"}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
8492
- }
8493
- function isTaskReady(task, completedTaskIds) {
8494
- return task.status === "pending" && task.dependsOn.every((taskId) => completedTaskIds.has(taskId));
8495
- }
8496
- function completedTaskIdsFromPlan(plan) {
8497
- return new Set(flattenPlanTasks(plan).filter((task) => task.status === "completed").map((task) => task.id));
8471
+ return model;
8498
8472
  }
8499
- function deriveStatus(plan, readyTasks, blockedTasks) {
8500
- const tasks = flattenPlanTasks(plan);
8501
- const completed = tasks.filter((task) => task.status === "completed").length;
8502
- if (completed === tasks.length && tasks.length > 0) {
8503
- return "completed";
8504
- }
8505
- if (readyTasks.length > 0) {
8506
- return completed > 0 ? "running" : "ready";
8507
- }
8508
- if (blockedTasks.length > 0) {
8509
- return "blocked";
8473
+ var OpenAIResponsesProvider = class {
8474
+ definition;
8475
+ constructor(definition) {
8476
+ this.definition = ProviderDefinitionSchema.parse(definition);
8510
8477
  }
8511
- return "planned";
8512
- }
8513
- function updateTaskStatus(plan, taskId, status) {
8514
- const milestones = plan.milestones.map((milestone) => ({
8515
- ...milestone,
8516
- tasks: milestone.tasks.map((task) => task.id === taskId ? {
8517
- ...task,
8518
- status
8519
- } : task)
8520
- }));
8521
- return {
8522
- ...plan,
8523
- milestones
8524
- };
8525
- }
8526
- function maybeAppendNote(notes, note) {
8527
- return notes.at(-1) === note ? notes : [
8528
- ...notes,
8529
- note
8530
- ];
8531
- }
8532
- function renderToolResultSection(results) {
8533
- return results.map((result) => {
8534
- const lines = [
8535
- `## ${result.toolId}`,
8536
- `- success: ${result.success ? "yes" : "no"}`,
8537
- `- summary: ${result.summary}`
8538
- ];
8539
- if (result.stdout) {
8540
- lines.push("");
8541
- lines.push("```text");
8542
- lines.push(result.stdout);
8543
- lines.push("```");
8478
+ async healthCheck() {
8479
+ const apiKey = resolveApiKey(this.definition);
8480
+ if (!apiKey) {
8481
+ return {
8482
+ ok: false,
8483
+ message: `Missing ${this.definition.apiKeyEnv ?? "API key env var"}`
8484
+ };
8544
8485
  }
8545
- if (result.stderr) {
8546
- lines.push("");
8547
- lines.push("```text");
8548
- lines.push(result.stderr);
8549
- lines.push("```");
8486
+ return {
8487
+ ok: true,
8488
+ message: `${this.definition.baseUrl ?? "https://api.openai.com/v1"} (${this.definition.defaultModel ?? "model not set"})`
8489
+ };
8490
+ }
8491
+ async listModels(input) {
8492
+ const apiKey = resolveApiKey(this.definition);
8493
+ if (!apiKey) {
8494
+ throw new Error(`Missing ${this.definition.apiKeyEnv ?? "API key env var"} for provider "${this.definition.id}".`);
8550
8495
  }
8551
- return lines.join("\n");
8552
- }).join("\n\n");
8553
- }
8554
- function extractPackageScripts(toolResults) {
8555
- const packageResult = toolResults.find((result) => result.artifacts.some((artifact) => artifact.endsWith("package.json")) && result.stdout);
8556
- if (!packageResult?.stdout) {
8557
- return [];
8496
+ const baseUrl = this.definition.baseUrl ?? "https://api.openai.com/v1";
8497
+ const payload = await requestJson(joinUrl(baseUrl, "/models"), {
8498
+ method: "GET",
8499
+ headers: buildProviderHeaders(this.definition, apiKey, false)
8500
+ });
8501
+ return mapOpenAIStyleModels(this.definition.id, payload, input);
8558
8502
  }
8559
- try {
8560
- const parsed = JSON.parse(packageResult.stdout);
8561
- return Object.keys(parsed.scripts ?? {}).sort();
8562
- } catch {
8563
- return [];
8503
+ async generateText(input) {
8504
+ const apiKey = resolveApiKey(this.definition);
8505
+ if (!apiKey) {
8506
+ throw new Error(`Missing ${this.definition.apiKeyEnv ?? "API key env var"} for provider "${this.definition.id}".`);
8507
+ }
8508
+ const baseUrl = this.definition.baseUrl ?? "https://api.openai.com/v1";
8509
+ const model = requireModel(this.definition, input);
8510
+ const messages = toChatMessages(input).map((message) => ({
8511
+ role: message.role,
8512
+ content: [
8513
+ {
8514
+ type: "input_text",
8515
+ text: message.content
8516
+ }
8517
+ ]
8518
+ }));
8519
+ const response = await requestJson(`${baseUrl}/responses`, {
8520
+ method: "POST",
8521
+ headers: {
8522
+ ...buildProviderHeaders(this.definition, apiKey)
8523
+ },
8524
+ body: JSON.stringify({
8525
+ model,
8526
+ input: messages,
8527
+ ...typeof input.temperature === "number" ? {
8528
+ temperature: input.temperature
8529
+ } : {},
8530
+ ...typeof input.maxTokens === "number" ? {
8531
+ max_output_tokens: input.maxTokens
8532
+ } : {}
8533
+ })
8534
+ }, GENERATION_TIMEOUT_MS);
8535
+ const text = typeof response.output_text === "string" ? response.output_text : Array.isArray(response.output) ? response.output.flatMap((item) => typeof item === "object" && item !== null && "content" in item && Array.isArray(item.content) ? item.content : []).flatMap((item) => typeof item === "object" && item !== null && "text" in item ? [
8536
+ String(item.text)
8537
+ ] : []).join("\n") : "";
8538
+ return {
8539
+ text,
8540
+ model
8541
+ };
8564
8542
  }
8565
- }
8566
- var ExecutionOrchestrator = class {
8567
- toolRegistry;
8568
- toolRuntime;
8569
- constructor(toolRegistry = createDefaultToolRegistry(), toolRuntime = new ToolRuntime(toolRegistry)) {
8570
- this.toolRegistry = toolRegistry;
8571
- this.toolRuntime = toolRuntime;
8543
+ };
8544
+ var AnthropicMessagesProvider = class {
8545
+ definition;
8546
+ constructor(definition) {
8547
+ this.definition = ProviderDefinitionSchema.parse(definition);
8572
8548
  }
8573
- buildEnvelope(request, plan, sessionId = createSessionId()) {
8574
- const tasks = flattenPlanTasks(plan);
8575
- const completedTaskIds = completedTaskIdsFromPlan(plan);
8576
- const readyTasks = tasks.filter((task) => isTaskReady(task, completedTaskIds));
8577
- const blockedTasks = tasks.filter((task) => task.status !== "completed" && !isTaskReady(task, completedTaskIds));
8578
- const assignedAgents = this.selectAgentsForTasks(readyTasks);
8549
+ async healthCheck() {
8550
+ const apiKey = resolveApiKey(this.definition);
8551
+ if (!apiKey) {
8552
+ return {
8553
+ ok: false,
8554
+ message: `Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"}`
8555
+ };
8556
+ }
8579
8557
  return {
8580
- sessionId,
8581
- request,
8582
- plan,
8583
- readyTasks,
8584
- blockedTasks,
8585
- assignedAgents
8558
+ ok: true,
8559
+ message: `${this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages"} (${this.definition.defaultModel ?? "model not set"})`
8586
8560
  };
8587
8561
  }
8588
- createSessionSnapshot(envelope, overrides = {}) {
8589
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
8590
- const status = deriveStatus(envelope.plan, envelope.readyTasks, envelope.blockedTasks);
8591
- return SessionSnapshotSchema.parse({
8592
- id: envelope.sessionId,
8593
- goal: envelope.request.goal,
8594
- cwd: envelope.request.cwd,
8595
- startedAt: timestamp,
8596
- updatedAt: timestamp,
8597
- status,
8598
- request: envelope.request,
8599
- plan: envelope.plan,
8600
- readyTaskIds: envelope.readyTasks.map((task) => task.id),
8601
- blockedTaskIds: envelope.blockedTasks.map((task) => task.id),
8602
- completedTaskIds: Array.from(completedTaskIdsFromPlan(envelope.plan)),
8603
- assignedAgents: envelope.assignedAgents.map((agent) => agent.role),
8604
- notes: [
8605
- "Initial session snapshot created from the current plan."
8606
- ],
8607
- events: [],
8608
- ...overrides
8562
+ async listModels(input) {
8563
+ const apiKey = resolveApiKey(this.definition);
8564
+ if (!apiKey) {
8565
+ throw new Error(`Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"} for provider "${this.definition.id}".`);
8566
+ }
8567
+ const endpoint = this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages";
8568
+ const modelsEndpoint = endpoint.endsWith("/messages") ? `${endpoint.slice(0, -"/messages".length)}/models` : joinUrl(endpoint, "/models");
8569
+ const payload = await requestJson(modelsEndpoint, {
8570
+ method: "GET",
8571
+ headers: {
8572
+ "anthropic-version": "2023-06-01",
8573
+ "x-api-key": apiKey,
8574
+ ...this.definition.headers
8575
+ }
8609
8576
  });
8577
+ return mapAnthropicModels(this.definition.id, payload, input);
8610
8578
  }
8611
- async continueSession(session, options = {}) {
8612
- let workingPlan = session.plan;
8613
- let notes = [
8614
- ...session.notes
8615
- ];
8616
- let events = [
8617
- ...session.events
8618
- ];
8619
- const maxAutoTasks = options.maxAutoTasks ?? 2;
8620
- let executedTasks = 0;
8621
- while (executedTasks < maxAutoTasks) {
8622
- const envelope = this.buildEnvelope(session.request, workingPlan, session.id);
8623
- const autoTask = envelope.readyTasks.find((task) => this.canAutoExecuteTask(task));
8624
- if (!autoTask) {
8625
- const nextTask = envelope.readyTasks[0];
8626
- if (nextTask) {
8627
- const note = `Execution paused at ${nextTask.id} (${nextTask.title}): no built-in executor is wired for ${nextTask.agentRole} yet.`;
8628
- notes = maybeAppendNote(notes, note);
8629
- }
8630
- return this.createSessionSnapshot(this.buildEnvelope(session.request, workingPlan, session.id), {
8631
- startedAt: session.startedAt,
8632
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8633
- notes,
8634
- events
8635
- });
8636
- }
8637
- events.push({
8638
- id: createEventId("task-started", autoTask.id),
8639
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8640
- type: "task-started",
8641
- taskId: autoTask.id,
8642
- agentRole: autoTask.agentRole,
8643
- message: `Started ${autoTask.id}: ${autoTask.title}`,
8644
- toolResults: [],
8645
- artifacts: []
8646
- });
8647
- const outcome = await this.executeTask(session.id, autoTask, session.request, workingPlan);
8648
- workingPlan = updateTaskStatus(workingPlan, autoTask.id, outcome.status === "completed" ? "completed" : "blocked");
8649
- notes = maybeAppendNote(notes, outcome.summary);
8650
- events.push({
8651
- id: createEventId(outcome.status === "completed" ? "task-completed" : "task-blocked", autoTask.id),
8652
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8653
- type: outcome.status === "completed" ? "task-completed" : "task-blocked",
8654
- taskId: autoTask.id,
8655
- agentRole: autoTask.agentRole,
8656
- message: outcome.summary,
8657
- toolResults: outcome.toolResults,
8658
- artifacts: outcome.artifacts
8579
+ async generateText(input) {
8580
+ const apiKey = resolveApiKey(this.definition);
8581
+ if (!apiKey) {
8582
+ throw new Error(`Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"} for provider "${this.definition.id}".`);
8583
+ }
8584
+ const endpoint = this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages";
8585
+ const model = requireModel(this.definition, input);
8586
+ const messages = toChatMessages(input);
8587
+ const systemMessage = messages.find((message) => message.role === "system")?.content;
8588
+ const conversation = messages.filter((message) => message.role !== "system").map((message) => ({
8589
+ role: message.role === "assistant" ? "assistant" : "user",
8590
+ content: message.content
8591
+ }));
8592
+ const response = await requestJson(endpoint, {
8593
+ method: "POST",
8594
+ headers: {
8595
+ "content-type": "application/json",
8596
+ "x-api-key": apiKey,
8597
+ "anthropic-version": "2023-06-01",
8598
+ ...this.definition.headers
8599
+ },
8600
+ body: JSON.stringify({
8601
+ model,
8602
+ messages: conversation,
8603
+ ...systemMessage ? {
8604
+ system: systemMessage
8605
+ } : {},
8606
+ max_tokens: input.maxTokens ?? 2048,
8607
+ ...typeof input.temperature === "number" ? {
8608
+ temperature: input.temperature
8609
+ } : {}
8610
+ })
8611
+ }, GENERATION_TIMEOUT_MS);
8612
+ const content = Array.isArray(response.content) ? response.content : [];
8613
+ const text = content.flatMap((item) => typeof item === "object" && item !== null && "text" in item ? [
8614
+ String(item.text)
8615
+ ] : []).join("\n");
8616
+ return {
8617
+ text,
8618
+ model
8619
+ };
8620
+ }
8621
+ };
8622
+ var OpenAICompatibleProvider = class {
8623
+ definition;
8624
+ constructor(definition) {
8625
+ this.definition = ProviderDefinitionSchema.parse(definition);
8626
+ }
8627
+ async healthCheck() {
8628
+ if (!this.definition.baseUrl) {
8629
+ return {
8630
+ ok: false,
8631
+ message: "Missing baseUrl"
8632
+ };
8633
+ }
8634
+ const apiKey = resolveApiKey(this.definition);
8635
+ if (this.definition.apiKeyEnv && !apiKey) {
8636
+ return {
8637
+ ok: false,
8638
+ message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"}), missing ${this.definition.apiKeyEnv}`
8639
+ };
8640
+ }
8641
+ try {
8642
+ await requestJson(joinUrl(this.definition.baseUrl, "/models"), {
8643
+ method: "GET",
8644
+ headers: buildProviderHeaders(this.definition, apiKey, false)
8645
+ }, 2e3);
8646
+ } catch (error) {
8647
+ return {
8648
+ ok: false,
8649
+ message: error instanceof Error ? error.message : String(error)
8650
+ };
8651
+ }
8652
+ return {
8653
+ ok: true,
8654
+ message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"})`
8655
+ };
8656
+ }
8657
+ async listModels(input) {
8658
+ if (!this.definition.baseUrl) {
8659
+ throw new Error(`Provider "${this.definition.id}" requires baseUrl.`);
8660
+ }
8661
+ const apiKey = resolveApiKey(this.definition);
8662
+ const payload = await requestJson(joinUrl(this.definition.baseUrl, "/models"), {
8663
+ method: "GET",
8664
+ headers: buildProviderHeaders(this.definition, apiKey, false)
8665
+ });
8666
+ return mapOpenAIStyleModels(this.definition.id, payload, input);
8667
+ }
8668
+ async generateText(input) {
8669
+ if (!this.definition.baseUrl) {
8670
+ throw new Error(`Provider "${this.definition.id}" requires baseUrl.`);
8671
+ }
8672
+ const apiKey = resolveApiKey(this.definition);
8673
+ const model = requireModel(this.definition, input);
8674
+ const response = await requestJson(`${this.definition.baseUrl}/chat/completions`, {
8675
+ method: "POST",
8676
+ headers: buildProviderHeaders(this.definition, apiKey),
8677
+ body: JSON.stringify({
8678
+ model,
8679
+ messages: toChatMessages(input),
8680
+ ...typeof input.temperature === "number" ? {
8681
+ temperature: input.temperature
8682
+ } : {},
8683
+ ...typeof input.maxTokens === "number" ? {
8684
+ max_tokens: input.maxTokens
8685
+ } : {}
8686
+ })
8687
+ }, GENERATION_TIMEOUT_MS);
8688
+ const choices = Array.isArray(response.choices) ? response.choices : [];
8689
+ const firstChoice = choices.length > 0 && typeof choices[0] === "object" && choices[0] !== null ? choices[0] : null;
8690
+ const message = firstChoice && typeof firstChoice.message === "object" && firstChoice.message !== null ? firstChoice.message : null;
8691
+ return {
8692
+ text: typeof message?.content === "string" ? message.content : "",
8693
+ model
8694
+ };
8695
+ }
8696
+ };
8697
+ var OllamaProvider = class {
8698
+ definition;
8699
+ constructor(definition) {
8700
+ this.definition = ProviderDefinitionSchema.parse(definition);
8701
+ }
8702
+ async healthCheck() {
8703
+ const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
8704
+ try {
8705
+ await requestJson(`${baseUrl}/api/tags`, { method: "GET" }, 2e3);
8706
+ return {
8707
+ ok: true,
8708
+ message: `${baseUrl} (${this.definition.defaultModel ?? "model not set"})`
8709
+ };
8710
+ } catch (error) {
8711
+ return {
8712
+ ok: false,
8713
+ message: error instanceof Error ? error.message : String(error)
8714
+ };
8715
+ }
8716
+ }
8717
+ async listModels(input) {
8718
+ const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
8719
+ const payload = await requestJson(`${baseUrl}/api/tags`, { method: "GET" }, 2e3);
8720
+ return mapOllamaModels(this.definition.id, payload, input);
8721
+ }
8722
+ async generateText(input) {
8723
+ const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
8724
+ const model = requireModel(this.definition, input);
8725
+ const response = await requestJson(`${baseUrl}/api/generate`, {
8726
+ method: "POST",
8727
+ headers: {
8728
+ "content-type": "application/json",
8729
+ ...this.definition.headers
8730
+ },
8731
+ body: JSON.stringify({
8732
+ model,
8733
+ prompt: input.userPrompt ?? toPromptText(input),
8734
+ ...input.systemPrompt ? {
8735
+ system: input.systemPrompt
8736
+ } : {},
8737
+ stream: false,
8738
+ ...typeof input.temperature === "number" ? {
8739
+ options: {
8740
+ temperature: input.temperature
8741
+ }
8742
+ } : {}
8743
+ })
8744
+ }, GENERATION_TIMEOUT_MS);
8745
+ return {
8746
+ text: typeof response.response === "string" ? response.response : "",
8747
+ model
8748
+ };
8749
+ }
8750
+ };
8751
+ async function createCustomModuleProvider(definition, cwd) {
8752
+ if (!definition.modulePath) {
8753
+ throw new Error(`Provider "${definition.id}" requires modulePath.`);
8754
+ }
8755
+ const modulePath = import_node_path3.default.isAbsolute(definition.modulePath) ? definition.modulePath : import_node_path3.default.join(cwd, definition.modulePath);
8756
+ await (0, import_promises3.access)(modulePath);
8757
+ const module2 = await import((0, import_node_url.pathToFileURL)(modulePath).href);
8758
+ const createProvider = typeof module2.createProvider === "function" ? module2.createProvider : typeof module2.default === "function" ? module2.default : null;
8759
+ if (!createProvider) {
8760
+ throw new Error(`Custom provider module "${modulePath}" must export createProvider(definition).`);
8761
+ }
8762
+ return createProvider(definition);
8763
+ }
8764
+ var BUILTIN_FACTORIES = {
8765
+ "openai-responses": {
8766
+ driver: "openai-responses",
8767
+ create: async (definition) => new OpenAIResponsesProvider(definition)
8768
+ },
8769
+ "anthropic-messages": {
8770
+ driver: "anthropic-messages",
8771
+ create: async (definition) => new AnthropicMessagesProvider(definition)
8772
+ },
8773
+ "openai-compatible": {
8774
+ driver: "openai-compatible",
8775
+ create: async (definition) => new OpenAICompatibleProvider(definition)
8776
+ },
8777
+ ollama: {
8778
+ driver: "ollama",
8779
+ create: async (definition) => new OllamaProvider(definition)
8780
+ },
8781
+ "custom-module": {
8782
+ driver: "custom-module",
8783
+ create: (definition, cwd) => createCustomModuleProvider(definition, cwd)
8784
+ }
8785
+ };
8786
+ var BrainProviderRegistry = class {
8787
+ cwd;
8788
+ factories;
8789
+ constructor(cwd = process.cwd(), factories = Object.values(BUILTIN_FACTORIES)) {
8790
+ this.cwd = cwd;
8791
+ this.factories = new Map(factories.map((factory) => [factory.driver, factory]));
8792
+ }
8793
+ listDrivers() {
8794
+ return Array.from(this.factories.keys()).sort();
8795
+ }
8796
+ async createProvider(definition) {
8797
+ const normalized = ProviderDefinitionSchema.parse(definition);
8798
+ const factory = this.factories.get(normalized.driver);
8799
+ if (!factory) {
8800
+ throw new Error(`No provider factory registered for driver "${normalized.driver}".`);
8801
+ }
8802
+ return factory.create(normalized, this.cwd);
8803
+ }
8804
+ async healthCheck(definition) {
8805
+ const provider = await this.createProvider(definition);
8806
+ return provider.healthCheck();
8807
+ }
8808
+ async listModels(definition, input) {
8809
+ const provider = await this.createProvider(definition);
8810
+ if (provider.listModels) {
8811
+ return provider.listModels(input);
8812
+ }
8813
+ return filterModels(definition.models.map((modelId) => ProviderModelSchema.parse({
8814
+ id: modelId,
8815
+ providerId: definition.id
8816
+ })), input);
8817
+ }
8818
+ };
8819
+ function createDefaultBrainProviderRegistry(cwd = process.cwd()) {
8820
+ return new BrainProviderRegistry(cwd);
8821
+ }
8822
+
8823
+ // ../brains/dist/resolver.js
8824
+ var BrainResolver = class {
8825
+ config;
8826
+ registry;
8827
+ constructor(config, registry = createDefaultBrainProviderRegistry()) {
8828
+ this.config = config;
8829
+ this.registry = registry;
8830
+ }
8831
+ async resolve(role) {
8832
+ const settings = resolveBrainSettings(this.config, role);
8833
+ const provider = findProviderById(this.config, settings.providerId);
8834
+ if (!provider) {
8835
+ throw new Error(`Brain role "${role}" points to unknown provider "${settings.providerId}".`);
8836
+ }
8837
+ const model = resolveBrainModel(this.config, role);
8838
+ if (!model) {
8839
+ throw new Error(`Brain role "${role}" does not resolve to a model.`);
8840
+ }
8841
+ return {
8842
+ role,
8843
+ settings,
8844
+ provider,
8845
+ model,
8846
+ client: await this.registry.createProvider(provider)
8847
+ };
8848
+ }
8849
+ };
8850
+
8851
+ // ../agent-runtime/dist/autonomous.js
8852
+ var MAX_TOOL_OUTPUT_CHARS = 4e3;
8853
+ var MAX_PARSE_RETRIES = 2;
8854
+ var DEFAULT_MAX_REPAIR_ATTEMPTS = 2;
8855
+ var MODEL_ACTION_TOOLS = [
8856
+ "file.list",
8857
+ "file.search",
8858
+ "file.read",
8859
+ "file.write",
8860
+ "file.patch",
8861
+ "shell.exec",
8862
+ "tests.run",
8863
+ "git.status",
8864
+ "git.diff"
8865
+ ];
8866
+ function createAgentActionSchema(allowedTools) {
8867
+ return external_exports.discriminatedUnion("type", [
8868
+ external_exports.object({
8869
+ type: external_exports.literal("tool"),
8870
+ tool: external_exports.enum(MODEL_ACTION_TOOLS).refine((tool) => allowedTools.includes(tool), {
8871
+ message: `Tool must be one of: ${allowedTools.join(", ")}`
8872
+ }),
8873
+ input: external_exports.record(external_exports.union([
8874
+ external_exports.string(),
8875
+ external_exports.number(),
8876
+ external_exports.boolean(),
8877
+ external_exports.null()
8878
+ ])).default({}),
8879
+ reason: external_exports.string().min(1).optional()
8880
+ }),
8881
+ external_exports.object({
8882
+ type: external_exports.literal("finish"),
8883
+ summary: external_exports.string().min(1)
8884
+ }),
8885
+ external_exports.object({
8886
+ type: external_exports.literal("block"),
8887
+ reason: external_exports.string().min(1)
8888
+ })
8889
+ ]);
8890
+ }
8891
+ function truncateForModel(value) {
8892
+ if (!value) {
8893
+ return value;
8894
+ }
8895
+ if (value.length <= MAX_TOOL_OUTPUT_CHARS) {
8896
+ return value;
8897
+ }
8898
+ return `${value.slice(0, MAX_TOOL_OUTPUT_CHARS)}
8899
+ ... [truncated]`;
8900
+ }
8901
+ function extractJsonObject(raw) {
8902
+ const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
8903
+ const candidate = (fenced?.[1] ?? raw).trim();
8904
+ const firstBrace = candidate.indexOf("{");
8905
+ const lastBrace = candidate.lastIndexOf("}");
8906
+ if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
8907
+ throw new Error("Expected a JSON object in the model response.");
8908
+ }
8909
+ return JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
8910
+ }
8911
+ function renderToolResultForModel(result) {
8912
+ const sections = [
8913
+ `tool: ${result.toolId}`,
8914
+ `success: ${result.success ? "true" : "false"}`,
8915
+ `summary: ${result.summary}`
8916
+ ];
8917
+ if (result.stdout) {
8918
+ sections.push(`stdout:
8919
+ ${truncateForModel(result.stdout)}`);
8920
+ }
8921
+ if (result.stderr) {
8922
+ sections.push(`stderr:
8923
+ ${truncateForModel(result.stderr)}`);
8924
+ }
8925
+ if (result.artifacts.length > 0) {
8926
+ sections.push(`artifacts: ${result.artifacts.join(", ")}`);
8927
+ }
8928
+ return sections.join("\n");
8929
+ }
8930
+ function isReadOnlyShellCommand(command) {
8931
+ const normalized = command.trim().toLowerCase();
8932
+ return [
8933
+ "pwd",
8934
+ "ls",
8935
+ "find ",
8936
+ "rg ",
8937
+ "grep ",
8938
+ "cat ",
8939
+ "sed ",
8940
+ "head ",
8941
+ "tail ",
8942
+ "git status",
8943
+ "git diff"
8944
+ ].some((prefix) => normalized === prefix || normalized.startsWith(prefix));
8945
+ }
8946
+ function isVerificationCommand(command) {
8947
+ const normalized = command.trim().toLowerCase();
8948
+ return [
8949
+ "npm test",
8950
+ "npm run test",
8951
+ "npm run build",
8952
+ "npm run lint",
8953
+ "pnpm test",
8954
+ "pnpm build",
8955
+ "pnpm lint",
8956
+ "yarn test",
8957
+ "yarn build",
8958
+ "yarn lint",
8959
+ "tsc",
8960
+ "vitest",
8961
+ "jest"
8962
+ ].some((prefix) => normalized === prefix || normalized.startsWith(prefix));
8963
+ }
8964
+ function isMutatingAction(action) {
8965
+ if (action.type !== "tool") {
8966
+ return false;
8967
+ }
8968
+ if (action.tool === "file.write" || action.tool === "file.patch") {
8969
+ return true;
8970
+ }
8971
+ if (action.tool !== "shell.exec") {
8972
+ return false;
8973
+ }
8974
+ const command = typeof action.input.command === "string" ? action.input.command : "";
8975
+ return command.length > 0 && !isReadOnlyShellCommand(command);
8976
+ }
8977
+ function isVerificationAction(action) {
8978
+ if (action.type !== "tool") {
8979
+ return false;
8980
+ }
8981
+ if (action.tool === "tests.run") {
8982
+ return true;
8983
+ }
8984
+ if (action.tool !== "shell.exec") {
8985
+ return false;
8986
+ }
8987
+ const command = typeof action.input.command === "string" ? action.input.command : "";
8988
+ return command.length > 0 && isVerificationCommand(command);
8989
+ }
8990
+ function buildSystemPrompt(agent, task, request, allowedTools) {
8991
+ const toolShape = allowedTools.join("|");
8992
+ return [
8993
+ `You are Kimbho's ${agent.role}.`,
8994
+ `Purpose: ${agent.purpose}`,
8995
+ `Goal: ${request.goal}`,
8996
+ `Current task: ${task.id} - ${task.title}`,
8997
+ `Task description: ${task.description}`,
8998
+ `Acceptance criteria:`,
8999
+ ...task.acceptanceCriteria.map((item) => `- ${item}`),
9000
+ `Likely files: ${task.filesLikelyTouched.join(", ") || "(not specified)"}`,
9001
+ `Workspace state: ${request.workspaceState}`,
9002
+ `Allowed tools: ${allowedTools.join(", ")}`,
9003
+ `Respond with exactly one JSON object and no markdown.`,
9004
+ `Tool action shape: {"type":"tool","tool":"${toolShape}","input":{...},"reason":"why this step matters"}`,
9005
+ `Finish shape: {"type":"finish","summary":"what was completed and verified"}`,
9006
+ `Block shape: {"type":"block","reason":"why you cannot proceed safely"}`,
9007
+ `Rules:`,
9008
+ `- Use one action per response.`,
9009
+ `- Use file.list and file.search to explore the workspace before editing.`,
9010
+ `- Prefer file.read before editing existing files.`,
9011
+ `- Use file.patch for existing files when possible; use file.write for new files or full replacements.`,
9012
+ `- Use git.diff to inspect the current patch after changes when helpful.`,
9013
+ `- Use shell.exec for non-interactive commands only.`,
9014
+ `- Keep paths relative to the workspace.`,
9015
+ `- After changing code, run verification with tests.run or shell.exec when appropriate.`,
9016
+ `- Do not claim success unless the task acceptance criteria are satisfied.`,
9017
+ `- If the task is underspecified, make a pragmatic implementation choice and continue.`
9018
+ ].join("\n");
9019
+ }
9020
+ function buildInitialUserPrompt(task, request) {
9021
+ return [
9022
+ `Complete this task in the workspace.`,
9023
+ `Task type: ${task.type}`,
9024
+ `Risk level: ${task.riskLevel}`,
9025
+ `Outputs expected: ${task.outputs.join(", ") || "(not specified)"}`,
9026
+ `Constraints: ${request.constraints.join(", ") || "(none)"}`,
9027
+ `Choose the next single action now.`
9028
+ ].join("\n");
9029
+ }
9030
+ function buildToolResultUserMessage(step, result) {
9031
+ return [
9032
+ `Step ${step} tool result:`,
9033
+ renderToolResultForModel(result),
9034
+ `Choose the next single action now.`
9035
+ ].join("\n\n");
9036
+ }
9037
+ function createToolFailureResult(toolId, error) {
9038
+ const message = error instanceof Error ? error.message : String(error);
9039
+ return {
9040
+ toolId,
9041
+ success: false,
9042
+ summary: `${toolId} failed: ${message}`,
9043
+ stderr: message,
9044
+ artifacts: []
9045
+ };
9046
+ }
9047
+ async function writeTranscriptArtifact(cwd, sessionId, taskId, entries) {
9048
+ await ensureKimbhoDir(cwd);
9049
+ const outputPath = import_node_path4.default.join(resolveKimbhoDir(cwd), "logs", `${sessionId}-${taskId}-autonomous.md`);
9050
+ const content = [
9051
+ `# Autonomous Task Transcript`,
9052
+ ``,
9053
+ `- session: ${sessionId}`,
9054
+ `- task: ${taskId}`,
9055
+ ``,
9056
+ ...entries.flatMap((entry) => {
9057
+ const lines = [
9058
+ `## Step ${entry.step}`,
9059
+ ``,
9060
+ `### Model Response`,
9061
+ `\`\`\`json`,
9062
+ entry.response.trim(),
9063
+ `\`\`\``
9064
+ ];
9065
+ if (entry.toolResult) {
9066
+ lines.push("");
9067
+ lines.push(`### Tool Result`);
9068
+ lines.push(`- tool: ${entry.toolResult.toolId}`);
9069
+ lines.push(`- success: ${entry.toolResult.success ? "true" : "false"}`);
9070
+ lines.push(`- summary: ${entry.toolResult.summary}`);
9071
+ if (entry.toolResult.stdout) {
9072
+ lines.push("");
9073
+ lines.push("```text");
9074
+ lines.push(truncateForModel(entry.toolResult.stdout) ?? "");
9075
+ lines.push("```");
9076
+ }
9077
+ if (entry.toolResult.stderr) {
9078
+ lines.push("");
9079
+ lines.push("```text");
9080
+ lines.push(truncateForModel(entry.toolResult.stderr) ?? "");
9081
+ lines.push("```");
9082
+ }
9083
+ }
9084
+ if (entry.runtimeNote) {
9085
+ lines.push("");
9086
+ lines.push(`### Runtime Note`);
9087
+ lines.push(entry.runtimeNote);
9088
+ }
9089
+ lines.push("");
9090
+ return lines;
9091
+ })
9092
+ ].join("\n");
9093
+ await (0, import_promises4.writeFile)(outputPath, `${content}
9094
+ `, "utf8");
9095
+ return outputPath;
9096
+ }
9097
+ var AutonomousTaskExecutor = class {
9098
+ config;
9099
+ resolver;
9100
+ toolRegistry;
9101
+ toolRuntime;
9102
+ constructor(config, toolRegistry, toolRuntime) {
9103
+ this.config = config;
9104
+ this.resolver = new BrainResolver(config);
9105
+ this.toolRegistry = toolRegistry;
9106
+ this.toolRuntime = toolRuntime;
9107
+ }
9108
+ async execute(sessionId, task, request, options = {}) {
9109
+ const agent = AGENT_CATALOG[task.agentRole];
9110
+ const brain = await this.resolver.resolve(agent.brainRole);
9111
+ const allowedTools = this.toolRegistry.byRole(agent.role).map((tool) => tool.id);
9112
+ const actionSchema = createAgentActionSchema(allowedTools);
9113
+ const messages = [
9114
+ {
9115
+ role: "user",
9116
+ content: buildInitialUserPrompt(task, request)
9117
+ }
9118
+ ];
9119
+ const toolResults = [];
9120
+ const artifacts = /* @__PURE__ */ new Set();
9121
+ const transcript = [];
9122
+ const maxSteps = options.maxSteps ?? 10;
9123
+ const maxRepairAttempts = options.maxRepairAttempts ?? DEFAULT_MAX_REPAIR_ATTEMPTS;
9124
+ let changedWorkspace = false;
9125
+ let verifiedAfterLatestChange = false;
9126
+ let repairRequiredBeforeVerification = false;
9127
+ let repairAppliedSinceFailure = false;
9128
+ let verificationFailures = 0;
9129
+ let lastVerificationFailure = null;
9130
+ for (let step = 1; step <= maxSteps; step += 1) {
9131
+ let responseText = "";
9132
+ let parsedAction = null;
9133
+ for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt += 1) {
9134
+ const response = await brain.client.generateText({
9135
+ model: brain.model,
9136
+ systemPrompt: buildSystemPrompt(agent, task, request, allowedTools),
9137
+ messages,
9138
+ ...typeof brain.settings.temperature === "number" ? {
9139
+ temperature: brain.settings.temperature
9140
+ } : {},
9141
+ ...typeof brain.settings.maxTokens === "number" ? {
9142
+ maxTokens: brain.settings.maxTokens
9143
+ } : {}
9144
+ });
9145
+ responseText = response.text;
9146
+ try {
9147
+ parsedAction = actionSchema.parse(extractJsonObject(response.text));
9148
+ break;
9149
+ } catch (error) {
9150
+ if (attempt === MAX_PARSE_RETRIES) {
9151
+ throw error;
9152
+ }
9153
+ messages.push({
9154
+ role: "assistant",
9155
+ content: response.text
9156
+ });
9157
+ messages.push({
9158
+ role: "user",
9159
+ content: [
9160
+ "Your previous response was invalid.",
9161
+ error instanceof Error ? error.message : String(error),
9162
+ "Return exactly one valid JSON object matching the required action schema."
9163
+ ].join("\n")
9164
+ });
9165
+ }
9166
+ }
9167
+ if (!parsedAction) {
9168
+ throw new Error("Model did not return a valid action.");
9169
+ }
9170
+ const transcriptEntry = {
9171
+ step,
9172
+ response: responseText,
9173
+ parsedAction
9174
+ };
9175
+ messages.push({
9176
+ role: "assistant",
9177
+ content: JSON.stringify(parsedAction)
9178
+ });
9179
+ if (parsedAction.type === "finish") {
9180
+ if (changedWorkspace && !verifiedAfterLatestChange) {
9181
+ transcriptEntry.runtimeNote = "Finish rejected because code changed without a successful verification step.";
9182
+ transcript.push(transcriptEntry);
9183
+ messages.push({
9184
+ role: "user",
9185
+ content: [
9186
+ "You tried to finish after making code changes without a successful verification step.",
9187
+ "Run tests.run or a build/lint/test shell command, inspect failures, and only finish after verification passes."
9188
+ ].join("\n")
9189
+ });
9190
+ continue;
9191
+ }
9192
+ const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript.concat(transcriptEntry));
9193
+ artifacts.add(transcriptPath2);
9194
+ return {
9195
+ status: "completed",
9196
+ summary: parsedAction.summary,
9197
+ toolResults,
9198
+ artifacts: Array.from(artifacts)
9199
+ };
9200
+ }
9201
+ if (parsedAction.type === "block") {
9202
+ const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript.concat(transcriptEntry));
9203
+ artifacts.add(transcriptPath2);
9204
+ return {
9205
+ status: "blocked",
9206
+ summary: parsedAction.reason,
9207
+ toolResults,
9208
+ artifacts: Array.from(artifacts)
9209
+ };
9210
+ }
9211
+ if (!allowedTools.includes(parsedAction.tool)) {
9212
+ transcriptEntry.runtimeNote = `Rejected disallowed tool "${parsedAction.tool}" for ${agent.role}.`;
9213
+ transcript.push(transcriptEntry);
9214
+ messages.push({
9215
+ role: "user",
9216
+ content: [
9217
+ `The tool "${parsedAction.tool}" is not allowed for ${agent.role}.`,
9218
+ `Choose one of the allowed tools instead: ${allowedTools.join(", ")}.`
9219
+ ].join("\n")
9220
+ });
9221
+ continue;
9222
+ }
9223
+ if (isVerificationAction(parsedAction) && repairRequiredBeforeVerification && !repairAppliedSinceFailure) {
9224
+ transcriptEntry.runtimeNote = "Verification rerun rejected because no repair action succeeded after the last failed verification.";
9225
+ transcript.push(transcriptEntry);
9226
+ messages.push({
9227
+ role: "user",
9228
+ content: [
9229
+ "Verification already failed for this task.",
9230
+ "Inspect the failure output, apply a repair, and only then rerun verification.",
9231
+ lastVerificationFailure ? `Latest failure summary: ${lastVerificationFailure.summary}` : "Latest failure summary: unavailable."
9232
+ ].join("\n")
9233
+ });
9234
+ continue;
9235
+ }
9236
+ const result = await this.toolRuntime.run(parsedAction.tool, parsedAction.input, { cwd: request.cwd }).catch((error) => createToolFailureResult(parsedAction.tool, error));
9237
+ const mutatingAction = isMutatingAction(parsedAction);
9238
+ const verificationAction = isVerificationAction(parsedAction);
9239
+ if (mutatingAction && result.success) {
9240
+ changedWorkspace = true;
9241
+ verifiedAfterLatestChange = false;
9242
+ if (repairRequiredBeforeVerification) {
9243
+ repairAppliedSinceFailure = true;
9244
+ }
9245
+ }
9246
+ if (verificationAction) {
9247
+ verifiedAfterLatestChange = result.success;
9248
+ if (result.success) {
9249
+ verificationFailures = 0;
9250
+ repairRequiredBeforeVerification = false;
9251
+ repairAppliedSinceFailure = false;
9252
+ lastVerificationFailure = null;
9253
+ } else {
9254
+ verificationFailures += 1;
9255
+ repairRequiredBeforeVerification = true;
9256
+ repairAppliedSinceFailure = false;
9257
+ lastVerificationFailure = result;
9258
+ }
9259
+ }
9260
+ transcriptEntry.toolResult = result;
9261
+ transcript.push(transcriptEntry);
9262
+ toolResults.push(result);
9263
+ for (const artifact of result.artifacts) {
9264
+ artifacts.add(artifact);
9265
+ }
9266
+ const followUp = [
9267
+ buildToolResultUserMessage(step, result)
9268
+ ];
9269
+ if (mutatingAction && result.success) {
9270
+ followUp.push("Code or workspace state changed. Inspect the diff if needed and run verification before finishing.");
9271
+ }
9272
+ if (verificationAction && !result.success) {
9273
+ transcriptEntry.runtimeNote = [
9274
+ `Verification failed (${verificationFailures}/${maxRepairAttempts} repair attempts used).`,
9275
+ "Executor requires a repair action before the next verification run."
9276
+ ].join(" ");
9277
+ followUp.push(`Verification failed. Inspect the failure output, repair the issue, and run verification again before finishing. Repair attempts used: ${verificationFailures}/${maxRepairAttempts}.`);
9278
+ if (verificationFailures >= maxRepairAttempts) {
9279
+ transcriptEntry.runtimeNote = [
9280
+ transcriptEntry.runtimeNote,
9281
+ "Repair budget exhausted; task escalated as blocked."
9282
+ ].join(" ");
9283
+ const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
9284
+ artifacts.add(transcriptPath2);
9285
+ if (task.agentRole !== "test-debugger") {
9286
+ return {
9287
+ status: "handoff",
9288
+ summary: `Verification failed ${verificationFailures} time${verificationFailures === 1 ? "" : "s"} for ${task.id}; handing off to test-debugger.`,
9289
+ toolResults,
9290
+ artifacts: Array.from(artifacts)
9291
+ };
9292
+ }
9293
+ return {
9294
+ status: "blocked",
9295
+ summary: `Verification failed ${verificationFailures} time${verificationFailures === 1 ? "" : "s"} for ${task.id}; repair budget exhausted.`,
9296
+ toolResults,
9297
+ artifacts: Array.from(artifacts)
9298
+ };
9299
+ }
9300
+ }
9301
+ messages.push({
9302
+ role: "user",
9303
+ content: followUp.join("\n\n")
8659
9304
  });
8660
- executedTasks += 1;
8661
9305
  }
8662
- const postLimitEnvelope = this.buildEnvelope(session.request, workingPlan, session.id);
8663
- if (postLimitEnvelope.readyTasks.length > 0) {
8664
- notes = maybeAppendNote(notes, `Auto execution limit reached after ${executedTasks} task${executedTasks === 1 ? "" : "s"}.`);
9306
+ const transcriptPath = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
9307
+ artifacts.add(transcriptPath);
9308
+ return {
9309
+ status: "paused",
9310
+ summary: `Autonomous executor reached the step limit (${maxSteps}) for ${task.id}.`,
9311
+ toolResults,
9312
+ artifacts: Array.from(artifacts)
9313
+ };
9314
+ }
9315
+ };
9316
+
9317
+ // ../agent-runtime/dist/orchestrator.js
9318
+ var import_promises6 = require("node:fs/promises");
9319
+ var import_node_path6 = __toESM(require("node:path"), 1);
9320
+
9321
+ // ../tools/dist/types.js
9322
+ var ToolPermissionSchema = external_exports.enum([
9323
+ "safe",
9324
+ "approval-required",
9325
+ "destructive"
9326
+ ]);
9327
+ var ToolDescriptorSchema = external_exports.object({
9328
+ id: external_exports.string().min(1),
9329
+ description: external_exports.string().min(1),
9330
+ permission: ToolPermissionSchema.default("safe"),
9331
+ timeoutMs: external_exports.number().int().positive(),
9332
+ retryable: external_exports.boolean().default(false),
9333
+ producesArtifacts: external_exports.boolean().default(false),
9334
+ allowedRoles: external_exports.array(AgentRoleSchema).default([])
9335
+ });
9336
+
9337
+ // ../tools/dist/registry.js
9338
+ var BUILTIN_TOOLS = [
9339
+ {
9340
+ id: "file.read",
9341
+ description: "Read a file from the workspace.",
9342
+ permission: "safe",
9343
+ timeoutMs: 5e3,
9344
+ retryable: false,
9345
+ producesArtifacts: false,
9346
+ allowedRoles: [
9347
+ "session-orchestrator",
9348
+ "repo-analyst",
9349
+ "planner",
9350
+ "execution-manager",
9351
+ "frontend-specialist",
9352
+ "backend-specialist",
9353
+ "database-specialist",
9354
+ "infra-specialist",
9355
+ "test-debugger",
9356
+ "reviewer",
9357
+ "integrator"
9358
+ ]
9359
+ },
9360
+ {
9361
+ id: "file.list",
9362
+ description: "List workspace files, optionally filtered by a path or pattern.",
9363
+ permission: "safe",
9364
+ timeoutMs: 1e4,
9365
+ retryable: true,
9366
+ producesArtifacts: false,
9367
+ allowedRoles: [
9368
+ "session-orchestrator",
9369
+ "repo-analyst",
9370
+ "planner",
9371
+ "execution-manager",
9372
+ "frontend-specialist",
9373
+ "backend-specialist",
9374
+ "database-specialist",
9375
+ "infra-specialist",
9376
+ "test-debugger",
9377
+ "reviewer",
9378
+ "integrator"
9379
+ ]
9380
+ },
9381
+ {
9382
+ id: "file.search",
9383
+ description: "Search workspace file contents for a text or regex pattern.",
9384
+ permission: "safe",
9385
+ timeoutMs: 15e3,
9386
+ retryable: true,
9387
+ producesArtifacts: false,
9388
+ allowedRoles: [
9389
+ "session-orchestrator",
9390
+ "repo-analyst",
9391
+ "planner",
9392
+ "execution-manager",
9393
+ "frontend-specialist",
9394
+ "backend-specialist",
9395
+ "database-specialist",
9396
+ "infra-specialist",
9397
+ "test-debugger",
9398
+ "reviewer",
9399
+ "integrator"
9400
+ ]
9401
+ },
9402
+ {
9403
+ id: "file.write",
9404
+ description: "Write or replace a file in the workspace.",
9405
+ permission: "approval-required",
9406
+ timeoutMs: 1e4,
9407
+ retryable: true,
9408
+ producesArtifacts: true,
9409
+ allowedRoles: [
9410
+ "frontend-specialist",
9411
+ "backend-specialist",
9412
+ "database-specialist",
9413
+ "infra-specialist",
9414
+ "test-debugger",
9415
+ "integrator"
9416
+ ]
9417
+ },
9418
+ {
9419
+ id: "file.patch",
9420
+ description: "Apply a structured patch to workspace files.",
9421
+ permission: "approval-required",
9422
+ timeoutMs: 1e4,
9423
+ retryable: true,
9424
+ producesArtifacts: true,
9425
+ allowedRoles: [
9426
+ "frontend-specialist",
9427
+ "backend-specialist",
9428
+ "database-specialist",
9429
+ "infra-specialist",
9430
+ "test-debugger",
9431
+ "integrator"
9432
+ ]
9433
+ },
9434
+ {
9435
+ id: "shell.exec",
9436
+ description: "Run a shell command inside the workspace.",
9437
+ permission: "approval-required",
9438
+ timeoutMs: 12e4,
9439
+ retryable: true,
9440
+ producesArtifacts: true,
9441
+ allowedRoles: [
9442
+ "repo-analyst",
9443
+ "execution-manager",
9444
+ "backend-specialist",
9445
+ "infra-specialist",
9446
+ "test-debugger",
9447
+ "reviewer",
9448
+ "integrator"
9449
+ ]
9450
+ },
9451
+ {
9452
+ id: "git.status",
9453
+ description: "Inspect the current git working tree and diff state.",
9454
+ permission: "safe",
9455
+ timeoutMs: 1e4,
9456
+ retryable: true,
9457
+ producesArtifacts: false,
9458
+ allowedRoles: [
9459
+ "session-orchestrator",
9460
+ "repo-analyst",
9461
+ "execution-manager",
9462
+ "reviewer",
9463
+ "integrator"
9464
+ ]
9465
+ },
9466
+ {
9467
+ id: "git.diff",
9468
+ description: "Inspect the current git diff, optionally scoped to a file path.",
9469
+ permission: "safe",
9470
+ timeoutMs: 15e3,
9471
+ retryable: true,
9472
+ producesArtifacts: false,
9473
+ allowedRoles: [
9474
+ "session-orchestrator",
9475
+ "repo-analyst",
9476
+ "execution-manager",
9477
+ "frontend-specialist",
9478
+ "backend-specialist",
9479
+ "database-specialist",
9480
+ "infra-specialist",
9481
+ "test-debugger",
9482
+ "reviewer",
9483
+ "integrator"
9484
+ ]
9485
+ },
9486
+ {
9487
+ id: "tests.run",
9488
+ description: "Execute verification commands such as tests, linting, and builds.",
9489
+ permission: "approval-required",
9490
+ timeoutMs: 3e5,
9491
+ retryable: true,
9492
+ producesArtifacts: true,
9493
+ allowedRoles: [
9494
+ "frontend-specialist",
9495
+ "backend-specialist",
9496
+ "database-specialist",
9497
+ "infra-specialist",
9498
+ "execution-manager",
9499
+ "test-debugger",
9500
+ "reviewer",
9501
+ "integrator"
9502
+ ]
9503
+ }
9504
+ ];
9505
+ var ToolRegistry = class {
9506
+ tools = /* @__PURE__ */ new Map();
9507
+ constructor(seed = BUILTIN_TOOLS) {
9508
+ for (const descriptor of seed) {
9509
+ const normalized = ToolDescriptorSchema.parse(descriptor);
9510
+ this.tools.set(normalized.id, normalized);
8665
9511
  }
8666
- return this.createSessionSnapshot(postLimitEnvelope, {
8667
- startedAt: session.startedAt,
8668
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8669
- notes,
8670
- events
8671
- });
8672
9512
  }
8673
- selectAgentsForTasks(tasks) {
8674
- const roles = new Set(tasks.map((task) => task.agentRole));
8675
- return Array.from(roles).map((role) => AGENT_CATALOG[role]).sort((left, right) => left.role.localeCompare(right.role));
9513
+ list() {
9514
+ return Array.from(this.tools.values()).sort((left, right) => left.id.localeCompare(right.id));
8676
9515
  }
8677
- toolsForAgent(role) {
8678
- return this.toolRegistry.byRole(role);
9516
+ get(id) {
9517
+ return this.tools.get(id);
8679
9518
  }
8680
- canAutoExecuteTask(task) {
8681
- return task.agentRole === "repo-analyst" || task.agentRole === "planner";
9519
+ byRole(role) {
9520
+ return this.list().filter((tool) => tool.allowedRoles.includes(role));
8682
9521
  }
8683
- async executeTask(sessionId, task, request, plan) {
8684
- if (task.agentRole === "repo-analyst") {
8685
- return this.executeRepoAnalysisTask(sessionId, task, request);
8686
- }
8687
- if (task.agentRole === "planner") {
8688
- return this.executePlannerTask(sessionId, task, request, plan);
9522
+ };
9523
+ function createDefaultToolRegistry() {
9524
+ return new ToolRegistry(BUILTIN_TOOLS);
9525
+ }
9526
+
9527
+ // ../tools/dist/runtime.js
9528
+ var import_promises5 = require("node:fs/promises");
9529
+ var import_node_path5 = __toESM(require("node:path"), 1);
9530
+ var import_node_process = __toESM(require("node:process"), 1);
9531
+ var import_node_child_process = require("node:child_process");
9532
+ var import_node_os = require("node:os");
9533
+ var DEFAULT_CAPTURE_LIMIT = 16e3;
9534
+ var DEFAULT_IGNORE_GLOBS = [
9535
+ "!**/node_modules/**",
9536
+ "!**/.git/**",
9537
+ "!**/.kimbho/**",
9538
+ "!**/dist/**",
9539
+ "!**/build/**",
9540
+ "!**/.next/**"
9541
+ ];
9542
+ var DEFAULT_IGNORE_SEGMENTS = /* @__PURE__ */ new Set([
9543
+ "node_modules",
9544
+ ".git",
9545
+ ".kimbho",
9546
+ "dist",
9547
+ "build",
9548
+ ".next"
9549
+ ]);
9550
+ function parseLimitValue(value, fallback) {
9551
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
9552
+ return value;
9553
+ }
9554
+ if (typeof value === "string") {
9555
+ const parsed = Number.parseInt(value, 10);
9556
+ if (Number.isInteger(parsed) && parsed > 0) {
9557
+ return parsed;
8689
9558
  }
8690
- return {
8691
- status: "blocked",
8692
- summary: `Task ${task.id} is ready, but no executor is available for ${task.agentRole}.`,
8693
- toolResults: [],
8694
- artifacts: []
8695
- };
8696
9559
  }
8697
- async executeRepoAnalysisTask(sessionId, task, request) {
8698
- const context = { cwd: request.cwd };
8699
- const toolResults = await Promise.all([
8700
- this.safeRunTool("git.status", {}, context),
8701
- this.safeRunTool("file.read", { path: "package.json" }, context),
8702
- this.safeRunTool("file.read", { path: "README.md" }, context)
8703
- ]);
8704
- const successfulResults = toolResults.filter((result) => result.success);
8705
- const scripts = extractPackageScripts(successfulResults);
8706
- const artifactPath = await this.writeLogArtifact(sessionId, "repo-analysis", request.cwd, [
8707
- `# Repo Analysis`,
8708
- ``,
8709
- `Goal: ${request.goal}`,
8710
- `Workspace: ${request.cwd}`,
8711
- `Task: ${task.id} - ${task.title}`,
8712
- ``,
8713
- `## Observations`,
8714
- `- Successful tool probes: ${successfulResults.length}/${toolResults.length}`,
8715
- `- Workspace state: ${request.workspaceState}`,
8716
- `- Detected scripts: ${scripts.length > 0 ? scripts.join(", ") : "none"}`,
8717
- ``,
8718
- renderToolResultSection(toolResults)
8719
- ].join("\n"));
8720
- return {
8721
- status: "completed",
8722
- summary: `Completed ${task.id}: captured repo analysis and saved ${import_node_path4.default.basename(artifactPath)}.`,
8723
- toolResults,
8724
- artifacts: [
8725
- artifactPath
8726
- ]
8727
- };
9560
+ return fallback;
9561
+ }
9562
+ function truncateOutput(value) {
9563
+ if (!value) {
9564
+ return value;
8728
9565
  }
8729
- async executePlannerTask(sessionId, task, request, plan) {
8730
- const artifactPath = await this.writeLogArtifact(sessionId, "architecture-brief", request.cwd, [
8731
- `# Architecture Brief`,
8732
- ``,
8733
- `Goal: ${request.goal}`,
8734
- `Summary: ${plan.summary}`,
8735
- `Repo Strategy: ${plan.repoStrategy.mode} - ${plan.repoStrategy.reasoning}`,
8736
- ``,
8737
- `## Assumptions`,
8738
- ...plan.assumptions.map((assumption) => `- ${assumption}`),
8739
- ``,
8740
- `## Milestones`,
8741
- ...plan.milestones.flatMap((milestone) => [
8742
- `### ${milestone.title}`,
8743
- milestone.objective,
8744
- ...milestone.tasks.map((milestoneTask) => `- ${milestoneTask.id}: ${milestoneTask.title} [${milestoneTask.agentRole}]`),
8745
- ``
8746
- ]),
8747
- `## Verification`,
8748
- ...plan.verificationChecklist.map((item) => `- ${item}`)
8749
- ].join("\n"));
8750
- return {
8751
- status: "completed",
8752
- summary: `Completed ${task.id}: wrote architecture brief ${import_node_path4.default.basename(artifactPath)}.`,
8753
- toolResults: [],
8754
- artifacts: [
8755
- artifactPath
8756
- ]
8757
- };
9566
+ if (value.length <= DEFAULT_CAPTURE_LIMIT) {
9567
+ return value;
8758
9568
  }
8759
- async safeRunTool(toolId, input, context) {
8760
- try {
8761
- return await this.toolRuntime.run(toolId, input, context);
8762
- } catch (error) {
8763
- const message = error instanceof Error ? error.message : String(error);
8764
- return {
8765
- toolId,
8766
- success: false,
8767
- summary: `${toolId} failed: ${message}`,
8768
- stderr: message,
8769
- artifacts: []
8770
- };
9569
+ const omitted = value.length - DEFAULT_CAPTURE_LIMIT;
9570
+ return `${value.slice(0, DEFAULT_CAPTURE_LIMIT)}
9571
+ ... [truncated ${omitted} chars]`;
9572
+ }
9573
+ function isIgnoredWorkspacePath(value) {
9574
+ const normalized = value.replace(/\\/g, "/");
9575
+ const segments = normalized.split("/");
9576
+ return segments.some((segment) => DEFAULT_IGNORE_SEGMENTS.has(segment));
9577
+ }
9578
+ function extractPatchArtifacts(cwd, patch) {
9579
+ const paths = /* @__PURE__ */ new Set();
9580
+ for (const line of patch.split("\n")) {
9581
+ if (!line.startsWith("+++ ") && !line.startsWith("--- ")) {
9582
+ continue;
8771
9583
  }
8772
- }
8773
- async writeLogArtifact(sessionId, label, cwd, content) {
8774
- await ensureKimbhoDir(cwd);
8775
- const logsDir = import_node_path4.default.join(resolveKimbhoDir(cwd), "logs");
8776
- const outputPath = import_node_path4.default.join(logsDir, `${sessionId}-${label}.md`);
8777
- await (0, import_promises4.writeFile)(outputPath, `${content}
8778
- `, "utf8");
8779
- return outputPath;
8780
- }
8781
- };
8782
-
8783
- // src/commands/agents.ts
8784
- function createAgentsCommand() {
8785
- return new Command("agents").description("Show the current agent hierarchy and allowed tools.").option("--active", "Show only agents assigned to the latest session", false).action(async (options) => {
8786
- const orchestrator = new ExecutionOrchestrator();
8787
- const session = options.active ? await loadLatestSession(process.cwd()) : null;
8788
- const profiles = session ? listAgentProfiles().filter((agent) => session.assignedAgents.includes(agent.role)) : listAgentProfiles();
8789
- if (session) {
8790
- console.log(`Session: ${session.id}`);
8791
- console.log(`Ready tasks: ${session.readyTaskIds.join(", ") || "none"}`);
8792
- console.log(`Blocked tasks: ${session.blockedTaskIds.join(", ") || "none"}`);
9584
+ const raw = line.slice(4).trim();
9585
+ if (!raw || raw === "/dev/null") {
9586
+ continue;
8793
9587
  }
8794
- for (const agent of profiles) {
8795
- const tools = orchestrator.toolsForAgent(agent.role).map((tool) => tool.id);
8796
- console.log(`${agent.role}`);
8797
- console.log(` brain: ${agent.brainRole}`);
8798
- console.log(` purpose: ${agent.purpose}`);
8799
- console.log(` tools: ${tools.join(", ")}`);
8800
- console.log(` concurrency: ${agent.maxConcurrentTasks}`);
9588
+ const normalized = raw.startsWith("a/") || raw.startsWith("b/") ? raw.slice(2) : raw;
9589
+ try {
9590
+ paths.add(resolveWorkspacePath(cwd, normalized));
9591
+ } catch {
9592
+ continue;
8801
9593
  }
8802
- });
9594
+ }
9595
+ return Array.from(paths);
8803
9596
  }
8804
-
8805
- // src/commands/brains.ts
8806
- var import_node_process2 = __toESM(require("node:process"), 1);
8807
-
8808
- // ../brains/dist/templates.js
8809
- var BUILTIN_PROVIDER_TEMPLATES = [
8810
- {
8811
- id: "openai",
8812
- label: "OpenAI",
8813
- driver: "openai-responses",
8814
- defaultBaseUrl: "https://api.openai.com/v1",
8815
- defaultApiKeyEnv: "OPENAI_API_KEY",
8816
- defaultModel: "gpt-5",
8817
- notes: "Uses the OpenAI Responses API."
8818
- },
8819
- {
8820
- id: "anthropic",
8821
- label: "Anthropic",
8822
- driver: "anthropic-messages",
8823
- defaultBaseUrl: "https://api.anthropic.com/v1/messages",
8824
- defaultApiKeyEnv: "ANTHROPIC_API_KEY",
8825
- notes: "Uses the Anthropic Messages API."
8826
- },
8827
- {
8828
- id: "openrouter",
8829
- label: "OpenRouter",
8830
- driver: "openai-compatible",
8831
- defaultBaseUrl: "https://openrouter.ai/api/v1",
8832
- defaultApiKeyEnv: "OPENROUTER_API_KEY",
8833
- notes: "Routes through the OpenAI-compatible OpenRouter API."
8834
- },
8835
- {
8836
- id: "ollama",
8837
- label: "Ollama",
8838
- driver: "ollama",
8839
- defaultBaseUrl: "http://localhost:11434",
8840
- defaultModel: "qwen2.5-coder:7b",
8841
- notes: "Runs against a local Ollama server."
8842
- },
8843
- {
8844
- id: "lmstudio",
8845
- label: "LM Studio",
8846
- driver: "openai-compatible",
8847
- defaultBaseUrl: "http://localhost:1234/v1",
8848
- notes: "Works with LM Studio's local OpenAI-compatible server."
9597
+ function resolveWorkspacePath(cwd, filePath) {
9598
+ const resolved = import_node_path5.default.resolve(cwd, filePath);
9599
+ const relative = import_node_path5.default.relative(cwd, resolved);
9600
+ if (relative.startsWith("..") || import_node_path5.default.isAbsolute(relative)) {
9601
+ throw new Error(`Path "${filePath}" escapes the workspace.`);
8849
9602
  }
8850
- ];
8851
- function listProviderTemplates() {
8852
- return BUILTIN_PROVIDER_TEMPLATES.slice().sort((left, right) => left.id.localeCompare(right.id));
9603
+ return resolved;
8853
9604
  }
8854
- function findProviderTemplate(templateId) {
8855
- return BUILTIN_PROVIDER_TEMPLATES.find((template) => template.id === templateId);
9605
+ async function runSpawn(command, args, cwd, timeoutMs) {
9606
+ return new Promise((resolve, reject) => {
9607
+ const child = (0, import_node_child_process.spawn)(command, args, {
9608
+ cwd,
9609
+ env: import_node_process.default.env,
9610
+ stdio: [
9611
+ "ignore",
9612
+ "pipe",
9613
+ "pipe"
9614
+ ]
9615
+ });
9616
+ const stdout = [];
9617
+ const stderr = [];
9618
+ let settled = false;
9619
+ let timedOut = false;
9620
+ const timer = setTimeout(() => {
9621
+ timedOut = true;
9622
+ child.kill("SIGTERM");
9623
+ setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
9624
+ }, timeoutMs);
9625
+ child.stdout.on("data", (chunk) => {
9626
+ stdout.push(String(chunk));
9627
+ });
9628
+ child.stderr.on("data", (chunk) => {
9629
+ stderr.push(String(chunk));
9630
+ });
9631
+ child.on("error", (error) => {
9632
+ if (settled) {
9633
+ return;
9634
+ }
9635
+ settled = true;
9636
+ clearTimeout(timer);
9637
+ reject(error);
9638
+ });
9639
+ child.on("close", (code) => {
9640
+ if (settled) {
9641
+ return;
9642
+ }
9643
+ settled = true;
9644
+ clearTimeout(timer);
9645
+ resolve({
9646
+ code,
9647
+ stdout: stdout.join(""),
9648
+ stderr: stderr.join(""),
9649
+ timedOut
9650
+ });
9651
+ });
9652
+ });
8856
9653
  }
8857
- function trimTrailingSlash(value) {
8858
- return value.replace(/\/+$/, "");
9654
+ async function runShellCommand(toolId, command, cwd, timeoutMs) {
9655
+ const shell = import_node_process.default.env.SHELL ?? "/bin/sh";
9656
+ const result = await runSpawn(shell, [
9657
+ "-lc",
9658
+ command
9659
+ ], cwd, timeoutMs);
9660
+ const success = !result.timedOut && result.code === 0;
9661
+ const summary = result.timedOut ? `Command timed out after ${timeoutMs}ms.` : success ? `Command completed successfully.` : `Command exited with code ${result.code ?? "unknown"}.`;
9662
+ return ToolResultSchema.parse({
9663
+ toolId,
9664
+ success,
9665
+ summary,
9666
+ stdout: truncateOutput(result.stdout),
9667
+ stderr: truncateOutput(result.stderr)
9668
+ });
8859
9669
  }
8860
- function normalizeTemplateBaseUrl(templateId, baseUrl) {
8861
- if (!baseUrl) {
8862
- return void 0;
8863
- }
8864
- const normalized = trimTrailingSlash(baseUrl);
8865
- if (templateId !== "lmstudio") {
8866
- return normalized;
9670
+ async function executeFileRead(input, context) {
9671
+ const rawPath = typeof input.path === "string" ? input.path : null;
9672
+ if (!rawPath) {
9673
+ throw new Error("file.read requires a string path.");
8867
9674
  }
9675
+ const targetPath = resolveWorkspacePath(context.cwd, rawPath);
9676
+ const contents = await (0, import_promises5.readFile)(targetPath, "utf8");
9677
+ return ToolResultSchema.parse({
9678
+ toolId: "file.read",
9679
+ success: true,
9680
+ summary: `Read ${import_node_path5.default.relative(context.cwd, targetPath) || import_node_path5.default.basename(targetPath)}.`,
9681
+ stdout: truncateOutput(contents),
9682
+ artifacts: [
9683
+ targetPath
9684
+ ]
9685
+ });
9686
+ }
9687
+ async function executeFileList(input, context, timeoutMs) {
9688
+ const root = typeof input.path === "string" && input.path.trim().length > 0 ? input.path : ".";
9689
+ const pattern = typeof input.pattern === "string" && input.pattern.trim().length > 0 ? input.pattern : null;
9690
+ const limit = parseLimitValue(input.limit, 200);
9691
+ const searchRoot = resolveWorkspacePath(context.cwd, root);
9692
+ const relativeRoot = import_node_path5.default.relative(context.cwd, searchRoot) || ".";
9693
+ const rootArg = relativeRoot === "." ? "." : relativeRoot;
9694
+ let result;
8868
9695
  try {
8869
- const parsed = new URL(normalized);
8870
- if (!parsed.pathname || parsed.pathname === "/") {
8871
- parsed.pathname = "/v1";
8872
- return trimTrailingSlash(parsed.toString());
9696
+ result = await runSpawn("rg", [
9697
+ "--files",
9698
+ ...DEFAULT_IGNORE_GLOBS.flatMap((glob) => [
9699
+ "-g",
9700
+ glob
9701
+ ]),
9702
+ rootArg
9703
+ ], context.cwd, timeoutMs);
9704
+ } catch (error) {
9705
+ const message = error instanceof Error ? error.message : String(error);
9706
+ if (!message.includes("ENOENT")) {
9707
+ throw error;
8873
9708
  }
8874
- } catch {
8875
- return normalized;
8876
- }
8877
- return normalized;
8878
- }
8879
- function buildProviderFromTemplate(templateId, options = {}) {
8880
- const template = findProviderTemplate(templateId);
8881
- if (!template) {
8882
- throw new Error(`Unknown provider template "${templateId}".`);
9709
+ result = await runSpawn("find", [
9710
+ rootArg,
9711
+ "-type",
9712
+ "f"
9713
+ ], context.cwd, timeoutMs);
8883
9714
  }
8884
- return ProviderDefinitionSchema.parse({
8885
- id: options.providerId ?? template.id,
8886
- label: options.label ?? template.label,
8887
- driver: template.driver,
8888
- ...normalizeTemplateBaseUrl(templateId, options.baseUrl) ?? template.defaultBaseUrl ? {
8889
- baseUrl: normalizeTemplateBaseUrl(templateId, options.baseUrl) ?? template.defaultBaseUrl
8890
- } : {},
8891
- ...options.apiKeyEnv ?? template.defaultApiKeyEnv ? {
8892
- apiKeyEnv: options.apiKeyEnv ?? template.defaultApiKeyEnv
8893
- } : {},
8894
- ...options.model ?? template.defaultModel ? {
8895
- defaultModel: options.model ?? template.defaultModel
8896
- } : {},
8897
- ...options.models && options.models.length > 0 ? {
8898
- models: options.models
8899
- } : {}
9715
+ const lines = result.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !isIgnoredWorkspacePath(line));
9716
+ const filtered = pattern ? lines.filter((line) => line.toLowerCase().includes(pattern.toLowerCase())) : lines;
9717
+ const sliced = filtered.slice(0, limit);
9718
+ const success = !result.timedOut && result.code === 0;
9719
+ return ToolResultSchema.parse({
9720
+ toolId: "file.list",
9721
+ success,
9722
+ summary: success ? `Listed ${sliced.length} file${sliced.length === 1 ? "" : "s"} from ${rootArg}.` : result.timedOut ? `File listing timed out after ${timeoutMs}ms.` : `File listing failed with code ${result.code ?? "unknown"}.`,
9723
+ stdout: sliced.join("\n"),
9724
+ stderr: truncateOutput(result.stderr)
8900
9725
  });
8901
9726
  }
8902
-
8903
- // ../brains/dist/registry.js
8904
- var import_promises5 = require("node:fs/promises");
8905
- var import_node_path5 = __toESM(require("node:path"), 1);
8906
- var import_node_url = require("node:url");
8907
- function resolveApiKey(definition) {
8908
- if (!definition.apiKeyEnv) {
8909
- return null;
9727
+ async function executeFileSearch(input, context, timeoutMs) {
9728
+ const pattern = typeof input.pattern === "string" ? input.pattern.trim() : "";
9729
+ const root = typeof input.path === "string" && input.path.trim().length > 0 ? input.path : ".";
9730
+ const limit = parseLimitValue(input.limit, 50);
9731
+ if (!pattern) {
9732
+ throw new Error("file.search requires a non-empty pattern.");
9733
+ }
9734
+ const searchRoot = resolveWorkspacePath(context.cwd, root);
9735
+ const relativeRoot = import_node_path5.default.relative(context.cwd, searchRoot) || ".";
9736
+ const rootArg = relativeRoot === "." ? "." : relativeRoot;
9737
+ let result;
9738
+ try {
9739
+ result = await runSpawn("rg", [
9740
+ "-n",
9741
+ "--no-heading",
9742
+ "--color",
9743
+ "never",
9744
+ "--max-columns",
9745
+ "240",
9746
+ ...DEFAULT_IGNORE_GLOBS.flatMap((glob) => [
9747
+ "-g",
9748
+ glob
9749
+ ]),
9750
+ pattern,
9751
+ rootArg
9752
+ ], context.cwd, timeoutMs);
9753
+ } catch (error) {
9754
+ const message = error instanceof Error ? error.message : String(error);
9755
+ if (!message.includes("ENOENT")) {
9756
+ throw error;
9757
+ }
9758
+ result = await runSpawn("grep", [
9759
+ "-R",
9760
+ "-n",
9761
+ pattern,
9762
+ rootArg
9763
+ ], context.cwd, timeoutMs);
8910
9764
  }
8911
- return process.env[definition.apiKeyEnv] ?? null;
8912
- }
8913
- function makeAbortSignal(timeoutMs) {
8914
- const controller = new AbortController();
8915
- setTimeout(() => controller.abort(), timeoutMs).unref?.();
8916
- return controller.signal;
8917
- }
8918
- async function requestJson(url, init, timeoutMs = 3e4) {
8919
- const response = await fetch(url, {
8920
- ...init,
8921
- signal: makeAbortSignal(timeoutMs)
9765
+ const isNoMatch = result.code === 1 && !result.timedOut;
9766
+ const lines = result.stdout.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0 && !isIgnoredWorkspacePath(line)).slice(0, limit);
9767
+ const success = !result.timedOut && result.code === 0 || isNoMatch;
9768
+ return ToolResultSchema.parse({
9769
+ toolId: "file.search",
9770
+ success,
9771
+ summary: isNoMatch ? `No matches found for "${pattern}" in ${rootArg}.` : success ? `Found ${lines.length} match${lines.length === 1 ? "" : "es"} for "${pattern}" in ${rootArg}.` : result.timedOut ? `File search timed out after ${timeoutMs}ms.` : `File search failed with code ${result.code ?? "unknown"}.`,
9772
+ stdout: lines.join("\n"),
9773
+ stderr: truncateOutput(result.stderr)
8922
9774
  });
8923
- if (!response.ok) {
8924
- throw new Error(`Request failed with ${response.status} ${response.statusText}`);
8925
- }
8926
- return response.json();
8927
- }
8928
- function trimTrailingSlash2(value) {
8929
- return value.replace(/\/+$/, "");
8930
- }
8931
- function joinUrl(baseUrl, suffix) {
8932
- const normalizedBase = trimTrailingSlash2(baseUrl);
8933
- const normalizedSuffix = suffix.startsWith("/") ? suffix : `/${suffix}`;
8934
- return `${normalizedBase}${normalizedSuffix}`;
8935
9775
  }
8936
- function isOpenRouter(definition) {
8937
- return definition.baseUrl?.includes("openrouter.ai") ?? false;
8938
- }
8939
- function buildProviderHeaders(definition, apiKey, includeJsonContentType = true) {
8940
- const headers = {
8941
- ...definition.headers
8942
- };
8943
- if (includeJsonContentType) {
8944
- headers["content-type"] = "application/json";
9776
+ async function executeFileWrite(input, context) {
9777
+ const rawPath = typeof input.path === "string" ? input.path : null;
9778
+ const content = typeof input.content === "string" ? input.content : null;
9779
+ if (!rawPath) {
9780
+ throw new Error("file.write requires a string path.");
8945
9781
  }
8946
- if (apiKey) {
8947
- headers.authorization = `Bearer ${apiKey}`;
9782
+ if (content === null) {
9783
+ throw new Error("file.write requires a string content field.");
8948
9784
  }
8949
- if (isOpenRouter(definition)) {
8950
- const siteUrl = process.env.OPENROUTER_SITE_URL;
8951
- const appName = process.env.OPENROUTER_APP_NAME ?? "Kimbho CLI";
8952
- if (siteUrl) {
8953
- headers["HTTP-Referer"] = siteUrl;
8954
- }
8955
- headers["X-Title"] = appName;
9785
+ const targetPath = resolveWorkspacePath(context.cwd, rawPath);
9786
+ let existed = true;
9787
+ try {
9788
+ await (0, import_promises5.access)(targetPath);
9789
+ } catch {
9790
+ existed = false;
8956
9791
  }
8957
- return headers;
8958
- }
8959
- function filterModels(models, input = {}) {
8960
- const normalized = input.search?.trim().toLowerCase();
8961
- const filtered = normalized ? models.filter((model) => [model.id, model.name].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes(normalized))) : models;
8962
- return typeof input.limit === "number" ? filtered.slice(0, input.limit) : filtered;
8963
- }
8964
- function mapOpenAIStyleModels(providerId, payload, input) {
8965
- const response = payload;
8966
- const data = Array.isArray(response.data) ? response.data : [];
8967
- const models = data.flatMap((item) => {
8968
- if (typeof item !== "object" || item === null) {
8969
- return [];
8970
- }
8971
- const record = item;
8972
- const pricing = typeof record.pricing === "object" && record.pricing !== null ? record.pricing : null;
8973
- const architecture = typeof record.architecture === "object" && record.architecture !== null ? record.architecture : null;
8974
- return [
8975
- ProviderModelSchema.parse({
8976
- id: String(record.id ?? ""),
8977
- ...typeof record.name === "string" ? {
8978
- name: record.name
8979
- } : {},
8980
- ...typeof record.description === "string" ? {
8981
- description: record.description
8982
- } : {},
8983
- ...typeof record.context_length === "number" ? {
8984
- contextLength: record.context_length
8985
- } : {},
8986
- ...pricing && typeof pricing.prompt === "string" ? {
8987
- promptPrice: pricing.prompt
8988
- } : {},
8989
- ...pricing && typeof pricing.completion === "string" ? {
8990
- completionPrice: pricing.completion
8991
- } : {},
8992
- ...architecture && typeof architecture.modality === "string" ? {
8993
- modality: architecture.modality
8994
- } : {},
8995
- providerId
8996
- })
8997
- ];
9792
+ await (0, import_promises5.mkdir)(import_node_path5.default.dirname(targetPath), { recursive: true });
9793
+ await (0, import_promises5.writeFile)(targetPath, content, "utf8");
9794
+ return ToolResultSchema.parse({
9795
+ toolId: "file.write",
9796
+ success: true,
9797
+ summary: `${existed ? "Updated" : "Created"} ${import_node_path5.default.relative(context.cwd, targetPath) || import_node_path5.default.basename(targetPath)}.`,
9798
+ artifacts: [
9799
+ targetPath
9800
+ ]
8998
9801
  });
8999
- return filterModels(models, input);
9000
9802
  }
9001
- function mapAnthropicModels(providerId, payload, input) {
9002
- const response = payload;
9003
- const data = Array.isArray(response.data) ? response.data : [];
9004
- const models = data.flatMap((item) => {
9005
- if (typeof item !== "object" || item === null) {
9006
- return [];
9007
- }
9008
- const record = item;
9009
- return [
9010
- ProviderModelSchema.parse({
9011
- id: String(record.id ?? ""),
9012
- ...typeof record.display_name === "string" ? {
9013
- name: record.display_name
9014
- } : {},
9015
- providerId
9016
- })
9017
- ];
9018
- });
9019
- return filterModels(models, input);
9803
+ async function executeShell(input, context, timeoutMs) {
9804
+ const command = typeof input.command === "string" ? input.command : null;
9805
+ if (!command) {
9806
+ throw new Error("shell.exec requires a command string.");
9807
+ }
9808
+ return runShellCommand("shell.exec", command, context.cwd, timeoutMs);
9020
9809
  }
9021
- function mapOllamaModels(providerId, payload, input) {
9022
- const response = payload;
9023
- const data = Array.isArray(response.models) ? response.models : [];
9024
- const models = data.flatMap((item) => {
9025
- if (typeof item !== "object" || item === null) {
9026
- return [];
9027
- }
9028
- const record = item;
9029
- const details = typeof record.details === "object" && record.details !== null ? record.details : null;
9030
- const id = typeof record.model === "string" ? record.model : typeof record.name === "string" ? record.name : "";
9031
- return [
9032
- ProviderModelSchema.parse({
9033
- id,
9034
- ...typeof record.name === "string" ? {
9035
- name: record.name
9036
- } : {},
9037
- ...details && typeof details.family === "string" ? {
9038
- modality: details.family
9039
- } : {},
9040
- ...details && typeof details.parameter_size === "string" ? {
9041
- description: details.parameter_size
9042
- } : {},
9043
- providerId
9044
- })
9045
- ];
9810
+ async function executeGitStatus(_input, context, timeoutMs) {
9811
+ return runShellCommand("git.status", "git status --short --branch", context.cwd, timeoutMs);
9812
+ }
9813
+ async function executeGitDiff(input, context, timeoutMs) {
9814
+ const rawPath = typeof input.path === "string" && input.path.trim().length > 0 ? input.path : null;
9815
+ const args = [
9816
+ "diff",
9817
+ "--no-ext-diff",
9818
+ "--stat",
9819
+ "--patch",
9820
+ "--unified=3"
9821
+ ];
9822
+ if (rawPath) {
9823
+ const targetPath = resolveWorkspacePath(context.cwd, rawPath);
9824
+ const relativeTarget = import_node_path5.default.relative(context.cwd, targetPath) || ".";
9825
+ args.push("--", relativeTarget);
9826
+ }
9827
+ const repoProbe = await runSpawn("git", [
9828
+ "rev-parse",
9829
+ "--is-inside-work-tree"
9830
+ ], context.cwd, timeoutMs);
9831
+ if (repoProbe.code !== 0 || repoProbe.stdout.trim() !== "true") {
9832
+ return ToolResultSchema.parse({
9833
+ toolId: "git.diff",
9834
+ success: false,
9835
+ summary: "git diff unavailable because the workspace is not a git repository.",
9836
+ stderr: "Not a git repository.",
9837
+ artifacts: []
9838
+ });
9839
+ }
9840
+ const result = await runSpawn("git", args, context.cwd, timeoutMs);
9841
+ const isClean = result.code === 0 && result.stdout.trim().length === 0;
9842
+ const success = !result.timedOut && (result.code === 0 || isClean);
9843
+ return ToolResultSchema.parse({
9844
+ toolId: "git.diff",
9845
+ success,
9846
+ summary: isClean ? "No git diff changes detected." : success ? `Captured git diff${rawPath ? ` for ${rawPath}` : ""}.` : result.timedOut ? `git diff timed out after ${timeoutMs}ms.` : `git diff failed with code ${result.code ?? "unknown"}.`,
9847
+ stdout: truncateOutput(result.stdout),
9848
+ stderr: truncateOutput(result.stderr)
9046
9849
  });
9047
- return filterModels(models, input);
9048
9850
  }
9049
- function toChatMessages(input) {
9050
- const messages = [];
9051
- if (input.systemPrompt) {
9052
- messages.push({
9053
- role: "system",
9054
- content: input.systemPrompt
9055
- });
9851
+ async function executeTests(input, context, timeoutMs) {
9852
+ const command = typeof input.command === "string" && input.command.trim().length > 0 ? input.command : "npm test";
9853
+ const result = await runShellCommand("tests.run", command, context.cwd, timeoutMs);
9854
+ return ToolResultSchema.parse({
9855
+ ...result,
9856
+ summary: result.success ? `Verification command passed: ${command}` : `Verification command failed: ${command}`
9857
+ });
9858
+ }
9859
+ async function executeFilePatch(input, context, timeoutMs) {
9860
+ const patch = typeof input.patch === "string" ? input.patch : null;
9861
+ if (!patch) {
9862
+ throw new Error("file.patch requires a unified diff in the patch field.");
9056
9863
  }
9057
- if (input.messages && input.messages.length > 0) {
9058
- messages.push(...input.messages);
9059
- } else if (input.userPrompt) {
9060
- messages.push({
9061
- role: "user",
9062
- content: input.userPrompt
9864
+ const tempDir = await (0, import_promises5.mkdtemp)(import_node_path5.default.join((0, import_node_os.tmpdir)(), "kimbho-patch-"));
9865
+ const patchPath = import_node_path5.default.join(tempDir, "change.patch");
9866
+ try {
9867
+ await (0, import_promises5.writeFile)(patchPath, patch, "utf8");
9868
+ const result = await runSpawn("git", [
9869
+ "apply",
9870
+ "--recount",
9871
+ "--whitespace=nowarn",
9872
+ patchPath
9873
+ ], context.cwd, timeoutMs);
9874
+ const success = !result.timedOut && result.code === 0;
9875
+ return ToolResultSchema.parse({
9876
+ toolId: "file.patch",
9877
+ success,
9878
+ summary: success ? "Patch applied successfully." : result.timedOut ? `Patch timed out after ${timeoutMs}ms.` : `Patch failed with code ${result.code ?? "unknown"}.`,
9879
+ stdout: truncateOutput(result.stdout),
9880
+ stderr: truncateOutput(result.stderr),
9881
+ artifacts: extractPatchArtifacts(context.cwd, patch)
9063
9882
  });
9883
+ } finally {
9884
+ await (0, import_promises5.rm)(tempDir, { recursive: true, force: true });
9064
9885
  }
9065
- return messages;
9066
- }
9067
- function toPromptText(input) {
9068
- const messages = toChatMessages(input);
9069
- return messages.map((message) => `${message.role.toUpperCase()}: ${message.content}`).join("\n\n");
9070
9886
  }
9071
- function requireModel(definition, input) {
9072
- const model = input.model ?? definition.defaultModel;
9073
- if (!model) {
9074
- throw new Error(`Provider "${definition.id}" does not have a resolved model.`);
9887
+ var ToolRuntime = class {
9888
+ registry;
9889
+ executors;
9890
+ constructor(registry = createDefaultToolRegistry()) {
9891
+ this.registry = registry;
9892
+ this.executors = /* @__PURE__ */ new Map([
9893
+ [
9894
+ "file.read",
9895
+ (input, context) => executeFileRead(input, context)
9896
+ ],
9897
+ [
9898
+ "file.list",
9899
+ executeFileList
9900
+ ],
9901
+ [
9902
+ "file.search",
9903
+ executeFileSearch
9904
+ ],
9905
+ [
9906
+ "file.write",
9907
+ (input, context) => executeFileWrite(input, context)
9908
+ ],
9909
+ [
9910
+ "file.patch",
9911
+ executeFilePatch
9912
+ ],
9913
+ [
9914
+ "shell.exec",
9915
+ executeShell
9916
+ ],
9917
+ [
9918
+ "git.status",
9919
+ executeGitStatus
9920
+ ],
9921
+ [
9922
+ "git.diff",
9923
+ executeGitDiff
9924
+ ],
9925
+ [
9926
+ "tests.run",
9927
+ executeTests
9928
+ ]
9929
+ ]);
9075
9930
  }
9076
- return model;
9931
+ async run(toolId, input, context) {
9932
+ const descriptor = this.registry.get(toolId);
9933
+ if (!descriptor) {
9934
+ throw new Error(`Unknown tool "${toolId}".`);
9935
+ }
9936
+ const executor = this.executors.get(toolId);
9937
+ if (!executor) {
9938
+ throw new Error(`No executor registered for "${toolId}".`);
9939
+ }
9940
+ return executor(input, context, descriptor.timeoutMs);
9941
+ }
9942
+ };
9943
+
9944
+ // ../agent-runtime/dist/orchestrator.js
9945
+ function createSessionId() {
9946
+ return `session-${Date.now()}`;
9077
9947
  }
9078
- var OpenAIResponsesProvider = class {
9079
- definition;
9080
- constructor(definition) {
9081
- this.definition = ProviderDefinitionSchema.parse(definition);
9948
+ function createEventId(type, taskId) {
9949
+ return `${type}-${taskId ?? "session"}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
9950
+ }
9951
+ function isTaskReady(task, completedTaskIds) {
9952
+ return task.status === "pending" && task.dependsOn.every((taskId) => completedTaskIds.has(taskId));
9953
+ }
9954
+ function completedTaskIdsFromPlan(plan) {
9955
+ return new Set(flattenPlanTasks(plan).filter((task) => task.status === "completed").map((task) => task.id));
9956
+ }
9957
+ function deriveStatus(plan, readyTasks, blockedTasks) {
9958
+ const tasks = flattenPlanTasks(plan);
9959
+ const completed = tasks.filter((task) => task.status === "completed").length;
9960
+ if (completed === tasks.length && tasks.length > 0) {
9961
+ return "completed";
9082
9962
  }
9083
- async healthCheck() {
9084
- const apiKey = resolveApiKey(this.definition);
9085
- if (!apiKey) {
9086
- return {
9087
- ok: false,
9088
- message: `Missing ${this.definition.apiKeyEnv ?? "API key env var"}`
9089
- };
9090
- }
9091
- return {
9092
- ok: true,
9093
- message: `${this.definition.baseUrl ?? "https://api.openai.com/v1"} (${this.definition.defaultModel ?? "model not set"})`
9094
- };
9963
+ if (readyTasks.length > 0) {
9964
+ return completed > 0 ? "running" : "ready";
9095
9965
  }
9096
- async listModels(input) {
9097
- const apiKey = resolveApiKey(this.definition);
9098
- if (!apiKey) {
9099
- throw new Error(`Missing ${this.definition.apiKeyEnv ?? "API key env var"} for provider "${this.definition.id}".`);
9100
- }
9101
- const baseUrl = this.definition.baseUrl ?? "https://api.openai.com/v1";
9102
- const payload = await requestJson(joinUrl(baseUrl, "/models"), {
9103
- method: "GET",
9104
- headers: buildProviderHeaders(this.definition, apiKey, false)
9105
- });
9106
- return mapOpenAIStyleModels(this.definition.id, payload, input);
9966
+ if (blockedTasks.length > 0) {
9967
+ return "blocked";
9107
9968
  }
9108
- async generateText(input) {
9109
- const apiKey = resolveApiKey(this.definition);
9110
- if (!apiKey) {
9111
- throw new Error(`Missing ${this.definition.apiKeyEnv ?? "API key env var"} for provider "${this.definition.id}".`);
9969
+ return "planned";
9970
+ }
9971
+ function updateTaskStatus(plan, taskId, status) {
9972
+ const milestones = plan.milestones.map((milestone) => ({
9973
+ ...milestone,
9974
+ tasks: milestone.tasks.map((task) => task.id === taskId ? {
9975
+ ...task,
9976
+ status
9977
+ } : task)
9978
+ }));
9979
+ return {
9980
+ ...plan,
9981
+ milestones
9982
+ };
9983
+ }
9984
+ function replaceTask(plan, taskId, mapper) {
9985
+ const milestones = plan.milestones.map((milestone) => ({
9986
+ ...milestone,
9987
+ tasks: milestone.tasks.map((task) => task.id === taskId ? mapper(task) : task)
9988
+ }));
9989
+ return {
9990
+ ...plan,
9991
+ milestones
9992
+ };
9993
+ }
9994
+ function maybeAppendNote(notes, note) {
9995
+ return notes.at(-1) === note ? notes : [
9996
+ ...notes,
9997
+ note
9998
+ ];
9999
+ }
10000
+ function mergeUnique(items) {
10001
+ return Array.from(new Set(items.filter((item) => item.trim().length > 0)));
10002
+ }
10003
+ function renderToolResultSection(results) {
10004
+ return results.map((result) => {
10005
+ const lines = [
10006
+ `## ${result.toolId}`,
10007
+ `- success: ${result.success ? "yes" : "no"}`,
10008
+ `- summary: ${result.summary}`
10009
+ ];
10010
+ if (result.stdout) {
10011
+ lines.push("");
10012
+ lines.push("```text");
10013
+ lines.push(result.stdout);
10014
+ lines.push("```");
9112
10015
  }
9113
- const baseUrl = this.definition.baseUrl ?? "https://api.openai.com/v1";
9114
- const model = requireModel(this.definition, input);
9115
- const messages = toChatMessages(input).map((message) => ({
9116
- role: message.role,
9117
- content: [
9118
- {
9119
- type: "input_text",
9120
- text: message.content
9121
- }
9122
- ]
9123
- }));
9124
- const response = await requestJson(`${baseUrl}/responses`, {
9125
- method: "POST",
9126
- headers: {
9127
- ...buildProviderHeaders(this.definition, apiKey)
9128
- },
9129
- body: JSON.stringify({
9130
- model,
9131
- input: messages,
9132
- ...typeof input.temperature === "number" ? {
9133
- temperature: input.temperature
9134
- } : {},
9135
- ...typeof input.maxTokens === "number" ? {
9136
- max_output_tokens: input.maxTokens
9137
- } : {}
9138
- })
9139
- });
9140
- const text = typeof response.output_text === "string" ? response.output_text : Array.isArray(response.output) ? response.output.flatMap((item) => typeof item === "object" && item !== null && "content" in item && Array.isArray(item.content) ? item.content : []).flatMap((item) => typeof item === "object" && item !== null && "text" in item ? [
9141
- String(item.text)
9142
- ] : []).join("\n") : "";
9143
- return {
9144
- text,
9145
- model
9146
- };
10016
+ if (result.stderr) {
10017
+ lines.push("");
10018
+ lines.push("```text");
10019
+ lines.push(result.stderr);
10020
+ lines.push("```");
10021
+ }
10022
+ return lines.join("\n");
10023
+ }).join("\n\n");
10024
+ }
10025
+ function extractPackageScripts(toolResults) {
10026
+ const packageResult = toolResults.find((result) => result.artifacts.some((artifact) => artifact.endsWith("package.json")) && result.stdout);
10027
+ if (!packageResult?.stdout) {
10028
+ return [];
9147
10029
  }
9148
- };
9149
- var AnthropicMessagesProvider = class {
9150
- definition;
9151
- constructor(definition) {
9152
- this.definition = ProviderDefinitionSchema.parse(definition);
10030
+ try {
10031
+ const parsed = JSON.parse(packageResult.stdout);
10032
+ return Object.keys(parsed.scripts ?? {}).sort();
10033
+ } catch {
10034
+ return [];
10035
+ }
10036
+ }
10037
+ function createDebuggerHandoffTask(task, outcome) {
10038
+ const failingResults = outcome.toolResults.filter((result) => !result.success);
10039
+ const failureSummaries = failingResults.slice(-3).map((result) => `${result.toolId}: ${result.summary}`);
10040
+ const artifactList = outcome.artifacts.slice(-5);
10041
+ const handoffDescriptionParts = [
10042
+ `Debugger handoff from ${task.agentRole}.`,
10043
+ `Original task: ${task.title}.`,
10044
+ `Handoff reason: ${outcome.summary}`,
10045
+ `Inspect the recent failures, reproduce the issue, apply a targeted repair, and rerun verification before finishing.`,
10046
+ failureSummaries.length > 0 ? `Recent failures:
10047
+ ${failureSummaries.map((item) => `- ${item}`).join("\n")}` : "",
10048
+ artifactList.length > 0 ? `Failure artifacts:
10049
+ ${artifactList.map((artifact) => `- ${artifact}`).join("\n")}` : "",
10050
+ `Original task description: ${task.description}`
10051
+ ].filter((part) => part.length > 0);
10052
+ return {
10053
+ ...task,
10054
+ title: task.title.startsWith("Debug ") ? task.title : `Debug ${task.title}`,
10055
+ description: handoffDescriptionParts.join("\n\n"),
10056
+ type: "verification",
10057
+ status: "pending",
10058
+ agentRole: "test-debugger",
10059
+ acceptanceCriteria: mergeUnique([
10060
+ ...task.acceptanceCriteria,
10061
+ "The failing verification is reproduced or explicitly explained.",
10062
+ "A targeted repair is applied or a concrete blocker is recorded.",
10063
+ "Verification passes after the repair."
10064
+ ]),
10065
+ outputs: mergeUnique([
10066
+ ...task.outputs,
10067
+ "Debugger handoff notes",
10068
+ "Verification evidence"
10069
+ ]),
10070
+ filesLikelyTouched: mergeUnique([
10071
+ ...task.filesLikelyTouched,
10072
+ ".kimbho/logs/"
10073
+ ]),
10074
+ riskLevel: "high"
10075
+ };
10076
+ }
10077
+ var ExecutionOrchestrator = class {
10078
+ toolRegistry;
10079
+ toolRuntime;
10080
+ constructor(toolRegistry = createDefaultToolRegistry(), toolRuntime = new ToolRuntime(toolRegistry)) {
10081
+ this.toolRegistry = toolRegistry;
10082
+ this.toolRuntime = toolRuntime;
9153
10083
  }
9154
- async healthCheck() {
9155
- const apiKey = resolveApiKey(this.definition);
9156
- if (!apiKey) {
9157
- return {
9158
- ok: false,
9159
- message: `Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"}`
9160
- };
9161
- }
10084
+ buildEnvelope(request, plan, sessionId = createSessionId()) {
10085
+ const tasks = flattenPlanTasks(plan);
10086
+ const completedTaskIds = completedTaskIdsFromPlan(plan);
10087
+ const readyTasks = tasks.filter((task) => isTaskReady(task, completedTaskIds));
10088
+ const blockedTasks = tasks.filter((task) => task.status !== "completed" && !isTaskReady(task, completedTaskIds));
10089
+ const assignedAgents = this.selectAgentsForTasks(readyTasks);
9162
10090
  return {
9163
- ok: true,
9164
- message: `${this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages"} (${this.definition.defaultModel ?? "model not set"})`
10091
+ sessionId,
10092
+ request,
10093
+ plan,
10094
+ readyTasks,
10095
+ blockedTasks,
10096
+ assignedAgents
9165
10097
  };
9166
10098
  }
9167
- async listModels(input) {
9168
- const apiKey = resolveApiKey(this.definition);
9169
- if (!apiKey) {
9170
- throw new Error(`Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"} for provider "${this.definition.id}".`);
9171
- }
9172
- const endpoint = this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages";
9173
- const modelsEndpoint = endpoint.endsWith("/messages") ? `${endpoint.slice(0, -"/messages".length)}/models` : joinUrl(endpoint, "/models");
9174
- const payload = await requestJson(modelsEndpoint, {
9175
- method: "GET",
9176
- headers: {
9177
- "anthropic-version": "2023-06-01",
9178
- "x-api-key": apiKey,
9179
- ...this.definition.headers
9180
- }
10099
+ createSessionSnapshot(envelope, overrides = {}) {
10100
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
10101
+ const status = deriveStatus(envelope.plan, envelope.readyTasks, envelope.blockedTasks);
10102
+ return SessionSnapshotSchema.parse({
10103
+ id: envelope.sessionId,
10104
+ goal: envelope.request.goal,
10105
+ cwd: envelope.request.cwd,
10106
+ startedAt: timestamp,
10107
+ updatedAt: timestamp,
10108
+ status,
10109
+ request: envelope.request,
10110
+ plan: envelope.plan,
10111
+ readyTaskIds: envelope.readyTasks.map((task) => task.id),
10112
+ blockedTaskIds: envelope.blockedTasks.map((task) => task.id),
10113
+ completedTaskIds: Array.from(completedTaskIdsFromPlan(envelope.plan)),
10114
+ assignedAgents: envelope.assignedAgents.map((agent) => agent.role),
10115
+ notes: [
10116
+ "Initial session snapshot created from the current plan."
10117
+ ],
10118
+ events: [],
10119
+ ...overrides
9181
10120
  });
9182
- return mapAnthropicModels(this.definition.id, payload, input);
9183
10121
  }
9184
- async generateText(input) {
9185
- const apiKey = resolveApiKey(this.definition);
9186
- if (!apiKey) {
9187
- throw new Error(`Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"} for provider "${this.definition.id}".`);
10122
+ async continueSession(session, options = {}) {
10123
+ let workingPlan = session.plan;
10124
+ let notes = [
10125
+ ...session.notes
10126
+ ];
10127
+ let events = [
10128
+ ...session.events
10129
+ ];
10130
+ const maxAutoTasks = options.maxAutoTasks ?? 2;
10131
+ let executedTasks = 0;
10132
+ while (executedTasks < maxAutoTasks) {
10133
+ const envelope = this.buildEnvelope(session.request, workingPlan, session.id);
10134
+ const autoTask = envelope.readyTasks.find((task) => this.canAutoExecuteTask(task));
10135
+ if (!autoTask) {
10136
+ const nextTask = envelope.readyTasks[0];
10137
+ if (nextTask) {
10138
+ const note = `Execution paused at ${nextTask.id} (${nextTask.title}): no built-in executor is wired for ${nextTask.agentRole} yet.`;
10139
+ notes = maybeAppendNote(notes, note);
10140
+ }
10141
+ return this.createSessionSnapshot(this.buildEnvelope(session.request, workingPlan, session.id), {
10142
+ startedAt: session.startedAt,
10143
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10144
+ notes,
10145
+ events
10146
+ });
10147
+ }
10148
+ events.push({
10149
+ id: createEventId("task-started", autoTask.id),
10150
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10151
+ type: "task-started",
10152
+ taskId: autoTask.id,
10153
+ agentRole: autoTask.agentRole,
10154
+ message: `Started ${autoTask.id}: ${autoTask.title}`,
10155
+ toolResults: [],
10156
+ artifacts: []
10157
+ });
10158
+ const outcome = await this.executeTask(session.id, autoTask, session.request, workingPlan, options);
10159
+ workingPlan = outcome.status === "handoff" ? replaceTask(workingPlan, autoTask.id, () => createDebuggerHandoffTask(autoTask, outcome)) : updateTaskStatus(workingPlan, autoTask.id, outcome.status === "completed" ? "completed" : outcome.status === "blocked" ? "blocked" : "pending");
10160
+ notes = maybeAppendNote(notes, outcome.summary);
10161
+ events.push({
10162
+ id: createEventId(outcome.status === "completed" ? "task-completed" : outcome.status === "blocked" ? "task-blocked" : outcome.status === "handoff" ? "task-handed-off" : "task-paused", autoTask.id),
10163
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10164
+ type: outcome.status === "completed" ? "task-completed" : outcome.status === "blocked" ? "task-blocked" : outcome.status === "handoff" ? "task-handed-off" : "task-paused",
10165
+ taskId: autoTask.id,
10166
+ agentRole: autoTask.agentRole,
10167
+ message: outcome.summary,
10168
+ toolResults: outcome.toolResults,
10169
+ artifacts: outcome.artifacts
10170
+ });
10171
+ executedTasks += 1;
9188
10172
  }
9189
- const endpoint = this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages";
9190
- const model = requireModel(this.definition, input);
9191
- const messages = toChatMessages(input);
9192
- const systemMessage = messages.find((message) => message.role === "system")?.content;
9193
- const conversation = messages.filter((message) => message.role !== "system").map((message) => ({
9194
- role: message.role === "assistant" ? "assistant" : "user",
9195
- content: message.content
9196
- }));
9197
- const response = await requestJson(endpoint, {
9198
- method: "POST",
9199
- headers: {
9200
- "content-type": "application/json",
9201
- "x-api-key": apiKey,
9202
- "anthropic-version": "2023-06-01",
9203
- ...this.definition.headers
9204
- },
9205
- body: JSON.stringify({
9206
- model,
9207
- messages: conversation,
9208
- ...systemMessage ? {
9209
- system: systemMessage
9210
- } : {},
9211
- max_tokens: input.maxTokens ?? 2048,
9212
- ...typeof input.temperature === "number" ? {
9213
- temperature: input.temperature
9214
- } : {}
9215
- })
10173
+ const postLimitEnvelope = this.buildEnvelope(session.request, workingPlan, session.id);
10174
+ if (postLimitEnvelope.readyTasks.length > 0) {
10175
+ notes = maybeAppendNote(notes, `Auto execution limit reached after ${executedTasks} task${executedTasks === 1 ? "" : "s"}.`);
10176
+ }
10177
+ return this.createSessionSnapshot(postLimitEnvelope, {
10178
+ startedAt: session.startedAt,
10179
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10180
+ notes,
10181
+ events
9216
10182
  });
9217
- const content = Array.isArray(response.content) ? response.content : [];
9218
- const text = content.flatMap((item) => typeof item === "object" && item !== null && "text" in item ? [
9219
- String(item.text)
9220
- ] : []).join("\n");
9221
- return {
9222
- text,
9223
- model
9224
- };
9225
10183
  }
9226
- };
9227
- var OpenAICompatibleProvider = class {
9228
- definition;
9229
- constructor(definition) {
9230
- this.definition = ProviderDefinitionSchema.parse(definition);
10184
+ selectAgentsForTasks(tasks) {
10185
+ const roles = new Set(tasks.map((task) => task.agentRole));
10186
+ return Array.from(roles).map((role) => AGENT_CATALOG[role]).sort((left, right) => left.role.localeCompare(right.role));
9231
10187
  }
9232
- async healthCheck() {
9233
- if (!this.definition.baseUrl) {
9234
- return {
9235
- ok: false,
9236
- message: "Missing baseUrl"
9237
- };
10188
+ toolsForAgent(role) {
10189
+ return this.toolRegistry.byRole(role);
10190
+ }
10191
+ canAutoExecuteTask(task) {
10192
+ return task.agentRole !== "session-orchestrator" && task.agentRole !== "execution-manager";
10193
+ }
10194
+ async executeTask(sessionId, task, request, plan, options = {}) {
10195
+ if (task.agentRole === "repo-analyst") {
10196
+ return this.executeRepoAnalysisTask(sessionId, task, request);
9238
10197
  }
9239
- const apiKey = resolveApiKey(this.definition);
9240
- if (this.definition.apiKeyEnv && !apiKey) {
9241
- return {
9242
- ok: false,
9243
- message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"}), missing ${this.definition.apiKeyEnv}`
9244
- };
10198
+ if (task.agentRole === "planner") {
10199
+ return this.executePlannerTask(sessionId, task, request, plan);
9245
10200
  }
9246
- try {
9247
- await requestJson(joinUrl(this.definition.baseUrl, "/models"), {
9248
- method: "GET",
9249
- headers: buildProviderHeaders(this.definition, apiKey, false)
9250
- }, 2e3);
9251
- } catch (error) {
10201
+ return this.executeAutonomousSpecialistTask(sessionId, task, request, options);
10202
+ }
10203
+ async executeAutonomousSpecialistTask(sessionId, task, request, options) {
10204
+ const config = await loadConfig(request.cwd);
10205
+ if (!config) {
9252
10206
  return {
9253
- ok: false,
9254
- message: error instanceof Error ? error.message : String(error)
10207
+ status: "blocked",
10208
+ summary: `Task ${task.id} cannot run because no .kimbho/config.json was found in ${request.cwd}.`,
10209
+ toolResults: [],
10210
+ artifacts: []
9255
10211
  };
9256
10212
  }
9257
- return {
9258
- ok: true,
9259
- message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"})`
9260
- };
9261
- }
9262
- async listModels(input) {
9263
- if (!this.definition.baseUrl) {
9264
- throw new Error(`Provider "${this.definition.id}" requires baseUrl.`);
9265
- }
9266
- const apiKey = resolveApiKey(this.definition);
9267
- const payload = await requestJson(joinUrl(this.definition.baseUrl, "/models"), {
9268
- method: "GET",
9269
- headers: buildProviderHeaders(this.definition, apiKey, false)
10213
+ const executor = new AutonomousTaskExecutor(config, this.toolRegistry, this.toolRuntime);
10214
+ return executor.execute(sessionId, task, request, {
10215
+ ...typeof options.maxAgentSteps === "number" ? {
10216
+ maxSteps: options.maxAgentSteps
10217
+ } : {},
10218
+ ...typeof options.maxRepairAttempts === "number" ? {
10219
+ maxRepairAttempts: options.maxRepairAttempts
10220
+ } : {}
9270
10221
  });
9271
- return mapOpenAIStyleModels(this.definition.id, payload, input);
9272
10222
  }
9273
- async generateText(input) {
9274
- if (!this.definition.baseUrl) {
9275
- throw new Error(`Provider "${this.definition.id}" requires baseUrl.`);
9276
- }
9277
- const apiKey = resolveApiKey(this.definition);
9278
- const model = requireModel(this.definition, input);
9279
- const response = await requestJson(`${this.definition.baseUrl}/chat/completions`, {
9280
- method: "POST",
9281
- headers: buildProviderHeaders(this.definition, apiKey),
9282
- body: JSON.stringify({
9283
- model,
9284
- messages: toChatMessages(input),
9285
- ...typeof input.temperature === "number" ? {
9286
- temperature: input.temperature
9287
- } : {},
9288
- ...typeof input.maxTokens === "number" ? {
9289
- max_tokens: input.maxTokens
9290
- } : {}
9291
- })
9292
- });
9293
- const choices = Array.isArray(response.choices) ? response.choices : [];
9294
- const firstChoice = choices.length > 0 && typeof choices[0] === "object" && choices[0] !== null ? choices[0] : null;
9295
- const message = firstChoice && typeof firstChoice.message === "object" && firstChoice.message !== null ? firstChoice.message : null;
10223
+ async executeRepoAnalysisTask(sessionId, task, request) {
10224
+ const context = { cwd: request.cwd };
10225
+ const toolResults = await Promise.all([
10226
+ this.safeRunTool("git.status", {}, context),
10227
+ this.safeRunTool("file.read", { path: "package.json" }, context),
10228
+ this.safeRunTool("file.read", { path: "README.md" }, context)
10229
+ ]);
10230
+ const successfulResults = toolResults.filter((result) => result.success);
10231
+ const scripts = extractPackageScripts(successfulResults);
10232
+ const artifactPath = await this.writeLogArtifact(sessionId, "repo-analysis", request.cwd, [
10233
+ `# Repo Analysis`,
10234
+ ``,
10235
+ `Goal: ${request.goal}`,
10236
+ `Workspace: ${request.cwd}`,
10237
+ `Task: ${task.id} - ${task.title}`,
10238
+ ``,
10239
+ `## Observations`,
10240
+ `- Successful tool probes: ${successfulResults.length}/${toolResults.length}`,
10241
+ `- Workspace state: ${request.workspaceState}`,
10242
+ `- Detected scripts: ${scripts.length > 0 ? scripts.join(", ") : "none"}`,
10243
+ ``,
10244
+ renderToolResultSection(toolResults)
10245
+ ].join("\n"));
9296
10246
  return {
9297
- text: typeof message?.content === "string" ? message.content : "",
9298
- model
10247
+ status: "completed",
10248
+ summary: `Completed ${task.id}: captured repo analysis and saved ${import_node_path6.default.basename(artifactPath)}.`,
10249
+ toolResults,
10250
+ artifacts: [
10251
+ artifactPath
10252
+ ]
9299
10253
  };
9300
10254
  }
9301
- };
9302
- var OllamaProvider = class {
9303
- definition;
9304
- constructor(definition) {
9305
- this.definition = ProviderDefinitionSchema.parse(definition);
10255
+ async executePlannerTask(sessionId, task, request, plan) {
10256
+ const artifactPath = await this.writeLogArtifact(sessionId, "architecture-brief", request.cwd, [
10257
+ `# Architecture Brief`,
10258
+ ``,
10259
+ `Goal: ${request.goal}`,
10260
+ `Summary: ${plan.summary}`,
10261
+ `Repo Strategy: ${plan.repoStrategy.mode} - ${plan.repoStrategy.reasoning}`,
10262
+ ``,
10263
+ `## Assumptions`,
10264
+ ...plan.assumptions.map((assumption) => `- ${assumption}`),
10265
+ ``,
10266
+ `## Milestones`,
10267
+ ...plan.milestones.flatMap((milestone) => [
10268
+ `### ${milestone.title}`,
10269
+ milestone.objective,
10270
+ ...milestone.tasks.map((milestoneTask) => `- ${milestoneTask.id}: ${milestoneTask.title} [${milestoneTask.agentRole}]`),
10271
+ ``
10272
+ ]),
10273
+ `## Verification`,
10274
+ ...plan.verificationChecklist.map((item) => `- ${item}`)
10275
+ ].join("\n"));
10276
+ return {
10277
+ status: "completed",
10278
+ summary: `Completed ${task.id}: wrote architecture brief ${import_node_path6.default.basename(artifactPath)}.`,
10279
+ toolResults: [],
10280
+ artifacts: [
10281
+ artifactPath
10282
+ ]
10283
+ };
9306
10284
  }
9307
- async healthCheck() {
9308
- const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
10285
+ async safeRunTool(toolId, input, context) {
9309
10286
  try {
9310
- await requestJson(`${baseUrl}/api/tags`, { method: "GET" }, 2e3);
9311
- return {
9312
- ok: true,
9313
- message: `${baseUrl} (${this.definition.defaultModel ?? "model not set"})`
9314
- };
10287
+ return await this.toolRuntime.run(toolId, input, context);
9315
10288
  } catch (error) {
10289
+ const message = error instanceof Error ? error.message : String(error);
9316
10290
  return {
9317
- ok: false,
9318
- message: error instanceof Error ? error.message : String(error)
10291
+ toolId,
10292
+ success: false,
10293
+ summary: `${toolId} failed: ${message}`,
10294
+ stderr: message,
10295
+ artifacts: []
9319
10296
  };
9320
10297
  }
9321
10298
  }
9322
- async listModels(input) {
9323
- const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
9324
- const payload = await requestJson(`${baseUrl}/api/tags`, { method: "GET" }, 2e3);
9325
- return mapOllamaModels(this.definition.id, payload, input);
9326
- }
9327
- async generateText(input) {
9328
- const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
9329
- const model = requireModel(this.definition, input);
9330
- const response = await requestJson(`${baseUrl}/api/generate`, {
9331
- method: "POST",
9332
- headers: {
9333
- "content-type": "application/json",
9334
- ...this.definition.headers
9335
- },
9336
- body: JSON.stringify({
9337
- model,
9338
- prompt: input.userPrompt ?? toPromptText(input),
9339
- ...input.systemPrompt ? {
9340
- system: input.systemPrompt
9341
- } : {},
9342
- stream: false,
9343
- ...typeof input.temperature === "number" ? {
9344
- options: {
9345
- temperature: input.temperature
9346
- }
9347
- } : {}
9348
- })
9349
- });
9350
- return {
9351
- text: typeof response.response === "string" ? response.response : "",
9352
- model
9353
- };
9354
- }
9355
- };
9356
- async function createCustomModuleProvider(definition, cwd) {
9357
- if (!definition.modulePath) {
9358
- throw new Error(`Provider "${definition.id}" requires modulePath.`);
9359
- }
9360
- const modulePath = import_node_path5.default.isAbsolute(definition.modulePath) ? definition.modulePath : import_node_path5.default.join(cwd, definition.modulePath);
9361
- await (0, import_promises5.access)(modulePath);
9362
- const module2 = await import((0, import_node_url.pathToFileURL)(modulePath).href);
9363
- const createProvider = typeof module2.createProvider === "function" ? module2.createProvider : typeof module2.default === "function" ? module2.default : null;
9364
- if (!createProvider) {
9365
- throw new Error(`Custom provider module "${modulePath}" must export createProvider(definition).`);
9366
- }
9367
- return createProvider(definition);
9368
- }
9369
- var BUILTIN_FACTORIES = {
9370
- "openai-responses": {
9371
- driver: "openai-responses",
9372
- create: async (definition) => new OpenAIResponsesProvider(definition)
9373
- },
9374
- "anthropic-messages": {
9375
- driver: "anthropic-messages",
9376
- create: async (definition) => new AnthropicMessagesProvider(definition)
9377
- },
9378
- "openai-compatible": {
9379
- driver: "openai-compatible",
9380
- create: async (definition) => new OpenAICompatibleProvider(definition)
9381
- },
9382
- ollama: {
9383
- driver: "ollama",
9384
- create: async (definition) => new OllamaProvider(definition)
9385
- },
9386
- "custom-module": {
9387
- driver: "custom-module",
9388
- create: (definition, cwd) => createCustomModuleProvider(definition, cwd)
10299
+ async writeLogArtifact(sessionId, label, cwd, content) {
10300
+ await ensureKimbhoDir(cwd);
10301
+ const logsDir = import_node_path6.default.join(resolveKimbhoDir(cwd), "logs");
10302
+ const outputPath = import_node_path6.default.join(logsDir, `${sessionId}-${label}.md`);
10303
+ await (0, import_promises6.writeFile)(outputPath, `${content}
10304
+ `, "utf8");
10305
+ return outputPath;
9389
10306
  }
9390
10307
  };
9391
- var BrainProviderRegistry = class {
9392
- cwd;
9393
- factories;
9394
- constructor(cwd = process.cwd(), factories = Object.values(BUILTIN_FACTORIES)) {
9395
- this.cwd = cwd;
9396
- this.factories = new Map(factories.map((factory) => [factory.driver, factory]));
9397
- }
9398
- listDrivers() {
9399
- return Array.from(this.factories.keys()).sort();
9400
- }
9401
- async createProvider(definition) {
9402
- const normalized = ProviderDefinitionSchema.parse(definition);
9403
- const factory = this.factories.get(normalized.driver);
9404
- if (!factory) {
9405
- throw new Error(`No provider factory registered for driver "${normalized.driver}".`);
10308
+
10309
+ // src/commands/agents.ts
10310
+ function createAgentsCommand() {
10311
+ return new Command("agents").description("Show the current agent hierarchy and allowed tools.").option("--active", "Show only agents assigned to the latest session", false).action(async (options) => {
10312
+ const orchestrator = new ExecutionOrchestrator();
10313
+ const session = options.active ? await loadLatestSession(process.cwd()) : null;
10314
+ const profiles = session ? listAgentProfiles().filter((agent) => session.assignedAgents.includes(agent.role)) : listAgentProfiles();
10315
+ if (session) {
10316
+ console.log(`Session: ${session.id}`);
10317
+ console.log(`Ready tasks: ${session.readyTaskIds.join(", ") || "none"}`);
10318
+ console.log(`Blocked tasks: ${session.blockedTaskIds.join(", ") || "none"}`);
9406
10319
  }
9407
- return factory.create(normalized, this.cwd);
9408
- }
9409
- async healthCheck(definition) {
9410
- const provider = await this.createProvider(definition);
9411
- return provider.healthCheck();
9412
- }
9413
- async listModels(definition, input) {
9414
- const provider = await this.createProvider(definition);
9415
- if (provider.listModels) {
9416
- return provider.listModels(input);
10320
+ for (const agent of profiles) {
10321
+ const tools = orchestrator.toolsForAgent(agent.role).map((tool) => tool.id);
10322
+ console.log(`${agent.role}`);
10323
+ console.log(` brain: ${agent.brainRole}`);
10324
+ console.log(` purpose: ${agent.purpose}`);
10325
+ console.log(` tools: ${tools.join(", ")}`);
10326
+ console.log(` concurrency: ${agent.maxConcurrentTasks}`);
9417
10327
  }
9418
- return filterModels(definition.models.map((modelId) => ProviderModelSchema.parse({
9419
- id: modelId,
9420
- providerId: definition.id
9421
- })), input);
9422
- }
9423
- };
9424
- function createDefaultBrainProviderRegistry(cwd = process.cwd()) {
9425
- return new BrainProviderRegistry(cwd);
10328
+ });
9426
10329
  }
9427
10330
 
9428
- // ../brains/dist/resolver.js
9429
- var BrainResolver = class {
9430
- config;
9431
- registry;
9432
- constructor(config, registry = createDefaultBrainProviderRegistry()) {
9433
- this.config = config;
9434
- this.registry = registry;
9435
- }
9436
- async resolve(role) {
9437
- const settings = resolveBrainSettings(this.config, role);
9438
- const provider = findProviderById(this.config, settings.providerId);
9439
- if (!provider) {
9440
- throw new Error(`Brain role "${role}" points to unknown provider "${settings.providerId}".`);
9441
- }
9442
- const model = resolveBrainModel(this.config, role);
9443
- if (!model) {
9444
- throw new Error(`Brain role "${role}" does not resolve to a model.`);
9445
- }
9446
- return {
9447
- role,
9448
- settings,
9449
- provider,
9450
- model,
9451
- client: await this.registry.createProvider(provider)
9452
- };
9453
- }
9454
- };
10331
+ // src/commands/brains.ts
10332
+ var import_node_process2 = __toESM(require("node:process"), 1);
9455
10333
 
9456
10334
  // src/ui/renderPlan.ts
9457
10335
  function humanizeRole(role) {
@@ -9996,7 +10874,7 @@ function createModelsCommand() {
9996
10874
  }
9997
10875
 
9998
10876
  // src/commands/plan.ts
9999
- var import_promises6 = require("node:fs/promises");
10877
+ var import_promises7 = require("node:fs/promises");
10000
10878
  var import_node_process6 = __toESM(require("node:process"), 1);
10001
10879
 
10002
10880
  // ../planner/dist/planner.js
@@ -10301,7 +11179,7 @@ function createPlan(input) {
10301
11179
 
10302
11180
  // src/commands/plan.ts
10303
11181
  async function detectWorkspaceState(cwd) {
10304
- const entries = await (0, import_promises6.readdir)(cwd);
11182
+ const entries = await (0, import_promises7.readdir)(cwd);
10305
11183
  const meaningfulEntries = entries.filter((entry) => entry !== ".kimbho" && !entry.startsWith("."));
10306
11184
  return meaningfulEntries.length === 0 ? "empty" : "existing";
10307
11185
  }
@@ -10464,7 +11342,7 @@ function parseCount(value) {
10464
11342
  return parsed;
10465
11343
  }
10466
11344
  function createResumeCommand() {
10467
- return new Command("resume").description("Resume the latest saved Kimbho session snapshot.").option("--json", "Print the latest session as JSON", false).option("--execute", "Continue auto-executing the ready-task frontier", false).option("--max-auto-tasks <count>", "Maximum ready tasks to auto-execute", parseCount, 2).action(async (options) => {
11345
+ return new Command("resume").description("Resume the latest saved Kimbho session snapshot.").option("--json", "Print the latest session as JSON", false).option("--execute", "Continue auto-executing the ready-task frontier", false).option("--max-auto-tasks <count>", "Maximum ready tasks to auto-execute", parseCount, 2).option("--max-agent-steps <count>", "Maximum tool/model steps per autonomous task", parseCount, 8).option("--max-repair-attempts <count>", "Maximum failed verification cycles per autonomous task", parseCount, 2).action(async (options) => {
10468
11346
  const cwd = import_node_process8.default.cwd();
10469
11347
  const session = await loadLatestSession(cwd);
10470
11348
  if (!session) {
@@ -10473,7 +11351,9 @@ function createResumeCommand() {
10473
11351
  return;
10474
11352
  }
10475
11353
  const snapshot = options.execute ? await new ExecutionOrchestrator().continueSession(session, {
10476
- maxAutoTasks: options.maxAutoTasks
11354
+ maxAutoTasks: options.maxAutoTasks,
11355
+ maxAgentSteps: options.maxAgentSteps,
11356
+ maxRepairAttempts: options.maxRepairAttempts
10477
11357
  }) : session;
10478
11358
  if (options.execute) {
10479
11359
  await saveSession(snapshot, cwd);
@@ -10487,10 +11367,10 @@ function createResumeCommand() {
10487
11367
  }
10488
11368
 
10489
11369
  // src/commands/run.ts
10490
- var import_promises7 = require("node:fs/promises");
11370
+ var import_promises8 = require("node:fs/promises");
10491
11371
  var import_node_process9 = __toESM(require("node:process"), 1);
10492
11372
  async function detectWorkspaceState2(cwd) {
10493
- const entries = await (0, import_promises7.readdir)(cwd);
11373
+ const entries = await (0, import_promises8.readdir)(cwd);
10494
11374
  const meaningfulEntries = entries.filter((entry) => entry !== ".kimbho" && !entry.startsWith("."));
10495
11375
  return meaningfulEntries.length === 0 ? "empty" : "existing";
10496
11376
  }
@@ -10507,7 +11387,7 @@ function createRunCommand() {
10507
11387
  "Explicit execution constraint; can be provided multiple times",
10508
11388
  (value, previous = []) => [...previous, value],
10509
11389
  []
10510
- ).option("--json", "Print the session snapshot as JSON", false).option("--snapshot-only", "Create the session without auto-executing ready tasks", false).option("--max-auto-tasks <count>", "Maximum ready tasks to auto-execute", parseCount2, 2).action(async (goal, options) => {
11390
+ ).option("--json", "Print the session snapshot as JSON", false).option("--snapshot-only", "Create the session without auto-executing ready tasks", false).option("--max-auto-tasks <count>", "Maximum ready tasks to auto-execute", parseCount2, 2).option("--max-agent-steps <count>", "Maximum tool/model steps per autonomous task", parseCount2, 8).option("--max-repair-attempts <count>", "Maximum failed verification cycles per autonomous task", parseCount2, 2).action(async (goal, options) => {
10511
11391
  const cwd = import_node_process9.default.cwd();
10512
11392
  const orchestrator = new ExecutionOrchestrator();
10513
11393
  let request;
@@ -10542,7 +11422,9 @@ function createRunCommand() {
10542
11422
  const envelope = orchestrator.buildEnvelope(request, plan);
10543
11423
  const initialSnapshot = orchestrator.createSessionSnapshot(envelope);
10544
11424
  const snapshot = options.snapshotOnly ? initialSnapshot : await orchestrator.continueSession(initialSnapshot, {
10545
- maxAutoTasks: options.maxAutoTasks
11425
+ maxAutoTasks: options.maxAutoTasks,
11426
+ maxAgentSteps: options.maxAgentSteps,
11427
+ maxRepairAttempts: options.maxRepairAttempts
10546
11428
  });
10547
11429
  const sessionPath = await saveSession(snapshot, cwd);
10548
11430
  if (options.json) {
@@ -10630,7 +11512,7 @@ function createProgram(onOpenShell) {
10630
11512
  }
10631
11513
 
10632
11514
  // src/shell.ts
10633
- var import_promises8 = require("node:readline/promises");
11515
+ var import_promises9 = require("node:readline/promises");
10634
11516
  var import_node_process10 = __toESM(require("node:process"), 1);
10635
11517
  var AMBER = "\x1B[38;5;214m";
10636
11518
  var BOLD = "\x1B[1m";
@@ -10658,6 +11540,9 @@ var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
10658
11540
  "select",
10659
11541
  "shell",
10660
11542
  "status",
11543
+ "ask",
11544
+ "chat",
11545
+ "reset-chat",
10661
11546
  "use-model"
10662
11547
  ]);
10663
11548
  var MODEL_SUBCOMMANDS = /* @__PURE__ */ new Set([
@@ -10672,6 +11557,7 @@ var BRAIN_ROLES = [
10672
11557
  "reviewer",
10673
11558
  "fast"
10674
11559
  ];
11560
+ var MAX_CHAT_MESSAGES = 12;
10675
11561
  function color(code, value) {
10676
11562
  return `${code}${value}${RESET}`;
10677
11563
  }
@@ -10741,6 +11627,9 @@ function renderHelp() {
10741
11627
  `${color(DIM, "Commands")}`,
10742
11628
  "/status Show the active role, provider, and workspace state.",
10743
11629
  "/brain <role> Change shell focus to planner, coder, reviewer, or fast.",
11630
+ "/ask <prompt> Send a prompt to the active model.",
11631
+ "/chat <prompt> Alias for /ask <prompt>.",
11632
+ "/reset-chat Clear the active model conversation history.",
10744
11633
  "/model Show current brain assignments.",
10745
11634
  "/providers List configured providers.",
10746
11635
  "/providers templates Show built-in provider templates.",
@@ -10760,7 +11649,7 @@ function renderHelp() {
10760
11649
  "/quit, /exit Leave the shell.",
10761
11650
  "",
10762
11651
  `${color(DIM, "Tip")}`,
10763
- "Type a natural-language goal directly and Kimbho will treat it as /run <goal>."
11652
+ "Type natural language directly to chat with the active model. Use /run when you want a plan/session."
10764
11653
  ].join("\n");
10765
11654
  }
10766
11655
  function renderStartupCard(cwd, state) {
@@ -10775,7 +11664,7 @@ function renderStartupCard(cwd, state) {
10775
11664
  renderCardLine("approval", state.approvalMode),
10776
11665
  renderCardLine("sandbox", state.sandboxMode),
10777
11666
  renderCardLine("preset", state.stackPreset),
10778
- renderCardLine("shortcuts", "/brain /providers /models /select /use-model /quit")
11667
+ renderCardLine("shortcuts", "/ask /run /brain /providers /models /quit")
10779
11668
  ];
10780
11669
  if (!state.configured) {
10781
11670
  cardLines.push("setup: run /init or /providers add <template> to create .kimbho/config.json");
@@ -10791,7 +11680,7 @@ function printHeader(cwd, state) {
10791
11680
  console.log(color(DIM, "Terminal-native coding agent"));
10792
11681
  console.log(renderStartupCard(cwd, state));
10793
11682
  console.log("");
10794
- console.log(color(DIM, "Tip: configure providers and models here, then describe the work and Kimbho will route it into /run."));
11683
+ console.log(color(DIM, "Tip: configure providers and models here, then chat directly. Use /run for planning and execution."));
10795
11684
  console.log(renderHelp());
10796
11685
  console.log("");
10797
11686
  }
@@ -10867,6 +11756,63 @@ function normalizeInputTokens(input) {
10867
11756
  head: firstToken.startsWith("/") ? firstToken.slice(1) : firstToken
10868
11757
  };
10869
11758
  }
11759
+ function trimConversation(messages) {
11760
+ if (messages.length <= MAX_CHAT_MESSAGES) {
11761
+ return messages;
11762
+ }
11763
+ return messages.slice(messages.length - MAX_CHAT_MESSAGES);
11764
+ }
11765
+ function clearConversation(runtime, role) {
11766
+ runtime.conversations[role] = [];
11767
+ }
11768
+ function getConversation(runtime, role) {
11769
+ return runtime.conversations[role] ?? [];
11770
+ }
11771
+ function clearAllConversations(runtime) {
11772
+ for (const role of BRAIN_ROLES) {
11773
+ runtime.conversations[role] = [];
11774
+ }
11775
+ }
11776
+ async function handleChatPrompt(cwd, prompt, runtime) {
11777
+ const config = await loadConfig(cwd);
11778
+ if (!config) {
11779
+ throw new Error("No config found. Run /init or /providers add <template> first.");
11780
+ }
11781
+ const registry = createDefaultBrainProviderRegistry(cwd);
11782
+ const resolver = new BrainResolver(config, registry);
11783
+ const brain = await resolver.resolve(runtime.focusRole);
11784
+ const history = getConversation(runtime, runtime.focusRole);
11785
+ const messages = trimConversation([
11786
+ ...history,
11787
+ {
11788
+ role: "user",
11789
+ content: prompt
11790
+ }
11791
+ ]);
11792
+ const result = await brain.client.generateText({
11793
+ model: brain.model,
11794
+ messages,
11795
+ ...brain.settings.promptPreamble ? {
11796
+ systemPrompt: brain.settings.promptPreamble
11797
+ } : {},
11798
+ ...typeof brain.settings.temperature === "number" ? {
11799
+ temperature: brain.settings.temperature
11800
+ } : {},
11801
+ ...typeof brain.settings.maxTokens === "number" ? {
11802
+ maxTokens: brain.settings.maxTokens
11803
+ } : {}
11804
+ });
11805
+ const nextConversation = trimConversation([
11806
+ ...messages,
11807
+ {
11808
+ role: "assistant",
11809
+ content: result.text
11810
+ }
11811
+ ]);
11812
+ runtime.conversations[runtime.focusRole] = nextConversation;
11813
+ console.log(color(DIM, `[${brain.role}] ${brain.provider.id}/${brain.model}`));
11814
+ console.log(result.text);
11815
+ }
10870
11816
  function defaultProviderIdForTemplate(templateId) {
10871
11817
  switch (templateId) {
10872
11818
  case "openai":
@@ -11145,6 +12091,7 @@ async function handleProvidersCommand(cwd, tokens, runtime) {
11145
12091
  throw new Error("Usage: /providers use <provider-id>");
11146
12092
  }
11147
12093
  const result = await useProviderGlobally(cwd, providerId);
12094
+ clearAllConversations(runtime);
11148
12095
  runtime.lastModels = null;
11149
12096
  console.log(`Updated ${result.outputPath}`);
11150
12097
  console.log(`All roles now use provider ${providerId}${result.model ? ` (${result.model})` : ""}`);
@@ -11189,6 +12136,7 @@ async function handleModelsCommand(cwd, tokens, runtime) {
11189
12136
  throw new Error("Usage: /models use <model-id>");
11190
12137
  }
11191
12138
  const outputPath = await assignModelGlobally(cwd, provider.id, modelId);
12139
+ clearAllConversations(runtime);
11192
12140
  console.log(`Updated ${outputPath}`);
11193
12141
  console.log(`Selected ${provider.id}/${modelId}`);
11194
12142
  console.log("Assigned all roles");
@@ -11230,6 +12178,7 @@ async function handleModelSelection(cwd, modelId, runtime) {
11230
12178
  throw new Error(`Active role "${runtime.focusRole}" points to unknown provider "${providerId}".`);
11231
12179
  }
11232
12180
  const outputPath = await assignModelGlobally(cwd, provider.id, modelId);
12181
+ clearAllConversations(runtime);
11233
12182
  console.log(`Updated ${outputPath}`);
11234
12183
  console.log(`Selected ${provider.id}/${modelId}`);
11235
12184
  console.log("Assigned all roles");
@@ -11275,10 +12224,22 @@ function toExternalCommandTokens(input, state) {
11275
12224
  `scaffold ${goal}`.trim()
11276
12225
  ];
11277
12226
  }
11278
- if (!firstToken.startsWith("/") && !TOP_LEVEL_COMMANDS.has(head) && !head.startsWith("-")) {
11279
- return [
12227
+ if (head === "run") {
12228
+ const goal = normalizedInput.replace(/^\/?run\b\s*/, "");
12229
+ return goal ? [
11280
12230
  "run",
11281
- normalizedInput
12231
+ goal
12232
+ ] : [
12233
+ "run"
12234
+ ];
12235
+ }
12236
+ if (head === "plan") {
12237
+ const goal = normalizedInput.replace(/^\/?plan\b\s*/, "");
12238
+ return goal ? [
12239
+ "plan",
12240
+ goal
12241
+ ] : [
12242
+ "plan"
11282
12243
  ];
11283
12244
  }
11284
12245
  tokens[0] = head;
@@ -11309,9 +12270,14 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
11309
12270
  return;
11310
12271
  }
11311
12272
  const { tokens, head } = normalizeInputTokens(trimmed);
12273
+ const firstToken = tokens[0];
11312
12274
  if (!head) {
11313
12275
  return;
11314
12276
  }
12277
+ if (!firstToken?.startsWith("/") && !TOP_LEVEL_COMMANDS.has(head) && !head.startsWith("-")) {
12278
+ await handleChatPrompt(cwd, trimmed, runtime);
12279
+ return;
12280
+ }
11315
12281
  if (head === "provider" || head === "providers") {
11316
12282
  const subcommand = tokens[1];
11317
12283
  const canHandleLocally = !subcommand || subcommand === "list" || subcommand === "templates" || subcommand === "check" || subcommand === "use" || subcommand === "add" && Boolean(tokens[2]) && !tokens[2]?.startsWith("-");
@@ -11339,6 +12305,19 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
11339
12305
  await printBrainAssignments(cwd);
11340
12306
  return;
11341
12307
  }
12308
+ if (head === "ask" || head === "chat") {
12309
+ const prompt = tokens.slice(1).join(" ").trim();
12310
+ if (!prompt) {
12311
+ throw new Error(`Usage: /${head} <prompt>`);
12312
+ }
12313
+ await handleChatPrompt(cwd, prompt, runtime);
12314
+ return;
12315
+ }
12316
+ if (head === "reset-chat") {
12317
+ clearConversation(runtime, runtime.focusRole);
12318
+ console.log(`Cleared ${runtime.focusRole} conversation history.`);
12319
+ return;
12320
+ }
11342
12321
  if (head === "models") {
11343
12322
  await handleModelsCommand(cwd, [
11344
12323
  "models",
@@ -11368,14 +12347,20 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
11368
12347
  await execute(externalTokens);
11369
12348
  }
11370
12349
  async function runInteractiveShell(options) {
11371
- const readline = (0, import_promises8.createInterface)({
12350
+ const readline = (0, import_promises9.createInterface)({
11372
12351
  input: import_node_process10.default.stdin,
11373
12352
  output: import_node_process10.default.stdout,
11374
12353
  terminal: Boolean(import_node_process10.default.stdin.isTTY && import_node_process10.default.stdout.isTTY)
11375
12354
  });
11376
12355
  const runtime = {
11377
12356
  focusRole: "coder",
11378
- lastModels: null
12357
+ lastModels: null,
12358
+ conversations: {
12359
+ planner: [],
12360
+ coder: [],
12361
+ reviewer: [],
12362
+ fast: []
12363
+ }
11379
12364
  };
11380
12365
  let closed = false;
11381
12366
  readline.on("SIGINT", () => {