@bobsworkshop/cli 0.7.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bob.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  import {
2
3
  buildLocalContext,
3
4
  callCloudFunction,
@@ -26,12 +27,466 @@ import {
26
27
  setConfigValue,
27
28
  stripCodeBlockFromResponse,
28
29
  updateManifestProgress
29
- } from "./chunk-NUMFL5IZ.js";
30
+ } from "./chunk-NZW7H2BY.js";
31
+ import {
32
+ agentExists,
33
+ createAgent,
34
+ getActiveAgentCount,
35
+ loadAgentMessages,
36
+ loadAgentSummary,
37
+ loadRegistry,
38
+ loadSession,
39
+ resetAgent,
40
+ resolveAgentName,
41
+ saveAgentMessage,
42
+ saveAgentSummary,
43
+ stopAgent
44
+ } from "./chunk-CAF7EJSC.js";
45
+ import {
46
+ loadPersonaPrompt
47
+ } from "./chunk-AQGIC65Q.js";
48
+ import {
49
+ __esm,
50
+ __export,
51
+ __toCommonJS
52
+ } from "./chunk-PNKVD2UK.js";
53
+
54
+ // src/core/agent-tools.ts
55
+ var agent_tools_exports = {};
56
+ __export(agent_tools_exports, {
57
+ AGENT_TOOLS_PROMPT: () => AGENT_TOOLS_PROMPT,
58
+ AgentToolExecutor: () => AgentToolExecutor,
59
+ clearAllPendingCommits: () => clearAllPendingCommits,
60
+ clearPendingCommit: () => clearPendingCommit,
61
+ clearPendingCommitsForTask: () => clearPendingCommitsForTask,
62
+ loadPendingCommits: () => loadPendingCommits,
63
+ parseToolCall: () => parseToolCall,
64
+ savePendingCommit: () => savePendingCommit,
65
+ stripToolCall: () => stripToolCall
66
+ });
67
+ import * as fs13 from "fs";
68
+ import * as path17 from "path";
69
+ import * as os6 from "os";
70
+ import { execSync } from "child_process";
71
+ function isCommandSafe(command) {
72
+ const normalized = command.trim().toLowerCase();
73
+ return SAFE_COMMAND_PREFIXES.some(
74
+ (prefix) => normalized.startsWith(prefix.toLowerCase())
75
+ );
76
+ }
77
+ function getPendingCommitsDir(cwd) {
78
+ const projectName = path17.basename(cwd);
79
+ return path17.join(BOB_DIR6, "projects", projectName, "agents", "pending-commits");
80
+ }
81
+ function savePendingCommit(commit, cwd) {
82
+ const dir = getPendingCommitsDir(cwd);
83
+ if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
84
+ const filename = `${commit.id}.json`;
85
+ fs13.writeFileSync(path17.join(dir, filename), JSON.stringify(commit, null, 2));
86
+ }
87
+ function loadPendingCommits(cwd) {
88
+ const dir = getPendingCommitsDir(cwd);
89
+ if (!fs13.existsSync(dir)) return [];
90
+ try {
91
+ return fs13.readdirSync(dir).filter((f) => f.endsWith(".json")).sort().map((f) => {
92
+ try {
93
+ return JSON.parse(fs13.readFileSync(path17.join(dir, f), "utf-8"));
94
+ } catch {
95
+ return null;
96
+ }
97
+ }).filter(Boolean);
98
+ } catch {
99
+ return [];
100
+ }
101
+ }
102
+ function clearPendingCommit(commitId, cwd) {
103
+ const dir = getPendingCommitsDir(cwd);
104
+ if (!fs13.existsSync(dir)) return;
105
+ try {
106
+ const filePath = path17.join(dir, `${commitId}.json`);
107
+ if (fs13.existsSync(filePath)) {
108
+ fs13.unlinkSync(filePath);
109
+ }
110
+ } catch {
111
+ }
112
+ }
113
+ function clearPendingCommitsForTask(taskId, cwd) {
114
+ const dir = getPendingCommitsDir(cwd);
115
+ if (!fs13.existsSync(dir)) return;
116
+ try {
117
+ const commits = loadPendingCommits(cwd);
118
+ for (const commit of commits) {
119
+ if (commit.taskId === taskId) {
120
+ const filePath = path17.join(dir, `${commit.id}.json`);
121
+ if (fs13.existsSync(filePath)) {
122
+ fs13.unlinkSync(filePath);
123
+ }
124
+ }
125
+ }
126
+ } catch {
127
+ }
128
+ }
129
+ function clearAllPendingCommits(cwd) {
130
+ const dir = getPendingCommitsDir(cwd);
131
+ if (!fs13.existsSync(dir)) return;
132
+ try {
133
+ const files = fs13.readdirSync(dir).filter((f) => f.endsWith(".json"));
134
+ for (const file of files) {
135
+ fs13.unlinkSync(path17.join(dir, file));
136
+ }
137
+ } catch {
138
+ }
139
+ }
140
+ function parseToolCall(response) {
141
+ const lines = response.split("\n");
142
+ for (let i = lines.length - 1; i >= 0; i--) {
143
+ const line = lines[i].trim();
144
+ if (line.toLowerCase().startsWith("tool_call:")) {
145
+ try {
146
+ const colonIdx = line.indexOf(":");
147
+ const jsonStr = line.slice(colonIdx + 1).trim();
148
+ const parsed = JSON.parse(jsonStr);
149
+ if (parsed.tool && parsed.params !== void 0) {
150
+ return parsed;
151
+ }
152
+ } catch {
153
+ }
154
+ }
155
+ const pythonMatch = line.match(/^(\w+)\s*\(\s*path\s*=\s*['"]([^'"]+)['"]\s*\)/);
156
+ if (pythonMatch) {
157
+ const toolName = pythonMatch[1];
158
+ if (ALL_TOOL_NAMES.includes(toolName)) {
159
+ return { tool: toolName, params: { path: pythonMatch[2] } };
160
+ }
161
+ }
162
+ if (ALL_TOOL_NAMES.includes(line) && i + 1 < lines.length) {
163
+ const nextLine = lines[i + 1].trim();
164
+ if (nextLine.startsWith("{")) {
165
+ try {
166
+ const params = JSON.parse(nextLine);
167
+ if (Object.keys(params).length > 0) {
168
+ return { tool: line, params };
169
+ }
170
+ } catch {
171
+ }
172
+ }
173
+ }
174
+ if (line.startsWith("{") && line.includes('"tool"')) {
175
+ try {
176
+ const parsed = JSON.parse(line);
177
+ if (parsed.tool && parsed.params !== void 0) {
178
+ return parsed;
179
+ }
180
+ } catch {
181
+ }
182
+ }
183
+ }
184
+ return null;
185
+ }
186
+ function stripToolCall(response) {
187
+ const lines = response.split("\n");
188
+ const result = [];
189
+ for (let i = 0; i < lines.length; i++) {
190
+ const line = lines[i].trim();
191
+ if (line.toLowerCase().startsWith("tool_call:")) continue;
192
+ if (ALL_TOOL_NAMES.includes(line)) {
193
+ if (i + 1 < lines.length && lines[i + 1].trim().startsWith("{")) i++;
194
+ continue;
195
+ }
196
+ if (line.startsWith('{"path"') || line.startsWith('{"message"') || line.startsWith('{"content"') || line.startsWith('{"tool"') || line.startsWith('{"agentName"') || line.startsWith('{"command"')) continue;
197
+ result.push(lines[i]);
198
+ }
199
+ return result.join("\n").trim();
200
+ }
201
+ var BOB_DIR6, SAFE_COMMAND_PREFIXES, ALL_TOOL_NAMES, AGENT_TOOLS_PROMPT, AgentToolExecutor;
202
+ var init_agent_tools = __esm({
203
+ "src/core/agent-tools.ts"() {
204
+ "use strict";
205
+ BOB_DIR6 = path17.join(os6.homedir(), ".bob");
206
+ SAFE_COMMAND_PREFIXES = [
207
+ "flutter pub add",
208
+ "flutter pub get",
209
+ "flutter pub upgrade",
210
+ "dart pub add",
211
+ "dart pub get",
212
+ "npm install",
213
+ "npm i ",
214
+ "npm add",
215
+ "yarn add",
216
+ "yarn install",
217
+ "pnpm add",
218
+ "pnpm install",
219
+ "pip install",
220
+ "pip3 install",
221
+ "cargo add",
222
+ "go get",
223
+ "composer require",
224
+ "bundle add",
225
+ "gem install",
226
+ "pod install",
227
+ "pod update"
228
+ ];
229
+ ALL_TOOL_NAMES = [
230
+ "readFile",
231
+ "deleteFile",
232
+ "writeOutput",
233
+ "readAgentOutput",
234
+ "gitCommit",
235
+ "gitPush",
236
+ "analyseFile",
237
+ "runBackup",
238
+ "runCommand"
239
+ ];
240
+ AGENT_TOOLS_PROMPT = `
241
+ ACTION TOOLS (for non-file operations):
242
+ Use the "toolCalls" array in your JSON response when you need an action.
243
+
244
+ readFile
245
+ params: { "path": "src/core/config-store.ts" }
246
+ Use when: You need to read an existing file before modifying it.
247
+
248
+ writeOutput
249
+ params: { "content": "your findings or completion summary" }
250
+ Use when: Recording your final decision, recommendation, or audit findings.
251
+
252
+ readAgentOutput
253
+ params: { "agentName": "architectBob", "taskId": "m_123_t1" }
254
+ Use when: Reading another agent's task output.
255
+
256
+ runCommand
257
+ params: { "command": "flutter pub add intl" }
258
+ Use when: A required package is missing and needs to be installed.
259
+ SAFE commands only: flutter pub add, npm install, yarn add, pip install, etc.
260
+ NEVER use for: rm, delete, format, drop, or any destructive operation.
261
+
262
+ gitCommit
263
+ params: { "message": "your commit message" }
264
+ Use when: Your work is complete and ready for DirectorBob review.
265
+ NOTE: Queues a commit request \u2014 DirectorBob reviews before approving.
266
+
267
+ gitPush
268
+ params: {}
269
+ Use when: DirectorBob has approved a commit and you need to push.
270
+
271
+ deleteFile
272
+ params: { "path": "src/old/file.ts" }
273
+ Use when: Removing a file that is no longer needed.
274
+
275
+ RULES:
276
+ - Add tools to the "toolCalls" array in your JSON response.
277
+ - You can include multiple tools \u2014 they execute in sequence after files are written.
278
+ - For file creation/modification use the "files" array in your JSON response.
279
+ - Always readFile before modifying an existing file.
280
+ - If a package import fails, use runCommand to install it first.
281
+ - gitCommit queues a review request \u2014 DirectorBob approves or denies.
282
+ `;
283
+ AgentToolExecutor = class {
284
+ cwd;
285
+ agentName;
286
+ taskId;
287
+ missionId;
288
+ filesWrittenThisTask;
289
+ constructor(cwd, agentName, taskId, missionId = "", filesWrittenThisTask = []) {
290
+ this.cwd = cwd;
291
+ this.agentName = agentName;
292
+ this.taskId = taskId;
293
+ this.missionId = missionId;
294
+ this.filesWrittenThisTask = filesWrittenThisTask;
295
+ }
296
+ async execute(toolCall) {
297
+ switch (toolCall.tool) {
298
+ case "readFile":
299
+ return this.readFile(toolCall.params);
300
+ case "deleteFile":
301
+ return this.deleteFile(toolCall.params);
302
+ case "writeOutput":
303
+ return this.writeOutput(toolCall.params);
304
+ case "readAgentOutput":
305
+ return this.readAgentOutput(toolCall.params);
306
+ case "gitCommit":
307
+ return this.gitCommitQueue(toolCall.params);
308
+ case "gitPush":
309
+ return this.gitPush();
310
+ case "analyseFile":
311
+ return this.analyseFile(toolCall.params);
312
+ case "runBackup":
313
+ return this.runBackup();
314
+ case "runCommand":
315
+ return this.runCommand(toolCall.params);
316
+ default:
317
+ return this.error(`Unknown tool: ${toolCall.tool}`);
318
+ }
319
+ }
320
+ readFile(params) {
321
+ const filePath = params.path;
322
+ if (!filePath) return this.error("readFile requires path.");
323
+ const absolutePath = this.resolvePath(filePath);
324
+ if (!absolutePath) return this.error(`Path outside project: ${filePath}`);
325
+ try {
326
+ if (!fs13.existsSync(absolutePath)) return this.error(`File does not exist: ${filePath}`);
327
+ const content = fs13.readFileSync(absolutePath, "utf-8");
328
+ const truncated = content.length > 8e3 ? content.slice(0, 8e3) + "\n... (truncated)" : content;
329
+ return { success: true, output: truncated, filesCreated: [], filesModified: [] };
330
+ } catch (e) {
331
+ return this.error(`Failed to read file: ${e.message}`);
332
+ }
333
+ }
334
+ deleteFile(params) {
335
+ const filePath = params.path;
336
+ if (!filePath) return this.error("deleteFile requires path.");
337
+ const absolutePath = this.resolvePath(filePath);
338
+ if (!absolutePath) return this.error(`Path outside project: ${filePath}`);
339
+ try {
340
+ if (!fs13.existsSync(absolutePath)) return this.error(`File does not exist: ${filePath}`);
341
+ this.backupFile(absolutePath, filePath);
342
+ fs13.unlinkSync(absolutePath);
343
+ return { success: true, output: `Deleted: ${filePath} (backup saved)`, filesCreated: [], filesModified: [] };
344
+ } catch (e) {
345
+ return this.error(`Failed to delete file: ${e.message}`);
346
+ }
347
+ }
348
+ writeOutput(params) {
349
+ const content = params.content;
350
+ if (!content) return this.error("writeOutput requires content.");
351
+ try {
352
+ const outputDir = path17.join(BOB_DIR6, "projects", path17.basename(this.cwd), "agents", this.agentName, "output");
353
+ if (!fs13.existsSync(outputDir)) fs13.mkdirSync(outputDir, { recursive: true });
354
+ fs13.writeFileSync(path17.join(outputDir, `${this.taskId}.md`), String(content), "utf-8");
355
+ return { success: true, output: `Output saved.`, filesCreated: [], filesModified: [] };
356
+ } catch (e) {
357
+ return this.error(`Failed to write output: ${e.message}`);
358
+ }
359
+ }
360
+ readAgentOutput(params) {
361
+ const { agentName, taskId } = params;
362
+ if (!agentName || !taskId) return this.error("readAgentOutput requires agentName and taskId.");
363
+ try {
364
+ const outputFile = path17.join(BOB_DIR6, "projects", path17.basename(this.cwd), "agents", agentName, "output", `${taskId}.md`);
365
+ if (!fs13.existsSync(outputFile)) return this.error(`No output found for @${agentName} task ${taskId}.`);
366
+ return { success: true, output: fs13.readFileSync(outputFile, "utf-8"), filesCreated: [], filesModified: [] };
367
+ } catch (e) {
368
+ return this.error(`Failed to read agent output: ${e.message}`);
369
+ }
370
+ }
371
+ // ─── RUN COMMAND ──────────────────────────────────────────────
372
+ runCommand(params) {
373
+ const command = params.command;
374
+ if (!command) return this.error("runCommand requires command.");
375
+ if (!isCommandSafe(command)) {
376
+ return this.error(
377
+ `Command not permitted: "${command}". Only package installation commands are allowed (flutter pub add, npm install, etc.). Never use runCommand for file operations or destructive commands.`
378
+ );
379
+ }
380
+ try {
381
+ const output = execSync(command, {
382
+ cwd: this.cwd,
383
+ timeout: 6e4,
384
+ encoding: "utf-8",
385
+ stdio: ["pipe", "pipe", "pipe"]
386
+ });
387
+ return {
388
+ success: true,
389
+ output: `Command succeeded: ${command}
390
+ ${output.slice(0, 500)}`,
391
+ filesCreated: [],
392
+ filesModified: []
393
+ };
394
+ } catch (e) {
395
+ const stderr = e.stderr ? e.stderr.toString().slice(0, 300) : "";
396
+ return this.error(`Command failed: "${command}"
397
+ ${e.message}
398
+ ${stderr}`);
399
+ }
400
+ }
401
+ // ─── GIT COMMIT → MULTI-QUEUE ─────────────────────────────────
402
+ // FIXED: checks for existing pending commit for this task before
403
+ // queuing a new one — prevents duplicate commits from retry loops.
404
+ gitCommitQueue(params) {
405
+ const message = params.message;
406
+ if (!message) return this.error("gitCommit requires message.");
407
+ const existing = loadPendingCommits(this.cwd);
408
+ const alreadyQueued = existing.some((c) => c.taskId === this.taskId);
409
+ if (alreadyQueued) {
410
+ return {
411
+ success: true,
412
+ output: `Commit already queued for this task \u2014 DirectorBob will review it shortly.`,
413
+ filesCreated: [],
414
+ filesModified: []
415
+ };
416
+ }
417
+ const commitId = `${this.taskId}_${Date.now()}`;
418
+ const pending = {
419
+ id: commitId,
420
+ message,
421
+ agentName: this.agentName,
422
+ taskId: this.taskId,
423
+ missionId: this.missionId,
424
+ filesChanged: this.filesWrittenThisTask,
425
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
426
+ };
427
+ savePendingCommit(pending, this.cwd);
428
+ return {
429
+ success: true,
430
+ output: `Commit queued for DirectorBob review: "${message}". ${this.filesWrittenThisTask.length} file(s) to review.`,
431
+ filesCreated: [],
432
+ filesModified: []
433
+ };
434
+ }
435
+ async gitPush() {
436
+ try {
437
+ const simpleGit3 = (await import("simple-git")).default;
438
+ const git = simpleGit3(this.cwd);
439
+ const branch = (await git.branchLocal()).current;
440
+ try {
441
+ await git.push("origin", branch);
442
+ } catch (pushErr) {
443
+ if (pushErr.message?.includes("no upstream")) {
444
+ await git.push(["--set-upstream", "origin", branch]);
445
+ } else {
446
+ throw pushErr;
447
+ }
448
+ }
449
+ return { success: true, output: `Pushed to ${branch}.`, filesCreated: [], filesModified: [] };
450
+ } catch (e) {
451
+ return this.error(`Git push failed: ${e.message}`);
452
+ }
453
+ }
454
+ analyseFile(params) {
455
+ const filePath = params.path;
456
+ if (!filePath) return this.error("analyseFile requires path.");
457
+ return { success: true, output: `Analysis queued for ${filePath}.`, filesCreated: [], filesModified: [] };
458
+ }
459
+ runBackup() {
460
+ return { success: true, output: "Backup initiated.", filesCreated: [], filesModified: [] };
461
+ }
462
+ resolvePath(filePath) {
463
+ const resolved = path17.resolve(this.cwd, filePath);
464
+ if (!resolved.startsWith(this.cwd)) return null;
465
+ return resolved;
466
+ }
467
+ backupFile(absolutePath, relativePath) {
468
+ try {
469
+ const backupDir = path17.join(this.cwd, ".bob-backups");
470
+ if (!fs13.existsSync(backupDir)) fs13.mkdirSync(backupDir, { recursive: true });
471
+ const backupName = relativePath.replace(/[\/\\]/g, "_") + `.${Date.now()}.bak`;
472
+ const backupPath = path17.join(backupDir, backupName);
473
+ fs13.copyFileSync(absolutePath, backupPath);
474
+ return backupPath;
475
+ } catch {
476
+ return null;
477
+ }
478
+ }
479
+ error(message) {
480
+ return { success: false, output: message, filesCreated: [], filesModified: [], error: message };
481
+ }
482
+ };
483
+ }
484
+ });
30
485
 
31
486
  // bin/bob.ts
32
487
  import { Command } from "commander";
33
- import chalk25 from "chalk";
34
- import * as path13 from "path";
488
+ import chalk31 from "chalk";
489
+ import * as path22 from "path";
35
490
 
36
491
  // src/commands/config.ts
37
492
  import chalk from "chalk";
@@ -538,7 +993,7 @@ function renderFrame(frame, frameHeight, statusText) {
538
993
  }
539
994
  }
540
995
  function sleep(ms) {
541
- return new Promise((resolve3) => setTimeout(resolve3, ms));
996
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
542
997
  }
543
998
 
544
999
  // src/ui/animations/deep-dive.ts
@@ -1021,7 +1476,7 @@ async function typewriterCharByChar(bgColor, textColor, line, maxWidth) {
1021
1476
  process.stdout.write("\n");
1022
1477
  }
1023
1478
  function sleep2(ms) {
1024
- return new Promise((resolve3) => setTimeout(resolve3, ms));
1479
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1025
1480
  }
1026
1481
 
1027
1482
  // src/commands/deepdive.ts
