@kimbho/kimbho-cli 0.1.3 → 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.3",
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({
@@ -8072,6 +8107,34 @@ function assignBrain(config, role, settings) {
8072
8107
  }
8073
8108
  });
8074
8109
  }
8110
+ function buildRetainedBrainSettings(current, providerId, model) {
8111
+ return {
8112
+ providerId,
8113
+ ...model ? {
8114
+ model
8115
+ } : {},
8116
+ ...typeof current.temperature === "number" ? {
8117
+ temperature: current.temperature
8118
+ } : {},
8119
+ ...typeof current.maxTokens === "number" ? {
8120
+ maxTokens: current.maxTokens
8121
+ } : {},
8122
+ ...current.promptPreamble ? {
8123
+ promptPreamble: current.promptPreamble
8124
+ } : {}
8125
+ };
8126
+ }
8127
+ function assignProviderToAllBrains(config, providerId, model) {
8128
+ return KimbhoConfigSchema.parse({
8129
+ ...config,
8130
+ brains: {
8131
+ planner: buildRetainedBrainSettings(config.brains.planner, providerId, model),
8132
+ coder: buildRetainedBrainSettings(config.brains.coder, providerId, model),
8133
+ reviewer: buildRetainedBrainSettings(config.brains.reviewer, providerId, model),
8134
+ fast: buildRetainedBrainSettings(config.brains.fast, providerId, model)
8135
+ }
8136
+ });
8137
+ }
8075
8138
  function resolveBrainSettings(config, role) {
8076
8139
  return config.brains[role];
8077
8140
  }
@@ -8136,1255 +8199,2137 @@ async function loadLatestSession(cwd = process.cwd()) {
8136
8199
  return loadSession(import_node_path2.default.join(sessionsDir, latest));
8137
8200
  }
8138
8201
 
8139
- // ../tools/dist/types.js
8140
- var ToolPermissionSchema = external_exports.enum([
8141
- "safe",
8142
- "approval-required",
8143
- "destructive"
8144
- ]);
8145
- var ToolDescriptorSchema = external_exports.object({
8146
- id: external_exports.string().min(1),
8147
- description: external_exports.string().min(1),
8148
- permission: ToolPermissionSchema.default("safe"),
8149
- timeoutMs: external_exports.number().int().positive(),
8150
- retryable: external_exports.boolean().default(false),
8151
- producesArtifacts: external_exports.boolean().default(false),
8152
- allowedRoles: external_exports.array(AgentRoleSchema).default([])
8153
- });
8154
-
8155
- // ../tools/dist/registry.js
8156
- var BUILTIN_TOOLS = [
8202
+ // ../brains/dist/templates.js
8203
+ var BUILTIN_PROVIDER_TEMPLATES = [
8157
8204
  {
8158
- id: "file.read",
8159
- description: "Read a file from the workspace.",
8160
- permission: "safe",
8161
- timeoutMs: 5e3,
8162
- retryable: false,
8163
- producesArtifacts: false,
8164
- allowedRoles: [
8165
- "session-orchestrator",
8166
- "repo-analyst",
8167
- "planner",
8168
- "execution-manager",
8169
- "frontend-specialist",
8170
- "backend-specialist",
8171
- "database-specialist",
8172
- "infra-specialist",
8173
- "test-debugger",
8174
- "reviewer",
8175
- "integrator"
8176
- ]
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."
8177
8212
  },
8178
8213
  {
8179
- id: "file.patch",
8180
- description: "Apply a structured patch to workspace files.",
8181
- permission: "approval-required",
8182
- timeoutMs: 1e4,
8183
- retryable: true,
8184
- producesArtifacts: true,
8185
- allowedRoles: [
8186
- "frontend-specialist",
8187
- "backend-specialist",
8188
- "database-specialist",
8189
- "infra-specialist",
8190
- "test-debugger",
8191
- "integrator"
8192
- ]
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."
8193
8220
  },
8194
8221
  {
8195
- id: "shell.exec",
8196
- description: "Run a shell command inside the workspace.",
8197
- permission: "approval-required",
8198
- timeoutMs: 12e4,
8199
- retryable: true,
8200
- producesArtifacts: true,
8201
- allowedRoles: [
8202
- "repo-analyst",
8203
- "execution-manager",
8204
- "backend-specialist",
8205
- "infra-specialist",
8206
- "test-debugger",
8207
- "reviewer",
8208
- "integrator"
8209
- ]
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."
8210
8228
  },
8211
8229
  {
8212
- id: "git.status",
8213
- description: "Inspect the current git working tree and diff state.",
8214
- permission: "safe",
8215
- timeoutMs: 1e4,
8216
- retryable: true,
8217
- producesArtifacts: false,
8218
- allowedRoles: [
8219
- "session-orchestrator",
8220
- "repo-analyst",
8221
- "execution-manager",
8222
- "reviewer",
8223
- "integrator"
8224
- ]
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."
8225
8236
  },
8226
8237
  {
8227
- id: "tests.run",
8228
- description: "Execute verification commands such as tests, linting, and builds.",
8229
- permission: "approval-required",
8230
- timeoutMs: 3e5,
8231
- retryable: true,
8232
- producesArtifacts: true,
8233
- allowedRoles: [
8234
- "execution-manager",
8235
- "test-debugger",
8236
- "reviewer",
8237
- "integrator"
8238
- ]
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."
8239
8243
  }
8240
8244
  ];
8241
- var ToolRegistry = class {
8242
- tools = /* @__PURE__ */ new Map();
8243
- constructor(seed = BUILTIN_TOOLS) {
8244
- for (const descriptor of seed) {
8245
- const normalized = ToolDescriptorSchema.parse(descriptor);
8246
- this.tools.set(normalized.id, normalized);
8247
- }
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;
8248
8257
  }
8249
- list() {
8250
- 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;
8251
8261
  }
8252
- get(id) {
8253
- 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;
8254
8270
  }
8255
- byRole(role) {
8256
- 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}".`);
8257
8277
  }
8258
- };
8259
- function createDefaultToolRegistry() {
8260
- 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
+ });
8261
8295
  }
8262
8296
 
8263
- // ../tools/dist/runtime.js
8297
+ // ../brains/dist/registry.js
8264
8298
  var import_promises3 = require("node:fs/promises");
8265
8299
  var import_node_path3 = __toESM(require("node:path"), 1);
