@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/README.md +139 -5
- package/dist/agent-store-DUAYA6SK.js +34 -0
- package/dist/agent-store-PI4KOFBE.js +35 -0
- package/dist/agent-store-PTYRRKJ5.js +35 -0
- package/dist/analyse-auto-BY4BAC2U.js +529 -0
- package/dist/analyse-auto-F3O5DPS2.js +530 -0
- package/dist/analyse-auto-FOHKFESD.js +530 -0
- package/dist/analyse-auto-I7TIDS4E.js +530 -0
- package/dist/analyse-auto-MD6IA3VH.js +529 -0
- package/dist/analyse-auto-PJUTCRBW.js +530 -0
- package/dist/analyse-auto-RCY7F4TU.js +530 -0
- package/dist/analyse-results-42IGEWAQ.js +9 -0
- package/dist/analyse-results-5SAMUHHK.js +9 -0
- package/dist/analyse-results-CAXJXEXA.js +9 -0
- package/dist/analyse-results-FV5G6X3P.js +9 -0
- package/dist/analyse-results-L26WBPUH.js +8 -0
- package/dist/analyse-results-QSTTMRQE.js +9 -0
- package/dist/analyse-results-XMY3HWMA.js +8 -0
- package/dist/bob.js +4544 -54
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-AQGIC65Q.js +922 -0
- package/dist/chunk-AYXEMVQS.js +925 -0
- package/dist/chunk-B23KYYX3.js +1196 -0
- package/dist/chunk-CAF7EJSC.js +213 -0
- package/dist/chunk-HU5PQOJI.js +1196 -0
- package/dist/chunk-JTMMSCF7.js +1212 -0
- package/dist/chunk-NZW7H2BY.js +1196 -0
- package/dist/chunk-PNKVD2UK.js +26 -0
- package/dist/persona-loader-3I5Y6CRD.js +15 -0
- package/dist/persona-loader-EVL7ORNP.js +15 -0
- package/dist/persona-loader-SEDW2PPJ.js +14 -0
- package/package.json +60 -59
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-
|
|
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
|
|
34
|
-
import * as
|
|
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((
|
|
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((
|
|
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((
|
|
1127
|
-
rl.question(MODE_DEEPDIVE2(" Select (1-" + dives.length + ") or 0 to cancel: "),
|
|
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((
|
|
1594
|
+
await new Promise((resolve4) => setTimeout(resolve4, 3e3));
|
|
1140
1595
|
animation.stop();
|
|
1141
|
-
await new Promise((
|
|
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((
|
|
1204
|
-
rl.question(MODE_DEEPDIVE2(" Select (1-" + messages.length + ") or 0 to cancel: "),
|
|
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((
|
|
1672
|
+
await Promise.all([divePromise, new Promise((resolve4) => setTimeout(resolve4, 3e3))]);
|
|
1218
1673
|
animation.stop();
|
|
1219
|
-
await new Promise((
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
1306
|
-
rl.question(MODE_DEEPDIVE2(" Leave deep dive for main conversation? (y/N): "),
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
2443
|
-
rl.question(WARNING9(` Remove ${provider} key? (y/n): `),
|
|
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
|
|
2960
|
+
const statusIcon2 = key.isActive ? SUCCESS10("\u25CF") : ERROR8("\u25CB");
|
|
2506
2961
|
const statusText = key.isActive ? SUCCESS10("Active") : ERROR8("Inactive");
|
|
2507
|
-
console.log(` ${
|
|
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((
|
|
2624
|
-
rl.question(INFO11(" Select (1-" + conversations.length + ") or 0 to cancel: "),
|
|
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((
|
|
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((
|
|
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((
|
|
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-
|
|
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-
|
|
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((
|
|
3478
|
-
rl.question(CYAN(" Approve push? (y/n): "),
|
|
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((
|
|
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((
|
|
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((
|
|
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((
|
|
4918
|
-
rl.question(INFO14(" Select (1-" + bobs.length + ") or 0 to cancel: "),
|
|
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((
|
|
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.
|
|
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 =
|
|
11256
|
+
const projectName = path22.basename(process.cwd());
|
|
6769
11257
|
const projectConvoId = getActiveConversationId(process.cwd()) || config.conversationId;
|
|
6770
11258
|
console.log("");
|
|
6771
|
-
console.log(
|
|
6772
|
-
console.log(
|
|
6773
|
-
console.log(` ${
|
|
6774
|
-
console.log(` ${
|
|
6775
|
-
console.log(` ${
|
|
6776
|
-
console.log(` ${
|
|
6777
|
-
console.log(` ${
|
|
6778
|
-
console.log(` ${
|
|
6779
|
-
console.log(` ${
|
|
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(
|
|
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();
|