scai 0.1.161 → 0.1.163
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
CHANGED
|
@@ -155,9 +155,6 @@ export class MainAgent {
|
|
|
155
155
|
async runWorkLoop() {
|
|
156
156
|
const MAX_TASK_STEPS = 5;
|
|
157
157
|
let stepCount = 0;
|
|
158
|
-
// ---------------------------
|
|
159
|
-
// 1️⃣ Task-level reasoning: decide remaining files / next action
|
|
160
|
-
// ---------------------------
|
|
161
158
|
while (stepCount < MAX_TASK_STEPS) {
|
|
162
159
|
await reasonNextTaskStep.run(this.context);
|
|
163
160
|
const nextAction = this.context.analysis?.iterationReasoning?.nextAction;
|
|
@@ -165,32 +162,43 @@ export class MainAgent {
|
|
|
165
162
|
this.logLine("TASK", "All selected files processed — task complete");
|
|
166
163
|
return;
|
|
167
164
|
}
|
|
168
|
-
// ---------------------------
|
|
169
|
-
// 2️⃣ Select next file to process
|
|
170
|
-
// ---------------------------
|
|
171
165
|
const taskStep = await iterationFileSelector.run(this.context);
|
|
172
166
|
if (!taskStep) {
|
|
173
167
|
this.logLine("TASK", "No eligible taskStep found — task complete");
|
|
174
168
|
return;
|
|
175
169
|
}
|
|
176
|
-
//
|
|
177
|
-
if (
|
|
170
|
+
// Set current step in context
|
|
171
|
+
if (this.context.task) {
|
|
178
172
|
this.context.task.currentStep = taskStep;
|
|
179
173
|
}
|
|
180
174
|
stepCount++;
|
|
175
|
+
taskStep.taskId = this.taskId;
|
|
176
|
+
taskStep.stepIndex = stepCount;
|
|
177
|
+
taskStep.status = "pending";
|
|
178
|
+
// ⬇️ Persist newly created step
|
|
179
|
+
persistTaskStepInsert(taskStep, getDbForRepo());
|
|
181
180
|
logInputOutput("Step to process", "input", taskStep);
|
|
182
181
|
this.logLine("TASK", `Processing taskStep ${stepCount}/${MAX_TASK_STEPS}`, undefined, taskStep.filePath);
|
|
183
182
|
// ---------------------------
|
|
184
|
-
//
|
|
183
|
+
// Start the step
|
|
184
|
+
// ---------------------------
|
|
185
|
+
taskStep.startTime = Date.now();
|
|
186
|
+
persistTaskStepStart(taskStep, getDbForRepo());
|
|
185
187
|
// ---------------------------
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
const stepAction = this.
|
|
188
|
+
// Step-level iterations: reason only about this file
|
|
189
|
+
// ---------------------------
|
|
190
|
+
const stepAction = await this.runStepIterations(taskStep);
|
|
189
191
|
if (stepAction === "complete") {
|
|
190
192
|
taskStep.status = "completed";
|
|
193
|
+
taskStep.endTime = Date.now();
|
|
194
|
+
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// optionally mark as pending or redo-step
|
|
198
|
+
taskStep.status = "pending";
|
|
199
|
+
persistTaskStepCompletion(taskStep, getDbForRepo());
|
|
191
200
|
}
|
|
192
201
|
}
|
|
193
|
-
// Safety exit
|
|
194
202
|
this.logLine("TASK", "Max task step limit reached — stopping work loop");
|
|
195
203
|
}
|
|
196
204
|
/* ───────────── step iterations ───────────── */
|
|
@@ -208,8 +216,7 @@ export class MainAgent {
|
|
|
208
216
|
}
|
|
209
217
|
return nextAction;
|
|
210
218
|
};
|
|
211
|
-
//
|
|
212
|
-
// allowing the agent to reason about completeness, redo work, or request feedback.
|
|
219
|
+
// Loop through iterations for a single task step (file)
|
|
213
220
|
while (loopCount < MAX_ITERATIONS) {
|
|
214
221
|
this.runCount++;
|
|
215
222
|
loopCount++;
|
|
@@ -221,11 +228,12 @@ export class MainAgent {
|
|
|
221
228
|
const nextAction = getNextIterationAction();
|
|
222
229
|
this.logLine("STEP-LOOP", "nextAction", undefined, nextAction);
|
|
223
230
|
if (nextAction === "complete")
|
|
224
|
-
return;
|
|
231
|
+
return "complete";
|
|
225
232
|
if (nextAction === "request-feedback")
|
|
226
|
-
return;
|
|
233
|
+
return "request-feedback";
|
|
227
234
|
// else: continue
|
|
228
235
|
}
|
|
236
|
+
return "continue";
|
|
229
237
|
}
|
|
230
238
|
/* ───────────── work iteration ───────────── */
|
|
231
239
|
async runWorkIteration(taskStep) {
|
|
@@ -312,7 +320,6 @@ export class MainAgent {
|
|
|
312
320
|
content: input.data ?? input.content,
|
|
313
321
|
context: this.context
|
|
314
322
|
});
|
|
315
|
-
this.logLine("EXECUTE", step.action, stop());
|
|
316
323
|
// Return ModuleIO (can remain minimal since output is untyped)
|
|
317
324
|
return { query: step.description ?? input.query, data: {} };
|
|
318
325
|
}
|
|
@@ -344,7 +351,6 @@ export class MainAgent {
|
|
|
344
351
|
allowed = constraints?.allowFileWrites ?? false;
|
|
345
352
|
break;
|
|
346
353
|
}
|
|
347
|
-
this.logLine("EXEC", "canExecutePhase", undefined, `phase=${phase}, docsOnly=${docsOnly}, allowed=${allowed}`);
|
|
348
354
|
return allowed;
|
|
349
355
|
}
|
|
350
356
|
/* ───────────── scope gates ───────────── */
|
|
@@ -368,7 +374,6 @@ export class MainAgent {
|
|
|
368
374
|
allowed = true;
|
|
369
375
|
break;
|
|
370
376
|
}
|
|
371
|
-
this.logLine("EXEC", "canExecuteScope", undefined, `phase=${phase}, scope=${scope}, allowed=${allowed}`);
|
|
372
377
|
return allowed;
|
|
373
378
|
}
|
|
374
379
|
/* ----------------------------------- */
|
|
@@ -544,3 +549,58 @@ export function bootTaskForRepo(context, db, logLine) {
|
|
|
544
549
|
logLine("TASK", `created task id = ${taskId}`);
|
|
545
550
|
return taskId;
|
|
546
551
|
}
|
|
552
|
+
export function persistTaskStepInsert(taskStep, db) {
|
|
553
|
+
if (taskStep.id)
|
|
554
|
+
return;
|
|
555
|
+
const nowIso = new Date().toISOString();
|
|
556
|
+
const result = db.prepare(`
|
|
557
|
+
INSERT INTO task_steps (
|
|
558
|
+
task_id,
|
|
559
|
+
file_path,
|
|
560
|
+
action,
|
|
561
|
+
status,
|
|
562
|
+
step_index,
|
|
563
|
+
start_time,
|
|
564
|
+
result_json,
|
|
565
|
+
notes,
|
|
566
|
+
created_at,
|
|
567
|
+
updated_at
|
|
568
|
+
)
|
|
569
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
570
|
+
`).run(taskStep.taskId, taskStep.filePath, taskStep.action ?? null, taskStep.status, taskStep.stepIndex ?? null, taskStep.startTime ?? null, taskStep.result != null ? JSON.stringify(taskStep.result) : null, taskStep.notes ?? null, nowIso, nowIso);
|
|
571
|
+
taskStep.id = result.lastInsertRowid;
|
|
572
|
+
}
|
|
573
|
+
export function persistTaskStepStart(taskStep, db) {
|
|
574
|
+
if (!taskStep.id)
|
|
575
|
+
return;
|
|
576
|
+
if (!taskStep.startTime) {
|
|
577
|
+
taskStep.startTime = Date.now();
|
|
578
|
+
}
|
|
579
|
+
db.prepare(`
|
|
580
|
+
UPDATE task_steps
|
|
581
|
+
SET
|
|
582
|
+
start_time = ?,
|
|
583
|
+
action = ?,
|
|
584
|
+
notes = ?,
|
|
585
|
+
updated_at = ?
|
|
586
|
+
WHERE id = ?
|
|
587
|
+
`).run(taskStep.startTime, taskStep.action ?? null, taskStep.notes ?? null, new Date().toISOString(), taskStep.id);
|
|
588
|
+
}
|
|
589
|
+
export function persistTaskStepCompletion(taskStep, db) {
|
|
590
|
+
if (!taskStep.id)
|
|
591
|
+
return;
|
|
592
|
+
if (!taskStep.endTime) {
|
|
593
|
+
taskStep.endTime = Date.now();
|
|
594
|
+
}
|
|
595
|
+
db.prepare(`
|
|
596
|
+
UPDATE task_steps
|
|
597
|
+
SET
|
|
598
|
+
status = ?,
|
|
599
|
+
end_time = ?,
|
|
600
|
+
action = ?,
|
|
601
|
+
result_json = ?,
|
|
602
|
+
notes = ?,
|
|
603
|
+
updated_at = ?
|
|
604
|
+
WHERE id = ?
|
|
605
|
+
`).run(taskStep.status, taskStep.endTime, taskStep.action ?? null, taskStep.result != null ? JSON.stringify(taskStep.result) : null, taskStep.notes ?? null, new Date().toISOString(), taskStep.id);
|
|
606
|
+
}
|
|
@@ -24,8 +24,9 @@ export const selectRelevantSourcesStep = {
|
|
|
24
24
|
const discarded = [];
|
|
25
25
|
const rationaleParts = [];
|
|
26
26
|
// ---------------------------
|
|
27
|
-
// 1️⃣ Classify files by evidence
|
|
27
|
+
// 1️⃣ Classify files by relevance, evidence, and mode
|
|
28
28
|
// ---------------------------
|
|
29
|
+
const mode = context.executionControl?.mode ?? "transform";
|
|
29
30
|
for (const [path, analysis] of Object.entries(fileAnalysis)) {
|
|
30
31
|
const isRelevant = analysis.action?.isRelevant === true;
|
|
31
32
|
if (!isRelevant) {
|
|
@@ -41,11 +42,17 @@ export const selectRelevantSourcesStep = {
|
|
|
41
42
|
catch {
|
|
42
43
|
code = undefined;
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
// 🔑 Mode-aware selection rule
|
|
46
|
+
const isSelected = mode === "transform"
|
|
47
|
+
? hasEvidence && canModify
|
|
48
|
+
: true; // analyze: relevance is sufficient
|
|
49
|
+
if (isSelected) {
|
|
45
50
|
selected.push({
|
|
46
51
|
path,
|
|
47
52
|
code,
|
|
48
|
-
selectionReason:
|
|
53
|
+
selectionReason: mode === "transform"
|
|
54
|
+
? "Selected: concrete evidence found and modification permitted."
|
|
55
|
+
: "Selected: relevant focus file for semantic analysis.",
|
|
49
56
|
});
|
|
50
57
|
}
|
|
51
58
|
else {
|
|
@@ -55,11 +55,9 @@ export function showTaskDetails(taskId) {
|
|
|
55
55
|
const isArray = Array.isArray(value);
|
|
56
56
|
const isObject = typeof value === "object" && !isArray;
|
|
57
57
|
if (typeof value === "string" && value.length < 80) {
|
|
58
|
-
// Short string: inline
|
|
59
58
|
console.log(chalk.yellow(`${label}:`), value);
|
|
60
59
|
}
|
|
61
60
|
else if (isArray) {
|
|
62
|
-
// Arrays: print first 5 items each on new line, then show count if truncated
|
|
63
61
|
console.log(chalk.yellow(`${label}:`));
|
|
64
62
|
const items = value;
|
|
65
63
|
const displayCount = Math.min(5, items.length);
|
|
@@ -71,24 +69,20 @@ export function showTaskDetails(taskId) {
|
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
71
|
else if (isObject) {
|
|
74
|
-
// Objects: pretty print JSON
|
|
75
72
|
console.log(chalk.yellow(`${label}:`));
|
|
76
73
|
console.log(indent + JSON.stringify(value, null, 2).replace(/\n/g, "\n" + indent));
|
|
77
74
|
}
|
|
78
75
|
else {
|
|
79
|
-
// Long string: print on next line with indentation
|
|
80
76
|
console.log(chalk.yellow(`${label}:`));
|
|
81
77
|
console.log(indent + String(value));
|
|
82
78
|
}
|
|
83
79
|
};
|
|
84
80
|
// Lifecycle
|
|
85
|
-
// Lifecycle
|
|
86
81
|
print("Initial query", task.initial_query);
|
|
87
|
-
// Final answer (canonical)
|
|
88
82
|
if (task.summary) {
|
|
89
83
|
console.log(chalk.green.bold("\nFinal answer:\n"));
|
|
90
84
|
console.log(chalk.white(task.summary));
|
|
91
|
-
console.log();
|
|
85
|
+
console.log();
|
|
92
86
|
}
|
|
93
87
|
// Intent
|
|
94
88
|
print("Agreed intent", task.agreed_intent);
|
|
@@ -124,6 +118,28 @@ export function showTaskDetails(taskId) {
|
|
|
124
118
|
// Bookkeeping
|
|
125
119
|
print("Created", task.created_at);
|
|
126
120
|
print("Updated", task.updated_at);
|
|
121
|
+
// ----------------- TASK STEPS -----------------
|
|
122
|
+
const steps = db.prepare(`
|
|
123
|
+
SELECT id, file_path, action, status, step_index, start_time, end_time, created_at, updated_at
|
|
124
|
+
FROM task_steps
|
|
125
|
+
WHERE task_id = ?
|
|
126
|
+
ORDER BY step_index ASC
|
|
127
|
+
`).all(taskId);
|
|
128
|
+
if (steps.length === 0) {
|
|
129
|
+
console.log(chalk.dim("\nNo task steps recorded."));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.log(chalk.bold("\nTask Steps:\n"));
|
|
133
|
+
steps.forEach(s => {
|
|
134
|
+
console.log(`${chalk.cyan(`[${s.id}]`)} ` +
|
|
135
|
+
`${chalk.yellow((s.status || "").padEnd(9))} ` +
|
|
136
|
+
`${s.file_path}` +
|
|
137
|
+
(s.action ? ` → ${s.action}` : "") +
|
|
138
|
+
` (index=${s.step_index ?? "-"})` +
|
|
139
|
+
(s.start_time ? ` started=${s.start_time}` : "") +
|
|
140
|
+
(s.end_time ? ` ended=${s.end_time}` : ""));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
127
143
|
}
|
|
128
144
|
/** Helper: truncate long text to a single line */
|
|
129
145
|
function oneLineTruncate(input, max = 80) {
|
package/dist/db/schema.js
CHANGED
|
@@ -52,6 +52,7 @@ export function initSchema() {
|
|
|
52
52
|
project_id INTEGER NOT NULL,
|
|
53
53
|
|
|
54
54
|
-- lifecycle
|
|
55
|
+
-- active | completed | paused | needs-feedback
|
|
55
56
|
status TEXT NOT NULL DEFAULT 'active',
|
|
56
57
|
|
|
57
58
|
-- user-facing / context
|
|
@@ -71,6 +72,9 @@ export function initSchema() {
|
|
|
71
72
|
-- routing decision
|
|
72
73
|
routing_decision_json TEXT,
|
|
73
74
|
|
|
75
|
+
-- 🔴 task-level feedback / questions (append-only)
|
|
76
|
+
task_questions_json TEXT,
|
|
77
|
+
|
|
74
78
|
-- bookkeeping
|
|
75
79
|
created_at TEXT NOT NULL,
|
|
76
80
|
updated_at TEXT NOT NULL,
|
|
@@ -85,6 +89,47 @@ export function initSchema() {
|
|
|
85
89
|
|
|
86
90
|
CREATE INDEX IF NOT EXISTS idx_tasks_status
|
|
87
91
|
ON tasks(status);
|
|
92
|
+
`);
|
|
93
|
+
// --- Task steps ---
|
|
94
|
+
db.exec(`
|
|
95
|
+
CREATE TABLE IF NOT EXISTS task_steps (
|
|
96
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
97
|
+
|
|
98
|
+
task_id INTEGER NOT NULL,
|
|
99
|
+
|
|
100
|
+
file_path TEXT NOT NULL,
|
|
101
|
+
action TEXT,
|
|
102
|
+
|
|
103
|
+
-- pending | completed | skipped | needs-feedback
|
|
104
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
105
|
+
|
|
106
|
+
start_time INTEGER,
|
|
107
|
+
end_time INTEGER,
|
|
108
|
+
|
|
109
|
+
result_json TEXT,
|
|
110
|
+
notes TEXT,
|
|
111
|
+
|
|
112
|
+
step_index INTEGER,
|
|
113
|
+
|
|
114
|
+
-- 🔴 step-level feedback / questions (append-only)
|
|
115
|
+
step_questions_json TEXT,
|
|
116
|
+
|
|
117
|
+
created_at TEXT NOT NULL,
|
|
118
|
+
updated_at TEXT NOT NULL,
|
|
119
|
+
|
|
120
|
+
FOREIGN KEY (task_id)
|
|
121
|
+
REFERENCES tasks(id)
|
|
122
|
+
ON DELETE CASCADE
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_task_steps_task
|
|
126
|
+
ON task_steps(task_id);
|
|
127
|
+
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_task_steps_status
|
|
129
|
+
ON task_steps(status);
|
|
130
|
+
|
|
131
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_task_steps_task_file
|
|
132
|
+
ON task_steps(task_id, file_path);
|
|
88
133
|
`);
|
|
89
134
|
// --- Core tables ---
|
|
90
135
|
db.exec(`
|
package/dist/scripts/dbcheck.js
CHANGED
|
@@ -90,6 +90,46 @@ else {
|
|
|
90
90
|
sampleTasks.forEach(t => console.log(` [${t.id}] "${t.initial_query}" (status=${t.status}, created=${t.created_at})`));
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
/* ───────────────────────── task_steps sanity ───────────────────────── */
|
|
94
|
+
header("🦾 task_steps");
|
|
95
|
+
const taskStepsExists = db
|
|
96
|
+
.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='task_steps'`)
|
|
97
|
+
.get();
|
|
98
|
+
if (!taskStepsExists) {
|
|
99
|
+
console.log("❌ task_steps table missing — you need to run initSchema with task_steps");
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const totalTaskSteps = tableCount("task_steps");
|
|
103
|
+
const pendingSteps = nonEmptyCount("task_steps", "status"); // all steps have status
|
|
104
|
+
console.log(`📊 total task_steps: ${totalTaskSteps}`);
|
|
105
|
+
console.log(`📝 task_steps with status: ${pendingSteps}`);
|
|
106
|
+
const sampleSteps = db.prepare(`
|
|
107
|
+
SELECT id, task_id, file_path, status, step_index, created_at
|
|
108
|
+
FROM task_steps
|
|
109
|
+
ORDER BY created_at DESC
|
|
110
|
+
LIMIT 3
|
|
111
|
+
`).all();
|
|
112
|
+
if (sampleSteps.length === 0) {
|
|
113
|
+
console.log("⚠️ no task_steps yet");
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log("✅ sample task_steps:");
|
|
117
|
+
sampleSteps.forEach(s => console.log(` [${s.id}] task ${s.task_id} - file "${s.file_path}" (status=${s.status}, index=${s.step_index}, created=${s.created_at})`));
|
|
118
|
+
}
|
|
119
|
+
// Check for steps referencing missing tasks
|
|
120
|
+
const danglingSteps = db.prepare(`
|
|
121
|
+
SELECT ts.id, ts.task_id
|
|
122
|
+
FROM task_steps ts
|
|
123
|
+
LEFT JOIN tasks t ON ts.task_id = t.id
|
|
124
|
+
WHERE t.id IS NULL
|
|
125
|
+
`).all();
|
|
126
|
+
if (danglingSteps.length > 0) {
|
|
127
|
+
console.log(`❌ ${danglingSteps.length} task_steps reference missing tasks`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log("✅ all task_steps reference valid tasks");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
93
133
|
/* ───────────────────────── files ───────────────────────── */
|
|
94
134
|
header("🗂 files");
|
|
95
135
|
const totalFiles = tableCount("files");
|