8266
- var import_node_process = __toESM(require("node:process"), 1);
8267
- var import_node_child_process = require("node:child_process");
8268
- var import_node_os = require("node:os");
8269
- var DEFAULT_CAPTURE_LIMIT = 16e3;
8270
- function truncateOutput(value) {
8271
- if (!value) {
8272
- return value;
8273
- }
8274
- if (value.length <= DEFAULT_CAPTURE_LIMIT) {
8275
- return value;
8300
+ var import_node_url = require("node:url");
8301
+ function resolveApiKey(definition) {
8302
+ if (!definition.apiKeyEnv) {
8303
+ return null;
8276
8304
  }
8277
- const omitted = value.length - DEFAULT_CAPTURE_LIMIT;
8278
- return `${value.slice(0, DEFAULT_CAPTURE_LIMIT)}
8279
- ... [truncated ${omitted} chars]`;
8305
+ return process.env[definition.apiKeyEnv] ?? null;
8280
8306
  }
8281
- function resolveWorkspacePath(cwd, filePath) {
8282
- const resolved = import_node_path3.default.resolve(cwd, filePath);
8283
- const relative = import_node_path3.default.relative(cwd, resolved);
8284
- if (relative.startsWith("..") || import_node_path3.default.isAbsolute(relative)) {
8285
- 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}`);
8286
8320
  }
8287
- return resolved;
8321
+ return response.json();
8288
8322
  }
8289
- async function runSpawn(command, args, cwd, timeoutMs) {
8290
- return new Promise((resolve, reject) => {
8291
- const child = (0, import_node_child_process.spawn)(command, args, {
8292
- cwd,
8293
- env: import_node_process.default.env,
8294
- stdio: [
8295
- "ignore",
8296
- "pipe",
8297
- "pipe"
8298
- ]
8299
- });
8300
- const stdout = [];
8301
- const stderr = [];
8302
- let settled = false;
8303
- let timedOut = false;
8304
- const timer = setTimeout(() => {
8305
- timedOut = true;
8306
- child.kill("SIGTERM");
8307
- setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
8308
- }, timeoutMs);
8309
- child.stdout.on("data", (chunk) => {
8310
- stdout.push(String(chunk));
8311
- });
8312
- child.stderr.on("data", (chunk) => {
8313
- stderr.push(String(chunk));
8314
- });
8315
- child.on("error", (error) => {
8316
- if (settled) {
8317
- return;
8318
- }
8319
- settled = true;
8320
- clearTimeout(timer);
8321
- reject(error);
8322
- });
8323
- child.on("close", (code) => {
8324
- if (settled) {
8325
- return;
8326
- }
8327
- settled = true;
8328
- clearTimeout(timer);
8329
- resolve({
8330
- code,
8331
- stdout: stdout.join(""),
8332
- stderr: stderr.join(""),
8333
- timedOut
8334
- });
8335
- });
8336
- });
8323
+ function trimTrailingSlash2(value) {
8324
+ return value.replace(/\/+$/, "");
8337
8325
  }
8338
- async function runShellCommand(toolId, command, cwd, timeoutMs) {
8339
- const shell = import_node_process.default.env.SHELL ?? "/bin/sh";
8340
- const result = await runSpawn(shell, [
8341
- "-lc",
8342
- command
8343
- ], cwd, timeoutMs);
8344
- const success = !result.timedOut && result.code === 0;
8345
- const summary = result.timedOut ? `Command timed out after ${timeoutMs}ms.` : success ? `Command completed successfully.` : `Command exited with code ${result.code ?? "unknown"}.`;
8346
- return ToolResultSchema.parse({
8347
- toolId,
8348
- success,
8349
- summary,
8350
- stdout: truncateOutput(result.stdout),
8351
- stderr: truncateOutput(result.stderr)
8352
- });
8326
+ function joinUrl(baseUrl, suffix) {
8327
+ const normalizedBase = trimTrailingSlash2(baseUrl);
8328
+ const normalizedSuffix = suffix.startsWith("/") ? suffix : `/${suffix}`;
8329
+ return `${normalizedBase}${normalizedSuffix}`;
8353
8330
  }
8354
- async function executeFileRead(input, context) {
8355
- const rawPath = typeof input.path === "string" ? input.path : null;
8356
- if (!rawPath) {
8357
- throw new Error("file.read requires a string path.");
8358
- }
8359
- const targetPath = resolveWorkspacePath(context.cwd, rawPath);
8360
- const contents = await (0, import_promises3.readFile)(targetPath, "utf8");
8361
- return ToolResultSchema.parse({
8362
- toolId: "file.read",
8363
- success: true,
8364
- summary: `Read ${import_node_path3.default.relative(context.cwd, targetPath) || import_node_path3.default.basename(targetPath)}.`,
8365
- stdout: truncateOutput(contents),
8366
- artifacts: [
8367
- targetPath
8368
- ]
8369
- });
8331
+ function isOpenRouter(definition) {
8332
+ return definition.baseUrl?.includes("openrouter.ai") ?? false;
8370
8333
  }
8371
- async function executeShell(input, context, timeoutMs) {
8372
- const command = typeof input.command === "string" ? input.command : null;
8373
- if (!command) {
8374
- 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";
8375
8340
  }
8376
- 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;
8377
8353
  }
8378
- async function executeGitStatus(_input, context, timeoutMs) {
8379
- 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;
8380
8358
  }
8381
- async function executeTests(input, context, timeoutMs) {
8382
- const command = typeof input.command === "string" && input.command.trim().length > 0 ? input.command : "npm test";
8383
- const result = await runShellCommand("tests.run", command, context.cwd, timeoutMs);
8384
- return ToolResultSchema.parse({
8385
- ...result,
8386
- 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
+ ];
8387
8393
  });
8394
+ return filterModels(models, input);
8388
8395
  }
8389
- async function executeFilePatch(input, context, timeoutMs) {
8390
- const patch = typeof input.patch === "string" ? input.patch : null;
8391
- if (!patch) {
8392
- 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
+ });
8393
8451
  }
8394
- const tempDir = await (0, import_promises3.mkdtemp)(import_node_path3.default.join((0, import_node_os.tmpdir)(), "kimbho-patch-"));
8395
- const patchPath = import_node_path3.default.join(tempDir, "change.patch");
8396
- try {
8397
- await (0, import_promises3.writeFile)(patchPath, patch, "utf8");
8398
- const result = await runSpawn("git", [
8399
- "apply",
8400
- "--recount",
8401
- "--whitespace=nowarn",
8402
- patchPath
8403
- ], context.cwd, timeoutMs);
8404
- const success = !result.timedOut && result.code === 0;
8405
- return ToolResultSchema.parse({
8406
- toolId: "file.patch",
8407
- success,
8408
- summary: success ? "Patch applied successfully." : result.timedOut ? `Patch timed out after ${timeoutMs}ms.` : `Patch failed with code ${result.code ?? "unknown"}.`,
8409
- stdout: truncateOutput(result.stdout),
8410
- stderr: truncateOutput(result.stderr),
8411
- 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
8412
8458
  });
8413
- } finally {
8414
- await (0, import_promises3.rm)(tempDir, { recursive: true, force: true });
8415
8459
  }
8460
+ return messages;
8416
8461
  }
8417
- var ToolRuntime = class {
8418
- registry;
8419
- executors;
8420
- constructor(registry = createDefaultToolRegistry()) {
8421
- this.registry = registry;
8422
- this.executors = /* @__PURE__ */ new Map([
8423
- [
8424
- "file.read",
8425
- (input, context) => executeFileRead(input, context)
8426
- ],
8427
- [
8428
- "file.patch",
8429
- executeFilePatch
8430
- ],
8431
- [
8432
- "shell.exec",
8433
- executeShell
8434
- ],
8435
- [
8436
- "git.status",
8437
- executeGitStatus
8438
- ],
8439
- [
8440
- "tests.run",
8441
- executeTests
8442
- ]
8443
- ]);
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.`);
8444
8470
  }
8445
- async run(toolId, input, context) {
8446
- const descriptor = this.registry.get(toolId);
8447
- if (!descriptor) {
8448
- throw new Error(`Unknown tool "${toolId}".`);
8449
- }
8450
- const executor = this.executors.get(toolId);
8451
- if (!executor) {
8452
- throw new Error(`No executor registered for "${toolId}".`);
8453
- }
8454
- return executor(input, context, descriptor.timeoutMs);
8455
- }
8456
- };
8457
-
8458
- // ../agent-runtime/dist/orchestrator.js
8459
- function createSessionId() {
8460
- return `session-${Date.now()}`;
8461
- }
8462
- function createEventId(type, taskId) {
8463
- return `${type}-${taskId ?? "session"}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
8464
- }
8465
- function isTaskReady(task, completedTaskIds) {
8466
- return task.status === "pending" && task.dependsOn.every((taskId) => completedTaskIds.has(taskId));
8467
- }
8468
- function completedTaskIdsFromPlan(plan) {
8469
- return new Set(flattenPlanTasks(plan).filter((task) => task.status === "completed").map((task) => task.id));
8471
+ return model;
8470
8472
  }
8471
- function deriveStatus(plan, readyTasks, blockedTasks) {
8472
- const tasks = flattenPlanTasks(plan);
8473
- const completed = tasks.filter((task) => task.status === "completed").length;
8474
- if (completed === tasks.length && tasks.length > 0) {
8475
- return "completed";
8476
- }
8477
- if (readyTasks.length > 0) {
8478
- return completed > 0 ? "running" : "ready";
8479
- }
8480
- if (blockedTasks.length > 0) {
8481
- return "blocked";
8473
+ var OpenAIResponsesProvider = class {
8474
+ definition;
8475
+ constructor(definition) {
8476
+ this.definition = ProviderDefinitionSchema.parse(definition);
8482
8477
  }
8483
- return "planned";
8484
- }
8485
- function updateTaskStatus(plan, taskId, status) {
8486
- const milestones = plan.milestones.map((milestone) => ({
8487
- ...milestone,
8488
- tasks: milestone.tasks.map((task) => task.id === taskId ? {
8489
- ...task,
8490
- status
8491
- } : task)
8492
- }));
8493
- return {
8494
- ...plan,
8495
- milestones
8496
- };
8497
- }
8498
- function maybeAppendNote(notes, note) {
8499
- return notes.at(-1) === note ? notes : [
8500
- ...notes,
8501
- note
8502
- ];
8503
- }
8504
- function renderToolResultSection(results) {
8505
- return results.map((result) => {
8506
- const lines = [
8507
- `## ${result.toolId}`,
8508
- `- success: ${result.success ? "yes" : "no"}`,
8509
- `- summary: ${result.summary}`
8510
- ];
8511
- if (result.stdout) {
8512
- lines.push("");
8513
- lines.push("```text");
8514
- lines.push(result.stdout);
8515
- 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
+ };
8516
8485
  }
8517
- if (result.stderr) {
8518
- lines.push("");
8519
- lines.push("```text");
8520
- lines.push(result.stderr);
8521
- 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}".`);
8522
8495
  }
8523
- return lines.join("\n");
8524
- }).join("\n\n");
8525
- }
8526
- function extractPackageScripts(toolResults) {
8527
- const packageResult = toolResults.find((result) => result.artifacts.some((artifact) => artifact.endsWith("package.json")) && result.stdout);
8528
- if (!packageResult?.stdout) {
8529
- 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);
8530
8502
  }
8531
- try {
8532
- const parsed = JSON.parse(packageResult.stdout);
8533
- return Object.keys(parsed.scripts ?? {}).sort();
8534
- } catch {
8535
- 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
+ };
8536
8542
  }
8537
- }
8538
- var ExecutionOrchestrator = class {
8539
- toolRegistry;
8540
- toolRuntime;
8541
- constructor(toolRegistry = createDefaultToolRegistry(), toolRuntime = new ToolRuntime(toolRegistry)) {
8542
- this.toolRegistry = toolRegistry;
8543
- this.toolRuntime = toolRuntime;
8543
+ };
8544
+ var AnthropicMessagesProvider = class {
8545
+ definition;
8546
+ constructor(definition) {
8547
+ this.definition = ProviderDefinitionSchema.parse(definition);
8544
8548
  }
8545
- buildEnvelope(request, plan, sessionId = createSessionId()) {
8546
- const tasks = flattenPlanTasks(plan);
8547
- const completedTaskIds = completedTaskIdsFromPlan(plan);
8548
- const readyTasks = tasks.filter((task) => isTaskReady(task, completedTaskIds));
8549
- const blockedTasks = tasks.filter((task) => task.status !== "completed" && !isTaskReady(task, completedTaskIds));
8550
- 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
+ }
8551
8557
  return {
8552
- sessionId,
8553
- request,
8554
- plan,
8555
- readyTasks,
8556
- blockedTasks,
8557
- assignedAgents
8558
+ ok: true,
8559
+ message: `${this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages"} (${this.definition.defaultModel ?? "model not set"})`
8558
8560
  };
8559
8561
  }
8560
- createSessionSnapshot(envelope, overrides = {}) {
8561
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
8562
- const status = deriveStatus(envelope.plan, envelope.readyTasks, envelope.blockedTasks);
8563
- return SessionSnapshotSchema.parse({
8564
- id: envelope.sessionId,
8565
- goal: envelope.request.goal,
8566
- cwd: envelope.request.cwd,
8567
- startedAt: timestamp,
8568
- updatedAt: timestamp,
8569
- status,
8570
- request: envelope.request,
8571
- plan: envelope.plan,
8572
- readyTaskIds: envelope.readyTasks.map((task) => task.id),
8573
- blockedTaskIds: envelope.blockedTasks.map((task) => task.id),
8574
- completedTaskIds: Array.from(completedTaskIdsFromPlan(envelope.plan)),
8575
- assignedAgents: envelope.assignedAgents.map((agent) => agent.role),
8576
- notes: [
8577
- "Initial session snapshot created from the current plan."
8578
- ],
8579
- events: [],
8580
- ...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
+ }
8581
8576
  });
8577
+ return mapAnthropicModels(this.definition.id, payload, input);
8582
8578
  }