@@ -1123,8 +1578,8 @@ function registerDeepDiveCommand(program2) {
1123
1578
  console.log(MODE_DEEPDIVE2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1124
1579
  console.log("");
1125
1580
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1126
- const answer = await new Promise((resolve3) => {
1127
- rl.question(MODE_DEEPDIVE2(" Select (1-" + dives.length + ") or 0 to cancel: "), resolve3);
1581
+ const answer = await new Promise((resolve4) => {
1582
+ rl.question(MODE_DEEPDIVE2(" Select (1-" + dives.length + ") or 0 to cancel: "), resolve4);
1128
1583
  });
1129
1584
  const selection = parseInt(answer.trim());
1130
1585
  if (isNaN(selection) || selection === 0 || selection < 1 || selection > dives.length) {
@@ -1136,9 +1591,9 @@ function registerDeepDiveCommand(program2) {
1136
1591
  const parentMessageId = selectedDive.parentMessageId;
1137
1592
  const initiatingPrompt = selectedDive.initiatingPrompt || "Deep dive session";
1138
1593
  const animation = startDeepDiveAnimation();
1139
- await new Promise((resolve3) => setTimeout(resolve3, 3e3));
1594
+ await new Promise((resolve4) => setTimeout(resolve4, 3e3));
1140
1595
  animation.stop();
1141
- await new Promise((resolve3) => setTimeout(resolve3, 300));
1596
+ await new Promise((resolve4) => setTimeout(resolve4, 300));
1142
1597
  await runDeepDiveSession(config, conversationId, parentMessageId, initiatingPrompt, rl);
1143
1598
  rl.close();
1144
1599
  } catch (error) {
@@ -1200,8 +1655,8 @@ async function enterDeepDive(config, conversationId, rl) {
1200
1655
  }
1201
1656
  console.log(MODE_DEEPDIVE2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1202
1657
  console.log("");
1203
- const answer = await new Promise((resolve3) => {
1204
- rl.question(MODE_DEEPDIVE2(" Select (1-" + messages.length + ") or 0 to cancel: "), resolve3);
1658
+ const answer = await new Promise((resolve4) => {
1659
+ rl.question(MODE_DEEPDIVE2(" Select (1-" + messages.length + ") or 0 to cancel: "), resolve4);
1205
1660
  });
1206
1661
  const selection = parseInt(answer.trim());
1207
1662
  if (isNaN(selection) || selection === 0 || selection < 1 || selection > messages.length) {
@@ -1214,13 +1669,13 @@ async function enterDeepDive(config, conversationId, rl) {
1214
1669
  const animation = startDeepDiveAnimation();
1215
1670
  const divePromise = callCloudFunction("initiateCLIDeepDive", { conversationId, parentMessageId, initiatingPrompt });
1216
1671
  try {
1217
- await Promise.all([divePromise, new Promise((resolve3) => setTimeout(resolve3, 3e3))]);
1672
+ await Promise.all([divePromise, new Promise((resolve4) => setTimeout(resolve4, 3e3))]);
1218
1673
  animation.stop();
1219
- await new Promise((resolve3) => setTimeout(resolve3, 300));
1674
+ await new Promise((resolve4) => setTimeout(resolve4, 300));
1220
1675
  await runDeepDiveSession(config, conversationId, parentMessageId, initiatingPrompt, rl);
1221
1676
  } catch (error) {
1222
1677
  animation.stop();
1223
- await new Promise((resolve3) => setTimeout(resolve3, 200));
1678
+ await new Promise((resolve4) => setTimeout(resolve4, 200));
1224
1679
  console.log(ERROR2(` \u274C Could not initiate deep dive: ${error.message}`));
1225
1680
  }
1226
1681
  }
@@ -1240,7 +1695,7 @@ async function runDeepDiveSession(config, conversationId, parentMessageId, initi
1240
1695
  console.log("");
1241
1696
  let lastBobResponse = "";
1242
1697
  let lastConstraints3 = [];
1243
- return new Promise((resolve3) => {
1698
+ return new Promise((resolve4) => {
1244
1699
  const deepDivePrompt = () => {
1245
1700
  rl.question(MODE_DEEPDIVE2(" \u{1F93F} You: "), async (input) => {
1246
1701
  const trimmed = input.trim();
@@ -1255,7 +1710,7 @@ async function runDeepDiveSession(config, conversationId, parentMessageId, initi
1255
1710
  console.log(MODE_DEEPDIVE2(" \u2551") + MUTED2(` Back in: ${conversationId.slice(0, 24)}...`));
1256
1711
  console.log(MODE_DEEPDIVE2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1257
1712
  console.log("");
1258
- resolve3();
1713
+ resolve4();
1259
1714
  return;
1260
1715
  }
1261
1716
  if (trimmed === "/promote") {
@@ -1271,7 +1726,7 @@ async function runDeepDiveSession(config, conversationId, parentMessageId, initi
1271
1726
  console.log(ERROR2(` \u274C Promote failed: ${error.message}`));
1272
1727
  console.log("");
1273
1728
  }
1274
- resolve3();
1729
+ resolve4();
1275
1730
  return;
1276
1731
  }
1277
1732
  if (trimmed === "/clear") {
@@ -1302,14 +1757,14 @@ async function runDeepDiveSession(config, conversationId, parentMessageId, initi
1302
1757
  console.log(BORDER(" \u2502"));
1303
1758
  console.log(BORDER(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
1304
1759
  console.log("");
1305
- const answer = await new Promise((resolve4) => {
1306
- rl.question(MODE_DEEPDIVE2(" Leave deep dive for main conversation? (y/N): "), resolve4);
1760
+ const answer = await new Promise((resolve5) => {
1761
+ rl.question(MODE_DEEPDIVE2(" Leave deep dive for main conversation? (y/N): "), resolve5);
1307
1762
  });
1308
1763
  if (answer.trim().toLowerCase() === "y") {
1309
1764
  console.log("");
1310
1765
  console.log(SUCCESS2(" \u2705 Surfacing from deep dive. Personalization Mode will activate in main conversation."));
1311
1766
  console.log("");
1312
- resolve3();
1767
+ resolve4();
1313
1768
  return;
1314
1769
  }
1315
1770
  console.log(MUTED2(" Staying in deep dive."));
@@ -1522,7 +1977,7 @@ async function playWelcomeAnimation() {
1522
1977
  await sleep3(800);
1523
1978
  }
1524
1979
  function sleep3(ms) {
1525
- return new Promise((resolve3) => setTimeout(resolve3, ms));
1980
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1526
1981
  }
1527
1982
 
1528
1983
  // src/core/provider-detect.ts
@@ -1817,7 +2272,7 @@ ${content}
1817
2272
  }
1818
2273
  rl.pause();
1819
2274
  const confirmPromptText = ERROR4(` \u{1F5D1}\uFE0F Delete ${filePath}? (y/n): `);
1820
- const confirm = await new Promise((resolve3) => {
2275
+ const confirm = await new Promise((resolve4) => {
1821
2276
  process.stdout.write(confirmPromptText);
1822
2277
  process.stdin.resume();
1823
2278
  process.stdin.setEncoding("utf-8");
@@ -1828,7 +2283,7 @@ ${content}
1828
2283
  inputBuffer += chunk.slice(0, newlineIdx);
1829
2284
  process.stdin.removeListener("data", onData);
1830
2285
  process.stdin.pause();
1831
- resolve3(inputBuffer.replace(/\r/g, "").trim());
2286
+ resolve4(inputBuffer.replace(/\r/g, "").trim());
1832
2287
  } else {
1833
2288
  inputBuffer += chunk;
1834
2289
  }
@@ -2439,8 +2894,8 @@ function registerByokCommand(program2) {
2439
2894
  return;
2440
2895
  }
2441
2896
  const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
2442
- const answer = await new Promise((resolve3) => {
2443
- rl.question(WARNING9(` Remove ${provider} key? (y/n): `), resolve3);
2897
+ const answer = await new Promise((resolve4) => {
2898
+ rl.question(WARNING9(` Remove ${provider} key? (y/n): `), resolve4);
2444
2899
  });
2445
2900
  rl.close();
2446
2901
  if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
@@ -2502,9 +2957,9 @@ function registerByokCommand(program2) {
2502
2957
  console.log(MUTED10(" Run `bob byok set <provider> <key>` to add one."));
2503
2958
  } else {
2504
2959
  for (const key of keys) {
2505
- const statusIcon = key.isActive ? SUCCESS10("\u25CF") : ERROR8("\u25CB");
2960
+ const statusIcon2 = key.isActive ? SUCCESS10("\u25CF") : ERROR8("\u25CB");
2506
2961
  const statusText = key.isActive ? SUCCESS10("Active") : ERROR8("Inactive");
2507
- console.log(` ${statusIcon} ${INFO10(key.provider.padEnd(12))} ${statusText} (via ${key.source})`);
2962
+ console.log(` ${statusIcon2} ${INFO10(key.provider.padEnd(12))} ${statusText} (via ${key.source})`);
2508
2963
  }
2509
2964
  }
2510
2965
  console.log(MUTED10(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
@@ -2620,8 +3075,8 @@ function registerConversationsCommand(program2) {
2620
3075
  }
2621
3076
  renderConversationList(conversations, config.conversationId, options.search, result, true);
2622
3077
  const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
2623
- const answer = await new Promise((resolve3) => {
2624
- rl.question(INFO11(" Select (1-" + conversations.length + ") or 0 to cancel: "), resolve3);
3078
+ const answer = await new Promise((resolve4) => {
3079
+ rl.question(INFO11(" Select (1-" + conversations.length + ") or 0 to cancel: "), resolve4);
2625
3080
  });
2626
3081
  rl.close();
2627
3082
  const selection = parseInt(answer.trim());
@@ -2859,7 +3314,7 @@ function truncate(text, max) {
2859
3314
  return text.length > max ? text.slice(0, max - 3) + "..." : text;
2860
3315
  }
2861
3316
  function sleep4(ms) {
2862
- return new Promise((resolve3) => setTimeout(resolve3, ms));
3317
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
2863
3318
  }
2864
3319
 
2865
3320
  // src/commands/fork.ts
@@ -2903,7 +3358,7 @@ function registerForkCommand(program2) {
2903
3358
  try {
2904
3359
  const result = await forkPromise;
2905
3360
  animation.stop();
2906
- await new Promise((resolve3) => setTimeout(resolve3, 200));
3361
+ await new Promise((resolve4) => setTimeout(resolve4, 200));
2907
3362
  if (result?.conversationId) {
2908
3363
  setActiveConversationId(result.conversationId, process.cwd());
2909
3364
  setConfigValue("conversationId", result.conversationId);
@@ -2938,7 +3393,7 @@ function registerForkCommand(program2) {
2938
3393
  }
2939
3394
  } catch (error) {
2940
3395
  animation.stop();
2941
- await new Promise((resolve3) => setTimeout(resolve3, 200));
3396
+ await new Promise((resolve4) => setTimeout(resolve4, 200));
2942
3397
  console.log("");
2943
3398
  console.log(ERROR10(` \u274C Fork failed: ${error.message}`));
2944
3399
  console.log("");
@@ -3007,7 +3462,7 @@ function registerAnalyseCommand(program2) {
3007
3462
  program2.command("analyse").description("Analyse the current project for bugs, features, improvements, and upgrades").option("--results", "Show analysis dashboard or filtered list").option("--bugs", "Show bugs list (interactive)").option("--features", "Show features list (interactive)").option("--improvements", "Show improvements list (interactive)").option("--upgrades", "Show upgrades list (interactive)").option("--sort <method>", "Sort by: priority (default) or file").option("--search <query>", "Filter results by keyword").option("--status", "Show current analysis job status").option("--auto", "Auto-fix mode: Bob triages and MiniBob implements").option("--confidence <number>", "Confidence gate for auto-fix (default: 90)", "90").option("--priority <level>", "Priority gate for auto-fix: critical, high, medium, low (default: critical)", "critical").action(async (options) => {
3008
3463
  const config = getConfig();
3009
3464
  if (options.auto) {
3010
- const { runAutoFix } = await import("./analyse-auto-DCC6UDFV.js");
3465
+ const { runAutoFix } = await import("./analyse-auto-I7TIDS4E.js");
3011
3466
  const category = options.bugs ? "bugs" : options.features ? "features" : options.improvements ? "improvements" : options.upgrades ? "upgrades" : void 0;
3012
3467
  await runAutoFix({
3013
3468
  category,
@@ -3017,7 +3472,7 @@ function registerAnalyseCommand(program2) {
3017
3472
  return;
3018
3473
  }
3019
3474
  if (options.bugs || options.features || options.improvements || options.upgrades) {
3020
- const { showInteractiveResults } = await import("./analyse-results-4S577CG5.js");
3475
+ const { showInteractiveResults } = await import("./analyse-results-QSTTMRQE.js");
3021
3476
  const category = options.bugs ? "bugs" : options.features ? "features" : options.improvements ? "improvements" : "upgrades";
3022
3477
  await showInteractiveResults(config, category, options.sort, options.search);
3023
3478
  return;
@@ -3474,8 +3929,8 @@ async function runTier3Autonomy(config, conversationId) {
3474
3929
  console.log(GREEN(" \u2705 All tasks complete!"));
3475
3930
  console.log(AMBER2(" \u{1F4E4} MiniBob wants to push to GitHub."));
3476
3931
  const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
3477
- const answer = await new Promise((resolve3) => {
3478
- rl.question(CYAN(" Approve push? (y/n): "), resolve3);
3932
+ const answer = await new Promise((resolve4) => {
3933
+ rl.question(CYAN(" Approve push? (y/n): "), resolve4);
3479
3934
  });
3480
3935
  rl.close();
3481
3936
  if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
@@ -3505,7 +3960,7 @@ async function runTier3Autonomy(config, conversationId) {
3505
3960
  } catch (pollError) {
3506
3961
  }
3507
3962
  if (running) {
3508
- await new Promise((resolve3) => setTimeout(resolve3, 2500));
3963
+ await new Promise((resolve4) => setTimeout(resolve4, 2500));
3509
3964
  }
3510
3965
  }
3511
3966
  console.log("");
@@ -4111,7 +4566,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
4111
4566
  }
4112
4567
  }
4113
4568
  if (running) {
4114
- await new Promise((resolve3) => setTimeout(resolve3, currentInterval));
4569
+ await new Promise((resolve4) => setTimeout(resolve4, currentInterval));
4115
4570
  }
4116
4571
  }
4117
4572
  }
@@ -4822,7 +5277,7 @@ async function dispatchAndShow(config, type, payload, targetSession) {
4822
5277
  }
4823
5278
  } catch {
4824
5279
  }
4825
- await new Promise((resolve3) => setTimeout(resolve3, 2e3));
5280
+ await new Promise((resolve4) => setTimeout(resolve4, 2e3));
4826
5281
  }
4827
5282
  spinner.stop();
4828
5283
  console.log("");
@@ -4914,8 +5369,8 @@ async function discoverAndConnect(config) {
4914
5369
  console.log(BORDER6(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
4915
5370
  console.log("");
4916
5371
  const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
4917
- const answer = await new Promise((resolve3) => {
4918
- rl.question(INFO14(" Select (1-" + bobs.length + ") or 0 to cancel: "), resolve3);
5372
+ const answer = await new Promise((resolve4) => {
5373
+ rl.question(INFO14(" Select (1-" + bobs.length + ") or 0 to cancel: "), resolve4);
4919
5374
  });
4920
5375
  rl.close();
4921
5376
  const selection = parseInt(answer.trim());
@@ -5340,7 +5795,7 @@ async function runCloudProfiler(options) {
5340
5795
  }
5341
5796
  }
5342
5797
  function sleep5(ms) {
5343
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5798
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
5344
5799
  }
5345
5800
 
5346
5801
  // src/ui/profile-dashboard.ts
@@ -6760,26 +7215,4059 @@ function registerBackupCommand(program2) {
6760
7215
  });
6761
7216
  }
6762
7217
 
7218
+ // src/commands/agent.ts
7219
+ import chalk28 from "chalk";
7220
+ import ora12 from "ora";
7221
+ import * as path15 from "path";
7222
+ import inquirer2 from "inquirer";
7223
+
7224
+ // src/ui/agent-hub.ts
7225
+ import * as readline8 from "readline";
7226
+ import * as path14 from "path";
7227
+ import chalk26 from "chalk";
7228
+
7229
+ // src/core/agent-parser.ts
7230
+ function parseHubInput(input, agents) {
7231
+ const trimmed = input.trim();
7232
+ const agentNames = agents.map((a) => a.name.toLowerCase());
7233
+ if (trimmed.startsWith("/")) {
7234
+ const parts = trimmed.slice(1).split(" ");
7235
+ return {
7236
+ type: "slash",
7237
+ targets: [],
7238
+ message: trimmed,
7239
+ slash: parts[0].toLowerCase(),
7240
+ slashArgs: parts.slice(1)
7241
+ };
7242
+ }
7243
+ if (trimmed.toLowerCase().startsWith("@all")) {
7244
+ const message = trimmed.slice(4).trim().replace(/^["']|["']$/g, "");
7245
+ return {
7246
+ type: "broadcast",
7247
+ targets: agents.map((a) => a.name),
7248
+ message
7249
+ };
7250
+ }
7251
+ const mentionMatch = trimmed.match(/^@(\w+)\s*(.*)/s);
7252
+ if (mentionMatch) {
7253
+ const name = mentionMatch[1].toLowerCase();
7254
+ const message = mentionMatch[2].trim().replace(/^["']|["']$/g, "");
7255
+ if (agentNames.includes(name)) {
7256
+ const actualName = agents.find(
7257
+ (a) => a.name.toLowerCase() === name
7258
+ ).name;
7259
+ return { type: "mention", targets: [actualName], message };
7260
+ }
7261
+ }
7262
+ return { type: "unknown", targets: [], message: trimmed };
7263
+ }
7264
+
7265
+ // src/core/agent-context.ts
7266
+ import * as fs11 from "fs";
7267
+ import * as path13 from "path";
7268
+ import * as os4 from "os";
7269
+ var BOB_DIR4 = path13.join(os4.homedir(), ".bob");
7270
+ function loadAgentProjectConfig(cwd) {
7271
+ const projectName = path13.basename(cwd);
7272
+ const configPath = path13.join(
7273
+ BOB_DIR4,
7274
+ "projects",
7275
+ projectName,
7276
+ "agents",
7277
+ "agent-config.json"
7278
+ );
7279
+ if (!fs11.existsSync(configPath)) return { protectedFiles: [], priorityFiles: [] };
7280
+ try {
7281
+ return JSON.parse(fs11.readFileSync(configPath, "utf-8"));
7282
+ } catch {
7283
+ return { protectedFiles: [], priorityFiles: [] };
7284
+ }
7285
+ }
7286
+ function buildAgentSystemPrompt(agent, projectContext, allAgents, cwd) {
7287
+ const otherAgents = allAgents.filter((a) => a.name !== agent.name).map((a) => `@${a.name}: ${a.task}`).join("\n");
7288
+ const personaPrompt = agent.personaId ? loadPersonaPrompt(agent.personaId) : null;
7289
+ const agentConfig = cwd ? loadAgentProjectConfig(cwd) : null;
7290
+ const protectedBlock = agentConfig?.protectedFiles.length ? `
7291
+ PROTECTED FILES \u2014 never modify these:
7292
+ ${agentConfig.protectedFiles.map((f) => ` - ${f}`).join("\n")}
7293
+ ` : "";
7294
+ const priorityBlock = agentConfig?.priorityFiles.length ? `
7295
+ PRIORITY FILES \u2014 read these first:
7296
+ ${agentConfig.priorityFiles.map((f) => ` - ${f}`).join("\n")}
7297
+ ` : "";
7298
+ return `${STANDARD_STYLE_PROMPT}
7299
+ ${personaPrompt ? `
7300
+ ${personaPrompt}
7301
+ ` : ""}
7302
+ You are @${agent.name} \u2014 an autonomous AI agent on a software engineering team.
7303
+
7304
+ YOUR ASSIGNED TASK:
7305
+ ${agent.task}
7306
+
7307
+ YOUR TEAM:
7308
+ ${otherAgents || "No other agents currently active."}
7309
+
7310
+ AGENT RULES:
7311
+ - Speak in first person as @${agent.name}.
7312
+ - Stay focused on your assigned task.
7313
+ - Reference teammates as @name when relevant.
7314
+ - For review/audit tasks: write your findings as a writeOutput action \u2014 do not keep reading files indefinitely.
7315
+ ${personaPrompt ? `- Your persona shapes your instincts. Embody it naturally.` : ""}
7316
+ ${protectedBlock}${priorityBlock}
7317
+ ${projectContext ? `PROJECT CONTEXT:
7318
+ ${projectContext}` : ""}`;
7319
+ }
7320
+ function buildCrossAgentContext(targetAgentName, allAgents, cwd) {
7321
+ const contextParts = [];
7322
+ for (const agent of allAgents) {
7323
+ if (agent.name === targetAgentName) continue;
7324
+ const summary = loadAgentSummary(agent.name, cwd);
7325
+ const messages = loadAgentMessages(agent.name, cwd);
7326
+ const lastFive = messages.slice(-5);
7327
+ if (!summary && lastFive.length === 0) continue;
7328
+ contextParts.push(`### Context from @${agent.name} ###`);
7329
+ contextParts.push(`Task: ${agent.task}`);
7330
+ if (summary) {
7331
+ contextParts.push(`Current State:
7332
+ ${summary}`);
7333
+ } else if (lastFive.length > 0) {
7334
+ contextParts.push("Recent messages:");
7335
+ for (const msg of lastFive) {
7336
+ contextParts.push(` ${msg.sender}: ${msg.content.slice(0, 200)}`);
7337
+ }
7338
+ }
7339
+ contextParts.push("");
7340
+ }
7341
+ return contextParts.join("\n");
7342
+ }
7343
+ function assembleAgentContext(projectContext, relevantFiles, crossAgentContext) {
7344
+ let fullContext = projectContext;
7345
+ if (relevantFiles) fullContext += `
7346
+
7347
+ ## RELEVANT FILES ##
7348
+ ${relevantFiles}`;
7349
+ if (crossAgentContext) fullContext += `
7350
+
7351
+ ## TEAM CONTEXT ##
7352
+ ${crossAgentContext}`;
7353
+ return fullContext;
7354
+ }
7355
+ function shouldAutoSummarize(messageCount) {
7356
+ return messageCount > 0 && messageCount % 10 === 0;
7357
+ }
7358
+
7359
+ // src/core/agent-summarizer.ts
7360
+ async function autoSummarizeAgent(agentName, agent, allAgents, cwd, localEndpoint) {
7361
+ const messages = loadAgentMessages(agentName, cwd);
7362
+ if (messages.length === 0) return null;
7363
+ const conversationText = messages.slice(-30).map((msg) => {
7364
+ const label = msg.sender === "agent" ? `@${agentName}` : msg.sender === "user" ? "User" : "System";
7365
+ return `[${label}]: ${msg.content}`;
7366
+ }).join("\n\n");
7367
+ const previousSummary = loadAgentSummary(agentName, cwd);
7368
+ const otherAgents = allAgents.filter((a) => a.name !== agentName).map((a) => `@${a.name}: ${a.task}`).join("\n");
7369
+ const prompt = `You are analyzing the work session of @${agentName}, an autonomous AI agent.
7370
+
7371
+ AGENT TASK:
7372
+ ${agent.task}
7373
+
7374
+ TEAM CONTEXT:
7375
+ ${otherAgents || "No other agents."}
7376
+
7377
+ ${previousSummary ? `PREVIOUS SUMMARY:
7378
+ ${previousSummary}
7379
+ ` : ""}
7380
+
7381
+ RECENT CONVERSATION:
7382
+ ${conversationText}
7383
+
7384
+ Generate a concise summary of this agent's current state. Return ONLY a plain text summary with NO markdown formatting. Use exactly this structure:
7385
+
7386
+ STATUS: one sentence on what the agent is currently doing
7387
+ DECISIONS: key decisions or recommendations made (one per line, start each with -)
7388
+ PROGRESS: what has been completed so far (one per line, start each with -)
7389
+ BLOCKERS: anything blocking progress or unresolved questions (one per line, start each with -, or "None" if clear)
7390
+ NEXT: what the agent plans to do next
7391
+
7392
+ Keep each section tight \u2014 1-3 lines maximum per section.`;
7393
+ try {
7394
+ const messages2 = [
7395
+ {
7396
+ role: "system",
7397
+ content: "You are summarizing an AI agent session. Return ONLY plain text. No markdown. No headers with #. No bold. No code fences. Follow the exact structure provided."
7398
+ },
7399
+ { role: "user", content: prompt }
7400
+ ];
7401
+ const rawResponse = await callLocalModel(localEndpoint, messages2);
7402
+ const summary = typeof rawResponse === "object" && rawResponse.text ? rawResponse.text : rawResponse;
7403
+ const cleanSummary = summary.trim();
7404
+ saveAgentSummary(agentName, cleanSummary, cwd);
7405
+ return cleanSummary;
7406
+ } catch (error) {
7407
+ console.error(`[AGENT_SUMMARIZER] Failed to summarize @${agentName}: ${error.message}`);
7408
+ return null;
7409
+ }
7410
+ }
7411
+
7412
+ // src/core/agent-caller.ts
7413
+ async function callAgent(agentName, userMessage, allAgents, cwd, localEndpoint) {
7414
+ const agent = allAgents.find((a) => a.name === agentName);
7415
+ if (!agent) {
7416
+ throw new Error(`Agent "@${agentName}" not found.`);
7417
+ }
7418
+ const projectContext = buildLocalContext(cwd);
7419
+ let relevantFiles = "";
7420
+ try {
7421
+ const retrieval = await getRelevantFileContents(
7422
+ `${agent.task}
7423
+
7424
+ ${userMessage}`,
7425
+ localEndpoint
7426
+ );
7427
+ relevantFiles = retrieval.fileContents;
7428
+ } catch {
7429
+ }
7430
+ const crossAgentContext = buildCrossAgentContext(
7431
+ agentName,
7432
+ allAgents,
7433
+ cwd
7434
+ );
7435
+ const fullContext = assembleAgentContext(
7436
+ projectContext,
7437
+ relevantFiles,
7438
+ crossAgentContext
7439
+ );
7440
+ const agentMessages = loadAgentMessages(agentName, cwd);
7441
+ const history = agentMessages.map((msg) => ({
7442
+ role: msg.sender === "agent" ? "assistant" : "user",
7443
+ content: msg.content
7444
+ }));
7445
+ const systemPrompt = buildAgentSystemPrompt(
7446
+ agent,
7447
+ fullContext,
7448
+ allAgents
7449
+ );
7450
+ const messages = [
7451
+ { role: "system", content: systemPrompt },
7452
+ ...history,
7453
+ { role: "user", content: userMessage }
7454
+ ];
7455
+ const rawResponse = await callLocalModel(localEndpoint, messages);
7456
+ const responseText = typeof rawResponse === "object" && rawResponse.text ? rawResponse.text : rawResponse;
7457
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7458
+ saveAgentMessage(
7459
+ agentName,
7460
+ { sender: "user", content: userMessage, timestamp: now },
7461
+ cwd
7462
+ );
7463
+ saveAgentMessage(
7464
+ agentName,
7465
+ { sender: "agent", content: responseText, timestamp: now },
7466
+ cwd
7467
+ );
7468
+ const newMessageCount = agentMessages.length + 2;
7469
+ const triggerSummarize = shouldAutoSummarize(newMessageCount);
7470
+ let summaryGenerated = false;
7471
+ let summary = null;
7472
+ if (triggerSummarize) {
7473
+ summary = await autoSummarizeAgent(
7474
+ agentName,
7475
+ agent,
7476
+ allAgents,
7477
+ cwd,
7478
+ localEndpoint
7479
+ );
7480
+ summaryGenerated = summary !== null;
7481
+ }
7482
+ return {
7483
+ response: responseText,
7484
+ agentName,
7485
+ messageCount: newMessageCount,
7486
+ shouldSummarize: triggerSummarize,
7487
+ summaryGenerated,
7488
+ summary
7489
+ };
7490
+ }
7491
+
7492
+ // src/ui/agent-renderer.ts
7493
+ import chalk25 from "chalk";
7494
+ var PURPLE = chalk25.hex("#AB47BC");
7495
+ var AMBER7 = chalk25.hex("#FFAB00");
7496
+ var GREEN6 = chalk25.hex("#66BB6A");
7497
+ var RED6 = chalk25.hex("#EF5350");
7498
+ var CYAN6 = chalk25.cyan;
7499
+ var GRAY6 = chalk25.gray;
7500
+ var BLUE3 = chalk25.hex("#42A5F5");
7501
+ var BORDER11 = chalk25.hex("#455A64");
7502
+ var WHITE3 = chalk25.white;
7503
+ var AGENT_COLOR_PAIRS = [
7504
+ {
7505
+ fg: chalk25.hex("#AB47BC"),
7506
+ bg: chalk25.hex("#AB47BC"),
7507
+ chip: chalk25.bgHex("#2D1F33").hex("#CE93D8")
7508
+ },
7509
+ {
7510
+ fg: chalk25.hex("#42A5F5"),
7511
+ bg: chalk25.hex("#42A5F5"),
7512
+ chip: chalk25.bgHex("#1A2A3A").hex("#90CAF9")
7513
+ },
7514
+ {
7515
+ fg: chalk25.hex("#66BB6A"),
7516
+ bg: chalk25.hex("#66BB6A"),
7517
+ chip: chalk25.bgHex("#1A2E1A").hex("#A5D6A7")
7518
+ },
7519
+ {
7520
+ fg: chalk25.hex("#FF7043"),
7521
+ bg: chalk25.hex("#FF7043"),
7522
+ chip: chalk25.bgHex("#2E1F1A").hex("#FFAB91")
7523
+ },
7524
+ {
7525
+ fg: chalk25.hex("#26C6DA"),
7526
+ bg: chalk25.hex("#26C6DA"),
7527
+ chip: chalk25.bgHex("#1A2A2E").hex("#80DEEA")
7528
+ },
7529
+ {
7530
+ fg: chalk25.hex("#EC407A"),
7531
+ bg: chalk25.hex("#EC407A"),
7532
+ chip: chalk25.bgHex("#2E1A22").hex("#F48FB1")
7533
+ },
7534
+ {
7535
+ fg: chalk25.hex("#FFCA28"),
7536
+ bg: chalk25.hex("#FFCA28"),
7537
+ chip: chalk25.bgHex("#2E2A1A").hex("#FFE082")
7538
+ },
7539
+ {
7540
+ fg: chalk25.hex("#78909C"),
7541
+ bg: chalk25.hex("#78909C"),
7542
+ chip: chalk25.bgHex("#1E2A2E").hex("#B0BEC5")
7543
+ }
7544
+ ];
7545
+ function getAgentColorPair(name, allNames) {
7546
+ const idx = allNames.indexOf(name) % AGENT_COLOR_PAIRS.length;
7547
+ const pair = AGENT_COLOR_PAIRS[Math.max(0, idx)];
7548
+ return { fg: pair.fg, chip: pair.chip };
7549
+ }
7550
+ function getAgentColor(name, allNames) {
7551
+ return getAgentColorPair(name, allNames).fg;
7552
+ }
7553
+ function renderAgentChip(name, allNames, active = true) {
7554
+ const { chip } = getAgentColorPair(name, allNames);
7555
+ const label = ` @${name} `;
7556
+ if (active) {
7557
+ return chip(` ${label} `);
7558
+ }
7559
+ return chalk25.bgHex("#1A1A1A").hex("#555555")(` ${label} `);
7560
+ }
7561
+ function stripMarkdown(text) {
7562
+ return text.replace(
7563
+ /```[\w]*\n([\s\S]*?)```/g,
7564
+ (_, code) => code.split("\n").map((line) => ` ${line}`).join("\n")
7565
+ ).replace(/```/g, "").replace(/^#{1,6}\s+(.+)$/gm, "$1").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/^[-*]{3,}$/gm, "\u2500".repeat(50)).replace(/^>\s?/gm, " ").replace(/^\s*(\d+)\.\s+/gm, " $1. ").replace(/^\s*[-*+]\s+/gm, " \u2022 ").replace(/\n{3,}/g, "\n\n").trim();
7566
+ }
7567
+ function wrapText2(text, maxWidth) {
7568
+ const lines = [];
7569
+ const paragraphs = text.split("\n");
7570
+ for (const paragraph of paragraphs) {
7571
+ if (paragraph.trim() === "") {
7572
+ lines.push("");
7573
+ continue;
7574
+ }
7575
+ const words = paragraph.split(" ");
7576
+ let currentLine = "";
7577
+ for (const word of words) {
7578
+ if ((currentLine + " " + word).trim().length > maxWidth) {
7579
+ if (currentLine) lines.push(currentLine.trim());
7580
+ currentLine = word;
7581
+ } else {
7582
+ currentLine = currentLine ? currentLine + " " + word : word;
7583
+ }
7584
+ }
7585
+ if (currentLine.trim()) lines.push(currentLine.trim());
7586
+ }
7587
+ return lines;
7588
+ }
7589
+ function renderAgentResponse(agentName, response, agentColor) {
7590
+ const maxWidth = 66;
7591
+ const cleanResponse = stripMarkdown(response);
7592
+ const lines = wrapText2(cleanResponse, maxWidth - 4);
7593
+ const headerPad = Math.max(0, maxWidth - agentName.length - 7);
7594
+ const header = `\u250C\u2500 @${agentName} ${"\u2500".repeat(headerPad)}\u2510`;
7595
+ const footer = `\u2514${"\u2500".repeat(maxWidth - 2)}\u2518`;
7596
+ console.log("");
7597
+ console.log(agentColor(` ${header}`));
7598
+ for (const line of lines) {
7599
+ const padded = line.padEnd(maxWidth - 4);
7600
+ console.log(agentColor(" \u2502") + ` ${padded}` + agentColor(" \u2502"));
7601
+ }
7602
+ console.log(agentColor(` ${footer}`));
7603
+ }
7604
+ function renderUserMessage2(message) {
7605
+ console.log("");
7606
+ console.log(GRAY6(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
7607
+ console.log(GREEN6(" You: ") + WHITE3(message));
7608
+ console.log(GRAY6(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
7609
+ }
7610
+ function renderHubHeader(projectName, agents) {
7611
+ const agentNames = agents.map((a) => a.name);
7612
+ const chips = agentNames.map((n) => renderAgentChip(n, agentNames, true)).join(" ");
7613
+ console.log("");
7614
+ console.log(BORDER11(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
7615
+ console.log(BORDER11(" \u2551") + PURPLE(" \u{1F310} Agent Hub") + GRAY6(` \u2014 ${projectName}`));
7616
+ console.log(BORDER11(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
7617
+ console.log(BORDER11(" \u2551"));
7618
+ console.log(BORDER11(" \u2551") + ` ${chips}`);
7619
+ console.log(BORDER11(" \u2551"));
7620
+ console.log(BORDER11(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
7621
+ console.log(BORDER11(" \u2551") + WHITE3(" Talk:"));
7622
+ console.log(
7623
+ BORDER11(" \u2551") + CYAN6(' @agentname "message"') + GRAY6(" \u2014 one agent")
7624
+ );
7625
+ console.log(
7626
+ BORDER11(" \u2551") + CYAN6(' @all "message"') + GRAY6(" \u2014 all agents")
7627
+ );
7628
+ console.log(BORDER11(" \u2551") + WHITE3(" Commands:"));
7629
+ console.log(BORDER11(" \u2551") + CYAN6(" /messages") + GRAY6(" \u2014 unified message view"));
7630
+ console.log(BORDER11(" \u2551") + CYAN6(" /status") + GRAY6(" \u2014 all agent statuses"));
7631
+ console.log(BORDER11(" \u2551") + CYAN6(" /summary") + GRAY6(" \u2014 agent summaries"));
7632
+ console.log(BORDER11(" \u2551") + CYAN6(" /exit") + GRAY6(" \u2014 leave hub"));
7633
+ console.log(BORDER11(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
7634
+ console.log("");
7635
+ }
7636
+ function renderHubStatus(agents, allNames, cwd) {
7637
+ console.log("");
7638
+ for (const agent of agents) {
7639
+ const session = loadSession(agent.name, cwd);
7640
+ const { fg } = getAgentColorPair(agent.name, allNames);
7641
+ const chip = renderAgentChip(agent.name, allNames, true);
7642
+ console.log(
7643
+ ` ${chip} ` + GREEN6("ACTIVE") + ` ` + GRAY6(`${session?.messageCount || 0} messages`)
7644
+ );
7645
+ console.log(
7646
+ GRAY6(
7647
+ ` Task: ${agent.task.slice(0, 55)}${agent.task.length > 55 ? "..." : ""}`
7648
+ )
7649
+ );
7650
+ }
7651
+ console.log("");
7652
+ }
7653
+ function renderHubSummary(agents, allNames, cwd) {
7654
+ console.log("");
7655
+ for (const agent of agents) {
7656
+ const summary = loadAgentSummary(agent.name, cwd);
7657
+ const chip = renderAgentChip(agent.name, allNames, true);
7658
+ console.log(` ${chip}`);
7659
+ if (summary) {
7660
+ const lines = summary.split("\n").filter((l) => l.trim()).slice(0, 4);
7661
+ for (const line of lines) {
7662
+ console.log(GRAY6(` ${line.slice(0, 60)}`));
7663
+ }
7664
+ } else {
7665
+ console.log(GRAY6(" No summary yet."));
7666
+ }
7667
+ console.log("");
7668
+ }
7669
+ }
7670
+ function renderUnifiedMessages(messages, allAgentNames, options = {}) {
7671
+ const PAGE_SIZE2 = options.pageSize || 50;
7672
+ const page = options.page || 1;
7673
+ let filtered = [...messages];
7674
+ if (options.filterAgent) {
7675
+ filtered = filtered.filter(
7676
+ (m) => m.agentName?.toLowerCase() === options.filterAgent.toLowerCase()
7677
+ );
7678
+ }
7679
+ if (options.search) {
7680
+ const query = options.search.toLowerCase();
7681
+ filtered = filtered.filter(
7682
+ (m) => m.content.toLowerCase().includes(query)
7683
+ );
7684
+ }
7685
+ const totalMessages = filtered.length;
7686
+ const totalPages = Math.max(1, Math.ceil(totalMessages / PAGE_SIZE2));
7687
+ const currentPage = Math.min(Math.max(1, page), totalPages);
7688
+ const startIdx = (currentPage - 1) * PAGE_SIZE2;
7689
+ const pageMessages = filtered.slice(startIdx, startIdx + PAGE_SIZE2);
7690
+ console.log("");
7691
+ console.log(BORDER11(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
7692
+ console.log(
7693
+ BORDER11(" \u2551") + AMBER7(" \u{1F4CB} Messages") + GRAY6(
7694
+ ` ${totalMessages} total` + (options.filterAgent ? ` filter: @${options.filterAgent}` : "") + (options.search ? ` search: "${options.search}"` : "")
7695
+ )
7696
+ );
7697
+ console.log(
7698
+ BORDER11(" \u2551") + GRAY6(` Page ${currentPage}/${totalPages}`)
7699
+ );
7700
+ console.log(BORDER11(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
7701
+ if (pageMessages.length === 0) {
7702
+ console.log(BORDER11(" \u2551") + GRAY6(" No messages found."));
7703
+ console.log(BORDER11(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
7704
+ console.log("");
7705
+ return { totalPages, currentPage, totalMessages };
7706
+ }
7707
+ for (const msg of pageMessages) {
7708
+ const time = new Date(msg.timestamp).toLocaleTimeString("en-US", {
7709
+ hour: "numeric",
7710
+ minute: "2-digit",
7711
+ hour12: true
7712
+ });
7713
+ if (msg.agentName === null) {
7714
+ console.log(BORDER11(" \u2551"));
7715
+ console.log(
7716
+ BORDER11(" \u2551") + chalk25.bgHex("#1A2E1A").hex("#A5D6A7")(" You ") + GRAY6(` ${time}`)
7717
+ );
7718
+ const lines = wrapText2(msg.content, 54);
7719
+ for (const line of lines) {
7720
+ console.log(BORDER11(" \u2551") + GREEN6(` ${line}`));
7721
+ }
7722
+ } else {
7723
+ const chip = renderAgentChip(msg.agentName, allAgentNames, true);
7724
+ const { fg } = getAgentColorPair(msg.agentName, allAgentNames);
7725
+ const clean = stripMarkdown(msg.content);
7726
+ console.log(BORDER11(" \u2551"));
7727
+ console.log(
7728
+ BORDER11(" \u2551") + ` ${chip}` + GRAY6(` ${time}`)
7729
+ );
7730
+ const lines = wrapText2(clean, 54);
7731
+ for (const line of lines) {
7732
+ console.log(BORDER11(" \u2551") + fg(` ${line}`));
7733
+ }
7734
+ }
7735
+ }
7736
+ console.log(BORDER11(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
7737
+ console.log(
7738
+ BORDER11(" \u2551") + GRAY6(` Page ${currentPage}/${totalPages}`) + (currentPage < totalPages ? CYAN6(" /page " + (currentPage + 1)) + GRAY6(" for next") : "")
7739
+ );
7740
+ console.log(BORDER11(" \u2551") + GRAY6(" /filter <name> /search <keyword> /back"));
7741
+ console.log(BORDER11(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
7742
+ console.log("");
7743
+ return { totalPages, currentPage, totalMessages };
7744
+ }
7745
+
7746
+ // src/ui/agent-hub.ts
7747
+ function buildUnifiedMessages(agentNames, cwd) {
7748
+ const allMessages = [];
7749
+ for (const agentName of agentNames) {
7750
+ const messages = loadAgentMessages(agentName, cwd);
7751
+ for (const msg of messages) {
7752
+ if (msg.sender === "user") {
7753
+ const alreadyAdded = allMessages.some(
7754
+ (m) => m.agentName === null && m.timestamp === msg.timestamp && m.content === msg.content
7755
+ );
7756
+ if (!alreadyAdded) {
7757
+ allMessages.push({
7758
+ agentName: null,
7759
+ content: msg.content,
7760
+ timestamp: msg.timestamp
7761
+ });
7762
+ }
7763
+ } else if (msg.sender === "agent") {
7764
+ allMessages.push({
7765
+ agentName,
7766
+ content: msg.content,
7767
+ timestamp: msg.timestamp
7768
+ });
7769
+ }
7770
+ }
7771
+ }
7772
+ allMessages.sort(
7773
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
7774
+ );
7775
+ return allMessages;
7776
+ }
7777
+ async function runMessagesView(agentNames, cwd, rl, initialFilter, initialSearch) {
7778
+ let currentPage = 1;
7779
+ let currentFilter = initialFilter || void 0;
7780
+ let currentSearch = initialSearch || void 0;
7781
+ const render = () => {
7782
+ const messages = buildUnifiedMessages(agentNames, cwd);
7783
+ renderUnifiedMessages(messages, agentNames, {
7784
+ filterAgent: currentFilter,
7785
+ search: currentSearch,
7786
+ page: currentPage,
7787
+ pageSize: 50
7788
+ });
7789
+ };
7790
+ render();
7791
+ return new Promise((resolve4) => {
7792
+ const messagesPrompt = () => {
7793
+ rl.question(AMBER7(" Messages > "), async (input) => {
7794
+ const trimmed = input.trim();
7795
+ if (!trimmed) {
7796
+ messagesPrompt();
7797
+ return;
7798
+ }
7799
+ if (trimmed === "/back" || trimmed === "/exit") {
7800
+ console.log("");
7801
+ resolve4();
7802
+ return;
7803
+ }
7804
+ const pageMatch = trimmed.match(/^\/page\s+(\d+)$/i);
7805
+ if (pageMatch) {
7806
+ currentPage = parseInt(pageMatch[1]);
7807
+ render();
7808
+ messagesPrompt();
7809
+ return;
7810
+ }
7811
+ const filterMatch = trimmed.match(/^\/filter\s+(\w+)$/i);
7812
+ if (filterMatch) {
7813
+ const name = filterMatch[1].toLowerCase();
7814
+ const found = agentNames.find(
7815
+ (n) => n.toLowerCase() === name || n.toLowerCase() === `${name}bob`
7816
+ );
7817
+ if (found) {
7818
+ currentFilter = found;
7819
+ currentPage = 1;
7820
+ console.log(GRAY6(` Filtering by @${found}`));
7821
+ } else {
7822
+ console.log(RED6(` \u274C Agent "@${name}" not found.`));
7823
+ console.log(GRAY6(` Available: ${agentNames.join(", ")}`));
7824
+ }
7825
+ render();
7826
+ messagesPrompt();
7827
+ return;
7828
+ }
7829
+ if (trimmed === "/filter") {
7830
+ currentFilter = void 0;
7831
+ currentPage = 1;
7832
+ console.log(GRAY6(" Filter cleared."));
7833
+ render();
7834
+ messagesPrompt();
7835
+ return;
7836
+ }
7837
+ const searchMatch = trimmed.match(/^\/search\s+(.+)$/i);
7838
+ if (searchMatch) {
7839
+ currentSearch = searchMatch[1].trim();
7840
+ currentPage = 1;
7841
+ render();
7842
+ messagesPrompt();
7843
+ return;
7844
+ }
7845
+ if (trimmed === "/search") {
7846
+ currentSearch = void 0;
7847
+ currentPage = 1;
7848
+ console.log(GRAY6(" Search cleared."));
7849
+ render();
7850
+ messagesPrompt();
7851
+ return;
7852
+ }
7853
+ console.log("");
7854
+ console.log(GRAY6(" Commands: /page <n> /filter <name> /filter /search <keyword> /search /back"));
7855
+ console.log("");
7856
+ messagesPrompt();
7857
+ });
7858
+ };
7859
+ messagesPrompt();
7860
+ });
7861
+ }
7862
+ async function runAgentHub(cwd) {
7863
+ const config = getConfig();
7864
+ if (!config.localEndpoint) {
7865
+ console.log("");
7866
+ console.log(chalk26.red(" \u274C Agent Hub (Tier 1) requires a local model."));
7867
+ console.log(chalk26.gray(" Run: bob config set localEndpoint http://127.0.0.1:11434/api/chat"));
7868
+ console.log("");
7869
+ return;
7870
+ }
7871
+ const registry = loadRegistry(cwd);
7872
+ if (registry.agents.length === 0) {
7873
+ console.log("");
7874
+ console.log(AMBER7(" \u26A0\uFE0F No agents found."));
7875
+ console.log(GRAY6(' Spawn one first: bob agent spawn <name> "<task>"'));
7876
+ console.log("");
7877
+ return;
7878
+ }
7879
+ const projectName = path14.basename(cwd);
7880
+ const agentNames = registry.agents.map((a) => a.name);
7881
+ renderHubHeader(projectName, registry.agents);
7882
+ const rl = readline8.createInterface({
7883
+ input: process.stdin,
7884
+ output: process.stdout
7885
+ });
7886
+ const prompt = () => {
7887
+ rl.question(PURPLE(" Hub > "), async (input) => {
7888
+ const trimmed = input.trim();
7889
+ if (!trimmed) {
7890
+ prompt();
7891
+ return;
7892
+ }
7893
+ const parsed = parseHubInput(trimmed, registry.agents);
7894
+ if (parsed.type === "slash" && parsed.slash === "exit") {
7895
+ console.log("");
7896
+ console.log(GRAY6(" \u{1F44B} Left hub. Agents are still running."));
7897
+ console.log(GRAY6(" Return anytime: bob agent hub"));
7898
+ console.log("");
7899
+ rl.close();
7900
+ return;
7901
+ }
7902
+ if (parsed.type === "slash" && parsed.slash === "messages") {
7903
+ const args = parsed.slashArgs || [];
7904
+ const filterIdx = args.indexOf("--filter");
7905
+ const filterAgent = filterIdx >= 0 ? args[filterIdx + 1] : void 0;
7906
+ const searchIdx = args.indexOf("--search");
7907
+ const searchQuery = searchIdx >= 0 ? args[searchIdx + 1] : void 0;
7908
+ await runMessagesView(agentNames, cwd, rl, filterAgent, searchQuery);
7909
+ prompt();
7910
+ return;
7911
+ }
7912
+ if (parsed.type === "slash" && parsed.slash === "status") {
7913
+ renderHubStatus(registry.agents, agentNames, cwd);
7914
+ prompt();
7915
+ return;
7916
+ }
7917
+ if (parsed.type === "slash" && parsed.slash === "summary") {
7918
+ renderHubSummary(registry.agents, agentNames, cwd);
7919
+ prompt();
7920
+ return;
7921
+ }
7922
+ if (parsed.type === "slash") {
7923
+ console.log("");
7924
+ console.log(GRAY6(" Commands: /messages /status /summary /exit"));
7925
+ console.log("");
7926
+ prompt();
7927
+ return;
7928
+ }
7929
+ if (parsed.type === "unknown") {
7930
+ console.log("");
7931
+ console.log(AMBER7(" \u26A0\uFE0F Use @name to talk to an agent."));
7932
+ console.log(GRAY6(` Available: ${agentNames.map((n) => `@${n}`).join(", ")}`));
7933
+ console.log(GRAY6(" Or use @all to broadcast to everyone."));
7934
+ console.log("");
7935
+ prompt();
7936
+ return;
7937
+ }
7938
+ if (!parsed.message) {
7939
+ console.log(AMBER7(" \u26A0\uFE0F Message cannot be empty."));
7940
+ prompt();
7941
+ return;
7942
+ }
7943
+ renderUserMessage2(
7944
+ parsed.type === "broadcast" ? `@all ${parsed.message}` : `@${parsed.targets[0]} ${parsed.message}`
7945
+ );
7946
+ for (const targetName of parsed.targets) {
7947
+ const agentColor = getAgentColor(targetName, agentNames);
7948
+ const typingText = ` @${targetName} is thinking...`;
7949
+ process.stdout.write(GRAY6(typingText));
7950
+ try {
7951
+ const result = await callAgent(
7952
+ targetName,
7953
+ parsed.message,
7954
+ registry.agents,
7955
+ cwd,
7956
+ config.localEndpoint
7957
+ );
7958
+ process.stdout.write("\r" + " ".repeat(typingText.length) + "\r");
7959
+ renderAgentResponse(targetName, result.response, agentColor);
7960
+ if (result.summaryGenerated && result.summary) {
7961
+ console.log("");
7962
+ console.log(AMBER7(` \u{1F9EC} @${targetName} session summarized (${result.messageCount} messages)`));
7963
+ console.log(GRAY6(" Summary saved. Other agents will see this context."));
7964
+ console.log(GRAY6(" View with: /summary"));
7965
+ }
7966
+ } catch (error) {
7967
+ process.stdout.write("\r" + " ".repeat(typingText.length) + "\r");
7968
+ console.log("");
7969
+ console.log(RED6(` \u274C @${targetName} error: ${error.message}`));
7970
+ console.log("");
7971
+ }
7972
+ if (parsed.targets.length > 1) {
7973
+ await new Promise((r) => setTimeout(r, 300));
7974
+ }
7975
+ }
7976
+ console.log("");
7977
+ prompt();
7978
+ });
7979
+ };
7980
+ prompt();
7981
+ }
7982
+
7983
+ // src/ui/agent-chat.ts
7984
+ import * as readline9 from "readline";
7985
+ import chalk27 from "chalk";
7986
+ var PAGE_SIZE = 50;
7987
+ function renderChatHeader(agentName, allAgentNames, messageCount, searchQuery) {
7988
+ const chip = renderAgentChip(agentName, allAgentNames, true);
7989
+ console.log("");
7990
+ console.log(BORDER11(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
7991
+ console.log(BORDER11(" \u2551") + ` ${chip} ` + GRAY6(`${messageCount} messages`));
7992
+ console.log(BORDER11(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
7993
+ console.log(BORDER11(" \u2551") + WHITE3(" Commands:"));
7994
+ console.log(BORDER11(" \u2551") + CYAN6(" /history") + GRAY6(" \u2014 show message history"));
7995
+ console.log(BORDER11(" \u2551") + CYAN6(" /search <keyword>") + GRAY6(" \u2014 search history"));
7996
+ console.log(BORDER11(" \u2551") + CYAN6(" /page <n>") + GRAY6(" \u2014 navigate history pages"));
7997
+ console.log(BORDER11(" \u2551") + CYAN6(" /summary") + GRAY6(" \u2014 show this agent's summary"));
7998
+ console.log(BORDER11(" \u2551") + CYAN6(" /clear") + GRAY6(" \u2014 clear screen"));
7999
+ console.log(BORDER11(" \u2551") + CYAN6(" /exit") + GRAY6(" \u2014 leave chat"));
8000
+ console.log(BORDER11(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
8001
+ if (searchQuery) {
8002
+ console.log(GRAY6(` Search active: "${searchQuery}"`));
8003
+ }
8004
+ console.log("");
8005
+ }
8006
+ function renderAgentHistory(agentName, allAgentNames, cwd, page = 1, searchQuery) {
8007
+ const messages = loadAgentMessages(agentName, cwd);
8008
+ const unified = messages.map((msg) => ({
8009
+ agentName: msg.sender === "agent" ? agentName : null,
8010
+ content: msg.content,
8011
+ timestamp: msg.timestamp
8012
+ }));
8013
+ const filtered = searchQuery ? unified.filter(
8014
+ (m) => m.content.toLowerCase().includes(searchQuery.toLowerCase())
8015
+ ) : unified;
8016
+ const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
8017
+ const currentPage = Math.min(Math.max(1, page), totalPages);
8018
+ const startIdx = (currentPage - 1) * PAGE_SIZE;
8019
+ const pageMessages = filtered.slice(startIdx, startIdx + PAGE_SIZE);
8020
+ if (pageMessages.length === 0) {
8021
+ console.log("");
8022
+ if (searchQuery) {
8023
+ console.log(GRAY6(` No messages matching "${searchQuery}".`));
8024
+ } else {
8025
+ console.log(GRAY6(" No messages yet. Start chatting below."));
8026
+ }
8027
+ console.log("");
8028
+ return { totalPages, currentPage };
8029
+ }
8030
+ const { fg } = getAgentColorPair(agentName, allAgentNames);
8031
+ console.log("");
8032
+ console.log(GRAY6(` \u2500\u2500 History \u2014 Page ${currentPage}/${totalPages} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`));
8033
+ console.log("");
8034
+ for (const msg of pageMessages) {
8035
+ const time = new Date(msg.timestamp).toLocaleTimeString("en-US", {
8036
+ hour: "numeric",
8037
+ minute: "2-digit",
8038
+ hour12: true
8039
+ });
8040
+ if (msg.agentName === null) {
8041
+ console.log(
8042
+ chalk27.bgHex("#1A2E1A").hex("#A5D6A7")(" You ") + GRAY6(` ${time}`)
8043
+ );
8044
+ const lines = wrapText2(msg.content, 60);
8045
+ for (const line of lines) {
8046
+ console.log(GREEN6(` ${line}`));
8047
+ }
8048
+ } else {
8049
+ const chip = renderAgentChip(agentName, allAgentNames, true);
8050
+ console.log(` ${chip} ` + GRAY6(time));
8051
+ const clean = stripMarkdown(msg.content);
8052
+ const lines = wrapText2(clean, 60);
8053
+ for (const line of lines) {
8054
+ console.log(fg(` ${line}`));
8055
+ }
8056
+ }
8057
+ console.log("");
8058
+ }
8059
+ console.log(GRAY6(` \u2500\u2500 End of page ${currentPage}/${totalPages} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`));
8060
+ if (currentPage < totalPages) {
8061
+ console.log(GRAY6(` Type /page ${currentPage + 1} to see more`));
8062
+ }
8063
+ console.log("");
8064
+ return { totalPages, currentPage };
8065
+ }
8066
+ async function runAgentChat(agentName, cwd, initialSearch) {
8067
+ const config = getConfig();
8068
+ if (!config.localEndpoint) {
8069
+ console.log("");
8070
+ console.log(chalk27.red(" \u274C Agent Chat requires a local model."));
8071
+ console.log(chalk27.gray(" Run: bob config set localEndpoint http://127.0.0.1:11434/api/chat"));
8072
+ console.log("");
8073
+ return;
8074
+ }
8075
+ const registry = loadRegistry(cwd);
8076
+ const allAgentNames = registry.agents.map((a) => a.name);
8077
+ const agent = registry.agents.find((a) => a.name === agentName);
8078
+ if (!agent) {
8079
+ console.log("");
8080
+ console.log(RED6(` \u274C Agent "@${agentName}" not found.`));
8081
+ console.log("");
8082
+ return;
8083
+ }
8084
+ const session = loadSession(agentName, cwd);
8085
+ const messageCount = session?.messageCount || 0;
8086
+ const agentColor = getAgentColor(agentName, allAgentNames);
8087
+ renderChatHeader(agentName, allAgentNames, messageCount, initialSearch);
8088
+ const messages = loadAgentMessages(agentName, cwd);
8089
+ if (messages.length > 0) {
8090
+ const totalPages = Math.ceil(messages.length / PAGE_SIZE);
8091
+ renderAgentHistory(
8092
+ agentName,
8093
+ allAgentNames,
8094
+ cwd,
8095
+ totalPages,
8096
+ initialSearch
8097
+ );
8098
+ }
8099
+ const rl = readline9.createInterface({
8100
+ input: process.stdin,
8101
+ output: process.stdout
8102
+ });
8103
+ let currentSearchQuery = initialSearch;
8104
+ let currentHistoryPage = Math.ceil(messages.length / PAGE_SIZE) || 1;
8105
+ const agentChip = renderAgentChip(agentName, allAgentNames, true);
8106
+ const prompt = () => {
8107
+ rl.question(
8108
+ ` ${agentChip} ${GRAY6(">")} `,
8109
+ async (input) => {
8110
+ const trimmed = input.trim();
8111
+ if (!trimmed) {
8112
+ prompt();
8113
+ return;
8114
+ }
8115
+ if (trimmed === "/exit" || trimmed === "/quit") {
8116
+ console.log("");
8117
+ console.log(GRAY6(` Left chat with @${agentName}.`));
8118
+ console.log(GRAY6(" Return anytime: bob agent chat " + agentName));
8119
+ console.log("");
8120
+ rl.close();
8121
+ return;
8122
+ }
8123
+ if (trimmed === "/clear") {
8124
+ console.clear();
8125
+ const updatedSession = loadSession(agentName, cwd);
8126
+ renderChatHeader(
8127
+ agentName,
8128
+ allAgentNames,
8129
+ updatedSession?.messageCount || 0,
8130
+ currentSearchQuery
8131
+ );
8132
+ prompt();
8133
+ return;
8134
+ }
8135
+ if (trimmed === "/history") {
8136
+ const result = renderAgentHistory(
8137
+ agentName,
8138
+ allAgentNames,
8139
+ cwd,
8140
+ 1,
8141
+ currentSearchQuery
8142
+ );
8143
+ currentHistoryPage = result.currentPage;
8144
+ prompt();
8145
+ return;
8146
+ }
8147
+ if (trimmed === "/summary") {
8148
+ const { loadAgentSummary: loadAgentSummary2 } = await import("./agent-store-PTYRRKJ5.js");
8149
+ const summary = loadAgentSummary2(agentName, cwd);
8150
+ console.log("");
8151
+ if (summary) {
8152
+ console.log(AMBER7(` \u{1F9EC} @${agentName} Summary`));
8153
+ console.log(GRAY6(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
8154
+ const lines = summary.split("\n").filter((l) => l.trim());
8155
+ for (const line of lines) {
8156
+ console.log(GRAY6(` ${line}`));
8157
+ }
8158
+ } else {
8159
+ console.log(GRAY6(" No summary yet. Summaries generate automatically every 10 messages."));
8160
+ }
8161
+ console.log("");
8162
+ prompt();
8163
+ return;
8164
+ }
8165
+ const pageMatch = trimmed.match(/^\/page\s+(\d+)$/i);
8166
+ if (pageMatch) {
8167
+ currentHistoryPage = parseInt(pageMatch[1]);
8168
+ renderAgentHistory(
8169
+ agentName,
8170
+ allAgentNames,
8171
+ cwd,
8172
+ currentHistoryPage,
8173
+ currentSearchQuery
8174
+ );
8175
+ prompt();
8176
+ return;
8177
+ }
8178
+ const searchMatch = trimmed.match(/^\/search\s+(.+)$/i);
8179
+ if (searchMatch) {
8180
+ currentSearchQuery = searchMatch[1].trim();
8181
+ currentHistoryPage = 1;
8182
+ renderAgentHistory(
8183
+ agentName,
8184
+ allAgentNames,
8185
+ cwd,
8186
+ 1,
8187
+ currentSearchQuery
8188
+ );
8189
+ prompt();
8190
+ return;
8191
+ }
8192
+ if (trimmed === "/search") {
8193
+ currentSearchQuery = void 0;
8194
+ console.log(GRAY6(" Search cleared."));
8195
+ prompt();
8196
+ return;
8197
+ }
8198
+ if (trimmed.startsWith("/")) {
8199
+ console.log("");
8200
+ console.log(GRAY6(" Commands: /history /summary /page <n> /search <keyword> /search /clear /exit"));
8201
+ console.log("");
8202
+ prompt();
8203
+ return;
8204
+ }
8205
+ renderUserMessage2(trimmed);
8206
+ const typingText = ` @${agentName} is thinking...`;
8207
+ process.stdout.write(GRAY6(typingText));
8208
+ try {
8209
+ const result = await callAgent(
8210
+ agentName,
8211
+ trimmed,
8212
+ registry.agents,
8213
+ cwd,
8214
+ config.localEndpoint
8215
+ );
8216
+ process.stdout.write("\r" + " ".repeat(typingText.length) + "\r");
8217
+ renderAgentResponse(agentName, result.response, agentColor);
8218
+ if (result.summaryGenerated && result.summary) {
8219
+ console.log("");
8220
+ console.log(AMBER7(` \u{1F9EC} Session summarized (${result.messageCount} messages)`));
8221
+ const lines = result.summary.split("\n").filter((l) => l.trim()).slice(0, 5);
8222
+ for (const line of lines) {
8223
+ console.log(GRAY6(` ${line.slice(0, 62)}`));
8224
+ }
8225
+ console.log(GRAY6(" Other agents will now see this context."));
8226
+ }
8227
+ } catch (error) {
8228
+ process.stdout.write("\r" + " ".repeat(typingText.length) + "\r");
8229
+ console.log("");
8230
+ console.log(RED6(` \u274C @${agentName} error: ${error.message}`));
8231
+ console.log("");
8232
+ }
8233
+ console.log("");
8234
+ prompt();
8235
+ }
8236
+ );
8237
+ };
8238
+ prompt();
8239
+ }
8240
+
8241
+ // src/commands/agent.ts
8242
+ var PURPLE3 = chalk28.hex("#AB47BC");
8243
+ var AMBER8 = chalk28.hex("#FFAB00");
8244
+ var GREEN8 = chalk28.hex("#66BB6A");
8245
+ var RED7 = chalk28.hex("#EF5350");
8246
+ var CYAN8 = chalk28.cyan;
8247
+ var GRAY7 = chalk28.gray;
8248
+ var BORDER12 = chalk28.hex("#455A64");
8249
+ var WHITE5 = chalk28.white;
8250
+ var TIER_AGENT_LIMITS = {
8251
+ "Explore": 10,
8252
+ "Free": 10,
8253
+ "Starter": 5,
8254
+ "Pro": 15,
8255
+ "Power": 50,
8256
+ "Grid": -1
8257
+ };
8258
+ async function fetchAgentLimit(config) {
8259
+ if (!config.loggedIn || !config.authToken) return 10;
8260
+ if (config.tier !== "platform") return 25;
8261
+ try {
8262
+ const result = await callCloudFunction("getCLIUserTier", {});
8263
+ const tier = result?.tier || "Starter";
8264
+ const limit = TIER_AGENT_LIMITS[tier];
8265
+ return limit !== void 0 ? limit : 5;
8266
+ } catch {
8267
+ return 5;
8268
+ }
8269
+ }
8270
+ function statusIcon(status) {
8271
+ switch (status) {
8272
+ case "active":
8273
+ return GREEN8("\u25CF");
8274
+ case "idle":
8275
+ return AMBER8("\u25CF");
8276
+ case "stopped":
8277
+ return GRAY7("\u25CB");
8278
+ default:
8279
+ return GRAY7("\u25CB");
8280
+ }
8281
+ }
8282
+ function statusLabel(status) {
8283
+ switch (status) {
8284
+ case "active":
8285
+ return GREEN8("ACTIVE");
8286
+ case "idle":
8287
+ return AMBER8("IDLE");
8288
+ case "stopped":
8289
+ return GRAY7("STOPPED");
8290
+ default:
8291
+ return GRAY7(status.toUpperCase());
8292
+ }
8293
+ }
8294
+ function formatTimeAgo(isoDate) {
8295
+ const now = Date.now();
8296
+ const then = new Date(isoDate).getTime();
8297
+ const diffMs = now - then;
8298
+ const diffMins = Math.floor(diffMs / 6e4);
8299
+ const diffHours = Math.floor(diffMs / 36e5);
8300
+ const diffDays = Math.floor(diffMs / 864e5);
8301
+ if (diffMins < 1) return "just now";
8302
+ if (diffMins < 60) return `${diffMins}m ago`;
8303
+ if (diffHours < 24) return `${diffHours}h ago`;
8304
+ return `${diffDays}d ago`;
8305
+ }
8306
+ function registerAgentCommand(program2) {
8307
+ const agentCmd = program2.command("agent").description("Manage your local multi-agent team");
8308
+ agentCmd.command("spawn <name> [task...]").description("Spawn a named agent with a task").option("--persona <id>", "Assign a persona (e.g. local:architectBob)").action(async (name, taskArgs, options) => {
8309
+ const config = getConfig();
8310
+ const cwd = process.cwd();
8311
+ const projectName = path15.basename(cwd);
8312
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
8313
+ console.log("");
8314
+ console.log(RED7(" \u274C Agent name must contain only letters, numbers, hyphens, and underscores."));
8315
+ console.log("");
8316
+ return;
8317
+ }
8318
+ const resolvedName = resolveAgentName(name, cwd);
8319
+ const limit = await fetchAgentLimit(config);
8320
+ const currentCount = getActiveAgentCount(cwd);
8321
+ if (limit !== -1 && currentCount >= limit) {
8322
+ console.log("");
8323
+ console.log(RED7(` \u274C Agent limit reached (${limit} agents).`));
8324
+ if (!config.loggedIn) {
8325
+ console.log(GRAY7(" Run `bob login` to increase your limit to 25."));
8326
+ } else if (config.tier !== "platform") {
8327
+ console.log(GRAY7(" Your local limit is 25. Stop an existing agent:"));
8328
+ console.log(GRAY7(" bob agent stop <name>"));
8329
+ } else {
8330
+ console.log(GRAY7(" Upgrade your Workshop plan for more agents."));
8331
+ console.log(GRAY7(" Or stop an existing agent: bob agent stop <name>"));
8332
+ }
8333
+ console.log("");
8334
+ return;
8335
+ }
8336
+ let task = taskArgs.join(" ").trim();
8337
+ if (!task) {
8338
+ const { inputTask } = await inquirer2.prompt([{
8339
+ type: "input",
8340
+ name: "inputTask",
8341
+ message: PURPLE3(` What is @${resolvedName}'s task?`),
8342
+ validate: (v) => v.trim() ? true : "Task cannot be empty."
8343
+ }]);
8344
+ task = inputTask.trim();
8345
+ }
8346
+ const personaId = options.persona || null;
8347
+ if (personaId) {
8348
+ const { BUILT_IN_PERSONAS } = await import("./persona-loader-3I5Y6CRD.js");
8349
+ if (personaId.startsWith("local:") && !BUILT_IN_PERSONAS[personaId]) {
8350
+ console.log("");
8351
+ console.log(RED7(` \u274C Unknown persona: "${personaId}"`));
8352
+ console.log(GRAY7(" Run `bob agent personas` to see available personas."));
8353
+ console.log("");
8354
+ return;
8355
+ }
8356
+ }
8357
+ const spinner = ora12({
8358
+ text: GRAY7(` Spawning @${resolvedName}...`),
8359
+ spinner: "dots"
8360
+ }).start();
8361
+ try {
8362
+ createAgent(resolvedName, task, personaId, cwd);
8363
+ spinner.stop();
8364
+ const newCount = getActiveAgentCount(cwd);
8365
+ const limitLabel = limit === -1 ? "unlimited" : String(limit);
8366
+ const nameNote = resolvedName.toLowerCase() === `${name.toLowerCase()}bob` ? GRAY7(' (All agents end in "Bob" by convention)') : GRAY7(` (Name adjusted: "${name}" \u2192 "${resolvedName}")`);
8367
+ console.log("");
8368
+ console.log(BORDER12(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
8369
+ console.log(BORDER12(" \u2551") + PURPLE3(` \u{1F916} Agent Spawned: @${resolvedName}`));
8370
+ console.log(BORDER12(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
8371
+ console.log(BORDER12(" \u2551") + GRAY7(` Project: ${projectName}`));
8372
+ console.log(BORDER12(" \u2551") + GRAY7(` Task: ${task.slice(0, 52)}${task.length > 52 ? "..." : ""}`));
8373
+ console.log(BORDER12(" \u2551") + GRAY7(` Persona: ${personaId || "Default Bob"}`));
8374
+ console.log(BORDER12(" \u2551") + GRAY7(` Agents: ${newCount}/${limitLabel} active`));
8375
+ console.log(BORDER12(" \u2551") + nameNote);
8376
+ console.log(BORDER12(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
8377
+ console.log(BORDER12(" \u2551") + GRAY7(" Commands:"));
8378
+ console.log(BORDER12(" \u2551") + CYAN8(` bob agent hub`) + GRAY7(" \u2014 command center"));
8379
+ console.log(BORDER12(" \u2551") + CYAN8(` bob agent chat ${resolvedName}`) + GRAY7(" \u2014 focused chat"));
8380
+ console.log(BORDER12(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
8381
+ console.log("");
8382
+ } catch (error) {
8383
+ spinner.stop();
8384
+ console.log(RED7(` \u274C Failed to spawn agent: ${error.message}`));
8385
+ console.log("");
8386
+ }
8387
+ });
8388
+ agentCmd.command("personas").description("List all available built-in personas").action(async () => {
8389
+ const { listBuiltInPersonas } = await import("./persona-loader-3I5Y6CRD.js");
8390
+ listBuiltInPersonas();
8391
+ });
8392
+ agentCmd.command("list").description("List all agents for the current project").action(async () => {
8393
+ const config = getConfig();
8394
+ const cwd = process.cwd();
8395
+ const projectName = path15.basename(cwd);
8396
+ const registry = loadRegistry(cwd);
8397
+ const limit = await fetchAgentLimit(config);
8398
+ const limitLabel = limit === -1 ? "unlimited" : String(limit);
8399
+ const activeCount = getActiveAgentCount(cwd);
8400
+ console.log("");
8401
+ console.log(PURPLE3(` \u{1F916} Agents \u2014 ${projectName}`));
8402
+ console.log(GRAY7(` ${activeCount}/${limitLabel} active`));
8403
+ console.log(GRAY7(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
8404
+ if (registry.agents.length === 0) {
8405
+ console.log("");
8406
+ console.log(GRAY7(" No agents yet."));
8407
+ console.log(GRAY7(' Run `bob agent spawn <name> "<task>"` to create one.'));
8408
+ console.log("");
8409
+ return;
8410
+ }
8411
+ console.log("");
8412
+ for (const agent of registry.agents) {
8413
+ const icon = statusIcon(agent.status);
8414
+ const label = statusLabel(agent.status);
8415
+ const ago = formatTimeAgo(agent.lastActive);
8416
+ const task = agent.task.slice(0, 46) + (agent.task.length > 46 ? "..." : "");
8417
+ console.log(
8418
+ ` ${icon} ${PURPLE3(`@${agent.name.padEnd(20)}`)} ${label.padEnd(10)} ${GRAY7(ago.padEnd(12))} ${WHITE5(task)}`
8419
+ );
8420
+ if (agent.personaId) {
8421
+ console.log(` ${GRAY7("Persona:")} ${CYAN8(agent.personaId)}`);
8422
+ }
8423
+ }
8424
+ console.log("");
8425
+ console.log(GRAY7(" Commands:"));
8426
+ console.log(GRAY7(" bob agent hub \u2014 Command center"));
8427
+ console.log(GRAY7(" bob agent chat <name> \u2014 Focused chat with history"));
8428
+ console.log(GRAY7(" bob agent personas \u2014 List available personas"));
8429
+ console.log(GRAY7(" bob agent status \u2014 Detailed status view"));
8430
+ console.log(GRAY7(' bob agent spawn <n> "<t>" \u2014 Spawn a new agent'));
8431
+ console.log(GRAY7(" bob agent stop <name> \u2014 Stop without resetting"));
8432
+ console.log(GRAY7(" bob agent reset <name> \u2014 Reset + clear history"));
8433
+ console.log("");
8434
+ });
8435
+ agentCmd.command("status").description("Show detailed status for all agents").action(async () => {
8436
+ const config = getConfig();
8437
+ const cwd = process.cwd();
8438
+ const projectName = path15.basename(cwd);
8439
+ const registry = loadRegistry(cwd);
8440
+ const limit = await fetchAgentLimit(config);
8441
+ const limitLabel = limit === -1 ? "unlimited" : String(limit);
8442
+ const activeCount = getActiveAgentCount(cwd);
8443
+ console.log("");
8444
+ console.log(BORDER12(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
8445
+ console.log(BORDER12(" \u2551") + PURPLE3(" \u{1F916} Agent Status"));
8446
+ console.log(BORDER12(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
8447
+ console.log(BORDER12(" \u2551") + GRAY7(` Project: ${projectName}`));
8448
+ console.log(BORDER12(" \u2551") + GRAY7(` Active: ${activeCount}/${limitLabel}`));
8449
+ console.log(BORDER12(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
8450
+ if (registry.agents.length === 0) {
8451
+ console.log("");
8452
+ console.log(GRAY7(' No agents yet. Run `bob agent spawn <name> "<task>"` to create one.'));
8453
+ console.log("");
8454
+ return;
8455
+ }
8456
+ for (const agent of registry.agents) {
8457
+ const session = loadSession(agent.name, cwd);
8458
+ const summary = loadAgentSummary(agent.name, cwd);
8459
+ const icon = statusIcon(agent.status);
8460
+ const ago = formatTimeAgo(agent.lastActive);
8461
+ console.log("");
8462
+ console.log(BORDER12(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
8463
+ console.log(
8464
+ BORDER12(" \u2502") + ` ${icon} ${PURPLE3(`@${agent.name}`)} ${statusLabel(agent.status)} ` + GRAY7(ago)
8465
+ );
8466
+ console.log(BORDER12(" \u2502"));
8467
+ console.log(BORDER12(" \u2502") + GRAY7(" Task:"));
8468
+ console.log(
8469
+ BORDER12(" \u2502") + WHITE5(` ${agent.task.slice(0, 55)}${agent.task.length > 55 ? "..." : ""}`)
8470
+ );
8471
+ if (agent.personaId) {
8472
+ console.log(BORDER12(" \u2502"));
8473
+ console.log(BORDER12(" \u2502") + GRAY7(" Persona: ") + CYAN8(agent.personaId));
8474
+ }
8475
+ if (session) {
8476
+ console.log(BORDER12(" \u2502"));
8477
+ console.log(
8478
+ BORDER12(" \u2502") + GRAY7(` Messages: ${session.messageCount} \u2502 Created: ${formatTimeAgo(session.createdAt)}`)
8479
+ );
8480
+ }
8481
+ if (summary) {
8482
+ console.log(BORDER12(" \u2502"));
8483
+ console.log(BORDER12(" \u2502") + AMBER8(" Last Summary:"));
8484
+ const summaryLines = summary.split("\n").filter((l) => l.trim()).slice(0, 4);
8485
+ for (const line of summaryLines) {
8486
+ console.log(
8487
+ BORDER12(" \u2502") + GRAY7(` ${line.slice(0, 55)}${line.length > 55 ? "..." : ""}`)
8488
+ );
8489
+ }
8490
+ }
8491
+ console.log(BORDER12(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
8492
+ }
8493
+ console.log("");
8494
+ });
8495
+ agentCmd.command("stop <name>").description("Stop an agent without resetting its history").action((name) => {
8496
+ const cwd = process.cwd();
8497
+ if (!agentExists(name, cwd)) {
8498
+ console.log("");
8499
+ console.log(RED7(` \u274C Agent "@${name}" not found.`));
8500
+ console.log("");
8501
+ return;
8502
+ }
8503
+ stopAgent(name, cwd);
8504
+ console.log("");
8505
+ console.log(GRAY7(` \u23F8\uFE0F @${name} stopped. History preserved.`));
8506
+ console.log(GRAY7(` Resume: bob agent spawn ${name} "<task>"`));
8507
+ console.log("");
8508
+ });
8509
+ agentCmd.command("reset <name>").description("Reset an agent \u2014 permanently clears all history").action(async (name) => {
8510
+ const cwd = process.cwd();
8511
+ if (!agentExists(name, cwd)) {
8512
+ console.log("");
8513
+ console.log(RED7(` \u274C Agent "@${name}" not found.`));
8514
+ console.log("");
8515
+ return;
8516
+ }
8517
+ const session = loadSession(name, cwd);
8518
+ console.log("");
8519
+ console.log(BORDER12(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
8520
+ console.log(BORDER12(" \u2502") + AMBER8(` \u26A0\uFE0F WARNING: Reset @${name}`));
8521
+ console.log(BORDER12(" \u2502"));
8522
+ console.log(BORDER12(" \u2502") + RED7(" This will permanently delete:"));
8523
+ console.log(BORDER12(" \u2502") + GRAY7(` \u2022 ${session?.messageCount || 0} messages of conversation history`));
8524
+ console.log(BORDER12(" \u2502") + GRAY7(" \u2022 All session summaries"));
8525
+ console.log(BORDER12(" \u2502") + GRAY7(" \u2022 All cross-agent context from this agent"));
8526
+ console.log(BORDER12(" \u2502"));
8527
+ console.log(BORDER12(" \u2502") + RED7(` Other agents that referenced @${name} will lose`));
8528
+ console.log(BORDER12(" \u2502") + RED7(" that context. This cannot be undone."));
8529
+ console.log(BORDER12(" \u2502"));
8530
+ console.log(BORDER12(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
8531
+ console.log("");
8532
+ const { confirmed } = await inquirer2.prompt([{
8533
+ type: "input",
8534
+ name: "confirmed",
8535
+ message: AMBER8(` Type "@${name}" to confirm reset:`),
8536
+ validate: (v) => v.trim() === `@${name}` || v.trim() === name ? true : `Type @${name} to confirm.`
8537
+ }]);
8538
+ if (confirmed.trim() !== `@${name}` && confirmed.trim() !== name) {
8539
+ console.log(GRAY7(" Cancelled."));
8540
+ console.log("");
8541
+ return;
8542
+ }
8543
+ resetAgent(name, cwd);
8544
+ console.log("");
8545
+ console.log(GREEN8(` \u2705 @${name} has been reset.`));
8546
+ console.log("");
8547
+ });
8548
+ agentCmd.command("reset-all").description("Reset ALL agents for the current project").action(async () => {
8549
+ const cwd = process.cwd();
8550
+ const registry = loadRegistry(cwd);
8551
+ if (registry.agents.length === 0) {
8552
+ console.log("");
8553
+ console.log(GRAY7(" No agents to reset."));
8554
+ console.log("");
8555
+ return;
8556
+ }
8557
+ const totalMessages = registry.agents.reduce((sum, a) => {
8558
+ const session = loadSession(a.name, cwd);
8559
+ return sum + (session?.messageCount || 0);
8560
+ }, 0);
8561
+ console.log("");
8562
+ console.log(BORDER12(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
8563
+ console.log(BORDER12(" \u2502") + AMBER8(" \u26A0\uFE0F WARNING: Reset ALL Agents"));
8564
+ console.log(BORDER12(" \u2502"));
8565
+ console.log(BORDER12(" \u2502") + RED7(" This will permanently delete:"));
8566
+ console.log(BORDER12(" \u2502") + GRAY7(` \u2022 ${registry.agents.length} agents`));
8567
+ console.log(BORDER12(" \u2502") + GRAY7(` \u2022 ${totalMessages} total messages`));
8568
+ console.log(BORDER12(" \u2502") + GRAY7(" \u2022 All summaries and cross-agent context"));
8569
+ console.log(BORDER12(" \u2502"));
8570
+ console.log(BORDER12(" \u2502") + RED7(" This cannot be undone."));
8571
+ console.log(BORDER12(" \u2502"));
8572
+ console.log(BORDER12(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
8573
+ console.log("");
8574
+ const { confirmed } = await inquirer2.prompt([{
8575
+ type: "confirm",
8576
+ name: "confirmed",
8577
+ message: AMBER8(" Reset ALL agents?"),
8578
+ default: false
8579
+ }]);
8580
+ if (!confirmed) {
8581
+ console.log(GRAY7(" Cancelled."));
8582
+ console.log("");
8583
+ return;
8584
+ }
8585
+ for (const agent of registry.agents) {
8586
+ resetAgent(agent.name, cwd);
8587
+ }
8588
+ console.log("");
8589
+ console.log(GREEN8(` \u2705 All ${registry.agents.length} agents reset.`));
8590
+ console.log("");
8591
+ });
8592
+ agentCmd.command("hub").description("Open the central hub \u2014 command center for all agents").action(async () => {
8593
+ await runAgentHub(process.cwd());
8594
+ });
8595
+ agentCmd.command("chat <name>").description("Focused chat session with a single agent \u2014 paginated history + search").option("--search <keyword>", "Pre-filter history by keyword").action(async (name, options) => {
8596
+ const cwd = process.cwd();
8597
+ if (!agentExists(name, cwd)) {
8598
+ console.log("");
8599
+ console.log(RED7(` \u274C Agent "@${name}" not found.`));
8600
+ const registry = loadRegistry(cwd);
8601
+ if (registry.agents.length > 0) {
8602
+ console.log(GRAY7(` Available: ${registry.agents.map((a) => `@${a.name}`).join(", ")}`));
8603
+ }
8604
+ console.log("");
8605
+ return;
8606
+ }
8607
+ await runAgentChat(name, cwd, options.search);
8608
+ });
8609
+ agentCmd.command("summary").description("Get a summary of all agent progress").action(() => {
8610
+ const cwd = process.cwd();
8611
+ const registry = loadRegistry(cwd);
8612
+ if (registry.agents.length === 0) {
8613
+ console.log("");
8614
+ console.log(GRAY7(" No agents to summarize."));
8615
+ console.log("");
8616
+ return;
8617
+ }
8618
+ console.log("");
8619
+ console.log(BORDER12(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
8620
+ console.log(BORDER12(" \u2551") + AMBER8(" \u{1F4CB} Agent Summary"));
8621
+ console.log(BORDER12(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
8622
+ for (const agent of registry.agents) {
8623
+ const summary = loadAgentSummary(agent.name, cwd);
8624
+ const session = loadSession(agent.name, cwd);
8625
+ const icon = statusIcon(agent.status);
8626
+ const ago = formatTimeAgo(agent.lastActive);
8627
+ console.log(BORDER12(" \u2551"));
8628
+ console.log(
8629
+ BORDER12(" \u2551") + ` ${icon} ${PURPLE3(`@${agent.name}`)} ` + GRAY7(`(${session?.messageCount || 0} messages, ${ago})`)
8630
+ );
8631
+ console.log(
8632
+ BORDER12(" \u2551") + GRAY7(` Task: ${agent.task.slice(0, 50)}${agent.task.length > 50 ? "..." : ""}`)
8633
+ );
8634
+ if (agent.personaId) {
8635
+ console.log(BORDER12(" \u2551") + GRAY7(` Persona: `) + CYAN8(agent.personaId));
8636
+ }
8637
+ if (summary) {
8638
+ const lines = summary.split("\n").filter((l) => l.trim()).slice(0, 3);
8639
+ for (const line of lines) {
8640
+ console.log(
8641
+ BORDER12(" \u2551") + GRAY7(` ${line.slice(0, 55)}${line.length > 55 ? "..." : ""}`)
8642
+ );
8643
+ }
8644
+ } else {
8645
+ console.log(BORDER12(" \u2551") + GRAY7(" No summary yet."));
8646
+ }
8647
+ }
8648
+ console.log(BORDER12(" \u2551"));
8649
+ console.log(BORDER12(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
8650
+ console.log("");
8651
+ });
8652
+ }
8653
+
8654
+ // src/commands/agent-run.ts
8655
+ import chalk30 from "chalk";
8656
+ import ora13 from "ora";
8657
+ import * as readline10 from "readline";
8658
+
8659
+ // src/core/agent-queue.ts
8660
+ import * as fs12 from "fs";
8661
+ import * as path16 from "path";
8662
+ import * as os5 from "os";
8663
+ var BOB_DIR5 = path16.join(os5.homedir(), ".bob");
8664
+ function getQueueDir(workingDir) {
8665
+ const cwd = workingDir || process.cwd();
8666
+ const projectName = path16.basename(cwd);
8667
+ return path16.join(BOB_DIR5, "projects", projectName, "agents");
8668
+ }
8669
+ function getQueuePath(missionId, workingDir) {
8670
+ return path16.join(getQueueDir(workingDir), `mission_${missionId}.json`);
8671
+ }
8672
+ function getActiveMissionPath(workingDir) {
8673
+ return path16.join(getQueueDir(workingDir), "active_mission.json");
8674
+ }
8675
+ function ensureQueueDir(workingDir) {
8676
+ const dir = getQueueDir(workingDir);
8677
+ if (!fs12.existsSync(dir)) fs12.mkdirSync(dir, { recursive: true });
8678
+ }
8679
+ function inferOperationType(instruction, targetFileExists) {
8680
+ if (!targetFileExists) return "CREATE";
8681
+ const lower = instruction.toLowerCase();
8682
+ if (lower.includes("replace") || lower.includes("rewrite") || lower.includes("rebuild") || lower.includes("start fresh") || lower.includes("completely new")) return "REPLACE";
8683
+ if (lower.includes("refactor") || lower.includes("restructure") || lower.includes("reorganize") || lower.includes("clean up") || lower.includes("migrate")) return "REFACTOR";
8684
+ return "PATCH";
8685
+ }
8686
+ function createMission(description, tasks, workingDir) {
8687
+ ensureQueueDir(workingDir);
8688
+ const missionId = `m_${Date.now()}`;
8689
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8690
+ const fullTasks = tasks.map((t, idx) => ({
8691
+ ...t,
8692
+ id: `${missionId}_t${idx + 1}`,
8693
+ missionId,
8694
+ status: "pending",
8695
+ operationType: t.operationType || "CREATE",
8696
+ attemptCount: 0,
8697
+ stagnationCount: 0,
8698
+ directorSurfaceCount: 0,
8699
+ lastSatisfactionScore: null,
8700
+ consecutiveLowCount: 0,
8701
+ result: null,
8702
+ filesCreated: [],
8703
+ filesModified: [],
8704
+ createdAt: now,
8705
+ startedAt: null,
8706
+ completedAt: null,
8707
+ notes: []
8708
+ }));
8709
+ const mission = {
8710
+ id: missionId,
8711
+ description,
8712
+ status: "planning",
8713
+ tasks: fullTasks,
8714
+ createdAt: now,
8715
+ startedAt: null,
8716
+ completedAt: null,
8717
+ totalFilesCreated: 0,
8718
+ totalFilesModified: 0
8719
+ };
8720
+ saveMission(mission, workingDir);
8721
+ setActiveMission(missionId, workingDir);
8722
+ return mission;
8723
+ }
8724
+ function saveMission(mission, workingDir) {
8725
+ ensureQueueDir(workingDir);
8726
+ fs12.writeFileSync(
8727
+ getQueuePath(mission.id, workingDir),
8728
+ JSON.stringify(mission, null, 2)
8729
+ );
8730
+ }
8731
+ function loadMission(missionId, workingDir) {
8732
+ const queuePath = getQueuePath(missionId, workingDir);
8733
+ if (!fs12.existsSync(queuePath)) return null;
8734
+ try {
8735
+ return JSON.parse(fs12.readFileSync(queuePath, "utf-8"));
8736
+ } catch {
8737
+ return null;
8738
+ }
8739
+ }
8740
+ function setActiveMission(missionId, workingDir) {
8741
+ ensureQueueDir(workingDir);
8742
+ fs12.writeFileSync(
8743
+ getActiveMissionPath(workingDir),
8744
+ JSON.stringify({ missionId, setAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
8745
+ );
8746
+ }
8747
+ function getActiveMissionId(workingDir) {
8748
+ const activePath = getActiveMissionPath(workingDir);
8749
+ if (!fs12.existsSync(activePath)) return null;
8750
+ try {
8751
+ const data = JSON.parse(fs12.readFileSync(activePath, "utf-8"));
8752
+ return data.missionId || null;
8753
+ } catch {
8754
+ return null;
8755
+ }
8756
+ }
8757
+ function updateTaskResult(mission, taskId, result, filesCreated = [], filesModified = [], workingDir) {
8758
+ const task = mission.tasks.find((t) => t.id === taskId);
8759
+ if (!task) return mission;
8760
+ task.result = result;
8761
+ task.filesCreated = filesCreated;
8762
+ task.filesModified = filesModified;
8763
+ task.status = "completed";
8764
+ task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
8765
+ mission.totalFilesCreated += filesCreated.length;
8766
+ mission.totalFilesModified += filesModified.length;
8767
+ saveMission(mission, workingDir);
8768
+ return mission;
8769
+ }
8770
+ function addTaskNote(mission, taskId, note, workingDir) {
8771
+ const task = mission.tasks.find((t) => t.id === taskId);
8772
+ if (!task) return mission;
8773
+ task.notes.push(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] ${note}`);
8774
+ saveMission(mission, workingDir);
8775
+ return mission;
8776
+ }
8777
+ function getReadyTasks(mission) {
8778
+ const completedIds = new Set(
8779
+ mission.tasks.filter((t) => t.status === "completed").map((t) => t.id)
8780
+ );
8781
+ return mission.tasks.filter((task) => {
8782
+ if (task.status !== "pending") return false;
8783
+ return task.dependsOn.every((depId) => completedIds.has(depId));
8784
+ });
8785
+ }
8786
+ function getRunningTasks(mission) {
8787
+ return mission.tasks.filter((t) => t.status === "running");
8788
+ }
8789
+ function isMissionComplete(mission) {
8790
+ return mission.tasks.every(
8791
+ (t) => t.status === "completed" || t.status === "skipped" || t.status === "failed" || t.status === "stagnated"
8792
+ );
8793
+ }
8794
+ function isMissionBlocked(mission) {
8795
+ const running = getRunningTasks(mission);
8796
+ const ready = getReadyTasks(mission);
8797
+ return running.length === 0 && ready.length === 0 && !isMissionComplete(mission);
8798
+ }
8799
+ function getMissionSummary(mission) {
8800
+ const total = mission.tasks.length;
8801
+ const completed = mission.tasks.filter((t) => t.status === "completed").length;
8802
+ const running = mission.tasks.filter((t) => t.status === "running").length;
8803
+ const pending = mission.tasks.filter((t) => t.status === "pending").length;
8804
+ const failed = mission.tasks.filter((t) => t.status === "failed").length;
8805
+ const stagnated = mission.tasks.filter((t) => t.status === "stagnated").length;
8806
+ const skipped = mission.tasks.filter((t) => t.status === "skipped").length;
8807
+ const percentComplete = total > 0 ? Math.round(completed / total * 100) : 0;
8808
+ return {
8809
+ total,
8810
+ completed,
8811
+ running,
8812
+ pending,
8813
+ failed,
8814
+ stagnated,
8815
+ skipped,
8816
+ percentComplete
8817
+ };
8818
+ }
8819
+
8820
+ // src/core/director-bob.ts
8821
+ import * as fs16 from "fs";
8822
+ import * as path20 from "path";
8823
+
8824
+ // src/core/agent-satisfaction.ts
8825
+ async function evaluateSatisfaction(task, agentOutput, localEndpoint) {
8826
+ const prompt = `You are @${task.assignedTo} evaluating your own work output against the assigned task.
8827
+
8828
+ ASSIGNED TASK:
8829
+ ${task.instruction}
8830
+
8831
+ YOUR OUTPUT:
8832
+ ${agentOutput}
8833
+
8834
+ Evaluate how satisfied you are with this output relative to completing the task.
8835
+ Satisfaction means the task is DONE \u2014 not that the work is high quality in general.
8836
+
8837
+ Score from 0 to 100 where:
8838
+ - 90-100: Task is fully complete. Nothing left to do.
8839
+ - 75-89: Task is mostly complete. Minor gaps remain.
8840
+ - 50-74: Task is partially complete. Significant work remains.
8841
+ - 25-49: Task has barely started. Most work still needed.
8842
+ - 0-24: Task has not been meaningfully addressed.
8843
+
8844
+ Respond with ONLY this JSON format on a single line:
8845
+ {"score": <0-100>, "reasoning": "<one sentence explaining the score>"}`;
8846
+ try {
8847
+ const messages = [
8848
+ {
8849
+ role: "system",
8850
+ content: "You are evaluating task completion. Respond with ONLY valid JSON on a single line. No markdown."
8851
+ },
8852
+ { role: "user", content: prompt }
8853
+ ];
8854
+ const rawResponse = await callLocalModel(localEndpoint, messages);
8855
+ const responseText = typeof rawResponse === "object" && rawResponse.text ? rawResponse.text : rawResponse;
8856
+ const jsonMatch = responseText.match(/\{[^}]*"score"[^}]*\}/);
8857
+ if (!jsonMatch) {
8858
+ return buildResult(task, 30, "Could not evaluate output \u2014 defaulting to low satisfaction.");
8859
+ }
8860
+ const parsed = JSON.parse(jsonMatch[0]);
8861
+ const score = Math.min(100, Math.max(0, parseInt(parsed.score) || 0));
8862
+ const reasoning = parsed.reasoning || "No reasoning provided.";
8863
+ return buildResult(task, score, reasoning);
8864
+ } catch {
8865
+ return buildResult(task, 30, "Evaluation failed \u2014 defaulting to low satisfaction.");
8866
+ }
8867
+ }
8868
+ function buildResult(task, score, reasoning) {
8869
+ const isDone = score >= task.satisfactionTarget;
8870
+ const newConsecutiveLow = isDone ? 0 : task.consecutiveLowCount + 1;
8871
+ const isStagnating = !isDone && newConsecutiveLow >= task.stagnationLimit;
8872
+ const newDirectorCount = isStagnating ? task.directorSurfaceCount + 1 : task.directorSurfaceCount;
8873
+ const alreadyHadDirectorHelp = task.directorSurfaceCount > 0;
8874
+ const needsDirector = isStagnating && !alreadyHadDirectorHelp && newDirectorCount <= task.directorLimit;
8875
+ const needsUser = isStagnating && (alreadyHadDirectorHelp || newDirectorCount > task.directorLimit);
8876
+ return {
8877
+ score,
8878
+ reasoning,
8879
+ isDone,
8880
+ isStagnating,
8881
+ needsDirector,
8882
+ needsUser
8883
+ };
8884
+ }
8885
+ function applySatisfactionResult(mission, taskId, result, workingDir) {
8886
+ const task = mission.tasks.find((t) => t.id === taskId);
8887
+ if (!task) return mission;
8888
+ task.lastSatisfactionScore = result.score;
8889
+ task.attemptCount += 1;
8890
+ if (result.isDone) {
8891
+ task.consecutiveLowCount = 0;
8892
+ } else {
8893
+ task.consecutiveLowCount += 1;
8894
+ }
8895
+ if (result.isStagnating) {
8896
+ task.stagnationCount += 1;
8897
+ task.directorSurfaceCount += 1;
8898
+ task.consecutiveLowCount = 0;
8899
+ }
8900
+ task.notes.push(
8901
+ `[Attempt ${task.attemptCount}] SAT: ${result.score}% \u2014 ${result.reasoning}`
8902
+ );
8903
+ saveMission(mission, workingDir);
8904
+ return mission;
8905
+ }
8906
+ function inferSatisfactionTarget(instruction) {
8907
+ const lower = instruction.toLowerCase();
8908
+ if (lower.includes("review") || lower.includes("approve") || lower.includes("verify") || lower.includes("validate")) return 75;
8909
+ if (lower.includes("design") || lower.includes("architect") || lower.includes("interface") || lower.includes("contract") || lower.includes("plan")) return 82;
8910
+ if (lower.includes("implement") || lower.includes("build") || lower.includes("create") || lower.includes("write") || lower.includes("develop")) return 75;
8911
+ if (lower.includes("test") || lower.includes("analyse") || lower.includes("analyze") || lower.includes("check")) return 78;
8912
+ return 75;
8913
+ }
8914
+ function inferStagnationLimit(instruction) {
8915
+ const lower = instruction.toLowerCase();
8916
+ if (lower.includes("design") || lower.includes("architect") || lower.includes("complex") || lower.includes("system")) return 4;
8917
+ return 3;
8918
+ }
8919
+ function getSatisfactionColor(score, target) {
8920
+ if (score >= target) return "#66BB6A";
8921
+ const pct = score / target;
8922
+ if (pct >= 0.8) return "#FFAB00";
8923
+ if (pct >= 0.5) return "#FF7043";
8924
+ return "#EF5350";
8925
+ }
8926
+ function getSatisfactionBar(score, target, width = 20) {
8927
+ const filled = Math.round(score / 100 * width);
8928
+ const empty = width - filled;
8929
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty);
8930
+ }
8931
+
8932
+ // src/core/agent-executor.ts
8933
+ init_agent_tools();
8934
+ import * as path18 from "path";
8935
+ import * as fs14 from "fs";
8936
+ var STRUCTURED_RESPONSE_SPEC = `
8937
+ RESPONSE FORMAT \u2014 you MUST respond with ONLY valid JSON matching this schema:
8938
+ {
8939
+ "thinking": "<your internal reasoning about how to approach this task \u2014 1-3 sentences>",
8940
+ "files": [
8941
+ {
8942
+ "path": "<relative file path e.g. lib/utils/string_utils.dart>",
8943
+ "operation": "CREATE" | "PATCH" | "REFACTOR" | "REPLACE",
8944
+ "content": "<the COMPLETE file content as a string>"
8945
+ }
8946
+ ],
8947
+ "toolCalls": [
8948
+ { "tool": "<toolName>", "params": { <params> } }
8949
+ ],
8950
+ "message": "<brief summary of what you did \u2014 1-2 sentences>"
8951
+ }
8952
+
8953
+ RULES:
8954
+ - Respond with ONLY the JSON object. No markdown. No explanation before or after.
8955
+ - "files" array can contain 0 or more file entries.
8956
+ - "content" must be the COMPLETE file content \u2014 not a diff, not a snippet.
8957
+ - "toolCalls" is an array \u2014 can contain multiple tool calls executed in sequence.
8958
+ - To write files AND commit in one response: put the file in "files" and gitCommit in "toolCalls".
8959
+ - Leave "toolCalls" as empty array [] if no tools needed.
8960
+ - If a package import is missing, use runCommand in toolCalls to install it first, then write the file on your next attempt.
8961
+ - If you need to readFile before making changes, add readFile to toolCalls and leave files empty.
8962
+ - "thinking" is for your reasoning \u2014 it will NOT be written to any file.
8963
+ - "message" is shown to the user \u2014 keep it concise.
8964
+ `;
8965
+ function readPubspec(cwd) {
8966
+ const pubspecPath = path18.join(cwd, "pubspec.yaml");
8967
+ if (!fs14.existsSync(pubspecPath)) return "";
8968
+ try {
8969
+ const content = fs14.readFileSync(pubspecPath, "utf-8");
8970
+ const lines = content.split("\n");
8971
+ const relevant = [];
8972
+ let inDeps = false;
8973
+ for (const line of lines) {
8974
+ if (line.startsWith("name:") || line.startsWith("description:")) {
8975
+ relevant.push(line);
8976
+ }
8977
+ if (line.startsWith("dependencies:") || line.startsWith("dev_dependencies:")) {
8978
+ inDeps = true;
8979
+ relevant.push(line);
8980
+ continue;
8981
+ }
8982
+ if (inDeps && (line.startsWith(" ") || line.trim() === "")) {
8983
+ relevant.push(line);
8984
+ continue;
8985
+ }
8986
+ if (inDeps && !line.startsWith(" ")) {
8987
+ inDeps = false;
8988
+ }
8989
+ }
8990
+ return relevant.join("\n").trim();
8991
+ } catch {
8992
+ return "";
8993
+ }
8994
+ }
8995
+ function readPackageJson(cwd) {
8996
+ const pkgPath = path18.join(cwd, "package.json");
8997
+ if (!fs14.existsSync(pkgPath)) return "";
8998
+ try {
8999
+ const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf-8"));
9000
+ const lines = [];
9001
+ if (pkg.name) lines.push(`name: ${pkg.name}`);
9002
+ if (pkg.dependencies) {
9003
+ lines.push("dependencies:");
9004
+ for (const dep of Object.keys(pkg.dependencies).slice(0, 20)) {
9005
+ lines.push(` ${dep}: ${pkg.dependencies[dep]}`);
9006
+ }
9007
+ }
9008
+ if (pkg.devDependencies) {
9009
+ lines.push("devDependencies:");
9010
+ for (const dep of Object.keys(pkg.devDependencies).slice(0, 10)) {
9011
+ lines.push(` ${dep}: ${pkg.devDependencies[dep]}`);
9012
+ }
9013
+ }
9014
+ return lines.join("\n");
9015
+ } catch {
9016
+ return "";
9017
+ }
9018
+ }
9019
+ function readProjectDependencies(cwd) {
9020
+ const pubspec = readPubspec(cwd);
9021
+ if (pubspec) return `PUBSPEC.YAML:
9022
+ ${pubspec}`;
9023
+ const pkg = readPackageJson(cwd);
9024
+ if (pkg) return `PACKAGE.JSON:
9025
+ ${pkg}`;
9026
+ const requirements = path18.join(cwd, "requirements.txt");
9027
+ if (fs14.existsSync(requirements)) {
9028
+ try {
9029
+ const content = fs14.readFileSync(requirements, "utf-8");
9030
+ return `REQUIREMENTS.TXT:
9031
+ ${content.slice(0, 500)}`;
9032
+ } catch {
9033
+ }
9034
+ }
9035
+ return "";
9036
+ }
9037
+ function parseStructuredResponse(rawResponse) {
9038
+ let jsonStr = rawResponse.trim();
9039
+ const fencedMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
9040
+ if (fencedMatch) {
9041
+ jsonStr = fencedMatch[1].trim();
9042
+ }
9043
+ const firstBrace = jsonStr.indexOf("{");
9044
+ if (firstBrace === -1) return null;
9045
+ let depth = 0;
9046
+ let lastBrace = -1;
9047
+ for (let i = firstBrace; i < jsonStr.length; i++) {
9048
+ if (jsonStr[i] === "{") depth++;
9049
+ if (jsonStr[i] === "}") {
9050
+ depth--;
9051
+ if (depth === 0) {
9052
+ lastBrace = i;
9053
+ break;
9054
+ }
9055
+ }
9056
+ }
9057
+ if (lastBrace === -1) return null;
9058
+ const candidate = jsonStr.slice(firstBrace, lastBrace + 1);
9059
+ try {
9060
+ const parsed = JSON.parse(candidate);
9061
+ if (typeof parsed !== "object" || parsed === null) return null;
9062
+ const response = {
9063
+ thinking: typeof parsed.thinking === "string" ? parsed.thinking : "",
9064
+ files: [],
9065
+ toolCalls: [],
9066
+ message: typeof parsed.message === "string" ? parsed.message : ""
9067
+ };
9068
+ if (Array.isArray(parsed.files)) {
9069
+ for (const file of parsed.files) {
9070
+ if (typeof file === "object" && file !== null && typeof file.path === "string" && file.path.trim() && typeof file.content === "string") {
9071
+ const op = ["CREATE", "PATCH", "REFACTOR", "REPLACE"].includes(file.operation) ? file.operation : "CREATE";
9072
+ response.files.push({
9073
+ path: file.path.trim(),
9074
+ operation: op,
9075
+ content: file.content
9076
+ });
9077
+ }
9078
+ }
9079
+ }
9080
+ if (Array.isArray(parsed.toolCalls)) {
9081
+ for (const tc of parsed.toolCalls) {
9082
+ if (typeof tc === "object" && tc !== null && typeof tc.tool === "string") {
9083
+ response.toolCalls.push({ tool: tc.tool, params: tc.params || {} });
9084
+ }
9085
+ }
9086
+ }
9087
+ if (response.toolCalls.length === 0 && parsed.toolCall && typeof parsed.toolCall === "object" && typeof parsed.toolCall.tool === "string") {
9088
+ response.toolCalls.push({
9089
+ tool: parsed.toolCall.tool,
9090
+ params: parsed.toolCall.params || {}
9091
+ });
9092
+ }
9093
+ return response;
9094
+ } catch {
9095
+ return null;
9096
+ }
9097
+ }
9098
+ function findMostRecentBackup(filePath, cwd) {
9099
+ const backupDir = path18.join(cwd, ".bob-backups");
9100
+ if (!fs14.existsSync(backupDir)) return null;
9101
+ const safeName = filePath.replace(/[\/\\]/g, "_");
9102
+ try {
9103
+ const backups = fs14.readdirSync(backupDir).filter((f) => f.startsWith(safeName) && f.endsWith(".bak")).sort().reverse();
9104
+ if (backups.length === 0) return null;
9105
+ return path18.join(backupDir, backups[0]);
9106
+ } catch {
9107
+ return null;
9108
+ }
9109
+ }
9110
+ function normalizeFileBlocks(response) {
9111
+ if (response.match(/```[\w]*\n\/\/ File:/)) return response;
9112
+ if (response.includes("// File:")) {
9113
+ const parts = response.split(/(\/\/ File: [^\n]+\n)/);
9114
+ let result = "";
9115
+ let i = 0;
9116
+ while (i < parts.length) {
9117
+ const part = parts[i];
9118
+ if (part.match(/^\/\/ File: [^\n]+\n$/)) {
9119
+ const fileHeader = part;
9120
+ const fileContent = parts[i + 1] || "";
9121
+ i += 2;
9122
+ result += "```typescript\n" + fileHeader + fileContent.trimEnd() + "\n```\n";
9123
+ } else {
9124
+ result += part;
9125
+ i++;
9126
+ }
9127
+ }
9128
+ return result;
9129
+ }
9130
+ return response;
9131
+ }
9132
+ function buildOperationContext(task, cwd) {
9133
+ const op = task.operationType;
9134
+ const filePathMatch = task.instruction.match(
9135
+ /(?:in|to|for|update|modify|patch|add to|refactor)\s+([\w\-\.\/\\]+\.\w+)/i
9136
+ ) || task.instruction.match(/(src\/[\w\-\.\/\\]+\.\w+|lib\/[\w\-\.\/\\]+\.\w+)/);
9137
+ const mentionedFile = filePathMatch ? filePathMatch[1] : null;
9138
+ const absolutePath = mentionedFile ? path18.join(cwd, mentionedFile) : null;
9139
+ const fileExists = absolutePath ? fs14.existsSync(absolutePath) : false;
9140
+ if (op === "CREATE" || !fileExists || !mentionedFile) {
9141
+ return `OPERATION: CREATE \u2014 write a new file with the complete implementation.`;
9142
+ }
9143
+ let currentContent = "";
9144
+ try {
9145
+ currentContent = fs14.readFileSync(absolutePath, "utf-8");
9146
+ } catch {
9147
+ return `OPERATION: ${op} \u2014 could not read existing file. Use readFile tool first.`;
9148
+ }
9149
+ const lineCount = currentContent.split("\n").length;
9150
+ if (op === "PATCH") {
9151
+ return `OPERATION: PATCH \u2014 make a TARGETED change to a specific section only.
9152
+
9153
+ CURRENT FILE: ${mentionedFile} (${lineCount} lines)
9154
+ \`\`\`
9155
+ ${currentContent}
9156
+ \`\`\`
9157
+
9158
+ YOUR JOB: Read the file above carefully. Identify exactly where the task requires a change. Apply ONLY that change. Output the COMPLETE file with your change applied.
9159
+
9160
+ PATCH RULES (MANDATORY):
9161
+ 1. In your "files" array, output the COMPLETE file with ONLY your targeted change applied \u2014 every single line, not just the changed section.
9162
+ 2. The file you output MUST be different from the current file above \u2014 if it is identical, you have failed the task.
9163
+ 3. PRESERVE every import \u2014 do not add, remove, or reorder imports unless the task explicitly requires it.
9164
+ 4. PRESERVE every export \u2014 every function/class that is currently exported must remain exported.
9165
+ 5. PRESERVE all existing logic \u2014 only touch the specific section the task describes.
9166
+ 6. DO NOT output a summary or description \u2014 output the actual modified file content in the "files" array.
9167
+ 7. The output file must be at least ${Math.floor(lineCount * 0.85)} lines (85% of original ${lineCount} lines).
9168
+
9169
+ EXAMPLE: If the task says "add a _foo() method", find the class body in the file above, add _foo() inside it, and output the COMPLETE file with that addition. Every other line stays exactly the same.`;
9170
+ }
9171
+ if (op === "REFACTOR") {
9172
+ return `OPERATION: REFACTOR \u2014 restructure the file while preserving its contract.
9173
+
9174
+ CURRENT FILE: ${mentionedFile} (${lineCount} lines)
9175
+ \`\`\`
9176
+ ${currentContent}
9177
+ \`\`\`
9178
+
9179
+ REFACTOR RULES (MANDATORY):
9180
+ 1. In your "files" array, output the COMPLETE refactored file.
9181
+ 2. PRESERVE every export \u2014 the public API of this file must not change.
9182
+ 3. PRESERVE all import references that other files depend on.
9183
+ 4. You may reorganize internals but must not remove functionality.
9184
+ 5. The output file must be at least ${Math.floor(lineCount * 0.7)} lines (70% of original ${lineCount} lines).`;
9185
+ }
9186
+ if (op === "REPLACE") {
9187
+ return `OPERATION: REPLACE \u2014 full rewrite of this file is explicitly authorized.
9188
+
9189
+ CURRENT FILE: ${mentionedFile} (${lineCount} lines)
9190
+ \`\`\`
9191
+ ${currentContent.slice(0, 2e3)}${currentContent.length > 2e3 ? "\n... (truncated for context)" : ""}
9192
+ \`\`\`
9193
+
9194
+ Write the complete replacement file in your "files" array. Preserve the same exports so dependents don't break.`;
9195
+ }
9196
+ return `OPERATION: ${op}`;
9197
+ }
9198
+ function validateCreateTaskFiles(task, filesCreated, filesModified, cwd) {
9199
+ if (task.operationType !== "CREATE") return { valid: true, missingFiles: [] };
9200
+ const filePathMatches = task.instruction.match(
9201
+ /(?:lib|src|test)\/[\w\-\.\/\\]+\.\w+/g
9202
+ ) || [];
9203
+ if (filePathMatches.length === 0) return { valid: true, missingFiles: [] };
9204
+ const missingFiles = [];
9205
+ const allFilesWritten = [...filesCreated, ...filesModified];
9206
+ for (const expectedPath of filePathMatches) {
9207
+ const absolutePath = path18.join(cwd, expectedPath);
9208
+ const normalizedExpected = expectedPath.replace(/\\/g, "/");
9209
+ const wasWritten = allFilesWritten.some((f) => {
9210
+ const normalizedF = f.replace(/\\/g, "/");
9211
+ return normalizedF.toLowerCase() === normalizedExpected.toLowerCase() || path18.join(cwd, f).replace(/\\/g, "/").toLowerCase() === absolutePath.replace(/\\/g, "/").toLowerCase();
9212
+ });
9213
+ if (!wasWritten && !fs14.existsSync(absolutePath)) {
9214
+ missingFiles.push(expectedPath);
9215
+ }
9216
+ }
9217
+ return {
9218
+ valid: missingFiles.length === 0,
9219
+ missingFiles
9220
+ };
9221
+ }
9222
+ function writeFileToDisk(filePath, content, cwd) {
9223
+ const absolutePath = path18.join(cwd, filePath);
9224
+ const isNew = !fs14.existsSync(absolutePath);
9225
+ let backupPath = null;
9226
+ try {
9227
+ const dir = path18.dirname(absolutePath);
9228
+ if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
9229
+ if (!isNew) {
9230
+ const backupDir = path18.join(cwd, ".bob-backups");
9231
+ if (!fs14.existsSync(backupDir)) fs14.mkdirSync(backupDir, { recursive: true });
9232
+ const safeName = filePath.replace(/[\/\\]/g, "_");
9233
+ const backupName = `${safeName}.${Date.now()}.bak`;
9234
+ backupPath = path18.join(backupDir, backupName);
9235
+ fs14.copyFileSync(absolutePath, backupPath);
9236
+ }
9237
+ fs14.writeFileSync(absolutePath, content, "utf-8");
9238
+ return { success: true, isNew, backupPath };
9239
+ } catch {
9240
+ return { success: false, isNew, backupPath: null };
9241
+ }
9242
+ }
9243
+ async function executeTaskAttempt(task, agent, allAgents, mission, cwd, localEndpoint, onEvent) {
9244
+ const emit = (type, message, data) => {
9245
+ if (onEvent) onEvent({ type, agentName: agent.name, taskId: task.id, message, data });
9246
+ };
9247
+ emit("thinking", `@${agent.name} [${task.operationType}] working on: ${task.instruction.slice(0, 60)}...`);
9248
+ try {
9249
+ const projectContext = buildLocalContext(cwd);
9250
+ let relevantFiles = "";
9251
+ try {
9252
+ const retrieval = await getRelevantFileContents(
9253
+ `${agent.task}
9254
+
9255
+ ${task.instruction}`,
9256
+ localEndpoint
9257
+ );
9258
+ relevantFiles = retrieval.fileContents;
9259
+ } catch {
9260
+ }
9261
+ const crossAgentContext = buildCrossAgentContext(agent.name, allAgents, cwd);
9262
+ const fullContext = assembleAgentContext(projectContext, relevantFiles, crossAgentContext);
9263
+ const projectDependencies = readProjectDependencies(cwd);
9264
+ const agentMessages = loadAgentMessages(agent.name, cwd);
9265
+ const history = agentMessages.slice(-20).map((msg) => ({
9266
+ role: msg.sender === "agent" ? "assistant" : "user",
9267
+ content: msg.content
9268
+ }));
9269
+ const personaPrompt = agent.personaId ? loadPersonaPrompt(agent.personaId) : null;
9270
+ const otherAgents = allAgents.filter((a) => a.name !== agent.name).map((a) => `@${a.name}: ${a.task}`).join("\n");
9271
+ const operationContext = buildOperationContext(task, cwd);
9272
+ const systemMessage = `${STANDARD_STYLE_PROMPT}
9273
+
9274
+ You are an autonomous AI agent. You MUST respond with ONLY valid JSON. No markdown wrapping. No text before or after the JSON object.`;
9275
+ const directorNotes = task.notes.length > 0 ? `
9276
+ DIRECTOR NOTES:
9277
+ ${task.notes.slice(-2).join("\n")}` : "";
9278
+ const userTurn = `You are @${agent.name} \u2014 an autonomous AI agent.
9279
+ ${personaPrompt ? `
9280
+ YOUR PERSONA:
9281
+ ${personaPrompt}
9282
+ ` : ""}
9283
+ YOUR SPECIALTY: ${agent.task}
9284
+
9285
+ YOUR TEAM:
9286
+ ${otherAgents || "No other agents currently active."}
9287
+
9288
+ ${projectDependencies ? `--- PROJECT DEPENDENCIES ---
9289
+ ${projectDependencies}
9290
+ IMPORTANT: Only import packages that are listed above. If you need a package that is NOT listed, use runCommand in toolCalls to install it first.
9291
+ --- END DEPENDENCIES ---
9292
+ ` : ""}
9293
+
9294
+ ${fullContext ? `--- PROJECT CONTEXT ---
9295
+ ${fullContext}
9296
+ --- END CONTEXT ---
9297
+ ` : ""}
9298
+
9299
+ AVAILABLE TOOLS (add to toolCalls array):
9300
+ - readFile: { "path": "relative/path" } \u2014 read a file before modifying it
9301
+ - writeOutput: { "content": "findings" } \u2014 save audit/review findings
9302
+ - readAgentOutput: { "agentName": "name", "taskId": "id" } \u2014 read another agent's output
9303
+ - runCommand: { "command": "flutter pub add intl" } \u2014 install a missing package (safe commands only)
9304
+ - gitCommit: { "message": "commit message" } \u2014 queue a commit for DirectorBob review
9305
+ - gitPush: {} \u2014 push after commit is approved
9306
+ - deleteFile: { "path": "relative/path" } \u2014 delete a file
9307
+
9308
+ --- CURRENT TASK ---
9309
+ Task ID: ${task.id}
9310
+ Instruction: ${task.instruction}
9311
+ Attempt: ${task.attemptCount + 1}
9312
+ Satisfaction Target: ${task.satisfactionTarget}%
9313
+ ${task.attemptCount > 0 ? `Previous score: ${task.lastSatisfactionScore}% \u2014 not done yet. Keep working.` : ""}${directorNotes}
9314
+
9315
+ ${operationContext}
9316
+
9317
+ ${STRUCTURED_RESPONSE_SPEC}
9318
+
9319
+ Execute this task now. Respond with ONLY the JSON object.`;
9320
+ const messages = [
9321
+ { role: "system", content: systemMessage },
9322
+ ...history,
9323
+ { role: "user", content: userTurn }
9324
+ ];
9325
+ const rawResponse = await callLocalModel(localEndpoint, messages);
9326
+ const fullResponse = typeof rawResponse === "object" && rawResponse.text ? rawResponse.text : rawResponse;
9327
+ const structured = parseStructuredResponse(fullResponse);
9328
+ let allFilesCreated = [];
9329
+ let allFilesModified = [];
9330
+ let allFilesWithBackups = [];
9331
+ let lastToolResult = null;
9332
+ let cleanResponse = "";
9333
+ if (structured) {
9334
+ cleanResponse = structured.message || structured.thinking || "";
9335
+ emit("response", cleanResponse);
9336
+ for (const file of structured.files) {
9337
+ const writeResult = writeFileToDisk(file.path, file.content, cwd);
9338
+ if (writeResult.success) {
9339
+ renderFileDiff(file.path, file.content, writeResult.isNew);
9340
+ if (writeResult.isNew) {
9341
+ allFilesCreated.push(file.path);
9342
+ } else {
9343
+ allFilesModified.push(file.path);
9344
+ }
9345
+ allFilesWithBackups.push({
9346
+ filePath: file.path,
9347
+ backupPath: writeResult.backupPath,
9348
+ isNew: writeResult.isNew
9349
+ });
9350
+ emit(
9351
+ "tool_call",
9352
+ `@${agent.name} ${writeResult.isNew ? "created" : "modified"}: ${file.path}`,
9353
+ { tool: writeResult.isNew ? "createFile" : "modifyFile", params: { path: file.path } }
9354
+ );
9355
+ }
9356
+ }
9357
+ if (allFilesCreated.length > 0 || allFilesModified.length > 0) {
9358
+ emit(
9359
+ "tool_result",
9360
+ `\u2705 Written: ${[...allFilesCreated, ...allFilesModified].join(", ")}`,
9361
+ { success: true, filesCreated: allFilesCreated, filesModified: allFilesModified }
9362
+ );
9363
+ }
9364
+ const executor = new AgentToolExecutor(
9365
+ cwd,
9366
+ agent.name,
9367
+ task.id,
9368
+ mission.id,
9369
+ allFilesWithBackups
9370
+ );
9371
+ for (const toolCall of structured.toolCalls) {
9372
+ emit("tool_call", `@${agent.name} using tool: ${toolCall.tool}`, toolCall);
9373
+ lastToolResult = await executor.execute(toolCall);
9374
+ emit(
9375
+ "tool_result",
9376
+ lastToolResult.success ? `\u2705 ${toolCall.tool}: ${lastToolResult.output.slice(0, 80)}` : `\u274C ${toolCall.tool} failed: ${lastToolResult.error}`,
9377
+ lastToolResult
9378
+ );
9379
+ }
9380
+ } else {
9381
+ emit("thinking", `@${agent.name} response was not valid JSON \u2014 using fallback parser.`);
9382
+ const normalizedResponse = normalizeFileBlocks(fullResponse);
9383
+ const proposals = extractAllProposedFiles(normalizedResponse);
9384
+ for (const proposed of proposals) {
9385
+ if (!proposed.isLocal) continue;
9386
+ renderFileDiff(proposed.filePath, proposed.content, proposed.isNew);
9387
+ const backupPath = proposed.isNew ? null : findMostRecentBackup(proposed.filePath, cwd);
9388
+ if (proposed.isNew) {
9389
+ allFilesCreated.push(proposed.filePath);
9390
+ } else {
9391
+ allFilesModified.push(proposed.filePath);
9392
+ }
9393
+ allFilesWithBackups.push({
9394
+ filePath: proposed.filePath,
9395
+ backupPath,
9396
+ isNew: proposed.isNew
9397
+ });
9398
+ emit(
9399
+ "tool_call",
9400
+ `@${agent.name} using tool: ${proposed.isNew ? "createFile" : "modifyFile"}`,
9401
+ { tool: proposed.isNew ? "createFile" : "modifyFile", params: { path: proposed.filePath } }
9402
+ );
9403
+ }
9404
+ await processAllProposedFiles(normalizedResponse, true);
9405
+ if (allFilesCreated.length > 0 || allFilesModified.length > 0) {
9406
+ emit(
9407
+ "tool_result",
9408
+ `\u2705 Written: ${[...allFilesCreated, ...allFilesModified].join(", ")}`,
9409
+ { success: true, filesCreated: allFilesCreated, filesModified: allFilesModified }
9410
+ );
9411
+ }
9412
+ const toolCall = parseToolCall(normalizedResponse);
9413
+ if (toolCall) {
9414
+ emit("tool_call", `@${agent.name} using tool: ${toolCall.tool}`, toolCall);
9415
+ const executor = new AgentToolExecutor(
9416
+ cwd,
9417
+ agent.name,
9418
+ task.id,
9419
+ mission.id,
9420
+ allFilesWithBackups
9421
+ );
9422
+ lastToolResult = await executor.execute(toolCall);
9423
+ emit(
9424
+ "tool_result",
9425
+ lastToolResult.success ? `\u2705 ${toolCall.tool}: ${lastToolResult.output.slice(0, 80)}` : `\u274C ${toolCall.tool} failed: ${lastToolResult.error}`,
9426
+ lastToolResult
9427
+ );
9428
+ }
9429
+ cleanResponse = stripToolCall(fullResponse).trim();
9430
+ emit("response", cleanResponse);
9431
+ }
9432
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9433
+ saveAgentMessage(agent.name, { sender: "user", content: userTurn, timestamp: now }, cwd);
9434
+ saveAgentMessage(
9435
+ agent.name,
9436
+ { sender: "agent", content: fullResponse, timestamp: now },
9437
+ cwd
9438
+ );
9439
+ if (allFilesCreated.length > 0 || allFilesModified.length > 0) {
9440
+ const currentTask = mission.tasks.find((t) => t.id === task.id);
9441
+ if (currentTask) {
9442
+ currentTask.filesCreated.push(...allFilesCreated);
9443
+ currentTask.filesModified.push(...allFilesModified);
9444
+ saveMission(mission, cwd);
9445
+ }
9446
+ }
9447
+ const existenceCheck = validateCreateTaskFiles(
9448
+ task,
9449
+ allFilesCreated,
9450
+ allFilesModified,
9451
+ cwd
9452
+ );
9453
+ if (!existenceCheck.valid) {
9454
+ emit(
9455
+ "error",
9456
+ `@${agent.name} ghost completion detected \u2014 expected files not found on disk: ${existenceCheck.missingFiles.join(", ")}. Forcing retry.`
9457
+ );
9458
+ const ghostSatisfaction = {
9459
+ score: 0,
9460
+ reasoning: `Ghost completion \u2014 files expected but not written: ${existenceCheck.missingFiles.join(", ")}. Agent must write the actual files.`,
9461
+ isDone: false,
9462
+ isStagnating: false,
9463
+ needsDirector: false,
9464
+ needsUser: false
9465
+ };
9466
+ applySatisfactionResult(mission, task.id, ghostSatisfaction, cwd);
9467
+ return {
9468
+ taskId: task.id,
9469
+ agentName: agent.name,
9470
+ response: cleanResponse,
9471
+ toolResult: lastToolResult,
9472
+ satisfaction: ghostSatisfaction,
9473
+ isDone: false,
9474
+ isStagnating: false,
9475
+ needsDirector: false,
9476
+ needsUser: false,
9477
+ filesCreated: allFilesCreated,
9478
+ filesModified: allFilesModified
9479
+ };
9480
+ }
9481
+ const filesWritten = allFilesCreated.length + allFilesModified.length;
9482
+ const gitCommitCall = structured?.toolCalls.find((tc) => tc.tool === "gitCommit");
9483
+ let writtenFilesContent = "";
9484
+ if (task.operationType === "CREATE" && filesWritten > 0) {
9485
+ const allWritten = [...allFilesCreated, ...allFilesModified];
9486
+ for (const filePath of allWritten) {
9487
+ const absolutePath = path18.join(cwd, filePath);
9488
+ if (fs14.existsSync(absolutePath)) {
9489
+ try {
9490
+ const content = fs14.readFileSync(absolutePath, "utf-8");
9491
+ writtenFilesContent += `
9492
+ File: ${filePath}
9493
+ \`\`\`
9494
+ ${content.slice(0, 1e3)}
9495
+ \`\`\`
9496
+ `;
9497
+ } catch {
9498
+ }
9499
+ }
9500
+ }
9501
+ }
9502
+ const satisfactionInput = [
9503
+ cleanResponse,
9504
+ filesWritten > 0 ? `Files written: ${[...allFilesCreated, ...allFilesModified].join(", ")}` : "",
9505
+ writtenFilesContent,
9506
+ lastToolResult?.success && gitCommitCall ? `Commit queued: ${lastToolResult.output.slice(0, 200)}` : lastToolResult?.success ? `Tool executed: ${lastToolResult.output.slice(0, 200)}` : ""
9507
+ ].filter(Boolean).join("\n\n");
9508
+ const satisfaction = await evaluateSatisfaction(task, satisfactionInput, localEndpoint);
9509
+ emit(
9510
+ "satisfaction",
9511
+ `SAT: ${satisfaction.score}% \u2192 target ${task.satisfactionTarget}% \u2014 ${satisfaction.isDone ? "DONE" : satisfaction.isStagnating ? "STAGNATING" : "working"}`,
9512
+ satisfaction
9513
+ );
9514
+ applySatisfactionResult(mission, task.id, satisfaction, cwd);
9515
+ if (satisfaction.isDone) {
9516
+ emit("done", `@${agent.name} completed: ${task.instruction.slice(0, 50)}`);
9517
+ } else if (satisfaction.isStagnating) {
9518
+ emit("stagnating", `@${agent.name} stagnating: ${task.instruction.slice(0, 50)}`);
9519
+ }
9520
+ const finalTask = mission.tasks.find((t) => t.id === task.id);
9521
+ return {
9522
+ taskId: task.id,
9523
+ agentName: agent.name,
9524
+ response: cleanResponse,
9525
+ toolResult: lastToolResult,
9526
+ satisfaction,
9527
+ isDone: satisfaction.isDone,
9528
+ isStagnating: satisfaction.isStagnating,
9529
+ needsDirector: satisfaction.needsDirector,
9530
+ needsUser: satisfaction.needsUser,
9531
+ filesCreated: finalTask?.filesCreated || allFilesCreated,
9532
+ filesModified: finalTask?.filesModified || allFilesModified
9533
+ };
9534
+ } catch (error) {
9535
+ emit("error", `@${agent.name} error: ${error.message}`);
9536
+ return {
9537
+ taskId: task.id,
9538
+ agentName: agent.name,
9539
+ response: "",
9540
+ toolResult: null,
9541
+ satisfaction: {
9542
+ score: 0,
9543
+ reasoning: `Execution error: ${error.message}`,
9544
+ isDone: false,
9545
+ isStagnating: false,
9546
+ needsDirector: false,
9547
+ needsUser: false
9548
+ },
9549
+ isDone: false,
9550
+ isStagnating: false,
9551
+ needsDirector: false,
9552
+ needsUser: false,
9553
+ filesCreated: [],
9554
+ filesModified: []
9555
+ };
9556
+ }
9557
+ }
9558
+
9559
+ // src/core/director-bob.ts
9560
+ init_agent_tools();
9561
+
9562
+ // src/core/agent-reviewer.ts
9563
+ import * as fs15 from "fs";
9564
+ import * as path19 from "path";
9565
+ import * as os7 from "os";
9566
+ var BOB_DIR7 = path19.join(os7.homedir(), ".bob");
9567
+ function findMostRecentBackup2(filePath, cwd) {
9568
+ const backupDir = path19.join(cwd, ".bob-backups");
9569
+ if (!fs15.existsSync(backupDir)) return null;
9570
+ const safeName = filePath.replace(/[\/\\]/g, "_");
9571
+ try {
9572
+ const backups = fs15.readdirSync(backupDir).filter((f) => f.startsWith(safeName) && f.endsWith(".bak")).sort().reverse();
9573
+ if (backups.length === 0) return null;
9574
+ return path19.join(backupDir, backups[0]);
9575
+ } catch {
9576
+ return null;
9577
+ }
9578
+ }
9579
+ function findOldestBackup(filePath, cwd) {
9580
+ const backupDir = path19.join(cwd, ".bob-backups");
9581
+ if (!fs15.existsSync(backupDir)) return null;
9582
+ const safeName = filePath.replace(/[\/\\]/g, "_");
9583
+ try {
9584
+ const backups = fs15.readdirSync(backupDir).filter((f) => f.startsWith(safeName) && f.endsWith(".bak")).sort();
9585
+ if (backups.length === 0) return null;
9586
+ return path19.join(backupDir, backups[0]);
9587
+ } catch {
9588
+ return null;
9589
+ }
9590
+ }
9591
+ function countImports(content) {
9592
+ return (content.match(/^import\s+/gm) || []).length;
9593
+ }
9594
+ function extractExports(content) {
9595
+ const matches = content.match(
9596
+ /^export\s+(?:default\s+)?(?:function|class|const|let|var|type|interface|enum|async\s+function)\s+(\w+)/gm
9597
+ ) || [];
9598
+ return matches.map((m) => {
9599
+ const nameMatch = m.match(/\s(\w+)\s*$/);
9600
+ return nameMatch ? nameMatch[1] : m;
9601
+ });
9602
+ }
9603
+ function programmaticCheck(originalContent, currentContent, operationType, isNewFile) {
9604
+ if (isNewFile) return { passed: true, reason: null };
9605
+ const originalLines = originalContent.split("\n").length;
9606
+ const currentLines = currentContent.split("\n").length;
9607
+ const reductionRatio = currentLines / originalLines;
9608
+ const minRatios = {
9609
+ PATCH: 0.85,
9610
+ REFACTOR: 0.7,
9611
+ REPLACE: 0.1,
9612
+ CREATE: 0
9613
+ };
9614
+ const minRatio = minRatios[operationType] ?? 0.8;
9615
+ if (reductionRatio < minRatio) {
9616
+ return {
9617
+ passed: false,
9618
+ reason: `File reduced to ${Math.round(reductionRatio * 100)}% of original size (minimum ${Math.round(minRatio * 100)}% for ${operationType} operations). Agent likely gutted the file instead of making a targeted change.`
9619
+ };
9620
+ }
9621
+ if (operationType === "PATCH" || operationType === "REFACTOR") {
9622
+ const originalExports = extractExports(originalContent);
9623
+ const currentExports = extractExports(currentContent);
9624
+ const missingExports = originalExports.filter((e) => !currentExports.includes(e));
9625
+ if (missingExports.length > 0) {
9626
+ return {
9627
+ passed: false,
9628
+ reason: `Missing exports after ${operationType}: ${missingExports.join(", ")}. These were present in the original and must be preserved.`
9629
+ };
9630
+ }
9631
+ }
9632
+ if (operationType === "PATCH") {
9633
+ const originalImports = countImports(originalContent);
9634
+ const currentImports = countImports(currentContent);
9635
+ const importDelta = Math.abs(currentImports - originalImports);
9636
+ if (importDelta > 2) {
9637
+ return {
9638
+ passed: false,
9639
+ reason: `Import count changed by ${importDelta} (${originalImports} \u2192 ${currentImports}). PATCH operations should add at most 2 imports. Agent may have rewritten the file.`
9640
+ };
9641
+ }
9642
+ }
9643
+ return { passed: true, reason: null };
9644
+ }
9645
+ async function reviewTaskCompletion(taskInstruction, agentName, agentMessage, filesWritten, attemptCount, cwd, localEndpoint, operationType) {
9646
+ const opType = operationType || "CREATE";
9647
+ let projectContext = "";
9648
+ try {
9649
+ const retrieval = await getRelevantFileContents(
9650
+ `${taskInstruction}
9651
+
9652
+ Reviewing completed task for: ${filesWritten.map((f) => f.filePath).join(", ")}`,
9653
+ localEndpoint
9654
+ );
9655
+ projectContext = retrieval.fileContents;
9656
+ } catch {
9657
+ }
9658
+ let fileEvidence = "";
9659
+ for (const file of filesWritten) {
9660
+ const absolutePath = path19.join(cwd, file.filePath);
9661
+ let currentContent = "[File not found on disk]";
9662
+ if (fs15.existsSync(absolutePath)) {
9663
+ try {
9664
+ currentContent = fs15.readFileSync(absolutePath, "utf-8");
9665
+ } catch {
9666
+ currentContent = "[Could not read file]";
9667
+ }
9668
+ }
9669
+ let originalContent = "";
9670
+ if (!file.isNew) {
9671
+ const backupPath = findOldestBackup(file.filePath, cwd);
9672
+ if (backupPath && fs15.existsSync(backupPath)) {
9673
+ try {
9674
+ originalContent = fs15.readFileSync(backupPath, "utf-8");
9675
+ } catch {
9676
+ }
9677
+ }
9678
+ }
9679
+ const currentLines = currentContent.split("\n").length;
9680
+ const originalLines = originalContent ? originalContent.split("\n").length : 0;
9681
+ fileEvidence += `
9682
+ --- FILE: ${file.filePath} ---
9683
+ `;
9684
+ fileEvidence += file.isNew ? `STATUS: NEW FILE (${currentLines} lines)
9685
+ ` : `STATUS: MODIFIED (${originalLines} \u2192 ${currentLines} lines)
9686
+ `;
9687
+ if (!file.isNew && originalContent) {
9688
+ fileEvidence += `
9689
+ ORIGINAL (pre-mission):
9690
+ \`\`\`
9691
+ ${originalContent.slice(0, 2e3)}${originalContent.length > 2e3 ? "\n...(truncated)" : ""}
9692
+ \`\`\`
9693
+ `;
9694
+ }
9695
+ fileEvidence += `
9696
+ CURRENT:
9697
+ \`\`\`
9698
+ ${currentContent.slice(0, 2e3)}${currentContent.length > 2e3 ? "\n...(truncated)" : ""}
9699
+ \`\`\`
9700
+ `;
9701
+ fileEvidence += `--- END FILE ---
9702
+ `;
9703
+ }
9704
+ const reviewPrompt = `TASK INSTRUCTION:
9705
+ ${taskInstruction}
9706
+
9707
+ OPERATION TYPE: ${opType}
9708
+ AGENT: @${agentName}
9709
+ ATTEMPT COUNT: ${attemptCount}
9710
+
9711
+ AGENT'S SUMMARY:
9712
+ "${agentMessage}"
9713
+
9714
+ FILES WRITTEN:
9715
+ ${fileEvidence}
9716
+
9717
+ ${projectContext ? `RELEVANT PROJECT CONTEXT:
9718
+ ${projectContext.slice(0, 2e3)}
9719
+ ` : ""}
9720
+
9721
+ Your job is to CONFIRM whether this task is complete and correct.
9722
+
9723
+ Ask yourself ONE question: Does the current file correctly implement what the task instruction asked for?
9724
+
9725
+ Only flag REVISION_NEEDED if you can identify a SPECIFIC, CONCRETE problem:
9726
+ - The specific feature/function requested is missing entirely
9727
+ - The code has an obvious syntax error that would prevent it from running
9728
+ - Existing functionality that was working before is now broken
9729
+
9730
+ Do NOT flag REVISION_NEEDED for:
9731
+ - Minor style differences
9732
+ - Comments being slightly different
9733
+ - The code looking different from what you would have written
9734
+ - Small variations between attempts that don't affect correctness
9735
+ - Things that look "not quite right" but work correctly
9736
+
9737
+ Respond with ONLY this JSON on a single line:
9738
+ {"verdict":"APPROVED"|"REVISION_NEEDED"|"ESCALATE","reason":"one concrete sentence","revisionNote":"specific actionable fix OR null if approved"}
9739
+
9740
+ VERDICT definitions:
9741
+ - APPROVED: The task instruction has been correctly implemented. Mark it done.
9742
+ - REVISION_NEEDED: There is a specific concrete problem stated above. Provide exact fix instruction.
9743
+ - ESCALATE: ${attemptCount >= 3 ? "Agent has made " + attemptCount + " attempts \u2014 escalate to user." : "Fundamental blocker requiring user intervention."}`;
9744
+ try {
9745
+ const messages = [
9746
+ { role: "system", content: TASK_COMPLETION_REVIEWER_PROMPT },
9747
+ { role: "user", content: reviewPrompt }
9748
+ ];
9749
+ const rawResponse = await callLocalModel(localEndpoint, messages);
9750
+ const responseText = typeof rawResponse === "object" && rawResponse.text ? rawResponse.text : rawResponse;
9751
+ return parseTaskCompletionResponse(responseText);
9752
+ } catch (error) {
9753
+ return {
9754
+ verdict: "APPROVED",
9755
+ reason: `Review failed: ${error.message}. Defaulting to approved.`,
9756
+ revisionNote: null,
9757
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
9758
+ };
9759
+ }
9760
+ }
9761
+ var TASK_COMPLETION_REVIEWER_PROMPT = `You are DirectorBob in TASK CONFIRMATION MODE.
9762
+
9763
+ Your job is to confirm whether an agent has correctly completed its assigned task.
9764
+ You are NOT hunting for problems. You are confirming correctness.
9765
+
9766
+ The agent has already self-evaluated and believes the task is done.
9767
+ Your role is to verify that belief is correct.
9768
+
9769
+ APPROVE unless you can point to a SPECIFIC, CONCRETE problem:
9770
+ - The requested feature is completely missing from the code
9771
+ - There is an obvious syntax error that would prevent compilation
9772
+ - Critical existing functionality has been removed or broken
9773
+
9774
+ Do NOT reject for vague reasons like "could be improved" or "slightly different than expected."
9775
+ Do NOT reject because something looks different between two versions of the file.
9776
+ Do NOT reject because you would have written it differently.
9777
+
9778
+ If the code implements what was asked and doesn't break anything \u2014 APPROVE IT.
9779
+
9780
+ Respond with ONLY valid JSON on a single line.`;
9781
+ function parseTaskCompletionResponse(response) {
9782
+ let jsonStr = response.trim();
9783
+ const fencedMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
9784
+ if (fencedMatch) jsonStr = fencedMatch[1].trim();
9785
+ const firstBrace = jsonStr.indexOf("{");
9786
+ if (firstBrace !== -1) {
9787
+ let depth = 0;
9788
+ let lastBrace = -1;
9789
+ for (let i = firstBrace; i < jsonStr.length; i++) {
9790
+ if (jsonStr[i] === "{") depth++;
9791
+ if (jsonStr[i] === "}") {
9792
+ depth--;
9793
+ if (depth === 0) {
9794
+ lastBrace = i;
9795
+ break;
9796
+ }
9797
+ }
9798
+ }
9799
+ if (lastBrace !== -1) {
9800
+ try {
9801
+ const parsed = JSON.parse(jsonStr.slice(firstBrace, lastBrace + 1));
9802
+ const verdict2 = ["APPROVED", "REVISION_NEEDED", "ESCALATE"].includes(parsed.verdict) ? parsed.verdict : "APPROVED";
9803
+ return {
9804
+ verdict: verdict2,
9805
+ reason: typeof parsed.reason === "string" ? parsed.reason : "No reason provided.",
9806
+ revisionNote: typeof parsed.revisionNote === "string" && parsed.revisionNote !== "null" ? parsed.revisionNote : null,
9807
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
9808
+ };
9809
+ } catch {
9810
+ }
9811
+ }
9812
+ }
9813
+ const upper = response.toUpperCase();
9814
+ let verdict = "APPROVED";
9815
+ if (upper.includes("ESCALATE")) verdict = "ESCALATE";
9816
+ else if (upper.includes("REVISION_NEEDED") || upper.includes("REVISION NEEDED")) verdict = "REVISION_NEEDED";
9817
+ return {
9818
+ verdict,
9819
+ reason: response.slice(0, 200).trim() || "Review completed.",
9820
+ revisionNote: verdict !== "APPROVED" ? response.slice(0, 200).trim() : null,
9821
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
9822
+ };
9823
+ }
9824
+ async function reviewCommit(taskInstruction, commitMessage, agentName, filesChanged, cwd, localEndpoint, operationType) {
9825
+ const fileReviews = [];
9826
+ const opType = operationType || "PATCH";
9827
+ for (const fileChange of filesChanged) {
9828
+ const absolutePath = path19.join(cwd, fileChange.filePath);
9829
+ let currentContent = "";
9830
+ if (fs15.existsSync(absolutePath)) {
9831
+ try {
9832
+ currentContent = fs15.readFileSync(absolutePath, "utf-8");
9833
+ } catch {
9834
+ currentContent = "[Could not read current file]";
9835
+ }
9836
+ } else {
9837
+ fileReviews.push({
9838
+ filePath: fileChange.filePath,
9839
+ verdict: "DENY",
9840
+ reason: "File does not exist on disk \u2014 agent may have failed to write it.",
9841
+ backupPath: fileChange.backupPath
9842
+ });
9843
+ continue;
9844
+ }
9845
+ const backupPath = fileChange.backupPath || findMostRecentBackup2(fileChange.filePath, cwd);
9846
+ let originalContent = "";
9847
+ let isNewFile = true;
9848
+ if (backupPath && fs15.existsSync(backupPath)) {
9849
+ try {
9850
+ originalContent = fs15.readFileSync(backupPath, "utf-8");
9851
+ isNewFile = false;
9852
+ } catch {
9853
+ originalContent = "[Could not read backup]";
9854
+ }
9855
+ }
9856
+ if (!isNewFile && originalContent && originalContent !== "[Could not read backup]") {
9857
+ const check = programmaticCheck(originalContent, currentContent, opType, isNewFile);
9858
+ if (!check.passed) {
9859
+ fileReviews.push({
9860
+ filePath: fileChange.filePath,
9861
+ verdict: "DENY",
9862
+ reason: check.reason,
9863
+ backupPath
9864
+ });
9865
+ continue;
9866
+ }
9867
+ }
9868
+ let projectContext = "";
9869
+ try {
9870
+ const retrieval = await getRelevantFileContents(
9871
+ `${taskInstruction}
9872
+
9873
+ Reviewing changes to: ${fileChange.filePath}`,
9874
+ localEndpoint
9875
+ );
9876
+ projectContext = retrieval.fileContents;
9877
+ } catch {
9878
+ }
9879
+ const reviewPrompt = buildFileReviewPrompt(
9880
+ taskInstruction,
9881
+ fileChange.filePath,
9882
+ originalContent,
9883
+ currentContent,
9884
+ projectContext,
9885
+ isNewFile,
9886
+ opType
9887
+ );
9888
+ try {
9889
+ const messages = [
9890
+ { role: "system", content: COMMIT_REVIEWER_SYSTEM_PROMPT },
9891
+ { role: "user", content: reviewPrompt }
9892
+ ];
9893
+ const rawResponse = await callLocalModel(localEndpoint, messages);
9894
+ const responseText = typeof rawResponse === "object" && rawResponse.text ? rawResponse.text : rawResponse;
9895
+ const fileReview = parseFileReviewResponse(responseText, fileChange.filePath, backupPath);
9896
+ fileReviews.push(fileReview);
9897
+ } catch (error) {
9898
+ fileReviews.push({
9899
+ filePath: fileChange.filePath,
9900
+ verdict: "WARN",
9901
+ reason: `Review failed: ${error.message}. Manual inspection recommended.`,
9902
+ backupPath
9903
+ });
9904
+ }
9905
+ }
9906
+ const denied = fileReviews.filter((f) => f.verdict === "DENY");
9907
+ const warned = fileReviews.filter((f) => f.verdict === "WARN");
9908
+ const approved = fileReviews.filter((f) => f.verdict === "APPROVE");
9909
+ let overallVerdict;
9910
+ let overallReason;
9911
+ let revisionNote = null;
9912
+ if (denied.length > 0) {
9913
+ overallVerdict = "DENY";
9914
+ overallReason = `${denied.length} file(s) failed review: ${denied.map((f) => f.filePath).join(", ")}`;
9915
+ revisionNote = denied.map((f) => `${f.filePath}: ${f.reason}`).join("\n");
9916
+ } else if (warned.length > 0) {
9917
+ overallVerdict = "APPROVE";
9918
+ overallReason = `${approved.length} file(s) approved. ${warned.length} file(s) have warnings.`;
9919
+ } else {
9920
+ overallVerdict = "APPROVE";
9921
+ overallReason = `All ${approved.length} file(s) passed review.`;
9922
+ }
9923
+ return {
9924
+ verdict: overallVerdict,
9925
+ reason: overallReason,
9926
+ revisionNote,
9927
+ filesReviewed: fileReviews,
9928
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
9929
+ };
9930
+ }
9931
+ var COMMIT_REVIEWER_SYSTEM_PROMPT = `You are DirectorBob in CODE REVIEW MODE.
9932
+
9933
+ Your role is to evaluate whether a code change is safe to commit.
9934
+ You are an experienced senior engineer reviewing a junior agent's work.
9935
+
9936
+ You are SKEPTICAL BY DEFAULT. Your job is to catch problems.
9937
+
9938
+ You evaluate against three criteria:
9939
+ 1. CORRECTNESS \u2014 Does the change accomplish what the task asked for?
9940
+ 2. SAFETY \u2014 Does the change preserve existing functionality without breaking anything?
9941
+ 3. FIT \u2014 Does the change follow the project's existing patterns and conventions?
9942
+
9943
+ You respond with ONLY this exact JSON format on a single line:
9944
+ {"verdict":"APPROVE"|"DENY"|"WARN","reason":"one paragraph","revisionNote":"specific fix instruction or null"}
9945
+
9946
+ VERDICT definitions:
9947
+ - APPROVE: Change is correct, safe, and fits the project. Commit it.
9948
+ - DENY: Change has a critical problem. Do not commit. Agent must revise.
9949
+ - WARN: Change has minor concerns but is acceptable. Commit with caution.
9950
+
9951
+ You MUST DENY if:
9952
+ - The file was substantially rewritten when only a small change was needed
9953
+ - Existing imports or exports were removed without being replaced
9954
+ - The change breaks obvious functionality
9955
+ - The file is shorter than the original by more than 30% with no clear justification
9956
+
9957
+ You MUST APPROVE if:
9958
+ - The change correctly implements the task
9959
+ - Existing code structure is preserved
9960
+ - New code fits the project's patterns`;
9961
+ function buildFileReviewPrompt(taskInstruction, filePath, originalContent, currentContent, projectContext, isNewFile, operationType) {
9962
+ const originalLines = originalContent.split("\n").length;
9963
+ const currentLines = currentContent.split("\n").length;
9964
+ const lineDiff = currentLines - originalLines;
9965
+ const reductionPercent = originalLines > 0 ? Math.round((originalLines - currentLines) / originalLines * 100) : 0;
9966
+ return `TASK INSTRUCTION: ${taskInstruction}
9967
+ OPERATION TYPE: ${operationType}
9968
+
9969
+ FILE BEING REVIEWED: ${filePath}
9970
+ ${isNewFile ? "STATUS: NEW FILE" : `STATUS: MODIFIED (${originalLines} \u2192 ${currentLines} lines, ${lineDiff >= 0 ? "+" : ""}${lineDiff}${reductionPercent > 0 ? `, ${reductionPercent}% reduction` : ""})`}
9971
+
9972
+ ${!isNewFile && originalContent ? `ORIGINAL FILE:
9973
+ \`\`\`
9974
+ ${originalContent.slice(0, 3e3)}${originalContent.length > 3e3 ? "\n...(truncated)" : ""}
9975
+ \`\`\`
9976
+ ` : ""}
9977
+ CURRENT FILE:
9978
+ \`\`\`
9979
+ ${currentContent.slice(0, 3e3)}${currentContent.length > 3e3 ? "\n...(truncated)" : ""}
9980
+ \`\`\`
9981
+
9982
+ ${projectContext ? `RELEVANT PROJECT CONTEXT:
9983
+ ${projectContext.slice(0, 1500)}
9984
+ ` : ""}
9985
+ Evaluate this ${operationType} change. Respond with ONLY the JSON verdict on a single line.`;
9986
+ }
9987
+ function parseFileReviewResponse(response, filePath, backupPath) {
9988
+ const jsonMatch = response.match(/\{[^{}]*"verdict"[^{}]*\}/);
9989
+ if (jsonMatch) {
9990
+ try {
9991
+ const parsed = JSON.parse(jsonMatch[0]);
9992
+ const verdict2 = ["APPROVE", "DENY", "WARN"].includes(parsed.verdict) ? parsed.verdict : "WARN";
9993
+ return { filePath, verdict: verdict2, reason: parsed.reason || "No reason provided.", backupPath };
9994
+ } catch {
9995
+ }
9996
+ }
9997
+ const upper = response.toUpperCase();
9998
+ let verdict = "WARN";
9999
+ if (upper.includes("DENY")) verdict = "DENY";
10000
+ else if (upper.includes("APPROVE")) verdict = "APPROVE";
10001
+ return {
10002
+ filePath,
10003
+ verdict,
10004
+ reason: response.slice(0, 200).trim() || "Review completed.",
10005
+ backupPath
10006
+ };
10007
+ }
10008
+ function restoreDeniedFiles(review, cwd) {
10009
+ const restored = [];
10010
+ const failed = [];
10011
+ for (const fileReview of review.filesReviewed) {
10012
+ if (fileReview.verdict !== "DENY") continue;
10013
+ if (!fileReview.backupPath) {
10014
+ failed.push(fileReview.filePath);
10015
+ continue;
10016
+ }
10017
+ try {
10018
+ const absolutePath = path19.join(cwd, fileReview.filePath);
10019
+ if (fs15.existsSync(fileReview.backupPath)) {
10020
+ const dir = path19.dirname(absolutePath);
10021
+ if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
10022
+ fs15.copyFileSync(fileReview.backupPath, absolutePath);
10023
+ restored.push(fileReview.filePath);
10024
+ } else {
10025
+ failed.push(fileReview.filePath);
10026
+ }
10027
+ } catch {
10028
+ failed.push(fileReview.filePath);
10029
+ }
10030
+ }
10031
+ return { restored, failed };
10032
+ }
10033
+ function saveCommitReview(review, missionId, taskId, cwd) {
10034
+ const projectName = path19.basename(cwd);
10035
+ const reviewDir = path19.join(BOB_DIR7, "projects", projectName, "agents", "commit-reviews");
10036
+ if (!fs15.existsSync(reviewDir)) fs15.mkdirSync(reviewDir, { recursive: true });
10037
+ fs15.writeFileSync(
10038
+ path19.join(reviewDir, `${taskId}.json`),
10039
+ JSON.stringify(review, null, 2)
10040
+ );
10041
+ }
10042
+
10043
+ // src/core/director-bob.ts
10044
+ var DEFAULT_DIRECTOR_LIMIT = 2;
10045
+ var MAX_PARALLEL_TASKS = 4;
10046
+ var TASK_LOOP_INTERVAL_MS = 500;
10047
+ var MAX_COMMIT_DENIALS = 3;
10048
+ function parseTaskMapResponse(rawResponse) {
10049
+ let jsonStr = rawResponse.trim();
10050
+ const fencedMatch = jsonStr.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
10051
+ if (fencedMatch) {
10052
+ jsonStr = fencedMatch[1].trim();
10053
+ }
10054
+ const firstBrace = jsonStr.indexOf("{");
10055
+ if (firstBrace === -1) return null;
10056
+ let depth = 0;
10057
+ let lastBrace = -1;
10058
+ for (let i = firstBrace; i < jsonStr.length; i++) {
10059
+ if (jsonStr[i] === "{") depth++;
10060
+ if (jsonStr[i] === "}") {
10061
+ depth--;
10062
+ if (depth === 0) {
10063
+ lastBrace = i;
10064
+ break;
10065
+ }
10066
+ }
10067
+ }
10068
+ if (lastBrace === -1) return null;
10069
+ const candidate = jsonStr.slice(firstBrace, lastBrace + 1);
10070
+ try {
10071
+ const parsed = JSON.parse(candidate);
10072
+ if (typeof parsed !== "object" || parsed === null) return null;
10073
+ if (!Array.isArray(parsed.tasks) || parsed.tasks.length === 0) return null;
10074
+ return {
10075
+ thinking: typeof parsed.thinking === "string" ? parsed.thinking : "",
10076
+ tasks: parsed.tasks
10077
+ };
10078
+ } catch {
10079
+ return null;
10080
+ }
10081
+ }
10082
+ function buildDirectorProjectContext(cwd) {
10083
+ const summaries = loadSummaries(cwd);
10084
+ const existingFiles = summaries ? Object.keys(summaries).slice(0, 20).join("\n") : "No indexed files found.";
10085
+ const hasVitest = fs16.existsSync(path20.join(cwd, "vitest.config.ts")) || fs16.existsSync(path20.join(cwd, "vitest.config.js"));
10086
+ const hasJest = fs16.existsSync(path20.join(cwd, "jest.config.ts")) || fs16.existsSync(path20.join(cwd, "jest.config.js"));
10087
+ const hasTests = hasVitest || hasJest;
10088
+ const assessmentLines = [];
10089
+ const fileExistsMap = {};
10090
+ if (summaries) {
10091
+ for (const filePath of Object.keys(summaries).slice(0, 15)) {
10092
+ const absolutePath = path20.join(cwd, filePath);
10093
+ if (fs16.existsSync(absolutePath)) {
10094
+ const content = fs16.readFileSync(absolutePath, "utf-8");
10095
+ const lines = content.split("\n").length;
10096
+ const isEmpty = content.trim().length < 20;
10097
+ const isPlaceholder = content.includes("// file content here") || content.includes("// TODO implement");
10098
+ if (isEmpty || isPlaceholder) {
10099
+ assessmentLines.push(`\u274C EMPTY: ${filePath}`);
10100
+ fileExistsMap[filePath] = false;
10101
+ } else {
10102
+ assessmentLines.push(`\u2705 EXISTS (${lines} lines): ${filePath}`);
10103
+ fileExistsMap[filePath] = true;
10104
+ }
10105
+ } else {
10106
+ assessmentLines.push(`\u{1F532} MISSING: ${filePath}`);
10107
+ fileExistsMap[filePath] = false;
10108
+ }
10109
+ }
10110
+ }
10111
+ return { existingFiles, hasTests, fileAssessment: assessmentLines.join("\n"), fileExistsMap };
10112
+ }
10113
+ async function generateTaskMap(missionDescription, agents, cwd, localEndpoint) {
10114
+ const agentList = agents.map((a) => `${a.name}: ${a.task}`).join("\n");
10115
+ const { existingFiles, hasTests, fileAssessment, fileExistsMap } = buildDirectorProjectContext(cwd);
10116
+ const systemPrompt = `You are DirectorBob \u2014 a senior engineering lead.
10117
+ You MUST respond with ONLY valid JSON matching the exact schema provided.
10118
+ No markdown. No explanation before or after the JSON object.`;
10119
+ const userPrompt = `MISSION: ${missionDescription}
10120
+
10121
+ AGENTS:
10122
+ ${agentList}
10123
+
10124
+ FILE ASSESSMENT (current project state):
10125
+ ${fileAssessment || "No assessment available \u2014 project may not be indexed."}
10126
+
10127
+ ALL EXISTING FILES:
10128
+ ${existingFiles}
10129
+
10130
+ RULES:
10131
+ 1. Generate 3 to 6 specific, actionable tasks.
10132
+ 2. Each task must reference EXACT file paths.
10133
+ 3. For files marked \u2705 EXISTS \u2014 write instructions as "ADD [specific thing] to [file]". Never "refactor" or "rewrite". Never generate CREATE tasks for files that already exist.
10134
+ 4. For files marked \u274C EMPTY or \u{1F532} MISSING \u2014 agents should create or fill them.
10135
+ 5. ${hasTests ? "Test tasks allowed." : "NO test tasks \u2014 no test framework configured."}
10136
+ 6. Agent names must NOT include @ symbol.
10137
+ 7. dependsOn uses t1, t2, t3 format.
10138
+ 8. operationType must be one of: CREATE, PATCH, REFACTOR, REPLACE
10139
+ - CREATE: file does not exist yet \u2014 NEVER use for files marked \u2705 EXISTS
10140
+ - PATCH: add or change a specific function or block in existing file
10141
+ - REFACTOR: structural change that preserves all exports
10142
+ - REPLACE: full rewrite \u2014 only for empty or placeholder files
10143
+
10144
+ Respond with ONLY this JSON structure:
10145
+ {
10146
+ "thinking": "<your reasoning about task decomposition \u2014 2-3 sentences>",
10147
+ "tasks": [
10148
+ {
10149
+ "assignedTo": "agentName",
10150
+ "instruction": "specific instruction referencing exact file path",
10151
+ "operationType": "CREATE|PATCH|REFACTOR|REPLACE",
10152
+ "dependsOn": []
10153
+ }
10154
+ ]
10155
+ }`;
10156
+ const messages = [
10157
+ { role: "system", content: systemPrompt },
10158
+ { role: "user", content: userPrompt }
10159
+ ];
10160
+ let parsed = null;
10161
+ try {
10162
+ const rawResponse = await callLocalModel(localEndpoint, messages);
10163
+ const responseText = typeof rawResponse === "object" && rawResponse.text ? rawResponse.text : rawResponse;
10164
+ parsed = parseTaskMapResponse(responseText);
10165
+ if (parsed) {
10166
+ console.log(`[DIRECTORBOB] Thinking: ${parsed.thinking.slice(0, 100)}...`);
10167
+ }
10168
+ } catch {
10169
+ }
10170
+ if (!parsed) {
10171
+ console.error(`[DIRECTORBOB] Task map attempt 1 failed \u2014 retrying...`);
10172
+ try {
10173
+ const retryMessages = [
10174
+ { role: "system", content: systemPrompt },
10175
+ {
10176
+ role: "user",
10177
+ content: userPrompt + "\n\nIMPORTANT: Your previous response was not valid JSON. Respond with ONLY the JSON object. No text before or after it."
10178
+ }
10179
+ ];
10180
+ const retryResponse = await callLocalModel(localEndpoint, retryMessages);
10181
+ const retryText = typeof retryResponse === "object" && retryResponse.text ? retryResponse.text : retryResponse;
10182
+ parsed = parseTaskMapResponse(retryText);
10183
+ } catch {
10184
+ }
10185
+ }
10186
+ if (!parsed) {
10187
+ console.error(`[DIRECTORBOB] Task map failed after retry \u2014 using fallback.`);
10188
+ return agents.map((agent, idx) => ({
10189
+ assignedTo: agent.name.replace(/^@+/, ""),
10190
+ instruction: `${agent.task}. Context: ${missionDescription}. Make surgical changes only.`,
10191
+ operationType: "CREATE",
10192
+ dependsOn: idx > 0 ? [`__TASK_${idx - 1}__`] : [],
10193
+ outputFile: null,
10194
+ satisfactionTarget: 75,
10195
+ stagnationLimit: 3,
10196
+ directorLimit: DEFAULT_DIRECTOR_LIMIT
10197
+ }));
10198
+ }
10199
+ return parsed.tasks.map((t) => {
10200
+ const deps = (t.dependsOn || []).map((dep) => {
10201
+ const match = dep.match(/t(\d+)/i);
10202
+ if (match) {
10203
+ const depIdx = parseInt(match[1]) - 1;
10204
+ return depIdx >= 0 && depIdx < parsed.tasks.length ? `__TASK_${depIdx}__` : null;
10205
+ }
10206
+ return null;
10207
+ }).filter(Boolean);
10208
+ const instructionLower = (t.instruction || "").toLowerCase();
10209
+ const mentionedFile = Object.keys(fileExistsMap).find(
10210
+ (f) => instructionLower.includes(f.toLowerCase())
10211
+ );
10212
+ const fileExists = mentionedFile ? fileExistsMap[mentionedFile] : false;
10213
+ const operationType = ["CREATE", "PATCH", "REFACTOR", "REPLACE"].includes(t.operationType) ? t.operationType : inferOperationType(t.instruction || "", fileExists);
10214
+ return {
10215
+ assignedTo: (t.assignedTo || "").replace(/^@+/, ""),
10216
+ instruction: t.instruction,
10217
+ operationType,
10218
+ dependsOn: deps,
10219
+ outputFile: t.outputFile || null,
10220
+ satisfactionTarget: inferSatisfactionTarget(t.instruction || ""),
10221
+ stagnationLimit: inferStagnationLimit(t.instruction || ""),
10222
+ directorLimit: DEFAULT_DIRECTOR_LIMIT
10223
+ };
10224
+ });
10225
+ }
10226
+ async function directorIntervene(task, stuckAgent, allAgents, mission, cwd, localEndpoint) {
10227
+ const agentSummaries = allAgents.map((a) => {
10228
+ const summary = loadAgentSummary(a.name, cwd);
10229
+ return `@${a.name} (${a.task}):
10230
+ ${summary || "No summary yet."}`;
10231
+ }).join("\n\n");
10232
+ const { existingFiles } = buildDirectorProjectContext(cwd);
10233
+ const prompt = `You are DirectorBob. @${stuckAgent.name} is stagnating.
10234
+
10235
+ STUCK TASK: ${task.instruction}
10236
+ OPERATION TYPE: ${task.operationType}
10237
+ AGENT: ${stuckAgent.name} \u2014 ${stuckAgent.task}
10238
+ LAST SCORE: ${task.lastSatisfactionScore}% / TARGET: ${task.satisfactionTarget}%
10239
+ ATTEMPTS: ${task.attemptCount}
10240
+
10241
+ RECENT NOTES:
10242
+ ${task.notes.slice(-3).join("\n")}
10243
+
10244
+ TEAM: ${agentSummaries}
10245
+ EXISTING FILES: ${existingFiles}
10246
+
10247
+ Give ONE specific directive. Reference actual file paths. 2 sentences max. Plain text only.
10248
+ ${task.operationType === "PATCH" ? "Remind the agent: output the COMPLETE file with ONLY the targeted change in the JSON files array \u2014 do NOT write a partial file." : ""}`;
10249
+ try {
10250
+ const messages = [
10251
+ { role: "system", content: "You are DirectorBob. 2-sentence directive. Reference real files. Plain text only." },
10252
+ { role: "user", content: prompt }
10253
+ ];
10254
+ const rawResponse = await callLocalModel(localEndpoint, messages);
10255
+ const note = typeof rawResponse === "object" && rawResponse.text ? rawResponse.text : rawResponse;
10256
+ return note.trim().split("\n")[0].slice(0, 200);
10257
+ } catch {
10258
+ return `Output the complete file with only the targeted change in your JSON response files array. Make surgical changes only.`;
10259
+ }
10260
+ }
10261
+ async function handlePendingCommits(cwd, localEndpoint, taskMap, mission, state, onEvent) {
10262
+ const pending = loadPendingCommits(cwd);
10263
+ if (pending.length === 0) return;
10264
+ const emit = (type, message, data) => {
10265
+ if (onEvent) onEvent({ type, agentName: "directorBob", taskId: "director", message, data });
10266
+ };
10267
+ for (const commit of pending) {
10268
+ emit("thinking", `DirectorBob reviewing commit from @${commit.agentName}: "${commit.message}"`);
10269
+ const task = taskMap.get(commit.taskId);
10270
+ const taskInstruction = task?.instruction || commit.message;
10271
+ const review = await reviewCommit(
10272
+ taskInstruction,
10273
+ commit.message,
10274
+ commit.agentName,
10275
+ commit.filesChanged,
10276
+ cwd,
10277
+ localEndpoint,
10278
+ task?.operationType
10279
+ );
10280
+ saveCommitReview(review, commit.missionId, commit.taskId, cwd);
10281
+ if (review.verdict === "APPROVE") {
10282
+ try {
10283
+ const simpleGit3 = (await import("simple-git")).default;
10284
+ const git = simpleGit3(cwd);
10285
+ await git.add(".");
10286
+ const result = await git.commit(commit.message);
10287
+ clearPendingCommitsForTask(commit.taskId, cwd);
10288
+ emit("done", `DirectorBob approved \u2705 committed: ${result.commit?.slice(0, 7)} \u2014 ${commit.message}`);
10289
+ state.commitDenialCounts.delete(commit.taskId);
10290
+ if (review.filesReviewed.some((f) => f.verdict === "WARN")) {
10291
+ emit(
10292
+ "thinking",
10293
+ `\u26A0\uFE0F Warnings on: ${review.filesReviewed.filter((f) => f.verdict === "WARN").map((f) => f.filePath).join(", ")}`
10294
+ );
10295
+ }
10296
+ } catch (e) {
10297
+ clearPendingCommitsForTask(commit.taskId, cwd);
10298
+ emit("error", `Commit failed after approval: ${e.message}`);
10299
+ }
10300
+ } else {
10301
+ const { restored, failed } = restoreDeniedFiles(review, cwd);
10302
+ clearPendingCommit(commit.id, cwd);
10303
+ const denials = (state.commitDenialCounts.get(commit.taskId) || 0) + 1;
10304
+ state.commitDenialCounts.set(commit.taskId, denials);
10305
+ emit("error", `DirectorBob DENIED commit from @${commit.agentName}: ${review.reason}`);
10306
+ if (restored.length > 0) {
10307
+ emit("thinking", `Restored ${restored.length} file(s) from backup: ${restored.join(", ")}`);
10308
+ }
10309
+ if (failed.length > 0) {
10310
+ emit("error", `Could not restore ${failed.length} file(s): ${failed.join(", ")}`);
10311
+ }
10312
+ if (denials >= MAX_COMMIT_DENIALS) {
10313
+ if (task) {
10314
+ updateTaskStatus(mission, task.id, "stagnated", cwd);
10315
+ addTaskNote(
10316
+ mission,
10317
+ task.id,
10318
+ `COMMIT DENIED ${denials} times. User intervention required. Last reason: ${review.reason}`,
10319
+ cwd
10320
+ );
10321
+ }
10322
+ state.paused = true;
10323
+ emit(
10324
+ "error",
10325
+ `@${commit.agentName} reached maximum commit denials (${MAX_COMMIT_DENIALS}). Mission paused \u2014 user intervention needed.`
10326
+ );
10327
+ emit(
10328
+ "thinking",
10329
+ `Type /resume after reviewing the task, or /skip ${commit.taskId} to skip it.`
10330
+ );
10331
+ } else if (review.revisionNote && task) {
10332
+ const revisionMessage = `COMMIT DENIED (${denials}/${MAX_COMMIT_DENIALS}): ${review.revisionNote}`;
10333
+ addTaskNote(mission, task.id, revisionMessage, cwd);
10334
+ emit("response", `DirectorBob \u2192 @${commit.agentName}: ${review.revisionNote}`);
10335
+ task.status = "pending";
10336
+ task.lastSatisfactionScore = null;
10337
+ saveMission(mission, cwd);
10338
+ emit(
10339
+ "thinking",
10340
+ `Task reopened for @${commit.agentName} (denial ${denials}/${MAX_COMMIT_DENIALS}). Agent will retry with feedback.`
10341
+ );
10342
+ } else if (review.revisionNote) {
10343
+ emit("response", `DirectorBob \u2192 @${commit.agentName}: ${review.revisionNote}`);
10344
+ }
10345
+ }
10346
+ }
10347
+ }
10348
+ async function runAutonomousLoop(mission, agents, cwd, localEndpoint, state, onEvent) {
10349
+ const emit = (type, message, data) => {
10350
+ if (onEvent) onEvent({ type, agentName: "directorBob", taskId: "director", message, data });
10351
+ };
10352
+ emit("thinking", "DirectorBob online. Autonomous loop starting...");
10353
+ const activeExecutions = /* @__PURE__ */ new Map();
10354
+ const taskMap = /* @__PURE__ */ new Map();
10355
+ for (const task of mission.tasks) {
10356
+ taskMap.set(task.id, task);
10357
+ }
10358
+ while (true) {
10359
+ if (state.aborted) {
10360
+ mission.status = "aborted";
10361
+ saveMission(mission, cwd);
10362
+ return { mission, completed: false, aborted: true, surfacedToUser: false };
10363
+ }
10364
+ if (state.paused) {
10365
+ await new Promise((r) => setTimeout(r, 1e3));
10366
+ continue;
10367
+ }
10368
+ if (activeExecutions.size === 0 && !state.aborted) {
10369
+ await handlePendingCommits(cwd, localEndpoint, taskMap, mission, state, onEvent);
10370
+ }
10371
+ if (isMissionComplete(mission)) {
10372
+ if (activeExecutions.size === 0 && !state.aborted) {
10373
+ await handlePendingCommits(cwd, localEndpoint, taskMap, mission, state, onEvent);
10374
+ }
10375
+ mission.status = "completed";
10376
+ mission.completedAt = (/* @__PURE__ */ new Date()).toISOString();
10377
+ saveMission(mission, cwd);
10378
+ emit("done", "All tasks complete. Mission accomplished.");
10379
+ return { mission, completed: true, aborted: false, surfacedToUser: false };
10380
+ }
10381
+ if (isMissionBlocked(mission) && activeExecutions.size === 0) {
10382
+ mission.status = "failed";
10383
+ saveMission(mission, cwd);
10384
+ emit("error", "Mission blocked \u2014 no tasks can proceed.");
10385
+ return {
10386
+ mission,
10387
+ completed: false,
10388
+ aborted: false,
10389
+ surfacedToUser: true,
10390
+ surfaceReason: "Mission blocked \u2014 dependency cycle or all tasks stagnated."
10391
+ };
10392
+ }
10393
+ const readyTasks = getReadyTasks(mission).filter((t) => !activeExecutions.has(t.id));
10394
+ const availableSlots = MAX_PARALLEL_TASKS - activeExecutions.size;
10395
+ const tasksToFire = readyTasks.slice(0, availableSlots);
10396
+ for (const task of tasksToFire) {
10397
+ const agent = agents.find((a) => a.name === task.assignedTo);
10398
+ if (!agent) {
10399
+ emit("error", `No agent found for: ${task.assignedTo}`);
10400
+ updateTaskStatus(mission, task.id, "failed", cwd);
10401
+ continue;
10402
+ }
10403
+ updateTaskStatus(mission, task.id, "running", cwd);
10404
+ emit("thinking", `Dispatching @${agent.name} \u2192 [${task.operationType}] ${task.instruction.slice(0, 50)}...`);
10405
+ if (state.satisfactionOverrides[agent.name] !== void 0) {
10406
+ task.satisfactionTarget = state.satisfactionOverrides[agent.name];
10407
+ addTaskNote(mission, task.id, `Target overridden to ${task.satisfactionTarget}% by user.`, cwd);
10408
+ }
10409
+ if (state.userInjections.length > 0) {
10410
+ const injection = state.userInjections.shift();
10411
+ if (injection) addTaskNote(mission, task.id, `Injection: ${injection}`, cwd);
10412
+ }
10413
+ const executionPromise = executeTaskAttempt(
10414
+ task,
10415
+ agent,
10416
+ agents,
10417
+ mission,
10418
+ cwd,
10419
+ localEndpoint,
10420
+ onEvent
10421
+ ).then(async (result) => {
10422
+ activeExecutions.delete(task.id);
10423
+ if (state.aborted) return result;
10424
+ if (result.isDone) {
10425
+ emit("thinking", `DirectorBob reviewing @${agent.name}'s completed work...`);
10426
+ const filesWritten = [
10427
+ ...result.filesCreated.map((f) => ({ filePath: f, isNew: true })),
10428
+ ...result.filesModified.map((f) => ({ filePath: f, isNew: false }))
10429
+ ];
10430
+ const completionReview = await reviewTaskCompletion(
10431
+ task.instruction,
10432
+ agent.name,
10433
+ result.response,
10434
+ filesWritten,
10435
+ task.attemptCount,
10436
+ cwd,
10437
+ localEndpoint,
10438
+ task.operationType
10439
+ );
10440
+ if (completionReview.verdict === "APPROVED") {
10441
+ updateTaskResult(
10442
+ mission,
10443
+ task.id,
10444
+ result.response,
10445
+ result.filesCreated,
10446
+ result.filesModified,
10447
+ cwd
10448
+ );
10449
+ emit("done", `DirectorBob \u2705 approved @${agent.name}'s work: ${task.instruction.slice(0, 50)}`);
10450
+ } else if (completionReview.verdict === "REVISION_NEEDED") {
10451
+ const revisionMsg = `TASK REVIEW: ${completionReview.revisionNote || completionReview.reason}`;
10452
+ addTaskNote(mission, task.id, revisionMsg, cwd);
10453
+ emit("response", `DirectorBob \u2192 @${agent.name}: ${completionReview.revisionNote || completionReview.reason}`);
10454
+ updateTaskStatus(mission, task.id, "pending", cwd);
10455
+ emit("thinking", `Task reopened for @${agent.name} \u2014 revision needed before marking complete.`);
10456
+ } else {
10457
+ updateTaskStatus(mission, task.id, "stagnated", cwd);
10458
+ addTaskNote(mission, task.id, `ESCALATED: ${completionReview.reason}`, cwd);
10459
+ emit("error", `DirectorBob escalating @${agent.name}'s task \u2014 user intervention needed: ${completionReview.reason}`);
10460
+ state.paused = true;
10461
+ }
10462
+ } else if (result.needsUser) {
10463
+ updateTaskStatus(mission, task.id, "stagnated", cwd);
10464
+ emit("error", `@${agent.name} needs user help: ${task.instruction.slice(0, 50)}`);
10465
+ state.paused = true;
10466
+ } else if (result.isStagnating && result.needsDirector) {
10467
+ emit("thinking", `DirectorBob intervening for @${agent.name}...`);
10468
+ const note = await directorIntervene(task, agent, agents, mission, cwd, localEndpoint);
10469
+ emit("response", `DirectorBob \u2192 @${agent.name}: ${note}`);
10470
+ addTaskNote(mission, task.id, `Director: ${note}`, cwd);
10471
+ updateTaskStatus(mission, task.id, "pending", cwd);
10472
+ } else {
10473
+ updateTaskStatus(mission, task.id, "pending", cwd);
10474
+ }
10475
+ return result;
10476
+ });
10477
+ activeExecutions.set(task.id, executionPromise);
10478
+ }
10479
+ if (activeExecutions.size > 0) {
10480
+ await Promise.race(activeExecutions.values());
10481
+ } else {
10482
+ await new Promise((r) => setTimeout(r, TASK_LOOP_INTERVAL_MS));
10483
+ }
10484
+ }
10485
+ }
10486
+ function updateTaskStatus(mission, taskId, status, workingDir) {
10487
+ const task = mission.tasks.find((t) => t.id === taskId);
10488
+ if (!task) return;
10489
+ task.status = status;
10490
+ if (status === "running" && !task.startedAt) {
10491
+ task.startedAt = (/* @__PURE__ */ new Date()).toISOString();
10492
+ }
10493
+ if ((status === "completed" || status === "failed" || status === "stagnated") && !task.completedAt) {
10494
+ task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
10495
+ }
10496
+ saveMission(mission, workingDir);
10497
+ }
10498
+
10499
+ // src/ui/agent-run-renderer.ts
10500
+ import chalk29 from "chalk";
10501
+ import * as fs17 from "fs";
10502
+ import * as path21 from "path";
10503
+ import { diffLines as diffLines2 } from "diff";
10504
+ var PURPLE4 = chalk29.hex("#AB47BC");
10505
+ var AMBER9 = chalk29.hex("#FFAB00");
10506
+ var GREEN9 = chalk29.hex("#66BB6A");
10507
+ var RED8 = chalk29.hex("#EF5350");
10508
+ var CYAN9 = chalk29.cyan;
10509
+ var GRAY8 = chalk29.gray;
10510
+ var BLUE4 = chalk29.hex("#42A5F5");
10511
+ var BORDER13 = chalk29.hex("#455A64");
10512
+ var WHITE6 = chalk29.white;
10513
+ var ORANGE2 = chalk29.hex("#FF7043");
10514
+ var DIRECTOR_COLOR = chalk29.hex("#FFD700");
10515
+ var BRAND_SECONDARY15 = chalk29.hex("#FFAB00");
10516
+ function isLeakedContent(line) {
10517
+ const t = line.trim();
10518
+ if (t.startsWith("[Tool:")) return true;
10519
+ if (t.startsWith("TOOL_CALL:")) return true;
10520
+ if (t.startsWith("tool_call:")) return true;
10521
+ if (t.startsWith("import ")) return true;
10522
+ if (t.startsWith("export ")) return true;
10523
+ if (t.startsWith("const ")) return true;
10524
+ if (t.startsWith("let ")) return true;
10525
+ if (t.startsWith("var ")) return true;
10526
+ if (t.startsWith("function ")) return true;
10527
+ if (t.startsWith("class ")) return true;
10528
+ if (t.startsWith("#!/")) return true;
10529
+ if (t.startsWith("// File:")) return true;
10530
+ if (t.startsWith("```")) return true;
10531
+ if (t === "{" || t === "}") return true;
10532
+ return false;
10533
+ }
10534
+ function cleanAgentResponse(response) {
10535
+ return response.split("\n").filter((l) => l.trim()).filter((l) => !isLeakedContent(l)).slice(0, 4);
10536
+ }
10537
+ function renderMissionHeader(mission, agentNames) {
10538
+ const chips = agentNames.map((n) => renderAgentChip(n, agentNames, true)).join(" ");
10539
+ console.log("");
10540
+ console.log(BORDER13(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
10541
+ console.log(BORDER13(" \u2551") + DIRECTOR_COLOR(" \u{1F3AC} DirectorBob ") + GRAY8("\u2014 Autonomous Mission Control"));
10542
+ console.log(BORDER13(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10543
+ console.log(BORDER13(" \u2551") + ` ${WHITE6(mission.description.slice(0, 56))}${mission.description.length > 56 ? GRAY8("...") : ""}`);
10544
+ console.log(BORDER13(" \u2551") + GRAY8(` Mission: ${mission.id} \u2502 Tasks: ${mission.tasks.length} \u2502 Team: ${agentNames.length} agents`));
10545
+ console.log(BORDER13(" \u2551"));
10546
+ console.log(BORDER13(" \u2551") + ` ${chips}`);
10547
+ console.log(BORDER13(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10548
+ console.log(BORDER13(" \u2551") + AMBER9(" Live Commands:"));
10549
+ console.log(BORDER13(" \u2551") + CYAN9(" /pause") + GRAY8(" \u2014 pause after active tasks"));
10550
+ console.log(BORDER13(" \u2551") + CYAN9(" /resume") + GRAY8(" \u2014 resume from pause"));
10551
+ console.log(BORDER13(" \u2551") + CYAN9(" /status") + GRAY8(" \u2014 full task map"));
10552
+ console.log(BORDER13(" \u2551") + CYAN9(" /view-targets") + GRAY8(" \u2014 satisfaction targets"));
10553
+ console.log(BORDER13(" \u2551") + CYAN9(" /set-target <agent> <n>") + GRAY8(" \u2014 adjust target"));
10554
+ console.log(BORDER13(" \u2551") + CYAN9(' /inject "note"') + GRAY8(" \u2014 director note"));
10555
+ console.log(BORDER13(" \u2551") + CYAN9(" /approve-commit") + GRAY8(" \u2014 approve pending commit"));
10556
+ console.log(BORDER13(" \u2551") + CYAN9(" /deny-commit") + GRAY8(" \u2014 deny pending commit"));
10557
+ console.log(BORDER13(" \u2551") + CYAN9(" /skip <taskId>") + GRAY8(" \u2014 skip a task"));
10558
+ console.log(BORDER13(" \u2551") + CYAN9(" /abort") + GRAY8(" \u2014 stop everything"));
10559
+ console.log(BORDER13(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
10560
+ console.log("");
10561
+ }
10562
+ function renderTaskMap(mission, agentNames) {
10563
+ const summary = getMissionSummary(mission);
10564
+ console.log("");
10565
+ console.log(DIRECTOR_COLOR(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
10566
+ console.log(DIRECTOR_COLOR(" \u2551") + AMBER9(" \u{1F4CB} Task Map") + GRAY8(` \u2014 ${summary.total} tasks`));
10567
+ console.log(DIRECTOR_COLOR(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10568
+ const running = mission.tasks.filter((t) => t.status === "running");
10569
+ const pending = mission.tasks.filter((t) => t.status === "pending");
10570
+ const completed = mission.tasks.filter((t) => t.status === "completed");
10571
+ const failed = mission.tasks.filter((t) => t.status === "failed" || t.status === "stagnated");
10572
+ const skipped = mission.tasks.filter((t) => t.status === "skipped");
10573
+ if (running.length > 0) {
10574
+ console.log(DIRECTOR_COLOR(" \u2551") + CYAN9(" \u23F3 RUNNING"));
10575
+ for (const task of running) renderTaskRow(task, agentNames, true);
10576
+ console.log(DIRECTOR_COLOR(" \u2551"));
10577
+ }
10578
+ if (pending.length > 0) {
10579
+ console.log(DIRECTOR_COLOR(" \u2551") + GRAY8(" \u23F8 QUEUED"));
10580
+ for (const task of pending) renderTaskRow(task, agentNames, false);
10581
+ console.log(DIRECTOR_COLOR(" \u2551"));
10582
+ }
10583
+ if (completed.length > 0) {
10584
+ console.log(DIRECTOR_COLOR(" \u2551") + GREEN9(" \u2705 COMPLETED"));
10585
+ for (const task of completed) renderTaskRow(task, agentNames, false);
10586
+ console.log(DIRECTOR_COLOR(" \u2551"));
10587
+ }
10588
+ if (failed.length > 0) {
10589
+ console.log(DIRECTOR_COLOR(" \u2551") + RED8(" \u274C FAILED / STAGNATED"));
10590
+ for (const task of failed) renderTaskRow(task, agentNames, false);
10591
+ console.log(DIRECTOR_COLOR(" \u2551"));
10592
+ }
10593
+ if (skipped.length > 0) {
10594
+ console.log(DIRECTOR_COLOR(" \u2551") + GRAY8(" \u23ED\uFE0F SKIPPED"));
10595
+ for (const task of skipped) renderTaskRow(task, agentNames, false);
10596
+ console.log(DIRECTOR_COLOR(" \u2551"));
10597
+ }
10598
+ console.log(DIRECTOR_COLOR(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10599
+ console.log(
10600
+ DIRECTOR_COLOR(" \u2551") + " " + GREEN9(`\u2705 ${summary.completed}`) + GRAY8(" done ") + CYAN9(`\u23F3 ${summary.running}`) + GRAY8(" running ") + GRAY8(`\u23F8 ${summary.pending}`) + GRAY8(" queued") + (summary.failed > 0 ? " " + RED8(`\u274C ${summary.failed}`) + GRAY8(" failed") : "") + (summary.stagnated > 0 ? " " + ORANGE2(`\u26A0\uFE0F ${summary.stagnated}`) + GRAY8(" stagnated") : "")
10601
+ );
10602
+ const barWidth = 46;
10603
+ const filled = Math.round(summary.percentComplete / 100 * barWidth);
10604
+ const empty = barWidth - filled;
10605
+ let barColor;
10606
+ if (summary.percentComplete >= 75) barColor = chalk29.green;
10607
+ else if (summary.percentComplete >= 50) barColor = chalk29.hex("#FFAB00");
10608
+ else if (summary.percentComplete >= 25) barColor = chalk29.hex("#FF7043");
10609
+ else barColor = chalk29.red;
10610
+ console.log(
10611
+ DIRECTOR_COLOR(" \u2551") + " " + GRAY8("[") + barColor("\u2588".repeat(filled)) + GRAY8("\u2591".repeat(empty)) + GRAY8("] ") + barColor(`${summary.percentComplete}%`) + GRAY8(` \u2014 ${summary.completed}/${summary.total}`)
10612
+ );
10613
+ console.log(DIRECTOR_COLOR(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
10614
+ console.log("");
10615
+ }
10616
+ function renderTaskRow(task, agentNames, showSatBar) {
10617
+ const statusIcon2 = getTaskStatusIcon(task.status);
10618
+ const chip = renderAgentChip(task.assignedTo, agentNames, task.status === "running");
10619
+ const instruction = task.instruction.slice(0, 44) + (task.instruction.length > 44 ? "..." : "");
10620
+ const depInfo = task.dependsOn.length > 0 ? GRAY8(` \u2190 ${task.dependsOn.length} dep${task.dependsOn.length > 1 ? "s" : ""}`) : "";
10621
+ console.log(DIRECTOR_COLOR(" \u2551") + ` ${statusIcon2} ${chip} ` + WHITE6(instruction) + depInfo);
10622
+ if (showSatBar && task.lastSatisfactionScore !== null) {
10623
+ const score = task.lastSatisfactionScore;
10624
+ const target = task.satisfactionTarget;
10625
+ const bar2 = getSatisfactionBar(score, target, 14);
10626
+ const color = chalk29.hex(getSatisfactionColor(score, target));
10627
+ console.log(
10628
+ DIRECTOR_COLOR(" \u2551") + ` ${color(bar2)} ` + color(`${score}%`) + GRAY8(` / ${target}% target`) + (task.attemptCount > 1 ? GRAY8(` \xB7 attempt ${task.attemptCount}`) : "")
10629
+ );
10630
+ }
10631
+ if ((task.status === "stagnated" || task.stagnationCount > 0) && task.notes.length > 0) {
10632
+ const lastNote = task.notes[task.notes.length - 1];
10633
+ console.log(DIRECTOR_COLOR(" \u2551") + ORANGE2(` \u26A0\uFE0F ${lastNote.slice(0, 60)}${lastNote.length > 60 ? "..." : ""}`));
10634
+ }
10635
+ }
10636
+ function renderExecutionEvent(event, agentNames) {
10637
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", {
10638
+ hour: "numeric",
10639
+ minute: "2-digit",
10640
+ second: "2-digit",
10641
+ hour12: true
10642
+ });
10643
+ const timeStamp = GRAY8(`[${time}]`);
10644
+ switch (event.type) {
10645
+ case "thinking": {
10646
+ if (event.agentName === "directorBob") {
10647
+ console.log(`${timeStamp} ${DIRECTOR_COLOR("\u{1F3AC} DirectorBob:")} ${GRAY8(event.message)}`);
10648
+ } else {
10649
+ const chip = renderAgentChip(event.agentName, agentNames, true);
10650
+ console.log(`${timeStamp} ${chip} ${GRAY8("thinking...")}`);
10651
+ }
10652
+ break;
10653
+ }
10654
+ case "response": {
10655
+ if (event.agentName === "directorBob") {
10656
+ console.log("");
10657
+ console.log(DIRECTOR_COLOR(" \u250C\u2500 DirectorBob \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
10658
+ const lines = event.message.split("\n").filter((l) => l.trim()).filter((l) => !isLeakedContent(l)).slice(0, 3);
10659
+ for (const line of lines) {
10660
+ console.log(DIRECTOR_COLOR(" \u2502") + DIRECTOR_COLOR(` ${line.slice(0, 58)}`));
10661
+ }
10662
+ console.log(DIRECTOR_COLOR(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
10663
+ console.log("");
10664
+ } else {
10665
+ const chip = renderAgentChip(event.agentName, agentNames, true);
10666
+ const { fg } = getAgentColorPair(event.agentName, agentNames);
10667
+ const lines = cleanAgentResponse(event.message);
10668
+ if (lines.length === 0) break;
10669
+ console.log("");
10670
+ console.log(` ${chip}`);
10671
+ for (const line of lines) {
10672
+ console.log(fg(` ${line.slice(0, 68)}${line.length > 68 ? "..." : ""}`));
10673
+ }
10674
+ }
10675
+ break;
10676
+ }
10677
+ case "tool_call": {
10678
+ const chip = renderAgentChip(event.agentName, agentNames, true);
10679
+ const toolName = event.data?.tool || "unknown";
10680
+ const toolColors = {
10681
+ createFile: GREEN9,
10682
+ modifyFile: AMBER9,
10683
+ readFile: CYAN9,
10684
+ writeOutput: BLUE4,
10685
+ readAgentOutput: BLUE4,
10686
+ gitCommit: PURPLE4,
10687
+ gitPush: PURPLE4
10688
+ };
10689
+ const toolColor = toolColors[toolName] || GRAY8;
10690
+ console.log(` ${chip} ${toolColor(`\u{1F527} ${toolName}`)}`);
10691
+ break;
10692
+ }
10693
+ case "tool_result": {
10694
+ if (event.data?.success) {
10695
+ console.log(GREEN9(` \u2705 ${event.message.slice(0, 72)}`));
10696
+ } else {
10697
+ console.log(RED8(` \u274C ${event.message.slice(0, 72)}`));
10698
+ }
10699
+ break;
10700
+ }
10701
+ case "satisfaction": {
10702
+ const score = event.data?.score ?? 0;
10703
+ const targetMatch = event.message.match(/target (\d+)%/);
10704
+ const target = targetMatch ? parseInt(targetMatch[1]) : 75;
10705
+ const isDone = event.data?.isDone;
10706
+ const isStagnating = event.data?.isStagnating;
10707
+ const color = chalk29.hex(getSatisfactionColor(score, target));
10708
+ const bar2 = getSatisfactionBar(score, target, 12);
10709
+ const statusTag = isDone ? GREEN9(" \u2705 DONE") : isStagnating ? ORANGE2(" \u26A0\uFE0F STAGNATING") : GRAY8(" working...");
10710
+ console.log(` ${color(bar2)} ` + color(`${score}%`) + GRAY8(` \u2192 target ${target}%`) + statusTag);
10711
+ break;
10712
+ }
10713
+ case "done": {
10714
+ console.log("");
10715
+ console.log(GREEN9(` \u2705 ${event.message}`));
10716
+ break;
10717
+ }
10718
+ case "stagnating": {
10719
+ console.log("");
10720
+ console.log(ORANGE2(` \u26A0\uFE0F ${event.message}`));
10721
+ break;
10722
+ }
10723
+ case "error": {
10724
+ console.log("");
10725
+ console.log(RED8(` \u274C ${event.message}`));
10726
+ break;
10727
+ }
10728
+ }
10729
+ }
10730
+ async function renderPostMissionFeedback(mission, cwd) {
10731
+ const readline11 = await import("readline");
10732
+ const path23 = await import("path");
10733
+ const fs18 = await import("fs");
10734
+ const os8 = await import("os");
10735
+ console.log("");
10736
+ console.log(DIRECTOR_COLOR(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
10737
+ console.log(DIRECTOR_COLOR(" \u2551") + AMBER9(" \u{1F4DD} Mission Feedback \u2014 Help train the agents"));
10738
+ console.log(DIRECTOR_COLOR(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10739
+ console.log(DIRECTOR_COLOR(" \u2551") + GRAY8(" Your feedback improves future missions."));
10740
+ console.log(DIRECTOR_COLOR(" \u2551") + GRAY8(" Press Enter to skip any task."));
10741
+ console.log(DIRECTOR_COLOR(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
10742
+ console.log("");
10743
+ const completedTasks = mission.tasks.filter((t) => t.status === "completed");
10744
+ const feedback = [];
10745
+ const rl = readline11.createInterface({ input: process.stdin, output: process.stdout });
10746
+ for (const task of completedTasks) {
10747
+ const chip = renderAgentChip(task.assignedTo, mission.tasks.map((t) => t.assignedTo), true);
10748
+ console.log(` ${chip} ${GRAY8(task.instruction.slice(0, 60))}`);
10749
+ const rating = await new Promise((resolve4) => {
10750
+ rl.question(AMBER9(" Rate (\u{1F44D} good / \u{1F44E} bad / skip): "), resolve4);
10751
+ });
10752
+ const trimmed = rating.trim().toLowerCase();
10753
+ if (trimmed === "" || trimmed === "skip") {
10754
+ console.log("");
10755
+ continue;
10756
+ }
10757
+ let comment = "";
10758
+ if (trimmed === "\u{1F44E}" || trimmed === "bad" || trimmed === "n") {
10759
+ comment = await new Promise((resolve4) => {
10760
+ rl.question(GRAY8(" What went wrong? (optional): "), resolve4);
10761
+ });
10762
+ }
10763
+ feedback.push({
10764
+ taskId: task.id,
10765
+ agentName: task.assignedTo,
10766
+ instruction: task.instruction,
10767
+ rating: trimmed === "\u{1F44D}" || trimmed === "good" || trimmed === "y" ? "good" : "bad",
10768
+ comment: comment.trim() || null,
10769
+ filesCreated: task.filesCreated,
10770
+ filesModified: task.filesModified,
10771
+ attemptCount: task.attemptCount
10772
+ });
10773
+ console.log("");
10774
+ }
10775
+ rl.close();
10776
+ if (feedback.length === 0) return;
10777
+ const projectName = path23.basename(cwd);
10778
+ const feedbackDir = path23.join(os8.homedir(), ".bob", "projects", projectName, "agents", "feedback");
10779
+ if (!fs18.existsSync(feedbackDir)) fs18.mkdirSync(feedbackDir, { recursive: true });
10780
+ const sessionFeedback = {
10781
+ missionId: mission.id,
10782
+ missionDescription: mission.description,
10783
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10784
+ tasks: feedback
10785
+ };
10786
+ const feedbackFile = path23.join(feedbackDir, `${mission.id}.json`);
10787
+ fs18.writeFileSync(feedbackFile, JSON.stringify(sessionFeedback, null, 2));
10788
+ const globalDir = path23.join(os8.homedir(), ".bob", "global", "agent-training");
10789
+ if (!fs18.existsSync(globalDir)) fs18.mkdirSync(globalDir, { recursive: true });
10790
+ const globalFile = path23.join(globalDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}_${mission.id}.json`);
10791
+ fs18.writeFileSync(globalFile, JSON.stringify(sessionFeedback, null, 2));
10792
+ console.log(GREEN9(` \u2705 Feedback saved. Thank you for helping train the agents.`));
10793
+ console.log(GRAY8(` Saved to: ~/.bob/projects/${projectName}/agents/feedback/`));
10794
+ console.log(GRAY8(` Global: ~/.bob/global/agent-training/`));
10795
+ console.log("");
10796
+ }
10797
+ function renderMissionComplete(mission) {
10798
+ const summary = getMissionSummary(mission);
10799
+ const duration = mission.startedAt && mission.completedAt ? Math.round((new Date(mission.completedAt).getTime() - new Date(mission.startedAt).getTime()) / 1e3) : 0;
10800
+ const minutes = Math.floor(duration / 60);
10801
+ const seconds = duration % 60;
10802
+ const durationLabel = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
10803
+ console.log("");
10804
+ console.log(BORDER13(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
10805
+ console.log(BORDER13(" \u2551") + DIRECTOR_COLOR(" \u{1F3C1} DirectorBob \u2014 Mission Complete"));
10806
+ console.log(BORDER13(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10807
+ console.log(BORDER13(" \u2551") + WHITE6(` ${mission.description.slice(0, 56)}`));
10808
+ console.log(BORDER13(" \u2551"));
10809
+ console.log(BORDER13(" \u2551") + GREEN9(` \u2705 Tasks completed: ${summary.completed}/${summary.total}`));
10810
+ if (summary.failed > 0) console.log(BORDER13(" \u2551") + RED8(` \u274C Tasks failed: ${summary.failed}`));
10811
+ if (summary.stagnated > 0) console.log(BORDER13(" \u2551") + ORANGE2(` \u26A0\uFE0F Tasks stagnated: ${summary.stagnated}`));
10812
+ if (summary.skipped > 0) console.log(BORDER13(" \u2551") + GRAY8(` \u23ED\uFE0F Tasks skipped: ${summary.skipped}`));
10813
+ console.log(BORDER13(" \u2551"));
10814
+ console.log(BORDER13(" \u2551") + CYAN9(` \u{1F4C1} Files created: ${mission.totalFilesCreated}`));
10815
+ console.log(BORDER13(" \u2551") + CYAN9(` \u270F\uFE0F Files modified: ${mission.totalFilesModified}`));
10816
+ if (duration > 0) console.log(BORDER13(" \u2551") + GRAY8(` \u23F1 Duration: ${durationLabel}`));
10817
+ console.log(BORDER13(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10818
+ console.log(BORDER13(" \u2551") + GRAY8(" Next steps:"));
10819
+ console.log(BORDER13(" \u2551") + CYAN9(" bob analyse --results") + GRAY8(" \u2014 review what changed"));
10820
+ console.log(BORDER13(" \u2551") + CYAN9(" bob backup restore") + GRAY8(" \u2014 undo if needed"));
10821
+ console.log(BORDER13(" \u2551") + CYAN9(' bob push "mission complete"') + GRAY8(" \u2014 commit changes"));
10822
+ console.log(BORDER13(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
10823
+ console.log("");
10824
+ }
10825
+ async function renderPostMissionCommitPrompt(mission, cwd) {
10826
+ const readline11 = await import("readline");
10827
+ const allCreated = [];
10828
+ const allModified = [];
10829
+ for (const task of mission.tasks) {
10830
+ if (task.status !== "completed") continue;
10831
+ for (const f of task.filesCreated) {
10832
+ if (!allCreated.includes(f)) allCreated.push(f);
10833
+ }
10834
+ for (const f of task.filesModified) {
10835
+ if (!allModified.includes(f) && !allCreated.includes(f)) allModified.push(f);
10836
+ }
10837
+ }
10838
+ const totalFiles = allCreated.length + allModified.length;
10839
+ if (totalFiles === 0) return;
10840
+ console.log("");
10841
+ console.log(BORDER13(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
10842
+ console.log(BORDER13(" \u2551") + AMBER9(" \u{1F4E6} Mission Changes \u2014 Ready to Commit"));
10843
+ console.log(BORDER13(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10844
+ console.log("");
10845
+ for (const filePath of allCreated) {
10846
+ const absolutePath = path21.join(cwd, filePath);
10847
+ let lineCount = 0;
10848
+ try {
10849
+ const content = fs17.readFileSync(absolutePath, "utf-8");
10850
+ lineCount = content.split("\n").length;
10851
+ } catch {
10852
+ }
10853
+ console.log(GREEN9(` \u25C6 Created ${filePath}`));
10854
+ console.log(chalk29.bgHex("#0D2B0D")(chalk29.white(` + New file (${lineCount} lines)`)));
10855
+ console.log("");
10856
+ }
10857
+ for (const filePath of allModified) {
10858
+ const absolutePath = path21.join(cwd, filePath);
10859
+ let additions = 0;
10860
+ let removals = 0;
10861
+ const diffPreview = [];
10862
+ try {
10863
+ const backupDir = path21.join(cwd, ".bob-backups");
10864
+ if (fs17.existsSync(backupDir) && fs17.existsSync(absolutePath)) {
10865
+ const safeName = filePath.replace(/[\/\\]/g, "_");
10866
+ const backups = fs17.readdirSync(backupDir).filter((f) => f.startsWith(safeName) && f.endsWith(".bak")).sort().reverse();
10867
+ if (backups.length > 0) {
10868
+ const originalContent = fs17.readFileSync(path21.join(backupDir, backups[0]), "utf-8");
10869
+ const currentContent = fs17.readFileSync(absolutePath, "utf-8");
10870
+ const changes = diffLines2(originalContent, currentContent);
10871
+ for (const change of changes) {
10872
+ const lines = change.value.split("\n").filter((l) => l !== "");
10873
+ for (const line of lines) {
10874
+ if (change.added) {
10875
+ additions++;
10876
+ if (diffPreview.length < 4) {
10877
+ diffPreview.push(chalk29.bgHex("#0D2B0D")(chalk29.white(` + ${line.slice(0, 60)}${line.length > 60 ? "..." : ""}`)));
10878
+ }
10879
+ } else if (change.removed) {
10880
+ removals++;
10881
+ if (diffPreview.length < 4) {
10882
+ diffPreview.push(chalk29.bgHex("#2D0D0D")(chalk29.white(` - ${line.slice(0, 60)}${line.length > 60 ? "..." : ""}`)));
10883
+ }
10884
+ }
10885
+ }
10886
+ }
10887
+ }
10888
+ }
10889
+ } catch {
10890
+ }
10891
+ console.log(BRAND_SECONDARY15(` \u25C6 Modified ${filePath}`));
10892
+ for (const line of diffPreview) console.log(line);
10893
+ if (additions > 0 || removals > 0) {
10894
+ console.log(GRAY8(` ${GREEN9(`+${additions}`)} ${RED8(`-${removals}`)}`));
10895
+ }
10896
+ console.log("");
10897
+ }
10898
+ console.log(BORDER13(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
10899
+ console.log(BORDER13(" \u2551") + GRAY8(` ${allCreated.length} created \u2502 ${allModified.length} modified \u2502 ${totalFiles} total`));
10900
+ console.log(BORDER13(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
10901
+ console.log("");
10902
+ const rl = readline11.createInterface({
10903
+ input: process.stdin,
10904
+ output: process.stdout
10905
+ });
10906
+ const answer = await new Promise((resolve4) => {
10907
+ rl.question(AMBER9(" Commit these changes? (y/n): "), resolve4);
10908
+ });
10909
+ if (answer.trim().toLowerCase() !== "y" && answer.trim().toLowerCase() !== "yes") {
10910
+ rl.close();
10911
+ console.log(GRAY8(" Skipped. Run `git add . && git commit` manually when ready."));
10912
+ console.log("");
10913
+ return;
10914
+ }
10915
+ const defaultMessage = `feat(agents): ${mission.description.slice(0, 60)}`;
10916
+ const messageAnswer = await new Promise((resolve4) => {
10917
+ rl.question(AMBER9(` Commit message (Enter for default: "${defaultMessage.slice(0, 40)}..."): `), resolve4);
10918
+ });
10919
+ rl.close();
10920
+ const commitMessage = messageAnswer.trim() || defaultMessage;
10921
+ try {
10922
+ const simpleGit3 = (await import("simple-git")).default;
10923
+ const git = simpleGit3(cwd);
10924
+ const isRepo = await git.checkIsRepo().catch(() => false);
10925
+ if (!isRepo) {
10926
+ console.log(RED8(" \u274C Not a git repository. Run `git init` first."));
10927
+ console.log("");
10928
+ return;
10929
+ }
10930
+ await git.add(".");
10931
+ const result = await git.commit(commitMessage);
10932
+ console.log("");
10933
+ console.log(GREEN9(` \u2705 Committed: ${result.commit?.slice(0, 7)} \u2014 ${commitMessage}`));
10934
+ console.log(GRAY8(" Run `git push` to push to remote."));
10935
+ console.log("");
10936
+ } catch (e) {
10937
+ console.log(RED8(` \u274C Commit failed: ${e.message}`));
10938
+ console.log("");
10939
+ }
10940
+ }
10941
+ function handleRunCommand(input, state, mission, cwd) {
10942
+ const trimmed = input.trim();
10943
+ if (trimmed === "/pause") {
10944
+ state.paused = true;
10945
+ return { handled: true, message: AMBER9(" \u23F8\uFE0F Pausing after active tasks complete...") };
10946
+ }
10947
+ if (trimmed === "/resume") {
10948
+ state.paused = false;
10949
+ return { handled: true, message: GREEN9(" \u25B6\uFE0F Resumed.") };
10950
+ }
10951
+ if (trimmed === "/abort") {
10952
+ state.aborted = true;
10953
+ return { handled: true, message: RED8(" \u{1F6D1} Aborting mission...") };
10954
+ }
10955
+ if (trimmed === "/status") {
10956
+ return { handled: true, showStatus: true };
10957
+ }
10958
+ if (trimmed === "/approve-commit") {
10959
+ state.pendingCommitApproval = { approved: true };
10960
+ return { handled: true, message: GREEN9(" \u2705 Commit approved \u2014 DirectorBob will execute on next cycle.") };
10961
+ }
10962
+ if (trimmed === "/deny-commit") {
10963
+ const { clearPendingCommit: clearPendingCommit2 } = (init_agent_tools(), __toCommonJS(agent_tools_exports));
10964
+ clearPendingCommit2(cwd);
10965
+ return { handled: true, message: RED8(" \u274C Commit denied. Files remain as-is.") };
10966
+ }
10967
+ if (trimmed === "/view-targets") {
10968
+ const lines = [""];
10969
+ lines.push(AMBER9(" Satisfaction Targets:"));
10970
+ lines.push(GRAY8(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
10971
+ for (const task of mission.tasks) {
10972
+ const statusIcon2 = getTaskStatusIcon(task.status);
10973
+ lines.push(
10974
+ ` ${statusIcon2} ${GRAY8(`@${task.assignedTo.padEnd(18)}`)} ` + chalk29.cyan(`${task.satisfactionTarget}%`) + (state.satisfactionOverrides[task.assignedTo] !== void 0 ? GREEN9(" (user override)") : "") + (task.status === "running" ? chalk29.cyan(" \u2190 active") : "")
10975
+ );
10976
+ }
10977
+ return { handled: true, message: lines.join("\n") };
10978
+ }
10979
+ const setTargetMatch = trimmed.match(/^\/set-target\s+(\w+)\s+(\d+)$/i);
10980
+ if (setTargetMatch) {
10981
+ const agentName = setTargetMatch[1];
10982
+ const target = Math.min(100, Math.max(0, parseInt(setTargetMatch[2])));
10983
+ state.satisfactionOverrides[agentName] = target;
10984
+ return { handled: true, message: GREEN9(` \u2705 @${agentName} satisfaction target \u2192 ${target}%`) };
10985
+ }
10986
+ const injectMatch = trimmed.match(/^\/inject\s+"(.+)"$/);
10987
+ if (injectMatch) {
10988
+ state.userInjections.push(injectMatch[1]);
10989
+ return { handled: true, message: GREEN9(` \u2705 Director note queued: "${injectMatch[1].slice(0, 50)}"`) };
10990
+ }
10991
+ const skipMatch = trimmed.match(/^\/skip\s+(\S+)$/i);
10992
+ if (skipMatch) {
10993
+ const taskRef = skipMatch[1];
10994
+ const task = mission.tasks.find(
10995
+ (t) => t.id === taskRef || t.id.endsWith(`_${taskRef}`) || t.id.includes(taskRef)
10996
+ );
10997
+ if (task) {
10998
+ task.status = "skipped";
10999
+ return { handled: true, message: GRAY8(` \u23ED\uFE0F Task skipped: ${task.instruction.slice(0, 50)}`) };
11000
+ }
11001
+ return { handled: true, message: RED8(` \u274C Task not found: ${taskRef}`) };
11002
+ }
11003
+ return { handled: false };
11004
+ }
11005
+ function getTaskStatusIcon(status) {
11006
+ switch (status) {
11007
+ case "completed":
11008
+ return GREEN9("\u2705");
11009
+ case "running":
11010
+ return CYAN9("\u23F3");
11011
+ case "pending":
11012
+ return GRAY8("\u23F8 ");
11013
+ case "failed":
11014
+ return RED8("\u274C");
11015
+ case "stagnated":
11016
+ return ORANGE2("\u26A0\uFE0F ");
11017
+ case "skipped":
11018
+ return GRAY8("\u23ED\uFE0F ");
11019
+ default:
11020
+ return GRAY8("\u25CB ");
11021
+ }
11022
+ }
11023
+
11024
+ // src/commands/agent-run.ts
11025
+ init_agent_tools();
11026
+ var AMBER10 = chalk30.hex("#FFAB00");
11027
+ var GREEN10 = chalk30.hex("#66BB6A");
11028
+ var RED9 = chalk30.hex("#EF5350");
11029
+ var CYAN10 = chalk30.cyan;
11030
+ var GRAY9 = chalk30.gray;
11031
+ function registerAgentRunCommand(program2) {
11032
+ program2.command("agent-run [mission...]").description("Launch DirectorBob \u2014 autonomous multi-agent mission execution").option("--satisfaction <number>", "Global satisfaction target override (0-100)").option("--stagnation <number>", "Global stagnation limit override", "3").option("--director-limit <number>", "Director surface limit before user escalation", "2").option("--dry-run", "Preview task map without executing").option("--resume", "Resume the last active mission").option("--no-feedback", "Skip post-mission feedback collection").option("--no-commit", "Skip post-mission commit prompt").action(async (missionArgs, options) => {
11033
+ const config = getConfig();
11034
+ const cwd = process.cwd();
11035
+ if (!config.localEndpoint) {
11036
+ console.log("");
11037
+ console.log(RED9(" \u274C bob agent run requires a local model."));
11038
+ console.log(GRAY9(" Run: bob config set localEndpoint http://127.0.0.1:11434/api/chat"));
11039
+ console.log("");
11040
+ return;
11041
+ }
11042
+ const registry = loadRegistry(cwd);
11043
+ if (registry.agents.length === 0) {
11044
+ console.log("");
11045
+ console.log(AMBER10(" \u26A0\uFE0F No agents found."));
11046
+ console.log(GRAY9(' Spawn agents first: bob agent spawn <name> "<task>"'));
11047
+ console.log("");
11048
+ return;
11049
+ }
11050
+ const agents = registry.agents.filter(
11051
+ (a) => a.status === "active" || a.status === "idle"
11052
+ );
11053
+ if (agents.length === 0) {
11054
+ console.log("");
11055
+ console.log(AMBER10(" \u26A0\uFE0F No active agents found."));
11056
+ console.log("");
11057
+ return;
11058
+ }
11059
+ if (options.resume) {
11060
+ const activeMissionId = getActiveMissionId(cwd);
11061
+ if (!activeMissionId) {
11062
+ console.log("");
11063
+ console.log(AMBER10(" \u26A0\uFE0F No active mission to resume."));
11064
+ console.log("");
11065
+ return;
11066
+ }
11067
+ const existingMission = loadMission(activeMissionId, cwd);
11068
+ if (!existingMission) {
11069
+ console.log("");
11070
+ console.log(RED9(" \u274C Could not load mission: " + activeMissionId));
11071
+ console.log("");
11072
+ return;
11073
+ }
11074
+ console.log("");
11075
+ console.log(AMBER10(` \u{1F504} Resuming mission: ${existingMission.description.slice(0, 50)}...`));
11076
+ await executeMission(existingMission, agents, cwd, config.localEndpoint, options);
11077
+ return;
11078
+ }
11079
+ let missionDescription = missionArgs.join(" ").trim();
11080
+ if (!missionDescription) {
11081
+ const rl = readline10.createInterface({
11082
+ input: process.stdin,
11083
+ output: process.stdout
11084
+ });
11085
+ missionDescription = await new Promise((resolve4) => {
11086
+ rl.question(AMBER10(" \u{1F3AC} What is the mission? > "), resolve4);
11087
+ });
11088
+ rl.close();
11089
+ if (!missionDescription.trim()) {
11090
+ console.log(RED9(" \u274C Mission cannot be empty."));
11091
+ return;
11092
+ }
11093
+ missionDescription = missionDescription.trim();
11094
+ }
11095
+ clearAllPendingCommits(cwd);
11096
+ console.log("");
11097
+ const planSpinner = ora13({
11098
+ text: AMBER10(" \u{1F3AC} DirectorBob is analyzing your team and building the task map..."),
11099
+ spinner: "dots"
11100
+ }).start();
11101
+ let taskDefs;
11102
+ try {
11103
+ taskDefs = await generateTaskMap(
11104
+ missionDescription,
11105
+ agents,
11106
+ cwd,
11107
+ config.localEndpoint
11108
+ );
11109
+ planSpinner.stop();
11110
+ } catch (error) {
11111
+ planSpinner.stop();
11112
+ console.log(RED9(` \u274C DirectorBob failed to generate task map: ${error.message}`));
11113
+ return;
11114
+ }
11115
+ if (options.satisfaction) {
11116
+ const target = parseInt(options.satisfaction);
11117
+ taskDefs = taskDefs.map((t) => ({ ...t, satisfactionTarget: target }));
11118
+ }
11119
+ if (options.stagnation) {
11120
+ const limit = parseInt(options.stagnation);
11121
+ taskDefs = taskDefs.map((t) => ({ ...t, stagnationLimit: limit }));
11122
+ }
11123
+ if (options.directorLimit) {
11124
+ const limit = parseInt(options.directorLimit);
11125
+ taskDefs = taskDefs.map((t) => ({ ...t, directorLimit: limit }));
11126
+ }
11127
+ const resolvedTasks = taskDefs.map((t) => ({
11128
+ ...t,
11129
+ dependsOn: (t.dependsOn || []).map((dep) => {
11130
+ const match = dep.match(/__TASK_(\d+)__/);
11131
+ return match ? `__RESOLVED_${match[1]}__` : dep;
11132
+ })
11133
+ }));
11134
+ const mission = createMission(missionDescription, resolvedTasks, cwd);
11135
+ mission.tasks.forEach((task) => {
11136
+ task.dependsOn = task.dependsOn.map((dep) => {
11137
+ const match = dep.match(/__RESOLVED_(\d+)__/);
11138
+ if (match) {
11139
+ const depIdx = parseInt(match[1]);
11140
+ return mission.tasks[depIdx]?.id || dep;
11141
+ }
11142
+ return dep;
11143
+ });
11144
+ });
11145
+ saveMission(mission, cwd);
11146
+ const agentNames = agents.map((a) => a.name);
11147
+ renderMissionHeader(mission, agentNames);
11148
+ renderTaskMap(mission, agentNames);
11149
+ if (options.dryRun) {
11150
+ console.log(CYAN10(" Dry run complete. No tasks executed."));
11151
+ console.log(GRAY9(" Run without --dry-run to execute."));
11152
+ console.log("");
11153
+ return;
11154
+ }
11155
+ console.log(AMBER10(" Starting in 3 seconds... (Ctrl+C to abort)"));
11156
+ await new Promise((r) => setTimeout(r, 3e3));
11157
+ await executeMission(mission, agents, cwd, config.localEndpoint, options);
11158
+ });
11159
+ program2.command("agent-status").description("Show status of the current active mission").action(() => {
11160
+ const cwd = process.cwd();
11161
+ const activeMissionId = getActiveMissionId(cwd);
11162
+ if (!activeMissionId) {
11163
+ console.log("");
11164
+ console.log(GRAY9(" No active mission."));
11165
+ console.log(GRAY9(' Start one: bob agent-run "your mission"'));
11166
+ console.log("");
11167
+ return;
11168
+ }
11169
+ const mission = loadMission(activeMissionId, cwd);
11170
+ if (!mission) {
11171
+ console.log("");
11172
+ console.log(RED9(" \u274C Could not load mission: " + activeMissionId));
11173
+ console.log("");
11174
+ return;
11175
+ }
11176
+ const registry = loadRegistry(cwd);
11177
+ const agentNames = registry.agents.map((a) => a.name);
11178
+ renderTaskMap(mission, agentNames);
11179
+ });
11180
+ }
11181
+ async function executeMission(mission, agents, cwd, localEndpoint, options) {
11182
+ const agentNames = agents.map((a) => a.name);
11183
+ const state = {
11184
+ paused: false,
11185
+ aborted: false,
11186
+ userInjections: [],
11187
+ satisfactionOverrides: {},
11188
+ pendingCommitApproval: null,
11189
+ commitDenialCounts: /* @__PURE__ */ new Map()
11190
+ };
11191
+ mission.status = "running";
11192
+ mission.startedAt = (/* @__PURE__ */ new Date()).toISOString();
11193
+ saveMission(mission, cwd);
11194
+ const rl = readline10.createInterface({
11195
+ input: process.stdin,
11196
+ output: process.stdout,
11197
+ terminal: false
11198
+ });
11199
+ rl.on("line", (input) => {
11200
+ const trimmed = input.trim();
11201
+ if (!trimmed) return;
11202
+ const result = handleRunCommand(trimmed, state, mission, cwd);
11203
+ if (result.message) console.log(result.message);
11204
+ if (trimmed === "/status") renderTaskMap(mission, agentNames);
11205
+ });
11206
+ const onEvent = (event) => {
11207
+ renderExecutionEvent(event, agentNames);
11208
+ };
11209
+ try {
11210
+ const result = await runAutonomousLoop(
11211
+ mission,
11212
+ agents,
11213
+ cwd,
11214
+ localEndpoint,
11215
+ state,
11216
+ onEvent
11217
+ );
11218
+ rl.close();
11219
+ if (result.completed) {
11220
+ renderMissionComplete(result.mission);
11221
+ if (options.commit !== false) {
11222
+ await renderPostMissionCommitPrompt(result.mission, cwd);
11223
+ }
11224
+ if (options.feedback !== false) {
11225
+ await renderPostMissionFeedback(result.mission, cwd);
11226
+ }
11227
+ } else if (result.aborted) {
11228
+ console.log("");
11229
+ console.log(RED9(" \u{1F6D1} Mission aborted."));
11230
+ console.log(GRAY9(" Resume anytime: bob agent-run --resume"));
11231
+ console.log("");
11232
+ } else if (result.surfacedToUser) {
11233
+ console.log("");
11234
+ console.log(AMBER10(" \u26A0\uFE0F Mission needs your attention."));
11235
+ console.log(RED9(` Reason: ${result.surfaceReason}`));
11236
+ console.log("");
11237
+ console.log(GRAY9(" Options:"));
11238
+ console.log(CYAN10(" bob agent-run --resume") + GRAY9(" \u2014 resume after resolving"));
11239
+ console.log(CYAN10(" bob agent chat <name>") + GRAY9(" \u2014 talk directly to stuck agent"));
11240
+ console.log(CYAN10(" bob agent-status") + GRAY9(" \u2014 see full task map"));
11241
+ console.log("");
11242
+ }
11243
+ } catch (error) {
11244
+ rl.close();
11245
+ console.log("");
11246
+ console.log(RED9(` \u274C Mission failed: ${error.message}`));
11247
+ console.log("");
11248
+ }
11249
+ }
11250
+
6763
11251
  // bin/bob.ts
6764
11252
  var program = new Command();
6765
- program.name("bob").description("Bob's CLI \u2014 AI coding assistant and Forge orchestrator").version("0.7.0");
11253
+ program.name("bob").description("Bob's CLI \u2014 AI coding assistant and Forge orchestrator").version("0.8.0");
6766
11254
  program.command("whoami").description("Show current authentication status and configuration").action(() => {
6767
11255
  const config = getConfig();
6768
- const projectName = path13.basename(process.cwd());
11256
+ const projectName = path22.basename(process.cwd());
6769
11257
  const projectConvoId = getActiveConversationId(process.cwd()) || config.conversationId;
6770
11258
  console.log("");
6771
- console.log(chalk25.bold(" \u{1F916} Bob's CLI"));
6772
- console.log(chalk25.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
6773
- console.log(` ${chalk25.cyan("Status:")} ${config.loggedIn ? chalk25.green("Logged in as " + config.email) : "Not logged in"}`);
6774
- console.log(` ${chalk25.cyan("Tier:")} ${config.tier === "platform" ? "Platform (Tier 3)" : "Local-first (Tier 1)"}`);
6775
- console.log(` ${chalk25.cyan("Provider:")} ${config.provider || "Not configured"}`);
6776
- console.log(` ${chalk25.cyan("Mode:")} ${config.personalizationMode ? "Personalized" : config.consultantMode ? "Consultant" : "Standard"}`);
6777
- console.log(` ${chalk25.cyan("IDRP:")} ${config.idrp ? "Enabled" : "Disabled"}`);
6778
- console.log(` ${chalk25.cyan("Project:")} ${projectName} (${process.cwd()})`);
6779
- console.log(` ${chalk25.cyan("Session:")} ${projectConvoId ? projectConvoId.slice(0, 20) + "..." : "None"}`);
11259
+ console.log(chalk31.bold(" \u{1F916} Bob's CLI"));
11260
+ console.log(chalk31.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
11261
+ console.log(` ${chalk31.cyan("Status:")} ${config.loggedIn ? chalk31.green("Logged in as " + config.email) : "Not logged in"}`);
11262
+ console.log(` ${chalk31.cyan("Tier:")} ${config.tier === "platform" ? "Platform (Tier 3)" : "Local-first (Tier 1)"}`);
11263
+ console.log(` ${chalk31.cyan("Provider:")} ${config.provider || "Not configured"}`);
11264
+ console.log(` ${chalk31.cyan("Mode:")} ${config.personalizationMode ? "Personalized" : config.consultantMode ? "Consultant" : "Standard"}`);
11265
+ console.log(` ${chalk31.cyan("IDRP:")} ${config.idrp ? "Enabled" : "Disabled"}`);
11266
+ console.log(` ${chalk31.cyan("Project:")} ${projectName} (${process.cwd()})`);
11267
+ console.log(` ${chalk31.cyan("Session:")} ${projectConvoId ? projectConvoId.slice(0, 20) + "..." : "None"}`);
6780
11268
  console.log("");
6781
11269
  if (!config.loggedIn) {
6782
- console.log(chalk25.gray(" Run `bob login` to authenticate."));
11270
+ console.log(chalk31.gray(" Run `bob login` to authenticate."));
6783
11271
  console.log("");
6784
11272
  }
6785
11273
  });
@@ -6799,4 +11287,6 @@ registerServeCommand(program);
6799
11287
  registerRemoteCommand(program);
6800
11288
  registerProfileCommand(program);
6801
11289
  registerBackupCommand(program);
11290
+ registerAgentCommand(program);
11291
+ registerAgentRunCommand(program);
6802
11292
  program.parse();