scai 0.1.166 → 0.1.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/MainAgent.js +107 -136
- package/dist/agents/evidenceVerifierStep.js +118 -39
- package/dist/agents/fileCheckStep.js +42 -7
- package/dist/agents/infoPlanGenStep.js +18 -31
- package/dist/agents/readinessGateStep.js +0 -1
- package/dist/agents/reasonNextTaskStep.js +50 -12
- package/dist/agents/scopeClassificationStep.js +49 -38
- package/dist/agents/selectRelevantSourcesStep.js +36 -77
- package/dist/agents/writeFileStep.js +1 -1
- package/dist/commands/AskCmd.js +4 -3
- package/dist/commands/TasksCmd.js +9 -3
- package/dist/db/fileIndex.js +33 -12
- package/package.json +1 -1
- package/dist/agents/routingDecisionStep.js +0 -69
package/dist/agents/MainAgent.js
CHANGED
|
@@ -19,18 +19,9 @@ import { selectRelevantSourcesStep } from "./selectRelevantSourcesStep.js";
|
|
|
19
19
|
import { iterationFileSelector } from "./iterationFileSelector.js";
|
|
20
20
|
import { finalAnswerModule } from "../pipeline/modules/finalAnswerModule.js";
|
|
21
21
|
import { reasonNextStep } from "./reasonNextStep.js";
|
|
22
|
+
import chalk from "chalk";
|
|
22
23
|
/* ───────────────────────── registry ───────────────────────── */
|
|
23
|
-
/**
|
|
24
|
-
* Registry mapping action names to their corresponding Module implementations.
|
|
25
|
-
* This registry is populated with built-in modules from the module registry.
|
|
26
|
-
*/
|
|
27
24
|
const MODULE_REGISTRY = Object.fromEntries(Object.entries(builtInModules).map(([name, mod]) => [name, mod]));
|
|
28
|
-
/**
|
|
29
|
-
* Resolves a module by its action name.
|
|
30
|
-
*
|
|
31
|
-
* @param action - The action name to resolve.
|
|
32
|
-
* @returns The Module implementation if found, otherwise undefined.
|
|
33
|
-
*/
|
|
34
25
|
function resolveModuleForAction(action) {
|
|
35
26
|
return MODULE_REGISTRY[action];
|
|
36
27
|
}
|
|
@@ -64,13 +55,17 @@ export class MainAgent {
|
|
|
64
55
|
}
|
|
65
56
|
/* ───────────── main run ───────────── */
|
|
66
57
|
async run() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
try {
|
|
59
|
+
this.runCount = 0;
|
|
60
|
+
await this.runBoot();
|
|
61
|
+
await this.runScope();
|
|
62
|
+
await this.runGrounding();
|
|
63
|
+
await this.runWorkLoop();
|
|
64
|
+
await this.runFinalize();
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
this.ui.stop(); // ← guaranteed cleanup
|
|
68
|
+
}
|
|
74
69
|
}
|
|
75
70
|
/* ───────────── boot ───────────── */
|
|
76
71
|
async runBoot() {
|
|
@@ -78,128 +73,141 @@ export class MainAgent {
|
|
|
78
73
|
await understandIntentStep.run({ context: this.context });
|
|
79
74
|
await resolveExecutionModeStep.run(this.context);
|
|
80
75
|
// Boot the task and get the real DB taskId
|
|
81
|
-
this.taskId = bootTaskForRepo(this.context, getDbForRepo(), this.logLine
|
|
82
|
-
// Ensure context.task exists and has all required fields
|
|
76
|
+
this.taskId = bootTaskForRepo(this.context, getDbForRepo(), (phase, step, ms, desc) => this.logLine(phase, step, ms, desc, { highlight: true }));
|
|
83
77
|
(_a = this.context).task || (_a.task = {
|
|
84
78
|
id: this.taskId,
|
|
85
|
-
projectId: 0,
|
|
79
|
+
projectId: 0,
|
|
86
80
|
status: "active",
|
|
87
81
|
initialQuery: this.context.initContext?.userQuery ?? "",
|
|
88
82
|
createdAt: new Date().toISOString(),
|
|
89
83
|
updatedAt: new Date().toISOString(),
|
|
90
84
|
taskSteps: [],
|
|
91
85
|
});
|
|
92
|
-
// If task existed but id wasn’t set, set it now
|
|
93
86
|
this.context.task.id = this.taskId;
|
|
94
|
-
|
|
95
|
-
/* ───────────── precheck ───────────── */
|
|
96
|
-
async runPrecheck() {
|
|
97
|
-
await fileCheckStep(this.context);
|
|
87
|
+
this.logLine("TASK", "Boot complete", undefined, `taskId=${this.taskId}`, { highlight: true });
|
|
98
88
|
}
|
|
99
89
|
/* ───────────── scope ───────────── */
|
|
100
90
|
async runScope() {
|
|
101
91
|
await scopeClassificationStep.run(this.context);
|
|
92
|
+
this.logLine("TASK", "Scope classification complete");
|
|
102
93
|
}
|
|
103
|
-
/* ───────────── grounding / info acquisition loop ───────────── */
|
|
104
94
|
async runGrounding() {
|
|
105
95
|
let ready = false;
|
|
106
|
-
while (
|
|
96
|
+
while (this.runCount < this.maxRuns) {
|
|
97
|
+
// ---------------- EVIDENCE PIPELINE ----------------
|
|
98
|
+
const t1 = this.startTimer();
|
|
99
|
+
await evidenceVerifierStep.run({ query: this.query, context: this.context });
|
|
100
|
+
this.logLine("ANALYSIS", "collectAnalysisEvidence", t1());
|
|
101
|
+
const t2 = this.startTimer();
|
|
102
|
+
await fileCheckStep(this.context);
|
|
103
|
+
this.logLine("ANALYSIS", "fileCheckStep", t2());
|
|
104
|
+
const t3 = this.startTimer();
|
|
105
|
+
await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
|
|
106
|
+
this.logLine("ANALYSIS", "selectRelevantSources", t3());
|
|
107
|
+
// ---------------- READINESS GATE ----------------
|
|
108
|
+
const t4 = this.startTimer();
|
|
109
|
+
await readinessGateStep.run(this.context);
|
|
110
|
+
this.logLine("HASINFO", "readinessGate", t4());
|
|
111
|
+
ready = this.context.analysis?.readiness?.decision === "ready";
|
|
112
|
+
if (ready) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
107
115
|
// ---------------- INFORMATION ACQUISITION ----------------
|
|
108
116
|
if (this.canExecutePhase("planning") &&
|
|
109
117
|
this.canExecuteScope("planning")) {
|
|
110
118
|
const t = this.startTimer();
|
|
111
|
-
// Generate info plan step(s) in context
|
|
112
119
|
await infoPlanGenStep.run(this.context);
|
|
113
120
|
const infoPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
121
|
+
// If we are about to execute a new info acquisition wave,
|
|
122
|
+
// wipe previous search results.
|
|
123
|
+
if (infoPlan.steps.length > 0 && this.context.initContext && this.context.analysis?.focus) {
|
|
124
|
+
this.context.initContext.relatedFiles = [];
|
|
125
|
+
this.context.analysis.focus.candidateFiles = [];
|
|
126
|
+
}
|
|
114
127
|
for (const step of infoPlan.steps) {
|
|
115
128
|
const stepIO = { query: this.query };
|
|
116
129
|
await this.executeStep(step, stepIO);
|
|
117
|
-
// Re-check any new files discovered
|
|
118
|
-
const t2 = this.startTimer();
|
|
119
|
-
await fileCheckStep(this.context);
|
|
120
|
-
this.logLine("PRECHECK", "postFileSearch", t2());
|
|
121
|
-
}
|
|
122
|
-
this.logLine("PLAN", "infoPlanGen", t());
|
|
123
|
-
}
|
|
124
|
-
// ---------------- DETERMINISTIC EVIDENCE VERIFICATION ----------------
|
|
125
|
-
const t1 = this.startTimer();
|
|
126
|
-
await evidenceVerifierStep.run({ query: this.query, context: this.context });
|
|
127
|
-
this.logLine("ANALYSIS", "collectAnalysisEvidence", t1(), undefined);
|
|
128
|
-
// Select grounded candidate files
|
|
129
|
-
const t2 = this.startTimer();
|
|
130
|
-
await selectRelevantSourcesStep.run({ query: this.query, context: this.context });
|
|
131
|
-
this.logLine("ANALYSIS", "selectRelevantSources", t2(), undefined);
|
|
132
|
-
// ---------------- READINESS GATE ----------------
|
|
133
|
-
const t3 = this.startTimer();
|
|
134
|
-
await readinessGateStep.run(this.context);
|
|
135
|
-
this.logLine("HASINFO", "readinessGate", t3(), undefined);
|
|
136
|
-
ready = this.context.analysis?.readiness?.decision === "ready";
|
|
137
|
-
if (!ready) {
|
|
138
|
-
this.runCount++;
|
|
139
|
-
// Previously resetInitContextForLoop removed; optionally clear temporary data
|
|
140
|
-
if (this.context.analysis) {
|
|
141
|
-
this.context.analysis.planSuggestion = undefined;
|
|
142
130
|
}
|
|
143
|
-
this.logLine("
|
|
131
|
+
this.logLine("PLAN", "infoPlanGen", t(), undefined, { highlight: false });
|
|
144
132
|
}
|
|
133
|
+
this.runCount++;
|
|
134
|
+
this.logLine("HASINFO", "Not ready — looping back to evidence collection", undefined, undefined, { highlight: false });
|
|
145
135
|
}
|
|
146
136
|
}
|
|
147
137
|
/* ───────────── finalize ───────────── */
|
|
148
138
|
async runFinalize() {
|
|
149
|
-
// generate final answer
|
|
150
139
|
await finalAnswerModule.run({ query: this.query, context: this.context });
|
|
151
|
-
// persist task and steps
|
|
152
140
|
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
141
|
+
this.logLine("TASK", "Finalize complete", undefined, undefined, { highlight: false });
|
|
153
142
|
}
|
|
154
143
|
/* ───────────── work loop ───────────── */
|
|
155
144
|
async runWorkLoop() {
|
|
145
|
+
const readinessDecision = this.context.analysis?.readiness?.decision;
|
|
146
|
+
const readinessConfidence = this.context.analysis?.readiness?.confidence ?? 0;
|
|
147
|
+
if (readinessDecision !== "ready") {
|
|
148
|
+
// ❌ Graceful fallback instead of throwing
|
|
149
|
+
this.context.task.status = "deferred";
|
|
150
|
+
this.context.task.reason = `Readiness not achieved (decision=${readinessDecision}, confidence=${readinessConfidence})`;
|
|
151
|
+
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
152
|
+
this.logLine("TASK", `Cannot start work loop — agent needs more evidence to safely proceed`, undefined, `Readiness: ${readinessDecision}, Confidence: ${readinessConfidence}`, { highlight: true });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (!this.context.task) {
|
|
156
|
+
throw new Error("runWorkLoop: missing task");
|
|
157
|
+
}
|
|
156
158
|
const MAX_TASK_STEPS = 5;
|
|
157
159
|
let stepCount = 0;
|
|
158
|
-
while (stepCount < MAX_TASK_STEPS
|
|
160
|
+
while (stepCount < MAX_TASK_STEPS &&
|
|
161
|
+
this.context.task.status === "active") {
|
|
159
162
|
await reasonNextTaskStep.run(this.context);
|
|
160
163
|
const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
|
|
164
|
+
// 🟡 Pause for user clarification
|
|
165
|
+
if (nextAction === "request-feedback") {
|
|
166
|
+
this.context.task.status = "paused";
|
|
167
|
+
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
168
|
+
this.logLine("TASK", "Execution paused — awaiting user clarification", undefined, undefined, { highlight: false });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// 🟢 Completed task
|
|
161
172
|
if (nextAction === "complete") {
|
|
162
|
-
this.
|
|
173
|
+
this.context.task.status = "completed";
|
|
174
|
+
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
175
|
+
this.logLine("TASK", "All selected files processed — task complete", undefined, undefined, { highlight: false });
|
|
163
176
|
return;
|
|
164
177
|
}
|
|
165
178
|
const taskStep = await iterationFileSelector.run(this.context);
|
|
166
179
|
if (!taskStep) {
|
|
167
|
-
this.
|
|
180
|
+
this.context.task.status = "completed";
|
|
181
|
+
persistTaskData(this.context, this.taskId, getDbForRepo(), this.logLine.bind(this));
|
|
182
|
+
this.logLine("TASK", "No eligible taskStep found — task complete", undefined, undefined, { highlight: false });
|
|
168
183
|
return;
|
|
169
184
|
}
|
|
170
|
-
|
|
171
|
-
if (this.context.task) {
|
|
172
|
-
this.context.task.currentStep = taskStep;
|
|
173
|
-
}
|
|
185
|
+
this.context.task.currentStep = taskStep;
|
|
174
186
|
stepCount++;
|
|
175
187
|
taskStep.taskId = this.taskId;
|
|
176
188
|
taskStep.stepIndex = stepCount;
|
|
177
189
|
taskStep.status = "pending";
|
|
178
|
-
// ⬇️ Persist newly created step
|
|
179
190
|
persistTaskStepInsert(taskStep, getDbForRepo());
|
|
180
|
-
|
|
181
|
-
this.logLine("TASK", `Processing taskStep ${stepCount}/${MAX_TASK_STEPS}`, undefined, taskStep.filePath);
|
|
182
|
-
// ---------------------------
|
|
183
|
-
// Start the step
|
|
184
|
-
// ---------------------------
|
|
191
|
+
this.logLine("NEW STEP", `Processing taskStep ${stepCount}`, undefined, taskStep.filePath, { highlight: true });
|
|
185
192
|
taskStep.startTime = Date.now();
|
|
186
193
|
persistTaskStepStart(taskStep, getDbForRepo());
|
|
187
194
|
// ---------------------------
|
|
188
|
-
// Step-level iterations
|
|
195
|
+
// Step-level iterations
|
|
189
196
|
// ---------------------------
|
|
190
197
|
const stepAction = await this.runStepIterations(taskStep);
|
|
191
198
|
if (stepAction === "complete") {
|
|
192
199
|
taskStep.status = "completed";
|
|
193
200
|
taskStep.endTime = Date.now();
|
|
194
201
|
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
202
|
+
this.logLine("STEP-DONE", `Completed taskStep ${stepCount}`, undefined, taskStep.filePath, { highlight: false });
|
|
195
203
|
}
|
|
196
204
|
else {
|
|
197
|
-
// optionally mark as pending or redo-step
|
|
198
205
|
taskStep.status = "pending";
|
|
199
206
|
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
207
|
+
this.logLine("STEP", `Pending taskStep ${stepCount}`, undefined, taskStep.filePath);
|
|
200
208
|
}
|
|
201
209
|
}
|
|
202
|
-
this.logLine("TASK", "Max task step limit reached — stopping work loop");
|
|
210
|
+
this.logLine("TASK", "Max task step limit reached — stopping work loop", undefined, undefined, { highlight: false });
|
|
203
211
|
}
|
|
204
212
|
/* ───────────── step iterations ───────────── */
|
|
205
213
|
async runStepIterations(taskStep) {
|
|
@@ -207,31 +215,22 @@ export class MainAgent {
|
|
|
207
215
|
let loopCount = 0;
|
|
208
216
|
const getNextIterationAction = () => {
|
|
209
217
|
const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
|
|
210
|
-
if (nextAction
|
|
211
|
-
|
|
212
|
-
nextAction !== "expand-scope" &&
|
|
213
|
-
nextAction !== "request-feedback" &&
|
|
214
|
-
nextAction !== "complete") {
|
|
215
|
-
return "complete"; // fallback
|
|
216
|
-
}
|
|
218
|
+
if (!["continue", "redo-step", "expand-scope", "request-feedback", "complete"].includes(nextAction ?? ""))
|
|
219
|
+
return "complete";
|
|
217
220
|
return nextAction;
|
|
218
221
|
};
|
|
219
|
-
// Loop through iterations for a single task step (file)
|
|
220
222
|
while (loopCount < MAX_ITERATIONS) {
|
|
221
223
|
this.runCount++;
|
|
222
224
|
loopCount++;
|
|
223
|
-
|
|
224
|
-
if (this.context.analysis?.iterationReasoning) {
|
|
225
|
+
if (this.context.analysis?.iterationReasoning)
|
|
225
226
|
this.context.analysis.iterationReasoning.nextAction = undefined;
|
|
226
|
-
}
|
|
227
227
|
await this.runWorkIteration(taskStep);
|
|
228
228
|
const nextAction = getNextIterationAction();
|
|
229
|
-
this.logLine("STEP-LOOP",
|
|
229
|
+
this.logLine("STEP-LOOP", `nextAction = ${nextAction}`);
|
|
230
230
|
if (nextAction === "complete")
|
|
231
231
|
return "complete";
|
|
232
232
|
if (nextAction === "request-feedback")
|
|
233
233
|
return "request-feedback";
|
|
234
|
-
// else: continue
|
|
235
234
|
}
|
|
236
235
|
return "continue";
|
|
237
236
|
}
|
|
@@ -239,56 +238,47 @@ export class MainAgent {
|
|
|
239
238
|
async runWorkIteration(taskStep) {
|
|
240
239
|
if (!this.context.analysis)
|
|
241
240
|
this.context.analysis = {};
|
|
242
|
-
// ---------------- ANALYSIS ----------------
|
|
243
241
|
if (this.canExecutePhase("analysis") && this.canExecuteScope("analysis")) {
|
|
244
242
|
const tAnalysis = this.startTimer();
|
|
245
243
|
await analysisPlanGenStep.run(this.context);
|
|
246
|
-
this.logLine("PLAN", "analysisPlanGen", tAnalysis());
|
|
244
|
+
this.logLine("PLAN", "analysisPlanGen", tAnalysis(), undefined, { highlight: false });
|
|
247
245
|
const analysisPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
248
246
|
for (const step of analysisPlan.steps) {
|
|
249
247
|
const tStep = this.startTimer();
|
|
250
248
|
await this.executeStep(step, { query: this.query });
|
|
251
|
-
this.logLine("STEP", step.action || "unnamedStep", tStep());
|
|
249
|
+
this.logLine("PLANNING-STEP", step.action || "unnamedStep", tStep());
|
|
252
250
|
}
|
|
253
|
-
if (this.context.analysis)
|
|
251
|
+
if (this.context.analysis)
|
|
254
252
|
this.context.analysis.planSuggestion = undefined;
|
|
255
|
-
}
|
|
256
253
|
}
|
|
257
|
-
// ---------------- TRANSFORM ----------------
|
|
258
254
|
if (this.canExecutePhase("transform") && this.canExecuteScope("transform")) {
|
|
259
255
|
const tTransform = this.startTimer();
|
|
260
256
|
await transformPlanGenStep.run(this.context);
|
|
261
|
-
this.logLine("PLAN", "transformPlanGen", tTransform());
|
|
257
|
+
this.logLine("PLAN", "transformPlanGen", tTransform(), undefined, { highlight: false });
|
|
262
258
|
const transformPlan = this.context.analysis?.planSuggestion?.plan ?? { steps: [] };
|
|
263
259
|
const firstStep = transformPlan.steps[0];
|
|
264
260
|
if (firstStep) {
|
|
265
261
|
const tStep = this.startTimer();
|
|
266
262
|
await this.executeStep(firstStep, { query: this.query });
|
|
267
|
-
this.logLine("STEP", `#1 (only) - ${firstStep.action || "unnamedStep"}`, tStep());
|
|
263
|
+
this.logLine("PLANNING-STEP", `#1 (only) - ${firstStep.action || "unnamedStep"}`, tStep());
|
|
268
264
|
}
|
|
269
|
-
if (this.context.analysis)
|
|
265
|
+
if (this.context.analysis)
|
|
270
266
|
this.context.analysis.planSuggestion = undefined;
|
|
271
|
-
}
|
|
272
|
-
// ---------------- WRITE ----------------
|
|
273
267
|
if (this.canExecutePhase("write") && this.canExecuteScope("write")) {
|
|
274
268
|
const tWrite = this.startTimer();
|
|
275
269
|
await writeFileStep.run({ query: this.query, context: this.context });
|
|
276
270
|
this.logLine("WRITE", "writeFileStep", tWrite());
|
|
277
271
|
}
|
|
278
|
-
// ---------------- VALIDATION ----------------
|
|
279
272
|
const tValidate = this.startTimer();
|
|
280
273
|
await validateChangesStep.run(this.context);
|
|
281
274
|
this.logLine("VALIDATION", "validateChangesStep", tValidate());
|
|
282
275
|
}
|
|
283
|
-
// ---------------- REASONING ----------------
|
|
284
276
|
const tReason = this.startTimer();
|
|
285
277
|
await reasonNextStep.run(this.context, taskStep);
|
|
286
278
|
this.logLine("REASONING", "reasonNextStep", tReason());
|
|
287
|
-
// ---------------- COLLABORATOR FEEDBACK ----------------
|
|
288
279
|
const tCollab = this.startTimer();
|
|
289
280
|
await collaboratorStep.run(this.context);
|
|
290
281
|
this.logLine("FEEDBACK", "collaboratorStep", tCollab());
|
|
291
|
-
// ---------------- INTEGRATE FEEDBACK ----------------
|
|
292
282
|
const tIntegrate = this.startTimer();
|
|
293
283
|
await integrateFeedbackStep.run(this.context);
|
|
294
284
|
this.logLine("FEEDBACK", "integrateFeedbackStep", tIntegrate());
|
|
@@ -304,9 +294,7 @@ export class MainAgent {
|
|
|
304
294
|
*/
|
|
305
295
|
async executeStep(step, input) {
|
|
306
296
|
const stop = this.startTimer();
|
|
307
|
-
// Set current step in context
|
|
308
297
|
this.context.currentStep = step;
|
|
309
|
-
// Resolve module for this action
|
|
310
298
|
const mod = resolveModuleForAction(step.action);
|
|
311
299
|
if (!mod) {
|
|
312
300
|
this.logLine("EXECUTE", step.action, stop(), "skipped (missing module)");
|
|
@@ -314,13 +302,7 @@ export class MainAgent {
|
|
|
314
302
|
}
|
|
315
303
|
try {
|
|
316
304
|
this.ui.update(`Running step: ${step.action}`);
|
|
317
|
-
|
|
318
|
-
await mod.run({
|
|
319
|
-
query: step.description ?? input.query,
|
|
320
|
-
content: input.data ?? input.content,
|
|
321
|
-
context: this.context
|
|
322
|
-
});
|
|
323
|
-
// Return ModuleIO (can remain minimal since output is untyped)
|
|
305
|
+
await mod.run({ query: step.description ?? input.query, content: input.data ?? input.content, context: this.context });
|
|
324
306
|
return { query: step.description ?? input.query, data: {} };
|
|
325
307
|
}
|
|
326
308
|
catch (err) {
|
|
@@ -342,12 +324,10 @@ export class MainAgent {
|
|
|
342
324
|
switch (phase) {
|
|
343
325
|
case "analysis":
|
|
344
326
|
case "planning":
|
|
345
|
-
// Read-only phases are always allowed
|
|
346
327
|
allowed = !docsOnly;
|
|
347
328
|
break;
|
|
348
329
|
case "transform":
|
|
349
330
|
case "write":
|
|
350
|
-
// Side-effect phases require explicit permission
|
|
351
331
|
allowed = constraints?.allowFileWrites ?? false;
|
|
352
332
|
break;
|
|
353
333
|
}
|
|
@@ -365,14 +345,10 @@ export class MainAgent {
|
|
|
365
345
|
let allowed = false;
|
|
366
346
|
switch (scope) {
|
|
367
347
|
case "none":
|
|
368
|
-
// Non-repo questions: analysis/planning only
|
|
369
348
|
allowed = phase === "analysis" || phase === "planning";
|
|
370
349
|
break;
|
|
371
|
-
|
|
372
|
-
case "multi-file":
|
|
373
|
-
case "repo-wide":
|
|
350
|
+
default:
|
|
374
351
|
allowed = true;
|
|
375
|
-
break;
|
|
376
352
|
}
|
|
377
353
|
return allowed;
|
|
378
354
|
}
|
|
@@ -396,38 +372,33 @@ export class MainAgent {
|
|
|
396
372
|
* @param fn - The function to execute while spinner is paused.
|
|
397
373
|
*/
|
|
398
374
|
withSpinnerPaused(fn) {
|
|
399
|
-
this.ui.pause(() => {
|
|
400
|
-
// Ensure spinner line is fully gone
|
|
401
|
-
process.stdout.write('\r\x1b[K');
|
|
402
|
-
fn();
|
|
403
|
-
});
|
|
375
|
+
this.ui.pause(() => { process.stdout.write('\r\x1b[K'); fn(); });
|
|
404
376
|
}
|
|
405
|
-
|
|
406
|
-
* Logs a line with timing information.
|
|
407
|
-
*
|
|
408
|
-
* @param phase - The phase of execution.
|
|
409
|
-
* @param step - The step being executed.
|
|
410
|
-
* @param ms - The execution time in milliseconds.
|
|
411
|
-
* @param desc - Optional description of the step.
|
|
412
|
-
*/
|
|
413
|
-
logLine(phase, step, ms, desc) {
|
|
377
|
+
logLine(phase, step, ms, desc, options) {
|
|
414
378
|
this.withSpinnerPaused(() => {
|
|
415
379
|
const suffix = desc ? ` — ${desc}` : "";
|
|
416
380
|
const timing = typeof ms === "number" ? ` (${ms}ms)` : "";
|
|
417
|
-
|
|
381
|
+
let line = `[AGENT] ${phase} :: ${step}${suffix}${timing}`;
|
|
382
|
+
// Coloring rules
|
|
383
|
+
if (phase === "NEW STEP" || phase === "STEP") {
|
|
384
|
+
line = chalk.green(line);
|
|
385
|
+
}
|
|
386
|
+
else if (options?.highlight) {
|
|
387
|
+
line = chalk.green(line);
|
|
388
|
+
}
|
|
389
|
+
// Print a blank line **before** NEW STEP only
|
|
390
|
+
if (phase === "NEW STEP")
|
|
391
|
+
console.log();
|
|
392
|
+
console.log(line);
|
|
418
393
|
});
|
|
419
394
|
}
|
|
420
|
-
/**
|
|
421
|
-
* Logs a message to the user output.
|
|
422
|
-
*
|
|
423
|
-
* @param message - The message to log.
|
|
424
|
-
*/
|
|
425
395
|
userOutput(message) {
|
|
426
396
|
this.withSpinnerPaused(() => {
|
|
427
397
|
console.log(`[USER OUTPUT] ${message}`);
|
|
428
398
|
});
|
|
429
399
|
}
|
|
430
400
|
}
|
|
401
|
+
// All helper functions (persistTaskData, bootTaskForRepo, persistTaskStep*) remain unchanged
|
|
431
402
|
/* ───────────── FOLDER CAPSULES SUMMARY HELPER ───────────── */
|
|
432
403
|
/**
|
|
433
404
|
* Generates a human-readable summary of folder capsules and logs it.
|