8583
- async continueSession(session, options = {}) {
8584
- let workingPlan = session.plan;
8585
- let notes = [
8586
- ...session.notes
8587
- ];
8588
- let events = [
8589
- ...session.events
8590
- ];
8591
- const maxAutoTasks = options.maxAutoTasks ?? 2;
8592
- let executedTasks = 0;
8593
- while (executedTasks < maxAutoTasks) {
8594
- const envelope = this.buildEnvelope(session.request, workingPlan, session.id);
8595
- const autoTask = envelope.readyTasks.find((task) => this.canAutoExecuteTask(task));
8596
- if (!autoTask) {
8597
- const nextTask = envelope.readyTasks[0];
8598
- if (nextTask) {
8599
- const note = `Execution paused at ${nextTask.id} (${nextTask.title}): no built-in executor is wired for ${nextTask.agentRole} yet.`;
8600
- notes = maybeAppendNote(notes, note);
8601
- }
8602
- return this.createSessionSnapshot(this.buildEnvelope(session.request, workingPlan, session.id), {
8603
- startedAt: session.startedAt,
8604
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8605
- notes,
8606
- events
8607
- });
8608
- }
8609
- events.push({
8610
- id: createEventId("task-started", autoTask.id),
8611
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8612
- type: "task-started",
8613
- taskId: autoTask.id,
8614
- agentRole: autoTask.agentRole,
8615
- message: `Started ${autoTask.id}: ${autoTask.title}`,
8616
- toolResults: [],
8617
- artifacts: []
8618
- });
8619
- const outcome = await this.executeTask(session.id, autoTask, session.request, workingPlan);
8620
- workingPlan = updateTaskStatus(workingPlan, autoTask.id, outcome.status === "completed" ? "completed" : "blocked");
8621
- notes = maybeAppendNote(notes, outcome.summary);
8622
- events.push({
8623
- id: createEventId(outcome.status === "completed" ? "task-completed" : "task-blocked", autoTask.id),
8624
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8625
- type: outcome.status === "completed" ? "task-completed" : "task-blocked",
8626
- taskId: autoTask.id,
8627
- agentRole: autoTask.agentRole,
8628
- message: outcome.summary,
8629
- toolResults: outcome.toolResults,
8630
- 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")
8631
9304
  });
8632
- executedTasks += 1;
8633
9305
  }
8634
- const postLimitEnvelope = this.buildEnvelope(session.request, workingPlan, session.id);
8635
- if (postLimitEnvelope.readyTasks.length > 0) {
8636
- 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);
8637
9511
  }
8638
- return this.createSessionSnapshot(postLimitEnvelope, {
8639
- startedAt: session.startedAt,
8640
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8641
- notes,
8642
- events
8643
- });
8644
9512
  }
8645
- selectAgentsForTasks(tasks) {
8646
- const roles = new Set(tasks.map((task) => task.agentRole));
8647
- 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));
8648
9515
  }
8649
- toolsForAgent(role) {
8650
- return this.toolRegistry.byRole(role);
9516
+ get(id) {
9517
+ return this.tools.get(id);
8651
9518
  }
8652
- canAutoExecuteTask(task) {
8653
- return task.agentRole === "repo-analyst" || task.agentRole === "planner";
9519
+ byRole(role) {
9520
+ return this.list().filter((tool) => tool.allowedRoles.includes(role));
8654
9521
  }
8655
- async executeTask(sessionId, task, request, plan) {
8656
- if (task.agentRole === "repo-analyst") {
8657
- return this.executeRepoAnalysisTask(sessionId, task, request);
8658
- }
8659
- if (task.agentRole === "planner") {
8660
- 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;
8661
9558
  }
8662
- return {
8663
- status: "blocked",
8664
- summary: `Task ${task.id} is ready, but no executor is available for ${task.agentRole}.`,
8665
- toolResults: [],
8666
- artifacts: []
8667
- };
8668
9559
  }
8669
- async executeRepoAnalysisTask(sessionId, task, request) {
8670
- const context = { cwd: request.cwd };
8671
- const toolResults = await Promise.all([
8672
- this.safeRunTool("git.status", {}, context),
8673
- this.safeRunTool("file.read", { path: "package.json" }, context),
8674
- this.safeRunTool("file.read", { path: "README.md" }, context)
8675
- ]);
8676
- const successfulResults = toolResults.filter((result) => result.success);
8677
- const scripts = extractPackageScripts(successfulResults);
8678
- const artifactPath = await this.writeLogArtifact(sessionId, "repo-analysis", request.cwd, [
8679
- `# Repo Analysis`,
8680
- ``,
8681
- `Goal: ${request.goal}`,
8682
- `Workspace: ${request.cwd}`,
8683
- `Task: ${task.id} - ${task.title}`,
8684
- ``,
8685
- `## Observations`,
8686
- `- Successful tool probes: ${successfulResults.length}/${toolResults.length}`,
8687
- `- Workspace state: ${request.workspaceState}`,
8688
- `- Detected scripts: ${scripts.length > 0 ? scripts.join(", ") : "none"}`,
8689
- ``,
8690
- renderToolResultSection(toolResults)
8691
- ].join("\n"));
8692
- return {
8693
- status: "completed",
8694
- summary: `Completed ${task.id}: captured repo analysis and saved ${import_node_path4.default.basename(artifactPath)}.`,
8695
- toolResults,
8696
- artifacts: [
8697
- artifactPath
8698
- ]
8699
- };
9560
+ return fallback;
9561
+ }
9562
+ function truncateOutput(value) {
9563
+ if (!value) {
9564
+ return value;
8700
9565
  }
8701
- async executePlannerTask(sessionId, task, request, plan) {
8702
- const artifactPath = await this.writeLogArtifact(sessionId, "architecture-brief", request.cwd, [
8703
- `# Architecture Brief`,
8704
- ``,
8705
- `Goal: ${request.goal}`,
8706
- `Summary: ${plan.summary}`,
8707
- `Repo Strategy: ${plan.repoStrategy.mode} - ${plan.repoStrategy.reasoning}`,
8708
- ``,
8709
- `## Assumptions`,
8710
- ...plan.assumptions.map((assumption) => `- ${assumption}`),
8711
- ``,
8712
- `## Milestones`,
8713
- ...plan.milestones.flatMap((milestone) => [
8714
- `### ${milestone.title}`,
8715
- milestone.objective,
8716
- ...milestone.tasks.map((milestoneTask) => `- ${milestoneTask.id}: ${milestoneTask.title} [${milestoneTask.agentRole}]`),
8717
- ``
8718
- ]),
8719
- `## Verification`,
8720
- ...plan.verificationChecklist.map((item) => `- ${item}`)
8721
- ].join("\n"));
8722
- return {
8723
- status: "completed",
8724
- summary: `Completed ${task.id}: wrote architecture brief ${import_node_path4.default.basename(artifactPath)}.`,
8725
- toolResults: [],
8726
- artifacts: [
8727
- artifactPath
8728
- ]
8729
- };
9566
+ if (value.length <= DEFAULT_CAPTURE_LIMIT) {
9567
+ return value;
8730
9568
  }
8731
- async safeRunTool(toolId, input, context) {
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;
9583
+ }
9584
+ const raw = line.slice(4).trim();
9585
+ if (!raw || raw === "/dev/null") {
9586
+ continue;
9587
+ }
9588
+ const normalized = raw.startsWith("a/") || raw.startsWith("b/") ? raw.slice(2) : raw;
8732
9589
  try {
8733
- return await this.toolRuntime.run(toolId, input, context);
8734
- } catch (error) {
8735
- const message = error instanceof Error ? error.message : String(error);
8736
- return {
8737
- toolId,
8738
- success: false,
8739
- summary: `${toolId} failed: ${message}`,
8740
- stderr: message,
8741
- artifacts: []
8742
- };
9590
+ paths.add(resolveWorkspacePath(cwd, normalized));
9591
+ } catch {
9592
+ continue;
8743
9593
  }
8744
9594
  }
8745
- async writeLogArtifact(sessionId, label, cwd, content) {
8746
- await ensureKimbhoDir(cwd);
8747
- const logsDir = import_node_path4.default.join(resolveKimbhoDir(cwd), "logs");
8748
- const outputPath = import_node_path4.default.join(logsDir, `${sessionId}-${label}.md`);
8749
- await (0, import_promises4.writeFile)(outputPath, `${content}
8750
- `, "utf8");
8751
- return outputPath;
9595
+ return Array.from(paths);
9596
+ }
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.`);
8752
9602
  }
8753
- };
8754
-
8755
- // src/commands/agents.ts
8756
- function createAgentsCommand() {
8757
- 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) => {
8758
- const orchestrator = new ExecutionOrchestrator();
8759
- const session = options.active ? await loadLatestSession(process.cwd()) : null;
8760
- const profiles = session ? listAgentProfiles().filter((agent) => session.assignedAgents.includes(agent.role)) : listAgentProfiles();
8761
- if (session) {
8762
- console.log(`Session: ${session.id}`);
8763
- console.log(`Ready tasks: ${session.readyTaskIds.join(", ") || "none"}`);
8764
- console.log(`Blocked tasks: ${session.blockedTaskIds.join(", ") || "none"}`);
8765
- }
8766
- for (const agent of profiles) {
8767
- const tools = orchestrator.toolsForAgent(agent.role).map((tool) => tool.id);
8768
- console.log(`${agent.role}`);
8769
- console.log(` brain: ${agent.brainRole}`);
8770
- console.log(` purpose: ${agent.purpose}`);
8771
- console.log(` tools: ${tools.join(", ")}`);
8772
- console.log(` concurrency: ${agent.maxConcurrentTasks}`);
9603
+ return resolved;
9604
+ }
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
+ });
9653
+ }
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
+ });
9669
+ }
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.");
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;
9695
+ try {
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;
8773
9708
  }
9709
+ result = await runSpawn("find", [
9710
+ rootArg,
9711
+ "-type",
9712
+ "f"
9713
+ ], context.cwd, timeoutMs);
9714
+ }
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)
8774
9725
  });
8775
9726
  }
8776
-
8777
- // src/commands/brains.ts
8778
- var import_node_process2 = __toESM(require("node:process"), 1);
8779
-
8780
- // ../brains/dist/templates.js
8781
- var BUILTIN_PROVIDER_TEMPLATES = [
8782
- {
8783
- id: "openai",
8784
- label: "OpenAI",
8785
- driver: "openai-responses",
8786
- defaultBaseUrl: "https://api.openai.com/v1",
8787
- defaultApiKeyEnv: "OPENAI_API_KEY",
8788
- defaultModel: "gpt-5",
8789
- notes: "Uses the OpenAI Responses API."
8790
- },
8791
- {
8792
- id: "anthropic",
8793
- label: "Anthropic",
8794
- driver: "anthropic-messages",
8795
- defaultBaseUrl: "https://api.anthropic.com/v1/messages",
8796
- defaultApiKeyEnv: "ANTHROPIC_API_KEY",
8797
- notes: "Uses the Anthropic Messages API."
8798
- },
8799
- {
8800
- id: "openrouter",
8801
- label: "OpenRouter",
8802
- driver: "openai-compatible",
8803
- defaultBaseUrl: "https://openrouter.ai/api/v1",
8804
- defaultApiKeyEnv: "OPENROUTER_API_KEY",
8805
- notes: "Routes through the OpenAI-compatible OpenRouter API."
8806
- },
8807
- {
8808
- id: "ollama",
8809
- label: "Ollama",
8810
- driver: "ollama",
8811
- defaultBaseUrl: "http://localhost:11434",
8812
- defaultModel: "qwen2.5-coder:7b",
8813
- notes: "Runs against a local Ollama server."
8814
- },
8815
- {
8816
- id: "lmstudio",
8817
- label: "LM Studio",
8818
- driver: "openai-compatible",
8819
- defaultBaseUrl: "http://localhost:1234/v1",
8820
- notes: "Works with LM Studio's local OpenAI-compatible server."
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);
8821
9764
  }
8822
- ];
8823
- function listProviderTemplates() {
8824
- return BUILTIN_PROVIDER_TEMPLATES.slice().sort((left, right) => left.id.localeCompare(right.id));
8825
- }
8826
- function findProviderTemplate(templateId) {
8827
- return BUILTIN_PROVIDER_TEMPLATES.find((template) => template.id === templateId);
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)
9774
+ });
8828
9775
  }
8829
- function buildProviderFromTemplate(templateId, options = {}) {
8830
- const template = findProviderTemplate(templateId);
8831
- if (!template) {
8832
- throw new Error(`Unknown provider template "${templateId}".`);
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.");
8833
9781
  }
8834
- return ProviderDefinitionSchema.parse({
8835
- id: options.providerId ?? template.id,
8836
- label: options.label ?? template.label,
8837
- driver: template.driver,
8838
- ...options.baseUrl ?? template.defaultBaseUrl ? {
8839
- baseUrl: options.baseUrl ?? template.defaultBaseUrl
8840
- } : {},
8841
- ...options.apiKeyEnv ?? template.defaultApiKeyEnv ? {
8842
- apiKeyEnv: options.apiKeyEnv ?? template.defaultApiKeyEnv
8843
- } : {},
8844
- ...options.model ?? template.defaultModel ? {
8845
- defaultModel: options.model ?? template.defaultModel
8846
- } : {},
8847
- ...options.models && options.models.length > 0 ? {
8848
- models: options.models
8849
- } : {}
9782
+ if (content === null) {
9783
+ throw new Error("file.write requires a string content field.");
9784
+ }
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;
9791
+ }
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
+ ]
8850
9801
  });
8851
9802
  }
8852
-
8853
- // ../brains/dist/registry.js
8854
- var import_promises5 = require("node:fs/promises");
8855
- var import_node_path5 = __toESM(require("node:path"), 1);
8856
- var import_node_url = require("node:url");
8857
- function resolveApiKey(definition) {
8858
- if (!definition.apiKeyEnv) {
8859
- return null;
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.");
8860
9807
  }
8861
- return process.env[definition.apiKeyEnv] ?? null;
9808
+ return runShellCommand("shell.exec", command, context.cwd, timeoutMs);
8862
9809
  }
8863
- function makeAbortSignal(timeoutMs) {
8864
- const controller = new AbortController();
8865
- setTimeout(() => controller.abort(), timeoutMs).unref?.();
8866
- return controller.signal;
9810
+ async function executeGitStatus(_input, context, timeoutMs) {
9811
+ return runShellCommand("git.status", "git status --short --branch", context.cwd, timeoutMs);
8867
9812
  }
8868
- async function requestJson(url, init, timeoutMs = 3e4) {
8869
- const response = await fetch(url, {
8870
- ...init,
8871
- signal: makeAbortSignal(timeoutMs)
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)
8872
9849
  });
8873
- if (!response.ok) {
8874
- throw new Error(`Request failed with ${response.status} ${response.statusText}`);
9850
+ }
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.");
9863
+ }
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)
9882
+ });
9883
+ } finally {
9884
+ await (0, import_promises5.rm)(tempDir, { recursive: true, force: true });
8875
9885
  }
8876
- return response.json();
8877
9886
  }
8878
- function trimTrailingSlash(value) {
8879
- return value.replace(/\/+$/, "");
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
+ ]);
9930
+ }
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()}`;
8880
9947
  }
8881
- function joinUrl(baseUrl, suffix) {
8882
- const normalizedBase = trimTrailingSlash(baseUrl);
8883
- const normalizedSuffix = suffix.startsWith("/") ? suffix : `/${suffix}`;
8884
- return `${normalizedBase}${normalizedSuffix}`;
9948
+ function createEventId(type, taskId) {
9949
+ return `${type}-${taskId ?? "session"}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
8885
9950
  }
8886
- function isOpenRouter(definition) {
8887
- return definition.baseUrl?.includes("openrouter.ai") ?? false;
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";
9962
+ }
9963
+ if (readyTasks.length > 0) {
9964
+ return completed > 0 ? "running" : "ready";
9965
+ }
9966
+ if (blockedTasks.length > 0) {
9967
+ return "blocked";
9968
+ }
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
+ };
8888
9983
  }
8889
- function buildProviderHeaders(definition, apiKey, includeJsonContentType = true) {
8890
- const headers = {
8891
- ...definition.headers
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
8892
9992
  };
8893
- if (includeJsonContentType) {
8894
- headers["content-type"] = "application/json";
8895
- }
8896
- if (apiKey) {
8897
- headers.authorization = `Bearer ${apiKey}`;
8898
- }
8899
- if (isOpenRouter(definition)) {
8900
- const siteUrl = process.env.OPENROUTER_SITE_URL;
8901
- const appName = process.env.OPENROUTER_APP_NAME ?? "Kimbho CLI";
8902
- if (siteUrl) {
8903
- headers["HTTP-Referer"] = siteUrl;
8904
- }
8905
- headers["X-Title"] = appName;
8906
- }
8907
- return headers;
8908
9993
  }
8909
- function filterModels(models, input = {}) {
8910
- const normalized = input.search?.trim().toLowerCase();
8911
- const filtered = normalized ? models.filter((model) => [model.id, model.name].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes(normalized))) : models;
8912
- return typeof input.limit === "number" ? filtered.slice(0, input.limit) : filtered;
9994
+ function maybeAppendNote(notes, note) {
9995
+ return notes.at(-1) === note ? notes : [
9996
+ ...notes,
9997
+ note
9998
+ ];
8913
9999
  }
8914
- function mapOpenAIStyleModels(providerId, payload, input) {
8915
- const response = payload;
8916
- const data = Array.isArray(response.data) ? response.data : [];
8917
- const models = data.flatMap((item) => {
8918
- if (typeof item !== "object" || item === null) {
8919
- return [];
8920
- }
8921
- const record = item;
8922
- const pricing = typeof record.pricing === "object" && record.pricing !== null ? record.pricing : null;
8923
- const architecture = typeof record.architecture === "object" && record.architecture !== null ? record.architecture : null;
8924
- return [
8925
- ProviderModelSchema.parse({
8926
- id: String(record.id ?? ""),
8927
- ...typeof record.name === "string" ? {
8928
- name: record.name
8929
- } : {},
8930
- ...typeof record.description === "string" ? {
8931
- description: record.description
8932
- } : {},
8933
- ...typeof record.context_length === "number" ? {
8934
- contextLength: record.context_length
8935
- } : {},
8936
- ...pricing && typeof pricing.prompt === "string" ? {
8937
- promptPrice: pricing.prompt
8938
- } : {},
8939
- ...pricing && typeof pricing.completion === "string" ? {
8940
- completionPrice: pricing.completion
8941
- } : {},
8942
- ...architecture && typeof architecture.modality === "string" ? {
8943
- modality: architecture.modality
8944
- } : {},
8945
- providerId
8946
- })
8947
- ];
8948
- });
8949
- return filterModels(models, input);
10000
+ function mergeUnique(items) {
10001
+ return Array.from(new Set(items.filter((item) => item.trim().length > 0)));
8950
10002
  }
8951
- function mapAnthropicModels(providerId, payload, input) {
8952
- const response = payload;
8953
- const data = Array.isArray(response.data) ? response.data : [];
8954
- const models = data.flatMap((item) => {
8955
- if (typeof item !== "object" || item === null) {
8956
- return [];
8957
- }
8958
- const record = item;
8959
- return [
8960
- ProviderModelSchema.parse({
8961
- id: String(record.id ?? ""),
8962
- ...typeof record.display_name === "string" ? {
8963
- name: record.display_name
8964
- } : {},
8965
- providerId
8966
- })
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}`
8967
10009
  ];
8968
- });
8969
- return filterModels(models, input);
8970
- }
8971
- function mapOllamaModels(providerId, payload, input) {
8972
- const response = payload;
8973
- const data = Array.isArray(response.models) ? response.models : [];
8974
- const models = data.flatMap((item) => {
8975
- if (typeof item !== "object" || item === null) {
8976
- return [];
10010
+ if (result.stdout) {
10011
+ lines.push("");
10012
+ lines.push("```text");
10013
+ lines.push(result.stdout);
10014
+ lines.push("```");
8977
10015
  }
8978
- const record = item;
8979
- const details = typeof record.details === "object" && record.details !== null ? record.details : null;
8980
- const id = typeof record.model === "string" ? record.model : typeof record.name === "string" ? record.name : "";
8981
- return [
8982
- ProviderModelSchema.parse({
8983
- id,
8984
- ...typeof record.name === "string" ? {
8985
- name: record.name
8986
- } : {},
8987
- ...details && typeof details.family === "string" ? {
8988
- modality: details.family
8989
- } : {},
8990
- ...details && typeof details.parameter_size === "string" ? {
8991
- description: details.parameter_size
8992
- } : {},
8993
- providerId
8994
- })
8995
- ];
8996
- });
8997
- return filterModels(models, input);
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");
8998
10024
  }
8999
- function toChatMessages(input) {
9000
- const messages = [];
9001
- if (input.systemPrompt) {
9002
- messages.push({
9003
- role: "system",
9004
- content: input.systemPrompt
9005
- });
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 [];
9006
10029
  }
9007
- if (input.messages && input.messages.length > 0) {
9008
- messages.push(...input.messages);
9009
- } else if (input.userPrompt) {
9010
- messages.push({
9011
- role: "user",
9012
- content: input.userPrompt
9013
- });
10030
+ try {
10031
+ const parsed = JSON.parse(packageResult.stdout);
10032
+ return Object.keys(parsed.scripts ?? {}).sort();
10033
+ } catch {
10034
+ return [];
9014
10035
  }
9015
- return messages;
9016
10036
  }
9017
- function toPromptText(input) {
9018
- const messages = toChatMessages(input);
9019
- return messages.map((message) => `${message.role.toUpperCase()}: ${message.content}`).join("\n\n");
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
+ };
9020
10076
  }
9021
- function requireModel(definition, input) {
9022
- const model = input.model ?? definition.defaultModel;
9023
- if (!model) {
9024
- throw new Error(`Provider "${definition.id}" does not have a resolved model.`);
10077
+ var ExecutionOrchestrator = class {
10078
+ toolRegistry;
10079
+ toolRuntime;
10080
+ constructor(toolRegistry = createDefaultToolRegistry(), toolRuntime = new ToolRuntime(toolRegistry)) {
10081
+ this.toolRegistry = toolRegistry;
10082
+ this.toolRuntime = toolRuntime;
9025
10083
  }
9026
- return model;
9027
- }
9028
- var OpenAIResponsesProvider = class {
9029
- definition;
9030
- constructor(definition) {
9031
- this.definition = ProviderDefinitionSchema.parse(definition);
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);
10090
+ return {
10091
+ sessionId,
10092
+ request,
10093
+ plan,
10094
+ readyTasks,
10095
+ blockedTasks,
10096
+ assignedAgents
10097
+ };
9032
10098
  }
9033
- async healthCheck() {
9034
- const apiKey = resolveApiKey(this.definition);
9035
- if (!apiKey) {
9036
- return {
9037
- ok: false,
9038
- message: `Missing ${this.definition.apiKeyEnv ?? "API key env var"}`
9039
- };
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
10120
+ });
10121
+ }
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;
9040
10172
  }
9041
- return {
9042
- ok: true,
9043
- message: `${this.definition.baseUrl ?? "https://api.openai.com/v1"} (${this.definition.defaultModel ?? "model not set"})`
9044
- };
9045
- }
9046
- async listModels(input) {
9047
- const apiKey = resolveApiKey(this.definition);
9048
- if (!apiKey) {
9049
- throw new Error(`Missing ${this.definition.apiKeyEnv ?? "API key env var"} for provider "${this.definition.id}".`);
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"}.`);
9050
10176
  }
9051
- const baseUrl = this.definition.baseUrl ?? "https://api.openai.com/v1";
9052
- const payload = await requestJson(joinUrl(baseUrl, "/models"), {
9053
- method: "GET",
9054
- headers: buildProviderHeaders(this.definition, apiKey, false)
10177
+ return this.createSessionSnapshot(postLimitEnvelope, {
10178
+ startedAt: session.startedAt,
10179
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10180
+ notes,
10181
+ events
9055
10182
  });
9056
- return mapOpenAIStyleModels(this.definition.id, payload, input);
9057
10183
  }
9058
- async generateText(input) {
9059
- const apiKey = resolveApiKey(this.definition);
9060
- if (!apiKey) {
9061
- throw new Error(`Missing ${this.definition.apiKeyEnv ?? "API key env var"} for provider "${this.definition.id}".`);
9062
- }
9063
- const baseUrl = this.definition.baseUrl ?? "https://api.openai.com/v1";
9064
- const model = requireModel(this.definition, input);
9065
- const messages = toChatMessages(input).map((message) => ({
9066
- role: message.role,
9067
- content: [
9068
- {
9069
- type: "input_text",
9070
- text: message.content
9071
- }
9072
- ]
9073
- }));
9074
- const response = await requestJson(`${baseUrl}/responses`, {
9075
- method: "POST",
9076
- headers: {
9077
- ...buildProviderHeaders(this.definition, apiKey)
9078
- },
9079
- body: JSON.stringify({
9080
- model,
9081
- input: messages,
9082
- ...typeof input.temperature === "number" ? {
9083
- temperature: input.temperature
9084
- } : {},
9085
- ...typeof input.maxTokens === "number" ? {
9086
- max_output_tokens: input.maxTokens
9087
- } : {}
9088
- })
9089
- });
9090
- 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 ? [
9091
- String(item.text)
9092
- ] : []).join("\n") : "";
9093
- return {
9094
- text,
9095
- model
9096
- };
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));
9097
10187
  }
9098
- };
9099
- var AnthropicMessagesProvider = class {
9100
- definition;
9101
- constructor(definition) {
9102
- this.definition = ProviderDefinitionSchema.parse(definition);
10188
+ toolsForAgent(role) {
10189
+ return this.toolRegistry.byRole(role);
9103
10190
  }
9104
- async healthCheck() {
9105
- const apiKey = resolveApiKey(this.definition);
9106
- if (!apiKey) {
9107
- return {
9108
- ok: false,
9109
- message: `Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"}`
9110
- };
9111
- }
9112
- return {
9113
- ok: true,
9114
- message: `${this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages"} (${this.definition.defaultModel ?? "model not set"})`
9115
- };
10191
+ canAutoExecuteTask(task) {
10192
+ return task.agentRole !== "session-orchestrator" && task.agentRole !== "execution-manager";
9116
10193
  }
9117
- async listModels(input) {
9118
- const apiKey = resolveApiKey(this.definition);
9119
- if (!apiKey) {
9120
- throw new Error(`Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"} for provider "${this.definition.id}".`);
10194
+ async executeTask(sessionId, task, request, plan, options = {}) {
10195
+ if (task.agentRole === "repo-analyst") {
10196
+ return this.executeRepoAnalysisTask(sessionId, task, request);
9121
10197
  }
9122
- const endpoint = this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages";
9123
- const modelsEndpoint = endpoint.endsWith("/messages") ? `${endpoint.slice(0, -"/messages".length)}/models` : joinUrl(endpoint, "/models");
9124
- const payload = await requestJson(modelsEndpoint, {
9125
- method: "GET",
9126
- headers: {
9127
- "anthropic-version": "2023-06-01",
9128
- "x-api-key": apiKey,
9129
- ...this.definition.headers
9130
- }
9131
- });
9132
- return mapAnthropicModels(this.definition.id, payload, input);
9133
- }
9134
- async generateText(input) {
9135
- const apiKey = resolveApiKey(this.definition);
9136
- if (!apiKey) {
9137
- throw new Error(`Missing ${this.definition.apiKeyEnv ?? "ANTHROPIC_API_KEY"} for provider "${this.definition.id}".`);
10198
+ if (task.agentRole === "planner") {
10199
+ return this.executePlannerTask(sessionId, task, request, plan);
9138
10200
  }
9139
- const endpoint = this.definition.baseUrl ?? "https://api.anthropic.com/v1/messages";
9140
- const model = requireModel(this.definition, input);
9141
- const messages = toChatMessages(input);
9142
- const systemMessage = messages.find((message) => message.role === "system")?.content;
9143
- const conversation = messages.filter((message) => message.role !== "system").map((message) => ({
9144
- role: message.role === "assistant" ? "assistant" : "user",
9145
- content: message.content
9146
- }));
9147
- const response = await requestJson(endpoint, {
9148
- method: "POST",
9149
- headers: {
9150
- "content-type": "application/json",
9151
- "x-api-key": apiKey,
9152
- "anthropic-version": "2023-06-01",
9153
- ...this.definition.headers
9154
- },
9155
- body: JSON.stringify({
9156
- model,
9157
- messages: conversation,
9158
- ...systemMessage ? {
9159
- system: systemMessage
9160
- } : {},
9161
- max_tokens: input.maxTokens ?? 2048,
9162
- ...typeof input.temperature === "number" ? {
9163
- temperature: input.temperature
9164
- } : {}
9165
- })
9166
- });
9167
- const content = Array.isArray(response.content) ? response.content : [];
9168
- const text = content.flatMap((item) => typeof item === "object" && item !== null && "text" in item ? [
9169
- String(item.text)
9170
- ] : []).join("\n");
9171
- return {
9172
- text,
9173
- model
9174
- };
9175
- }
9176
- };
9177
- var OpenAICompatibleProvider = class {
9178
- definition;
9179
- constructor(definition) {
9180
- this.definition = ProviderDefinitionSchema.parse(definition);
10201
+ return this.executeAutonomousSpecialistTask(sessionId, task, request, options);
9181
10202
  }
9182
- async healthCheck() {
9183
- if (!this.definition.baseUrl) {
10203
+ async executeAutonomousSpecialistTask(sessionId, task, request, options) {
10204
+ const config = await loadConfig(request.cwd);
10205
+ if (!config) {
9184
10206
  return {
9185
- ok: false,
9186
- message: "Missing baseUrl"
10207
+ status: "blocked",
10208
+ summary: `Task ${task.id} cannot run because no .kimbho/config.json was found in ${request.cwd}.`,
10209
+ toolResults: [],
10210
+ artifacts: []
9187
10211
  };
9188
10212
  }
9189
- const apiKey = resolveApiKey(this.definition);
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
+ } : {}
10221
+ });
10222
+ }
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"));
9190
10246
  return {
9191
- ok: !this.definition.apiKeyEnv || Boolean(apiKey),
9192
- message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"})${this.definition.apiKeyEnv && !apiKey ? `, missing ${this.definition.apiKeyEnv}` : ""}`
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
+ ]
9193
10253
  };
9194
10254
  }
9195
- async listModels(input) {
9196
- if (!this.definition.baseUrl) {
9197
- throw new Error(`Provider "${this.definition.id}" requires baseUrl.`);
9198
- }
9199
- const apiKey = resolveApiKey(this.definition);
9200
- const payload = await requestJson(joinUrl(this.definition.baseUrl, "/models"), {
9201
- method: "GET",
9202
- headers: buildProviderHeaders(this.definition, apiKey, false)
9203
- });
9204
- return mapOpenAIStyleModels(this.definition.id, payload, input);
9205
- }
9206
- async generateText(input) {
9207
- if (!this.definition.baseUrl) {
9208
- throw new Error(`Provider "${this.definition.id}" requires baseUrl.`);
9209
- }
9210
- const apiKey = resolveApiKey(this.definition);
9211
- const model = requireModel(this.definition, input);
9212
- const response = await requestJson(`${this.definition.baseUrl}/chat/completions`, {
9213
- method: "POST",
9214
- headers: buildProviderHeaders(this.definition, apiKey),
9215
- body: JSON.stringify({
9216
- model,
9217
- messages: toChatMessages(input),
9218
- ...typeof input.temperature === "number" ? {
9219
- temperature: input.temperature
9220
- } : {},
9221
- ...typeof input.maxTokens === "number" ? {
9222
- max_tokens: input.maxTokens
9223
- } : {}
9224
- })
9225
- });
9226
- const choices = Array.isArray(response.choices) ? response.choices : [];
9227
- const firstChoice = choices.length > 0 && typeof choices[0] === "object" && choices[0] !== null ? choices[0] : null;
9228
- const message = firstChoice && typeof firstChoice.message === "object" && firstChoice.message !== null ? firstChoice.message : null;
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"));
9229
10276
  return {
9230
- text: typeof message?.content === "string" ? message.content : "",
9231
- model
10277
+ status: "completed",
10278
+ summary: `Completed ${task.id}: wrote architecture brief ${import_node_path6.default.basename(artifactPath)}.`,
10279
+ toolResults: [],
10280
+ artifacts: [
10281
+ artifactPath
10282
+ ]
9232
10283
  };
9233
10284
  }
9234
- };
9235
- var OllamaProvider = class {
9236
- definition;
9237
- constructor(definition) {
9238
- this.definition = ProviderDefinitionSchema.parse(definition);
9239
- }
9240
- async healthCheck() {
9241
- const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
10285
+ async safeRunTool(toolId, input, context) {
9242
10286
  try {
9243
- await requestJson(`${baseUrl}/api/tags`, { method: "GET" }, 2e3);
9244
- return {
9245
- ok: true,
9246
- message: `${baseUrl} (${this.definition.defaultModel ?? "model not set"})`
9247
- };
10287
+ return await this.toolRuntime.run(toolId, input, context);
9248
10288
  } catch (error) {
10289
+ const message = error instanceof Error ? error.message : String(error);
9249
10290
  return {
9250
- ok: false,
9251
- message: error instanceof Error ? error.message : String(error)
10291
+ toolId,
10292
+ success: false,
10293
+ summary: `${toolId} failed: ${message}`,
10294
+ stderr: message,
10295
+ artifacts: []
9252
10296
  };
9253
10297
  }
9254
10298
  }
9255
- async listModels(input) {
9256
- const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
9257
- const payload = await requestJson(`${baseUrl}/api/tags`, { method: "GET" }, 2e3);
9258
- return mapOllamaModels(this.definition.id, payload, input);
9259
- }
9260
- async generateText(input) {
9261
- const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
9262
- const model = requireModel(this.definition, input);
9263
- const response = await requestJson(`${baseUrl}/api/generate`, {
9264
- method: "POST",
9265
- headers: {
9266
- "content-type": "application/json",
9267
- ...this.definition.headers
9268
- },
9269
- body: JSON.stringify({
9270
- model,
9271
- prompt: input.userPrompt ?? toPromptText(input),
9272
- ...input.systemPrompt ? {
9273
- system: input.systemPrompt
9274
- } : {},
9275
- stream: false,
9276
- ...typeof input.temperature === "number" ? {
9277
- options: {
9278
- temperature: input.temperature
9279
- }
9280
- } : {}
9281
- })
9282
- });
9283
- return {
9284
- text: typeof response.response === "string" ? response.response : "",
9285
- model
9286
- };
9287
- }
9288
- };
9289
- async function createCustomModuleProvider(definition, cwd) {
9290
- if (!definition.modulePath) {
9291
- throw new Error(`Provider "${definition.id}" requires modulePath.`);
9292
- }
9293
- const modulePath = import_node_path5.default.isAbsolute(definition.modulePath) ? definition.modulePath : import_node_path5.default.join(cwd, definition.modulePath);
9294
- await (0, import_promises5.access)(modulePath);
9295
- const module2 = await import((0, import_node_url.pathToFileURL)(modulePath).href);
9296
- const createProvider = typeof module2.createProvider === "function" ? module2.createProvider : typeof module2.default === "function" ? module2.default : null;
9297
- if (!createProvider) {
9298
- throw new Error(`Custom provider module "${modulePath}" must export createProvider(definition).`);
9299
- }
9300
- return createProvider(definition);
9301
- }
9302
- var BUILTIN_FACTORIES = {
9303
- "openai-responses": {
9304
- driver: "openai-responses",
9305
- create: async (definition) => new OpenAIResponsesProvider(definition)
9306
- },
9307
- "anthropic-messages": {
9308
- driver: "anthropic-messages",
9309
- create: async (definition) => new AnthropicMessagesProvider(definition)
9310
- },
9311
- "openai-compatible": {
9312
- driver: "openai-compatible",
9313
- create: async (definition) => new OpenAICompatibleProvider(definition)
9314
- },
9315
- ollama: {
9316
- driver: "ollama",
9317
- create: async (definition) => new OllamaProvider(definition)
9318
- },
9319
- "custom-module": {
9320
- driver: "custom-module",
9321
- 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;
9322
10306
  }
9323
10307
  };
9324
- var BrainProviderRegistry = class {
9325
- cwd;
9326
- factories;
9327
- constructor(cwd = process.cwd(), factories = Object.values(BUILTIN_FACTORIES)) {
9328
- this.cwd = cwd;
9329
- this.factories = new Map(factories.map((factory) => [factory.driver, factory]));
9330
- }
9331
- listDrivers() {
9332
- return Array.from(this.factories.keys()).sort();
9333
- }
9334
- async createProvider(definition) {
9335
- const normalized = ProviderDefinitionSchema.parse(definition);
9336
- const factory = this.factories.get(normalized.driver);
9337
- if (!factory) {
9338
- 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"}`);
9339
10319
  }
9340
- return factory.create(normalized, this.cwd);
9341
- }
9342
- async healthCheck(definition) {
9343
- const provider = await this.createProvider(definition);
9344
- return provider.healthCheck();
9345
- }
9346
- async listModels(definition, input) {
9347
- const provider = await this.createProvider(definition);
9348
- if (provider.listModels) {
9349
- 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}`);
9350
10327
  }
9351
- return filterModels(definition.models.map((modelId) => ProviderModelSchema.parse({
9352
- id: modelId,
9353
- providerId: definition.id
9354
- })), input);
9355
- }
9356
- };
9357
- function createDefaultBrainProviderRegistry(cwd = process.cwd()) {
9358
- return new BrainProviderRegistry(cwd);
10328
+ });
9359
10329
  }
9360
10330
 
9361
- // ../brains/dist/resolver.js
9362
- var BrainResolver = class {
9363
- config;
9364
- registry;
9365
- constructor(config, registry = createDefaultBrainProviderRegistry()) {
9366
- this.config = config;
9367
- this.registry = registry;
9368
- }
9369
- async resolve(role) {
9370
- const settings = resolveBrainSettings(this.config, role);
9371
- const provider = findProviderById(this.config, settings.providerId);
9372
- if (!provider) {
9373
- throw new Error(`Brain role "${role}" points to unknown provider "${settings.providerId}".`);
9374
- }
9375
- const model = resolveBrainModel(this.config, role);
9376
- if (!model) {
9377
- throw new Error(`Brain role "${role}" does not resolve to a model.`);
9378
- }
9379
- return {
9380
- role,
9381
- settings,
9382
- provider,
9383
- model,
9384
- client: await this.registry.createProvider(provider)
9385
- };
9386
- }
9387
- };
10331
+ // src/commands/brains.ts
10332
+ var import_node_process2 = __toESM(require("node:process"), 1);
9388
10333
 
9389
10334
  // src/ui/renderPlan.ts
9390
10335
  function humanizeRole(role) {
@@ -9860,7 +10805,7 @@ function createModelsCommand() {
9860
10805
  console.log(`Updated ${outputPath}`);
9861
10806
  console.log(`Synced ${models.length} models for ${provider.id}`);
9862
10807
  });
9863
- command.command("use").description("Select a model for a provider and optionally assign it to a brain role.").argument("<model>", "Model id to select").requiredOption("--provider <provider>", "Provider id to use").option("--role <role>", "Brain role to assign: planner, coder, reviewer, or fast").option("--set-default", "Also set this model as the provider default", false).option("--force", "Skip remote model validation", false).option("--temperature <value>", "Temperature override for the role", parseNumber2).option("--max-tokens <value>", "Max token override for the role", parseInteger).action(async (model, options) => {
10808
+ command.command("use").description("Select a model for a provider and assign it globally unless a specific role is requested.").argument("<model>", "Model id to select").requiredOption("--provider <provider>", "Provider id to use").option("--role <role>", "Brain role to assign: planner, coder, reviewer, or fast").option("--set-default", "Also set this model as the provider default", false).option("--force", "Skip remote model validation", false).option("--temperature <value>", "Temperature override for the role", parseNumber2).option("--max-tokens <value>", "Max token override for the role", parseInteger).action(async (model, options) => {
9864
10809
  let config = await loadConfig(import_node_process5.default.cwd());
9865
10810
  if (!config) {
9866
10811
  requireConfigMessage2();
@@ -9910,12 +10855,16 @@ function createModelsCommand() {
9910
10855
  maxTokens: options.maxTokens
9911
10856
  } : {}
9912
10857
  });
10858
+ } else {
10859
+ config = assignProviderToAllBrains(config, provider.id, model);
9913
10860
  }
9914
10861
  const outputPath = await saveConfig(config, import_node_process5.default.cwd());
9915
10862
  console.log(`Updated ${outputPath}`);
9916
10863
  console.log(`Selected ${provider.id}/${model}`);
9917
10864
  if (options.role) {
9918
10865
  console.log(`Assigned role ${options.role}`);
10866
+ } else {
10867
+ console.log("Assigned all roles");
9919
10868
  }
9920
10869
  if (shouldSetDefault) {
9921
10870
  console.log(`Set provider default model to ${model}`);
@@ -9925,7 +10874,7 @@ function createModelsCommand() {
9925
10874
  }
9926
10875
 
9927
10876
  // src/commands/plan.ts
9928
- var import_promises6 = require("node:fs/promises");
10877
+ var import_promises7 = require("node:fs/promises");
9929
10878
  var import_node_process6 = __toESM(require("node:process"), 1);
9930
10879
 
9931
10880
  // ../planner/dist/planner.js
@@ -10230,7 +11179,7 @@ function createPlan(input) {
10230
11179
 
10231
11180
  // src/commands/plan.ts
10232
11181
  async function detectWorkspaceState(cwd) {
10233
- const entries = await (0, import_promises6.readdir)(cwd);
11182
+ const entries = await (0, import_promises7.readdir)(cwd);
10234
11183
  const meaningfulEntries = entries.filter((entry) => entry !== ".kimbho" && !entry.startsWith("."));
10235
11184
  return meaningfulEntries.length === 0 ? "empty" : "existing";
10236
11185
  }
@@ -10393,7 +11342,7 @@ function parseCount(value) {
10393
11342
  return parsed;
10394
11343
  }
10395
11344
  function createResumeCommand() {
10396
- 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) => {
10397
11346
  const cwd = import_node_process8.default.cwd();
10398
11347
  const session = await loadLatestSession(cwd);
10399
11348
  if (!session) {
@@ -10402,7 +11351,9 @@ function createResumeCommand() {
10402
11351
  return;
10403
11352
  }
10404
11353
  const snapshot = options.execute ? await new ExecutionOrchestrator().continueSession(session, {
10405
- maxAutoTasks: options.maxAutoTasks
11354
+ maxAutoTasks: options.maxAutoTasks,
11355
+ maxAgentSteps: options.maxAgentSteps,
11356
+ maxRepairAttempts: options.maxRepairAttempts
10406
11357
  }) : session;
10407
11358
  if (options.execute) {
10408
11359
  await saveSession(snapshot, cwd);
@@ -10416,10 +11367,10 @@ function createResumeCommand() {
10416
11367
  }
10417
11368
 
10418
11369
  // src/commands/run.ts
10419
- var import_promises7 = require("node:fs/promises");
11370
+ var import_promises8 = require("node:fs/promises");
10420
11371
  var import_node_process9 = __toESM(require("node:process"), 1);
10421
11372
  async function detectWorkspaceState2(cwd) {
10422
- const entries = await (0, import_promises7.readdir)(cwd);
11373
+ const entries = await (0, import_promises8.readdir)(cwd);
10423
11374
  const meaningfulEntries = entries.filter((entry) => entry !== ".kimbho" && !entry.startsWith("."));
10424
11375
  return meaningfulEntries.length === 0 ? "empty" : "existing";
10425
11376
  }
@@ -10436,7 +11387,7 @@ function createRunCommand() {
10436
11387
  "Explicit execution constraint; can be provided multiple times",
10437
11388
  (value, previous = []) => [...previous, value],
10438
11389
  []
10439
- ).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) => {
10440
11391
  const cwd = import_node_process9.default.cwd();
10441
11392
  const orchestrator = new ExecutionOrchestrator();
10442
11393
  let request;
@@ -10471,7 +11422,9 @@ function createRunCommand() {
10471
11422
  const envelope = orchestrator.buildEnvelope(request, plan);
10472
11423
  const initialSnapshot = orchestrator.createSessionSnapshot(envelope);
10473
11424
  const snapshot = options.snapshotOnly ? initialSnapshot : await orchestrator.continueSession(initialSnapshot, {
10474
- maxAutoTasks: options.maxAutoTasks
11425
+ maxAutoTasks: options.maxAutoTasks,
11426
+ maxAgentSteps: options.maxAgentSteps,
11427
+ maxRepairAttempts: options.maxRepairAttempts
10475
11428
  });
10476
11429
  const sessionPath = await saveSession(snapshot, cwd);
10477
11430
  if (options.json) {
@@ -10559,7 +11512,7 @@ function createProgram(onOpenShell) {
10559
11512
  }
10560
11513
 
10561
11514
  // src/shell.ts
10562
- var import_promises8 = require("node:readline/promises");
11515
+ var import_promises9 = require("node:readline/promises");
10563
11516
  var import_node_process10 = __toESM(require("node:process"), 1);
10564
11517
  var AMBER = "\x1B[38;5;214m";
10565
11518
  var BOLD = "\x1B[1m";
@@ -10587,6 +11540,9 @@ var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
10587
11540
  "select",
10588
11541
  "shell",
10589
11542
  "status",
11543
+ "ask",
11544
+ "chat",
11545
+ "reset-chat",
10590
11546
  "use-model"
10591
11547
  ]);
10592
11548
  var MODEL_SUBCOMMANDS = /* @__PURE__ */ new Set([
@@ -10601,6 +11557,7 @@ var BRAIN_ROLES = [
10601
11557
  "reviewer",
10602
11558
  "fast"
10603
11559
  ];
11560
+ var MAX_CHAT_MESSAGES = 12;
10604
11561
  function color(code, value) {
10605
11562
  return `${code}${value}${RESET}`;
10606
11563
  }
@@ -10670,15 +11627,19 @@ function renderHelp() {
10670
11627
  `${color(DIM, "Commands")}`,
10671
11628
  "/status Show the active role, provider, and workspace state.",
10672
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.",
10673
11633
  "/model Show current brain assignments.",
10674
11634
  "/providers List configured providers.",
10675
11635
  "/providers templates Show built-in provider templates.",
10676
- "/providers add <tpl> [id] Add a provider from a built-in template.",
10677
- "/providers use <id> [role] Use a provider for the active role.",
11636
+ "/providers add <tpl> [id] [--base-url <url>] [--model <id>] [--api-key-env <env>]",
11637
+ " Add a provider from a built-in template.",
11638
+ "/providers use <id> Use a provider for all agent roles.",
10678
11639
  "/providers check Run provider health checks.",
10679
11640
  "/models [search] Discover models for the active role's provider.",
10680
11641
  "/select <n> Select a model from the last numbered /models list.",
10681
- "/use-model <id> Assign a model to the active role and provider.",
11642
+ "/use-model <id> Assign a model to all agent roles.",
10682
11643
  "/plan <goal> Create a structured implementation plan.",
10683
11644
  "/run <goal> Start a Kimbho execution session for a goal.",
10684
11645
  "/resume Show the latest saved session.",
@@ -10688,7 +11649,7 @@ function renderHelp() {
10688
11649
  "/quit, /exit Leave the shell.",
10689
11650
  "",
10690
11651
  `${color(DIM, "Tip")}`,
10691
- "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."
10692
11653
  ].join("\n");
10693
11654
  }
10694
11655
  function renderStartupCard(cwd, state) {
@@ -10703,7 +11664,7 @@ function renderStartupCard(cwd, state) {
10703
11664
  renderCardLine("approval", state.approvalMode),
10704
11665
  renderCardLine("sandbox", state.sandboxMode),
10705
11666
  renderCardLine("preset", state.stackPreset),
10706
- renderCardLine("shortcuts", "/brain /providers /models /select /use-model /quit")
11667
+ renderCardLine("shortcuts", "/ask /run /brain /providers /models /quit")
10707
11668
  ];
10708
11669
  if (!state.configured) {
10709
11670
  cardLines.push("setup: run /init or /providers add <template> to create .kimbho/config.json");
@@ -10719,7 +11680,7 @@ function printHeader(cwd, state) {
10719
11680
  console.log(color(DIM, "Terminal-native coding agent"));
10720
11681
  console.log(renderStartupCard(cwd, state));
10721
11682
  console.log("");
10722
- 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."));
10723
11684
  console.log(renderHelp());
10724
11685
  console.log("");
10725
11686
  }
@@ -10795,6 +11756,63 @@ function normalizeInputTokens(input) {
10795
11756
  head: firstToken.startsWith("/") ? firstToken.slice(1) : firstToken
10796
11757
  };
10797
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
+ }
10798
11816
  function defaultProviderIdForTemplate(templateId) {
10799
11817
  switch (templateId) {
10800
11818
  case "openai":
@@ -10811,6 +11829,52 @@ function defaultProviderIdForTemplate(templateId) {
10811
11829
  return `${templateId}-main`;
10812
11830
  }
10813
11831
  }
11832
+ function parseProviderAddOptions(tokens) {
11833
+ const templateId = tokens[2];
11834
+ if (!templateId) {
11835
+ throw new Error("Usage: /providers add <template> [provider-id] [--base-url <url>] [--model <id>] [--api-key-env <env>]");
11836
+ }
11837
+ let index = 3;
11838
+ let providerId;
11839
+ if (tokens[index] && !tokens[index]?.startsWith("-")) {
11840
+ providerId = tokens[index];
11841
+ index += 1;
11842
+ }
11843
+ const options = {
11844
+ templateId,
11845
+ ...providerId ? {
11846
+ providerId
11847
+ } : {}
11848
+ };
11849
+ while (index < tokens.length) {
11850
+ const flag = tokens[index];
11851
+ if (!flag?.startsWith("--")) {
11852
+ throw new Error(`Unexpected token "${flag}".`);
11853
+ }
11854
+ const value = tokens[index + 1];
11855
+ if (!value || value.startsWith("--")) {
11856
+ throw new Error(`Missing value for ${flag}.`);
11857
+ }
11858
+ switch (flag) {
11859
+ case "--label":
11860
+ options.label = value;
11861
+ break;
11862
+ case "--base-url":
11863
+ options.baseUrl = normalizeTemplateBaseUrl(templateId, value) ?? value;
11864
+ break;
11865
+ case "--api-key-env":
11866
+ options.apiKeyEnv = value;
11867
+ break;
11868
+ case "--model":
11869
+ options.model = value;
11870
+ break;
11871
+ default:
11872
+ throw new Error(`Unknown option "${flag}". Supported: --label, --base-url, --api-key-env, --model.`);
11873
+ }
11874
+ index += 2;
11875
+ }
11876
+ return options;
11877
+ }
10814
11878
  function buildCachedModels(provider, search, limit = 25) {
10815
11879
  const normalized = search?.trim().toLowerCase();
10816
11880
  const filtered = normalized ? provider.models.filter((modelId) => modelId.toLowerCase().includes(normalized)) : provider.models;
@@ -10857,7 +11921,7 @@ async function fetchModelsForProvider(cwd, config, provider, search, limit = 25)
10857
11921
  throw error;
10858
11922
  }
10859
11923
  }
10860
- async function assignModelToRole(cwd, role, providerId, model) {
11924
+ async function assignModelGlobally(cwd, providerId, model) {
10861
11925
  const config = await loadConfig(cwd);
10862
11926
  if (!config) {
10863
11927
  throw new Error("No config found. Run /init or /providers add first.");
@@ -10866,8 +11930,7 @@ async function assignModelToRole(cwd, role, providerId, model) {
10866
11930
  if (!provider) {
10867
11931
  throw new Error(`Unknown provider "${providerId}".`);
10868
11932
  }
10869
- const currentSettings = resolveBrainSettings(config, role);
10870
- const nextConfig = assignBrain(
11933
+ const nextConfig = assignProviderToAllBrains(
10871
11934
  upsertProvider(config, {
10872
11935
  ...provider,
10873
11936
  defaultModel: model,
@@ -10876,25 +11939,13 @@ async function assignModelToRole(cwd, role, providerId, model) {
10876
11939
  model
10877
11940
  ]))
10878
11941
  }),
10879
- role,
10880
- {
10881
- providerId,
10882
- model,
10883
- ...typeof currentSettings.temperature === "number" ? {
10884
- temperature: currentSettings.temperature
10885
- } : {},
10886
- ...typeof currentSettings.maxTokens === "number" ? {
10887
- maxTokens: currentSettings.maxTokens
10888
- } : {},
10889
- ...currentSettings.promptPreamble ? {
10890
- promptPreamble: currentSettings.promptPreamble
10891
- } : {}
10892
- }
11942
+ providerId,
11943
+ model
10893
11944
  );
10894
11945
  const outputPath = await saveConfig(nextConfig, cwd);
10895
11946
  return outputPath;
10896
11947
  }
10897
- async function useProviderForRole(cwd, role, providerId) {
11948
+ async function useProviderGlobally(cwd, providerId) {
10898
11949
  const config = await loadConfig(cwd);
10899
11950
  if (!config) {
10900
11951
  throw new Error("No config found. Run /init or /providers add first.");
@@ -10903,23 +11954,8 @@ async function useProviderForRole(cwd, role, providerId) {
10903
11954
  if (!provider) {
10904
11955
  throw new Error(`Unknown provider "${providerId}".`);
10905
11956
  }
10906
- const currentSettings = resolveBrainSettings(config, role);
10907
11957
  const resolvedModel = provider.defaultModel ?? provider.models[0] ?? null;
10908
- const nextConfig = assignBrain(config, role, {
10909
- providerId: provider.id,
10910
- ...resolvedModel ? {
10911
- model: resolvedModel
10912
- } : {},
10913
- ...typeof currentSettings.temperature === "number" ? {
10914
- temperature: currentSettings.temperature
10915
- } : {},
10916
- ...typeof currentSettings.maxTokens === "number" ? {
10917
- maxTokens: currentSettings.maxTokens
10918
- } : {},
10919
- ...currentSettings.promptPreamble ? {
10920
- promptPreamble: currentSettings.promptPreamble
10921
- } : {}
10922
- });
11958
+ const nextConfig = assignProviderToAllBrains(config, provider.id, resolvedModel);
10923
11959
  const outputPath = await saveConfig(nextConfig, cwd);
10924
11960
  return {
10925
11961
  outputPath,
@@ -10950,6 +11986,7 @@ async function printProviderList(cwd, focusRole) {
10950
11986
  console.log(` label: ${provider.label ?? "-"}`);
10951
11987
  console.log(` driver: ${provider.driver}`);
10952
11988
  console.log(` model: ${provider.defaultModel ?? "-"}`);
11989
+ console.log(` baseUrl: ${provider.baseUrl ?? "-"}`);
10953
11990
  console.log(` auth: ${envState}`);
10954
11991
  console.log(` cachedModels: ${provider.models.length}`);
10955
11992
  }
@@ -10964,9 +12001,21 @@ function printProviderTemplates() {
10964
12001
  console.log(` notes: ${template.notes}`);
10965
12002
  }
10966
12003
  }
10967
- async function addProviderFromTemplate(cwd, templateId, providerId) {
10968
- const provider = buildProviderFromTemplate(templateId, {
10969
- providerId: providerId ?? defaultProviderIdForTemplate(templateId)
12004
+ async function addProviderFromTemplate(cwd, options) {
12005
+ const provider = buildProviderFromTemplate(options.templateId, {
12006
+ providerId: options.providerId ?? defaultProviderIdForTemplate(options.templateId),
12007
+ ...options.label ? {
12008
+ label: options.label
12009
+ } : {},
12010
+ ...options.baseUrl ? {
12011
+ baseUrl: options.baseUrl
12012
+ } : {},
12013
+ ...options.apiKeyEnv ? {
12014
+ apiKeyEnv: options.apiKeyEnv
12015
+ } : {},
12016
+ ...options.model ? {
12017
+ model: options.model
12018
+ } : {}
10970
12019
  });
10971
12020
  const config = await loadConfig(cwd);
10972
12021
  if (!config) {
@@ -11025,30 +12074,27 @@ async function handleProvidersCommand(cwd, tokens, runtime) {
11025
12074
  return;
11026
12075
  }
11027
12076
  if (subcommand === "add") {
11028
- const templateId = tokens[2];
11029
- const providerId = tokens[3];
11030
- if (!templateId) {
11031
- throw new Error("Usage: /providers add <template> [provider-id]");
11032
- }
11033
- const outputPath = await addProviderFromTemplate(cwd, templateId, providerId);
11034
- const resolvedProviderId = providerId ?? defaultProviderIdForTemplate(templateId);
12077
+ const options = parseProviderAddOptions(tokens);
12078
+ const outputPath = await addProviderFromTemplate(cwd, options);
12079
+ const resolvedProviderId = options.providerId ?? defaultProviderIdForTemplate(options.templateId);
11035
12080
  console.log(`Updated ${outputPath}`);
11036
- console.log(`Added provider ${resolvedProviderId} from template ${templateId}`);
12081
+ console.log(`Added provider ${resolvedProviderId} from template ${options.templateId}`);
12082
+ if (options.baseUrl) {
12083
+ console.log(`Base URL ${options.baseUrl}`);
12084
+ }
11037
12085
  console.log(`Next: /providers use ${resolvedProviderId}`);
11038
12086
  return;
11039
12087
  }
11040
12088
  if (subcommand === "use") {
11041
12089
  const providerId = tokens[2];
11042
- const role = tokens[3];
11043
12090
  if (!providerId) {
11044
- throw new Error("Usage: /providers use <provider-id> [role]");
12091
+ throw new Error("Usage: /providers use <provider-id>");
11045
12092
  }
11046
- const targetRole = role && isBrainRole(role) ? role : runtime.focusRole;
11047
- const result = await useProviderForRole(cwd, targetRole, providerId);
11048
- runtime.focusRole = targetRole;
12093
+ const result = await useProviderGlobally(cwd, providerId);
12094
+ clearAllConversations(runtime);
11049
12095
  runtime.lastModels = null;
11050
12096
  console.log(`Updated ${result.outputPath}`);
11051
- console.log(`Focus role ${targetRole} now uses provider ${providerId}${result.model ? ` (${result.model})` : ""}`);
12097
+ console.log(`All roles now use provider ${providerId}${result.model ? ` (${result.model})` : ""}`);
11052
12098
  return;
11053
12099
  }
11054
12100
  throw new Error("Usage: /providers [list|templates|add|use|check]");
@@ -11089,10 +12135,11 @@ async function handleModelsCommand(cwd, tokens, runtime) {
11089
12135
  if (!modelId) {
11090
12136
  throw new Error("Usage: /models use <model-id>");
11091
12137
  }
11092
- const outputPath = await assignModelToRole(cwd, runtime.focusRole, provider.id, modelId);
12138
+ const outputPath = await assignModelGlobally(cwd, provider.id, modelId);
12139
+ clearAllConversations(runtime);
11093
12140
  console.log(`Updated ${outputPath}`);
11094
12141
  console.log(`Selected ${provider.id}/${modelId}`);
11095
- console.log(`Assigned role ${runtime.focusRole}`);
12142
+ console.log("Assigned all roles");
11096
12143
  return;
11097
12144
  }
11098
12145
  const search = !subcommand || MODEL_SUBCOMMANDS.has(subcommand) ? void 0 : tokens.slice(1).join(" ");
@@ -11118,7 +12165,7 @@ async function handleModelsCommand(cwd, tokens, runtime) {
11118
12165
  console.log(` ${index + 1}. ${renderModelLine2(model)}`);
11119
12166
  }
11120
12167
  console.log(``);
11121
- console.log(`Use /select <number> or /use-model <model-id> to assign one to ${runtime.focusRole}.`);
12168
+ console.log("Use /select <number> or /use-model <model-id> to assign one to all roles.");
11122
12169
  }
11123
12170
  async function handleModelSelection(cwd, modelId, runtime) {
11124
12171
  const config = await loadConfig(cwd);
@@ -11130,10 +12177,11 @@ async function handleModelSelection(cwd, modelId, runtime) {
11130
12177
  if (!provider) {
11131
12178
  throw new Error(`Active role "${runtime.focusRole}" points to unknown provider "${providerId}".`);
11132
12179
  }
11133
- const outputPath = await assignModelToRole(cwd, runtime.focusRole, provider.id, modelId);
12180
+ const outputPath = await assignModelGlobally(cwd, provider.id, modelId);
12181
+ clearAllConversations(runtime);
11134
12182
  console.log(`Updated ${outputPath}`);
11135
12183
  console.log(`Selected ${provider.id}/${modelId}`);
11136
- console.log(`Assigned role ${runtime.focusRole}`);
12184
+ console.log("Assigned all roles");
11137
12185
  }
11138
12186
  async function handleSelectCommand(cwd, tokens, runtime) {
11139
12187
  const rawIndex = tokens[1];
@@ -11176,10 +12224,22 @@ function toExternalCommandTokens(input, state) {
11176
12224
  `scaffold ${goal}`.trim()
11177
12225
  ];
11178
12226
  }
11179
- if (!firstToken.startsWith("/") && !TOP_LEVEL_COMMANDS.has(head) && !head.startsWith("-")) {
11180
- return [
12227
+ if (head === "run") {
12228
+ const goal = normalizedInput.replace(/^\/?run\b\s*/, "");
12229
+ return goal ? [
11181
12230
  "run",
11182
- 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"
11183
12243
  ];
11184
12244
  }
11185
12245
  tokens[0] = head;
@@ -11210,9 +12270,14 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
11210
12270
  return;
11211
12271
  }
11212
12272
  const { tokens, head } = normalizeInputTokens(trimmed);
12273
+ const firstToken = tokens[0];
11213
12274
  if (!head) {
11214
12275
  return;
11215
12276
  }
12277
+ if (!firstToken?.startsWith("/") && !TOP_LEVEL_COMMANDS.has(head) && !head.startsWith("-")) {
12278
+ await handleChatPrompt(cwd, trimmed, runtime);
12279
+ return;
12280
+ }
11216
12281
  if (head === "provider" || head === "providers") {
11217
12282
  const subcommand = tokens[1];
11218
12283
  const canHandleLocally = !subcommand || subcommand === "list" || subcommand === "templates" || subcommand === "check" || subcommand === "use" || subcommand === "add" && Boolean(tokens[2]) && !tokens[2]?.startsWith("-");
@@ -11240,6 +12305,19 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
11240
12305
  await printBrainAssignments(cwd);
11241
12306
  return;
11242
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
+ }
11243
12321
  if (head === "models") {
11244
12322
  await handleModelsCommand(cwd, [
11245
12323
  "models",
@@ -11269,14 +12347,20 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
11269
12347
  await execute(externalTokens);
11270
12348
  }
11271
12349
  async function runInteractiveShell(options) {
11272
- const readline = (0, import_promises8.createInterface)({
12350
+ const readline = (0, import_promises9.createInterface)({
11273
12351
  input: import_node_process10.default.stdin,
11274
12352
  output: import_node_process10.default.stdout,
11275
12353
  terminal: Boolean(import_node_process10.default.stdin.isTTY && import_node_process10.default.stdout.isTTY)
11276
12354
  });
11277
12355
  const runtime = {
11278
12356
  focusRole: "coder",
11279
- lastModels: null
12357
+ lastModels: null,
12358
+ conversations: {
12359
+ planner: [],
12360
+ coder: [],
12361
+ reviewer: [],
12362
+ fast: []
12363
+ }
11280
12364
  };
11281
12365
  let closed = false;
11282
12366
  readline.on("SIGINT", () => {