@synergenius/flow-weaver-pack-weaver 0.9.0 → 0.9.4
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/bot/ai-client.d.ts +22 -2
- package/dist/bot/ai-client.d.ts.map +1 -1
- package/dist/bot/ai-client.js +168 -20
- package/dist/bot/ai-client.js.map +1 -1
- package/dist/bot/ansi.d.ts +13 -0
- package/dist/bot/ansi.d.ts.map +1 -0
- package/dist/bot/ansi.js +13 -0
- package/dist/bot/ansi.js.map +1 -0
- package/dist/bot/assistant-core.d.ts +25 -0
- package/dist/bot/assistant-core.d.ts.map +1 -0
- package/dist/bot/assistant-core.js +272 -0
- package/dist/bot/assistant-core.js.map +1 -0
- package/dist/bot/assistant-tools.d.ts +10 -0
- package/dist/bot/assistant-tools.d.ts.map +1 -0
- package/dist/bot/assistant-tools.js +324 -0
- package/dist/bot/assistant-tools.js.map +1 -0
- package/dist/bot/audit-logger.d.ts.map +1 -1
- package/dist/bot/audit-logger.js +9 -5
- package/dist/bot/audit-logger.js.map +1 -1
- package/dist/bot/bot-manager.d.ts +49 -0
- package/dist/bot/bot-manager.d.ts.map +1 -0
- package/dist/bot/bot-manager.js +279 -0
- package/dist/bot/bot-manager.js.map +1 -0
- package/dist/bot/child-process-tracker.d.ts +6 -0
- package/dist/bot/child-process-tracker.d.ts.map +1 -0
- package/dist/bot/child-process-tracker.js +35 -0
- package/dist/bot/child-process-tracker.js.map +1 -0
- package/dist/bot/cli-provider.d.ts.map +1 -1
- package/dist/bot/cli-provider.js +13 -8
- package/dist/bot/cli-provider.js.map +1 -1
- package/dist/bot/conversation-store.d.ts +40 -0
- package/dist/bot/conversation-store.d.ts.map +1 -0
- package/dist/bot/conversation-store.js +182 -0
- package/dist/bot/conversation-store.js.map +1 -0
- package/dist/bot/error-classifier.d.ts +27 -0
- package/dist/bot/error-classifier.d.ts.map +1 -0
- package/dist/bot/error-classifier.js +71 -0
- package/dist/bot/error-classifier.js.map +1 -0
- package/dist/bot/error-guide.d.ts +5 -0
- package/dist/bot/error-guide.d.ts.map +1 -0
- package/dist/bot/error-guide.js +5 -0
- package/dist/bot/error-guide.js.map +1 -0
- package/dist/bot/knowledge-store.d.ts +17 -0
- package/dist/bot/knowledge-store.d.ts.map +1 -0
- package/dist/bot/knowledge-store.js +53 -0
- package/dist/bot/knowledge-store.js.map +1 -0
- package/dist/bot/paths.d.ts +11 -0
- package/dist/bot/paths.d.ts.map +1 -0
- package/dist/bot/paths.js +26 -0
- package/dist/bot/paths.js.map +1 -0
- package/dist/bot/retry-utils.d.ts +5 -0
- package/dist/bot/retry-utils.d.ts.map +1 -0
- package/dist/bot/retry-utils.js +5 -0
- package/dist/bot/retry-utils.js.map +1 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +12 -1
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/safety.d.ts +10 -0
- package/dist/bot/safety.d.ts.map +1 -0
- package/dist/bot/safety.js +14 -0
- package/dist/bot/safety.js.map +1 -0
- package/dist/bot/session-state.d.ts.map +1 -1
- package/dist/bot/session-state.js +3 -1
- package/dist/bot/session-state.js.map +1 -1
- package/dist/bot/steering.js +2 -2
- package/dist/bot/steering.js.map +1 -1
- package/dist/bot/step-executor.d.ts +10 -5
- package/dist/bot/step-executor.d.ts.map +1 -1
- package/dist/bot/step-executor.js +252 -3
- package/dist/bot/step-executor.js.map +1 -1
- package/dist/bot/system-prompt.d.ts +1 -1
- package/dist/bot/system-prompt.d.ts.map +1 -1
- package/dist/bot/system-prompt.js +69 -43
- package/dist/bot/system-prompt.js.map +1 -1
- package/dist/bot/task-decomposer.d.ts +24 -0
- package/dist/bot/task-decomposer.d.ts.map +1 -0
- package/dist/bot/task-decomposer.js +75 -0
- package/dist/bot/task-decomposer.js.map +1 -0
- package/dist/bot/task-queue.d.ts +17 -4
- package/dist/bot/task-queue.d.ts.map +1 -1
- package/dist/bot/task-queue.js +83 -5
- package/dist/bot/task-queue.js.map +1 -1
- package/dist/bot/terminal-renderer.d.ts +60 -0
- package/dist/bot/terminal-renderer.d.ts.map +1 -0
- package/dist/bot/terminal-renderer.js +204 -0
- package/dist/bot/terminal-renderer.js.map +1 -0
- package/dist/bot/tool-registry.d.ts +24 -0
- package/dist/bot/tool-registry.d.ts.map +1 -0
- package/dist/bot/tool-registry.js +458 -0
- package/dist/bot/tool-registry.js.map +1 -0
- package/dist/bot/types.d.ts +7 -0
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/bot/weaver-tools.d.ts +18 -0
- package/dist/bot/weaver-tools.d.ts.map +1 -0
- package/dist/bot/weaver-tools.js +124 -0
- package/dist/bot/weaver-tools.js.map +1 -0
- package/dist/cli-bridge.d.ts.map +1 -1
- package/dist/cli-bridge.js +5 -1
- package/dist/cli-bridge.js.map +1 -1
- package/dist/cli-handlers.d.ts +13 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +615 -48
- package/dist/cli-handlers.js.map +1 -1
- package/dist/mcp-tools.js +2 -2
- package/dist/mcp-tools.js.map +1 -1
- package/dist/node-types/abort-task.d.ts.map +1 -1
- package/dist/node-types/abort-task.js +4 -3
- package/dist/node-types/abort-task.js.map +1 -1
- package/dist/node-types/agent-execute.d.ts +38 -0
- package/dist/node-types/agent-execute.d.ts.map +1 -0
- package/dist/node-types/agent-execute.js +252 -0
- package/dist/node-types/agent-execute.js.map +1 -0
- package/dist/node-types/bot-report.d.ts +5 -3
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +39 -7
- package/dist/node-types/bot-report.js.map +1 -1
- package/dist/node-types/build-context.d.ts +3 -3
- package/dist/node-types/build-context.d.ts.map +1 -1
- package/dist/node-types/build-context.js +108 -24
- package/dist/node-types/build-context.js.map +1 -1
- package/dist/node-types/detect-provider.d.ts +2 -2
- package/dist/node-types/detect-provider.d.ts.map +1 -1
- package/dist/node-types/detect-provider.js +3 -1
- package/dist/node-types/detect-provider.js.map +1 -1
- package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
- package/dist/node-types/exec-validate-retry.js +43 -6
- package/dist/node-types/exec-validate-retry.js.map +1 -1
- package/dist/node-types/execute-plan.d.ts.map +1 -1
- package/dist/node-types/execute-plan.js +31 -8
- package/dist/node-types/execute-plan.js.map +1 -1
- package/dist/node-types/execute-target.d.ts.map +1 -1
- package/dist/node-types/execute-target.js +3 -1
- package/dist/node-types/execute-target.js.map +1 -1
- package/dist/node-types/fix-errors.d.ts.map +1 -1
- package/dist/node-types/fix-errors.js +21 -5
- package/dist/node-types/fix-errors.js.map +1 -1
- package/dist/node-types/genesis-observe.d.ts.map +1 -1
- package/dist/node-types/genesis-observe.js +3 -1
- package/dist/node-types/genesis-observe.js.map +1 -1
- package/dist/node-types/genesis-report.js +4 -1
- package/dist/node-types/genesis-report.js.map +1 -1
- package/dist/node-types/git-ops.d.ts.map +1 -1
- package/dist/node-types/git-ops.js +98 -4
- package/dist/node-types/git-ops.js.map +1 -1
- package/dist/node-types/index.d.ts +2 -0
- package/dist/node-types/index.d.ts.map +1 -1
- package/dist/node-types/index.js +2 -0
- package/dist/node-types/index.js.map +1 -1
- package/dist/node-types/load-config.d.ts +2 -2
- package/dist/node-types/load-config.d.ts.map +1 -1
- package/dist/node-types/load-config.js.map +1 -1
- package/dist/node-types/plan-task.d.ts.map +1 -1
- package/dist/node-types/plan-task.js +14 -2
- package/dist/node-types/plan-task.js.map +1 -1
- package/dist/node-types/read-workflow.js +8 -2
- package/dist/node-types/read-workflow.js.map +1 -1
- package/dist/node-types/receive-task.d.ts.map +1 -1
- package/dist/node-types/receive-task.js +35 -26
- package/dist/node-types/receive-task.js.map +1 -1
- package/dist/node-types/send-notify.js +2 -1
- package/dist/node-types/send-notify.js.map +1 -1
- package/dist/node-types/validate-gate.d.ts +18 -0
- package/dist/node-types/validate-gate.d.ts.map +1 -0
- package/dist/node-types/validate-gate.js +96 -0
- package/dist/node-types/validate-gate.js.map +1 -0
- package/dist/workflows/genesis-task.d.ts +20 -12
- package/dist/workflows/genesis-task.d.ts.map +1 -1
- package/dist/workflows/genesis-task.js +20 -12
- package/dist/workflows/genesis-task.js.map +1 -1
- package/dist/workflows/weaver-agent.d.ts +35 -0
- package/dist/workflows/weaver-agent.d.ts.map +1 -0
- package/dist/workflows/weaver-agent.js +777 -0
- package/dist/workflows/weaver-agent.js.map +1 -0
- package/dist/workflows/weaver-bot-batch.d.ts +19 -26
- package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
- package/dist/workflows/weaver-bot-batch.js +1043 -27
- package/dist/workflows/weaver-bot-batch.js.map +1 -1
- package/dist/workflows/weaver-bot.d.ts +21 -35
- package/dist/workflows/weaver-bot.d.ts.map +1 -1
- package/dist/workflows/weaver-bot.js +1119 -36
- package/dist/workflows/weaver-bot.js.map +1 -1
- package/flowweaver.manifest.json +21 -1
- package/package.json +5 -2
- package/src/bot/ai-client.ts +180 -19
- package/src/bot/ansi.ts +12 -0
- package/src/bot/assistant-core.ts +312 -0
- package/src/bot/assistant-tools.ts +318 -0
- package/src/bot/audit-logger.ts +6 -5
- package/src/bot/bot-manager.ts +293 -0
- package/src/bot/child-process-tracker.ts +40 -0
- package/src/bot/cli-provider.ts +13 -8
- package/src/bot/conversation-store.ts +222 -0
- package/src/bot/error-classifier.ts +90 -0
- package/src/bot/error-guide.ts +4 -0
- package/src/bot/knowledge-store.ts +59 -0
- package/src/bot/paths.ts +27 -0
- package/src/bot/retry-utils.ts +4 -0
- package/src/bot/runner.ts +12 -1
- package/src/bot/safety.ts +16 -0
- package/src/bot/session-state.ts +2 -1
- package/src/bot/steering.ts +2 -2
- package/src/bot/step-executor.ts +313 -5
- package/src/bot/system-prompt.ts +70 -47
- package/src/bot/task-decomposer.ts +100 -0
- package/src/bot/task-queue.ts +100 -8
- package/src/bot/terminal-renderer.ts +238 -0
- package/src/bot/tool-registry.ts +477 -0
- package/src/bot/types.ts +8 -0
- package/src/bot/weaver-tools.ts +134 -0
- package/src/cli-bridge.ts +7 -1
- package/src/cli-handlers.ts +624 -48
- package/src/mcp-tools.ts +2 -2
- package/src/node-types/abort-task.ts +5 -4
- package/src/node-types/agent-execute.ts +303 -0
- package/src/node-types/bot-report.ts +40 -9
- package/src/node-types/build-context.ts +112 -25
- package/src/node-types/detect-provider.ts +4 -3
- package/src/node-types/exec-validate-retry.ts +47 -8
- package/src/node-types/execute-plan.ts +32 -8
- package/src/node-types/execute-target.ts +2 -1
- package/src/node-types/fix-errors.ts +20 -5
- package/src/node-types/genesis-observe.ts +2 -1
- package/src/node-types/genesis-report.ts +1 -1
- package/src/node-types/git-ops.ts +93 -4
- package/src/node-types/index.ts +2 -0
- package/src/node-types/load-config.ts +3 -3
- package/src/node-types/plan-task.ts +15 -3
- package/src/node-types/read-workflow.ts +2 -2
- package/src/node-types/receive-task.ts +31 -26
- package/src/node-types/send-notify.ts +1 -1
- package/src/node-types/validate-gate.ts +112 -0
- package/src/workflows/genesis-task.ts +20 -12
- package/src/workflows/weaver-agent.ts +799 -0
- package/src/workflows/weaver-bot-batch.ts +1049 -27
- package/src/workflows/weaver-bot.ts +1123 -36
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import * as os from 'node:os';
|
|
4
|
-
import type { WeaverContext } from '../bot/types.js';
|
|
5
|
-
import { callAI, parseJsonResponse } from '../bot/ai-client.js';
|
|
4
|
+
import type { WeaverContext, StepLogEntry } from '../bot/types.js';
|
|
5
|
+
import { callAI, parseJsonResponse, normalizePlan } from '../bot/ai-client.js';
|
|
6
6
|
import { executeStep } from '../bot/step-executor.js';
|
|
7
7
|
import { validateFiles } from '../bot/file-validator.js';
|
|
8
8
|
import { auditEmit } from '../bot/audit-logger.js';
|
|
@@ -38,19 +38,29 @@ export async function weaverExecValidateRetry(
|
|
|
38
38
|
|
|
39
39
|
const { providerInfo: pInfo, projectDir } = env;
|
|
40
40
|
const maxAttempts = 3;
|
|
41
|
+
const taskDeadline = Date.now() + 180_000; // 3 minute max per task
|
|
41
42
|
let currentPlan = JSON.parse(context.planJson!);
|
|
42
43
|
let allFilesModified: string[] = [];
|
|
44
|
+
let allStepLog: StepLogEntry[] = [];
|
|
43
45
|
let lastExecResult: Record<string, unknown> = {};
|
|
44
46
|
let lastValidation: Array<{ file: string; valid: boolean; errors: string[] }> = [];
|
|
45
47
|
let allValid = false;
|
|
48
|
+
let prevErrorCount = Infinity;
|
|
46
49
|
|
|
47
50
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
51
|
+
// Task timeout — stop if we've exceeded the deadline
|
|
52
|
+
if (Date.now() > taskDeadline) {
|
|
53
|
+
console.error('\x1b[33m→ Task timeout (3 min), moving on\x1b[0m');
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
|
|
48
57
|
console.log(`\x1b[36m→ Attempt ${attempt}/${maxAttempts}\x1b[0m`);
|
|
49
58
|
auditEmit('step-start', { attempt, stepCount: currentPlan.steps?.length });
|
|
50
59
|
|
|
51
60
|
const execResult = await executePlanSteps(currentPlan, projectDir);
|
|
52
61
|
lastExecResult = execResult;
|
|
53
62
|
allFilesModified = [...new Set([...allFilesModified, ...execResult.filesModified])];
|
|
63
|
+
allStepLog.push(...execResult.stepLog);
|
|
54
64
|
auditEmit('step-complete', { attempt, filesModified: execResult.filesModified, errors: execResult.errors });
|
|
55
65
|
|
|
56
66
|
const validation = await validateFiles(execResult.filesModified, projectDir);
|
|
@@ -59,6 +69,13 @@ export async function weaverExecValidateRetry(
|
|
|
59
69
|
const errorCount = validation.filter(v => !v.valid).length;
|
|
60
70
|
auditEmit('validation-run', { attempt, allValid, errorCount });
|
|
61
71
|
|
|
72
|
+
// Stop retrying if errors didn't decrease — AI is stuck
|
|
73
|
+
if (attempt > 1 && errorCount >= prevErrorCount) {
|
|
74
|
+
console.error(`\x1b[33m→ Errors not decreasing (${errorCount} >= ${prevErrorCount}), stopping retry\x1b[0m`);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
prevErrorCount = errorCount;
|
|
78
|
+
|
|
62
79
|
// Collect design warnings from valid files
|
|
63
80
|
const designWarnings = validation
|
|
64
81
|
.filter(v => v.valid && v.warnings.length > 0)
|
|
@@ -97,16 +114,23 @@ export async function weaverExecValidateRetry(
|
|
|
97
114
|
try {
|
|
98
115
|
const mod = await import('../bot/system-prompt.js');
|
|
99
116
|
systemPrompt = await mod.buildSystemPrompt();
|
|
100
|
-
} catch {
|
|
117
|
+
} catch (err) {
|
|
118
|
+
if (process.env.WEAVER_VERBOSE) console.error('[exec-validate-retry] system prompt build failed:', err);
|
|
101
119
|
systemPrompt = 'You are Weaver. Return ONLY valid JSON.';
|
|
102
120
|
}
|
|
103
121
|
|
|
104
|
-
|
|
122
|
+
// Include step outputs so the AI has context from discovery steps
|
|
123
|
+
const outputContext = Object.entries(execResult.stepOutputs)
|
|
124
|
+
.map(([id, out]) => `--- Output of ${id} ---\n${out}`)
|
|
125
|
+
.join('\n\n');
|
|
126
|
+
|
|
127
|
+
const fixPrompt = `The following validation errors occurred:\n${errors}\n\n${outputContext ? `Discovery step outputs:\n${outputContext}\n\n` : ''}Provide a CONCRETE fix plan. Every patch-file step MUST include "file" (absolute path from the outputs above) and "patches" array with exact "find"/"replace" strings. Do NOT use placeholders.`;
|
|
105
128
|
|
|
106
129
|
const text = await callAI(pInfo, systemPrompt, fixPrompt, 8192);
|
|
107
130
|
|
|
108
|
-
|
|
109
|
-
|
|
131
|
+
const parsed = parseJsonResponse(text);
|
|
132
|
+
currentPlan = normalizePlan(parsed);
|
|
133
|
+
console.log(`\x1b[36m→ Fix plan: ${currentPlan.summary} (${currentPlan.steps.length} steps)\x1b[0m`);
|
|
110
134
|
} catch (err: unknown) {
|
|
111
135
|
const msg = err instanceof Error ? err.message : String(err);
|
|
112
136
|
console.error(`\x1b[31m→ Fix planning failed: ${msg}\x1b[0m`);
|
|
@@ -118,6 +142,7 @@ export async function weaverExecValidateRetry(
|
|
|
118
142
|
context.resultJson = JSON.stringify(lastExecResult);
|
|
119
143
|
context.validationResultJson = JSON.stringify(lastValidation);
|
|
120
144
|
context.filesModified = JSON.stringify(allFilesModified);
|
|
145
|
+
context.stepLogJson = JSON.stringify(allStepLog);
|
|
121
146
|
context.allValid = allValid;
|
|
122
147
|
|
|
123
148
|
return { onSuccess: allValid, onFailure: !allValid, ctx: JSON.stringify(context) };
|
|
@@ -126,9 +151,11 @@ export async function weaverExecValidateRetry(
|
|
|
126
151
|
async function executePlanSteps(
|
|
127
152
|
plan: { steps: Array<{ id: string; operation: string; description: string; args: Record<string, unknown> }> },
|
|
128
153
|
projectDir: string,
|
|
129
|
-
): Promise<{ success: boolean; filesModified: string[]; errors: string[]; stepsCompleted: number; stepsTotal: number }> {
|
|
154
|
+
): Promise<{ success: boolean; filesModified: string[]; errors: string[]; stepsCompleted: number; stepsTotal: number; stepLog: StepLogEntry[]; stepOutputs: Record<string, string> }> {
|
|
130
155
|
const filesModified: string[] = [];
|
|
131
156
|
const errors: string[] = [];
|
|
157
|
+
const stepLog: StepLogEntry[] = [];
|
|
158
|
+
const stepOutputs: Record<string, string> = {};
|
|
132
159
|
let completed = 0;
|
|
133
160
|
const steps = plan.steps ?? [];
|
|
134
161
|
|
|
@@ -136,23 +163,35 @@ async function executePlanSteps(
|
|
|
136
163
|
const steering = checkSteeringSignal();
|
|
137
164
|
if (steering === 'cancel') {
|
|
138
165
|
errors.push(`Cancelled at step ${step.id}`);
|
|
166
|
+
stepLog.push({ step: step.id, status: 'error', detail: 'Cancelled by steering signal' });
|
|
139
167
|
break;
|
|
140
168
|
}
|
|
141
169
|
|
|
142
170
|
try {
|
|
143
171
|
const result = await executeStep(step, projectDir);
|
|
172
|
+
if (result.blocked) {
|
|
173
|
+
console.error(`\x1b[33m ⚠ ${step.id}: ${result.blockReason}\x1b[0m`);
|
|
174
|
+
stepLog.push({ step: step.id, status: 'blocked', detail: result.blockReason });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
144
177
|
if (result.file) filesModified.push(result.file);
|
|
145
178
|
if (result.files) filesModified.push(...result.files);
|
|
179
|
+
// Capture step output for feeding into fix prompts
|
|
180
|
+
if (result.output) {
|
|
181
|
+
stepOutputs[step.id] = result.output.slice(0, 4000); // cap at 4k to fit in prompt
|
|
182
|
+
}
|
|
146
183
|
completed++;
|
|
147
184
|
console.log(`\x1b[32m + ${step.id}: ${step.description}\x1b[0m`);
|
|
185
|
+
stepLog.push({ step: step.id, status: 'ok', detail: step.description });
|
|
148
186
|
} catch (err: unknown) {
|
|
149
187
|
const msg = err instanceof Error ? err.message : String(err);
|
|
150
188
|
errors.push(`${step.id}: ${msg}`);
|
|
151
189
|
console.error(`\x1b[31m x ${step.id}: ${msg}\x1b[0m`);
|
|
190
|
+
stepLog.push({ step: step.id, status: 'error', detail: msg });
|
|
152
191
|
}
|
|
153
192
|
}
|
|
154
193
|
|
|
155
|
-
return { success: errors.length === 0, filesModified: [...new Set(filesModified)], errors, stepsCompleted: completed, stepsTotal: steps.length };
|
|
194
|
+
return { success: errors.length === 0, filesModified: [...new Set(filesModified)], errors, stepsCompleted: completed, stepsTotal: steps.length, stepLog, stepOutputs };
|
|
156
195
|
}
|
|
157
196
|
|
|
158
197
|
function checkSteeringSignal(): 'cancel' | null {
|
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import type { WeaverEnv } from '../bot/types.js';
|
|
5
|
-
import { executeStep } from '../bot/step-executor.js';
|
|
5
|
+
import { executeStep, resetPlanFileCounter } from '../bot/step-executor.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Executes plan steps via the flow-weaver CLI. Checks steering
|
|
@@ -39,6 +39,7 @@ export async function weaverExecutePlan(
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const { projectDir } = env;
|
|
42
|
+
resetPlanFileCounter(); // Reset per-plan write counter for safety guards
|
|
42
43
|
const plan = JSON.parse(planJson) as { steps: Array<{ id: string; operation: string; description: string; args: Record<string, unknown> }> };
|
|
43
44
|
const filesModified: string[] = [];
|
|
44
45
|
const filesCreated: string[] = [];
|
|
@@ -55,10 +56,27 @@ export async function weaverExecutePlan(
|
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
for (
|
|
59
|
+
for (let i = 0; i < plan.steps.length; i++) {
|
|
60
|
+
const step = plan.steps[i];
|
|
61
|
+
// Defensive: ensure step has required fields
|
|
62
|
+
if (!step || typeof step !== 'object') {
|
|
63
|
+
errors.push(`step-${i + 1}: Malformed step (not an object)`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const stepId = step.id ?? `step-${i + 1}`;
|
|
67
|
+
const stepDesc = step.description ?? step.operation ?? 'unknown';
|
|
68
|
+
if (!step.operation) {
|
|
69
|
+
errors.push(`${stepId}: Missing "operation" field`);
|
|
70
|
+
console.error(`\x1b[31m x ${stepId}: Missing operation\x1b[0m`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!step.args) {
|
|
74
|
+
step.args = {};
|
|
75
|
+
}
|
|
76
|
+
|
|
59
77
|
const steering = checkSteering();
|
|
60
78
|
if (steering === 'cancel') {
|
|
61
|
-
output.push(`Cancelled at
|
|
79
|
+
output.push(`Cancelled at ${stepId}`);
|
|
62
80
|
break;
|
|
63
81
|
}
|
|
64
82
|
if (steering === 'pause') {
|
|
@@ -68,18 +86,24 @@ export async function weaverExecutePlan(
|
|
|
68
86
|
|
|
69
87
|
try {
|
|
70
88
|
const result = await executeStep(step, projectDir);
|
|
89
|
+
if (result.blocked) {
|
|
90
|
+
errors.push(`${stepId}: BLOCKED - ${result.blockReason}`);
|
|
91
|
+
output.push(`${stepId}: BLOCKED - ${result.blockReason}`);
|
|
92
|
+
console.error(`\x1b[33m ⚠ ${stepId}: ${result.blockReason}\x1b[0m`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
71
95
|
if (result.file) {
|
|
72
96
|
if (result.created) filesCreated.push(result.file);
|
|
73
97
|
else filesModified.push(result.file);
|
|
74
98
|
}
|
|
75
99
|
completed++;
|
|
76
|
-
output.push(`${
|
|
77
|
-
console.log(`\x1b[32m + ${
|
|
100
|
+
output.push(`${stepId}: ${stepDesc} - done`);
|
|
101
|
+
console.log(`\x1b[32m + ${stepId}: ${stepDesc}\x1b[0m`);
|
|
78
102
|
} catch (err: unknown) {
|
|
79
103
|
const msg = err instanceof Error ? err.message : String(err);
|
|
80
|
-
errors.push(`${
|
|
81
|
-
output.push(`${
|
|
82
|
-
console.error(`\x1b[31m x ${
|
|
104
|
+
errors.push(`${stepId}: ${msg}`);
|
|
105
|
+
output.push(`${stepId}: FAILED - ${msg}`);
|
|
106
|
+
console.error(`\x1b[31m x ${stepId}: ${msg}\x1b[0m`);
|
|
83
107
|
}
|
|
84
108
|
}
|
|
85
109
|
|
|
@@ -137,7 +137,8 @@ When stabilize mode is active, only fix-up operations are allowed: removeNode, r
|
|
|
137
137
|
## Response Format
|
|
138
138
|
|
|
139
139
|
Return ONLY valid JSON. No markdown, no code fences, no explanation outside the JSON structure.`;
|
|
140
|
-
} catch {
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (process.env.WEAVER_VERBOSE) console.error('[execute-target] prompt build failed:', err);
|
|
141
142
|
return FALLBACK;
|
|
142
143
|
}
|
|
143
144
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WeaverEnv } from '../bot/types.js';
|
|
2
|
-
import { callAI, parseJsonResponse } from '../bot/ai-client.js';
|
|
2
|
+
import { callAI, parseJsonResponse, normalizePlan } from '../bot/ai-client.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* When validation fails, sends errors + context to the AI and
|
|
@@ -42,18 +42,33 @@ export async function weaverFixErrors(
|
|
|
42
42
|
try {
|
|
43
43
|
const mod = await import('../bot/system-prompt.js');
|
|
44
44
|
systemPrompt = await mod.buildSystemPrompt();
|
|
45
|
-
} catch {
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (process.env.WEAVER_VERBOSE) console.error('[fix-errors] system prompt build failed:', err);
|
|
46
47
|
systemPrompt = 'You are Weaver. Return ONLY valid JSON.';
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const errorSummary = errors.map(e => `${e.file}: ${e.errors.join(', ')}`).join('\n');
|
|
50
|
-
const userPrompt = `The following validation errors occurred
|
|
51
|
+
const userPrompt = `The following validation errors occurred:
|
|
52
|
+
${errorSummary}
|
|
53
|
+
|
|
54
|
+
Provide a fix plan as JSON: {"steps": [...], "summary": "..."}
|
|
55
|
+
|
|
56
|
+
Each step MUST have: "id" (string), "operation" (string), "description" (string), "args" (object).
|
|
57
|
+
|
|
58
|
+
Available operations for fixes:
|
|
59
|
+
- patch-file: Surgical find-and-replace. args: { "file": "path", "patches": [{ "find": "old text", "replace": "new text" }] }
|
|
60
|
+
PREFERRED for fixing @input annotations. Example: { "find": "@input portName", "replace": "@input [portName]" }
|
|
61
|
+
- run-shell: Execute a command. args: { "command": "..." }
|
|
62
|
+
- read-file: Read file content. args: { "file": "path" }
|
|
63
|
+
|
|
64
|
+
Return ONLY valid JSON. No explanation outside the JSON.`;
|
|
51
65
|
|
|
52
66
|
try {
|
|
53
67
|
const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
|
|
54
68
|
|
|
55
|
-
const
|
|
56
|
-
|
|
69
|
+
const parsed = parseJsonResponse(text);
|
|
70
|
+
const plan = normalizePlan(parsed);
|
|
71
|
+
console.log(`\x1b[36m→ Fix plan: ${plan.summary} (${plan.steps.length} steps)\x1b[0m`);
|
|
57
72
|
return { onSuccess: true, onFailure: false, env, taskJson, fixPlanJson: JSON.stringify(plan) };
|
|
58
73
|
} catch (err: unknown) {
|
|
59
74
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -77,8 +77,9 @@ export async function genesisObserve(
|
|
|
77
77
|
gitCommit = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
78
78
|
cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
79
79
|
}).trim();
|
|
80
|
-
} catch {
|
|
80
|
+
} catch (err) {
|
|
81
81
|
// Not a git repo or git unavailable
|
|
82
|
+
if (process.env.WEAVER_VERBOSE) console.error('[genesis-observe] git unavailable:', err);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
const existingWorkflows: string[] = [];
|
|
@@ -34,7 +34,7 @@ export function genesisReport(successCtx?: string, failCtx?: string, proposeFail
|
|
|
34
34
|
try {
|
|
35
35
|
const result = JSON.parse(context.applyResultJson) as { applied: number; failed: number };
|
|
36
36
|
summary += ` (applied: ${result.applied}, failed: ${result.failed})`;
|
|
37
|
-
} catch {
|
|
37
|
+
} catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[genesis-report] applyResultJson parse failed:', err); }
|
|
38
38
|
}
|
|
39
39
|
summary += elapsed ? ` [${elapsed}]` : '';
|
|
40
40
|
console.log(`\n\x1b[31m${summary}\x1b[0m\n`);
|
|
@@ -1,7 +1,62 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import * as path from 'node:path';
|
|
2
3
|
import type { WeaverContext } from '../bot/types.js';
|
|
3
4
|
import { auditEmit } from '../bot/audit-logger.js';
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Review staged diff for suspicious changes before committing.
|
|
8
|
+
* Returns an array of issues found (empty = safe to commit).
|
|
9
|
+
*/
|
|
10
|
+
function reviewStagedDiff(cwd: string): string[] {
|
|
11
|
+
const issues: string[] = [];
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const diff = execFileSync('git', ['diff', '--cached', '--stat'], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
15
|
+
if (!diff) return issues;
|
|
16
|
+
|
|
17
|
+
// Check for large deletions (file became empty or nearly empty)
|
|
18
|
+
const numstat = execFileSync('git', ['diff', '--cached', '--numstat'], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
19
|
+
for (const line of numstat.split('\n').filter(Boolean)) {
|
|
20
|
+
const [added, deleted, file] = line.split('\t');
|
|
21
|
+
if (!file || !added || !deleted) continue;
|
|
22
|
+
const addedN = parseInt(added, 10) || 0;
|
|
23
|
+
const deletedN = parseInt(deleted, 10) || 0;
|
|
24
|
+
|
|
25
|
+
// Flag: file lost >80% of its content with minimal additions
|
|
26
|
+
if (deletedN > 50 && addedN < 5 && deletedN > addedN * 10) {
|
|
27
|
+
issues.push(`${file}: deleted ${deletedN} lines, added only ${addedN} (possible truncation)`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Flag: file became empty (0 additions, all deletions)
|
|
31
|
+
if (addedN === 0 && deletedN > 10) {
|
|
32
|
+
issues.push(`${file}: file emptied (${deletedN} lines deleted, 0 added)`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check for sensitive patterns in added lines
|
|
37
|
+
const patchDiff = execFileSync('git', ['diff', '--cached', '-U0'], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
38
|
+
const sensitivePatterns = [
|
|
39
|
+
/api[_-]?key\s*[:=]\s*["'][^"']{20,}/i,
|
|
40
|
+
/secret\s*[:=]\s*["'][^"']{10,}/i,
|
|
41
|
+
/password\s*[:=]\s*["'][^"']{5,}/i,
|
|
42
|
+
];
|
|
43
|
+
for (const line of patchDiff.split('\n')) {
|
|
44
|
+
if (!line.startsWith('+') || line.startsWith('+++')) continue;
|
|
45
|
+
for (const pattern of sensitivePatterns) {
|
|
46
|
+
if (pattern.test(line)) {
|
|
47
|
+
issues.push('Possible credential/secret in staged changes');
|
|
48
|
+
return issues; // One is enough to block
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
// If diff commands fail, don't block — let commit proceed
|
|
54
|
+
if (process.env.WEAVER_VERBOSE) console.error('[git-ops] diff review failed:', err);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return issues;
|
|
58
|
+
}
|
|
59
|
+
|
|
5
60
|
/**
|
|
6
61
|
* Git operations on created/modified files: stage, commit, branch.
|
|
7
62
|
* Runs in parallel with notifications after execution.
|
|
@@ -48,25 +103,59 @@ export function weaverGitOps(ctx: string): { ctx: string } {
|
|
|
48
103
|
}
|
|
49
104
|
}
|
|
50
105
|
|
|
51
|
-
//
|
|
106
|
+
// Get actually-changed files from git (avoids phantom commits)
|
|
107
|
+
let changedFiles: Set<string>;
|
|
108
|
+
try {
|
|
109
|
+
const diff = execFileSync('git', ['diff', '--name-only'], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
110
|
+
const untracked = execFileSync('git', ['ls-files', '--others', '--exclude-standard'], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
111
|
+
changedFiles = new Set([...diff.split('\n'), ...untracked.split('\n')].filter(Boolean));
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (process.env.WEAVER_VERBOSE) console.error('[git-ops] git diff failed, using filesModified fallback:', err);
|
|
114
|
+
changedFiles = new Set(files); // fallback: trust filesModified
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Stage only files that are both in filesModified AND actually changed
|
|
118
|
+
let staged = 0;
|
|
52
119
|
for (const file of files) {
|
|
120
|
+
// Resolve to relative path for comparison
|
|
121
|
+
const relative = path.relative(projectDir, path.resolve(projectDir, file));
|
|
122
|
+
if (!changedFiles.has(relative) && !changedFiles.has(file)) continue;
|
|
53
123
|
try {
|
|
54
124
|
execFileSync('git', ['add', file], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
125
|
+
staged++;
|
|
55
126
|
} catch { /* ignore unstaged files */ }
|
|
56
127
|
}
|
|
57
128
|
|
|
129
|
+
if (staged === 0) {
|
|
130
|
+
results.push('No actual changes to commit');
|
|
131
|
+
auditEmit('git-operation', { branch: gitConfig.branch, filesCount: 0, results });
|
|
132
|
+
context.gitResultJson = JSON.stringify({ skipped: true, reason: 'no actual changes', results });
|
|
133
|
+
return { ctx: JSON.stringify(context) };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Diff review: check for suspicious changes before committing
|
|
137
|
+
const diffIssues = reviewStagedDiff(projectDir);
|
|
138
|
+
if (diffIssues.length > 0) {
|
|
139
|
+
// Unstage and skip commit — something looks wrong
|
|
140
|
+
try { execFileSync('git', ['reset', 'HEAD'], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); } catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[git-ops] reset failed:', err); }
|
|
141
|
+
results.push(`Commit blocked: ${diffIssues.join('; ')}`);
|
|
142
|
+
auditEmit('git-operation', { branch: gitConfig.branch, filesCount: 0, results, blocked: true });
|
|
143
|
+
context.gitResultJson = JSON.stringify({ skipped: true, reason: 'diff review failed', issues: diffIssues, results });
|
|
144
|
+
return { ctx: JSON.stringify(context) };
|
|
145
|
+
}
|
|
146
|
+
|
|
58
147
|
// Commit
|
|
59
148
|
const prefix = gitConfig.commitPrefix ?? 'weaver:';
|
|
60
|
-
const commitMsg = `${prefix} bot task (${
|
|
149
|
+
const commitMsg = `${prefix} bot task (${staged} file${staged === 1 ? '' : 's'})`;
|
|
61
150
|
try {
|
|
62
151
|
execFileSync('git', ['commit', '-m', commitMsg], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
63
152
|
results.push(`Committed: ${commitMsg}`);
|
|
64
|
-
|
|
153
|
+
if (process.env.WEAVER_VERBOSE) process.stderr.write(`\x1b[2m Git: ${commitMsg}\x1b[0m\n`);
|
|
65
154
|
} catch {
|
|
66
155
|
results.push('Nothing to commit');
|
|
67
156
|
}
|
|
68
157
|
|
|
69
|
-
auditEmit('git-operation', { branch: gitConfig.branch, filesCount:
|
|
158
|
+
auditEmit('git-operation', { branch: gitConfig.branch, filesCount: staged, results });
|
|
70
159
|
context.gitResultJson = JSON.stringify({ skipped: false, results });
|
|
71
160
|
return { ctx: JSON.stringify(context) };
|
|
72
161
|
}
|
package/src/node-types/index.ts
CHANGED
|
@@ -17,6 +17,8 @@ export { weaverValidateResult } from './validate-result.js';
|
|
|
17
17
|
export { weaverFixErrors } from './fix-errors.js';
|
|
18
18
|
export { weaverGitOps } from './git-ops.js';
|
|
19
19
|
export { weaverBotReport } from './bot-report.js';
|
|
20
|
+
export { weaverAgentExecute } from './agent-execute.js';
|
|
21
|
+
export { weaverValidateGate } from './validate-gate.js';
|
|
20
22
|
export { genesisLoadConfig } from './genesis-load-config.js';
|
|
21
23
|
export { genesisObserve } from './genesis-observe.js';
|
|
22
24
|
export { genesisDiffFingerprint } from './genesis-diff-fingerprint.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
-
import type {
|
|
3
|
+
import type { BotConfig } from '../bot/types.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Read .weaver.json, merge with defaults, and output the config object.
|
|
@@ -13,10 +13,10 @@ import type { WeaverConfig } from '../bot/types.js';
|
|
|
13
13
|
* @output config [order:1] - Weaver configuration
|
|
14
14
|
* @output onFailure [hidden]
|
|
15
15
|
*/
|
|
16
|
-
export function weaverLoadConfig(projectDir?: string): { projectDir: string; config:
|
|
16
|
+
export function weaverLoadConfig(projectDir?: string): { projectDir: string; config: BotConfig } {
|
|
17
17
|
const dir = projectDir || process.cwd();
|
|
18
18
|
const configPath = path.join(dir, '.weaver.json');
|
|
19
|
-
let config:
|
|
19
|
+
let config: BotConfig = { provider: 'auto' };
|
|
20
20
|
if (fs.existsSync(configPath)) {
|
|
21
21
|
config = { ...config, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) };
|
|
22
22
|
console.log(`\x1b[36m→ Loaded config from ${configPath}\x1b[0m`);
|
|
@@ -29,7 +29,12 @@ export async function weaverPlanTask(
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const { providerInfo: pInfo } = env;
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
if (!context.taskJson) {
|
|
34
|
+
context.planJson = JSON.stringify({ steps: [], summary: 'Planning failed: missing taskJson' });
|
|
35
|
+
return { onSuccess: false, onFailure: true, ctx: JSON.stringify(context) };
|
|
36
|
+
}
|
|
37
|
+
const task = JSON.parse(context.taskJson);
|
|
33
38
|
|
|
34
39
|
let systemPrompt: string;
|
|
35
40
|
try {
|
|
@@ -42,11 +47,18 @@ export async function weaverPlanTask(
|
|
|
42
47
|
} catch { /* older flow-weaver version */ }
|
|
43
48
|
const botPrompt = mod.buildBotSystemPrompt(context.contextBundle!, cliCommands);
|
|
44
49
|
systemPrompt = basePrompt + '\n\n' + botPrompt;
|
|
45
|
-
} catch {
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (process.env.WEAVER_VERBOSE) console.error('[plan-task] system prompt build failed:', err);
|
|
46
52
|
systemPrompt = 'You are Weaver, an AI workflow bot. Return ONLY valid JSON with a plan.';
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
const userPrompt = `Task: ${task.instruction}\nMode: ${task.mode ?? 'create'}\n${task.targets ? 'Targets: ' + task.targets.join(', ') : ''}
|
|
55
|
+
const userPrompt = `Task: ${task.instruction}\nMode: ${task.mode ?? 'create'}\n${task.targets ? 'Targets: ' + task.targets.join(', ') : ''}
|
|
56
|
+
|
|
57
|
+
Plan this task. IMPORTANT rules:
|
|
58
|
+
1. Every step MUST have complete, concrete args — no empty patches, no placeholders.
|
|
59
|
+
2. If you need to discover file contents or errors before fixing, plan ONLY the discovery steps (run-shell, read-file, list-files) in this first plan. The system will automatically provide a second planning round with actual errors if validation fails.
|
|
60
|
+
3. Do NOT plan patch-file steps unless you already know the exact find/replace strings.
|
|
61
|
+
4. Prefer run-shell with "npx flow-weaver validate <file> --json" to discover errors, then read-file to see the file, then patch-file with exact strings.`;
|
|
50
62
|
|
|
51
63
|
try {
|
|
52
64
|
const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
|
|
@@ -47,7 +47,7 @@ export function weaverReadWorkflow(ctx: string): { ctx: string } {
|
|
|
47
47
|
timeout: 30_000,
|
|
48
48
|
cwd: projectDir,
|
|
49
49
|
}).trim();
|
|
50
|
-
} catch {
|
|
50
|
+
} catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[read-workflow] diagram failed:', err); }
|
|
51
51
|
|
|
52
52
|
try {
|
|
53
53
|
description = execFileSync('flow-weaver', ['describe', filePath], {
|
|
@@ -56,7 +56,7 @@ export function weaverReadWorkflow(ctx: string): { ctx: string } {
|
|
|
56
56
|
timeout: 30_000,
|
|
57
57
|
cwd: projectDir,
|
|
58
58
|
}).trim();
|
|
59
|
-
} catch {
|
|
59
|
+
} catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[read-workflow] describe failed:', err); }
|
|
60
60
|
|
|
61
61
|
results.push({ file: target, source, diagram, description });
|
|
62
62
|
console.log(`\x1b[36m→ Read: ${target}\x1b[0m`);
|
|
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import type { WeaverEnv, WeaverContext } from '../bot/types.js';
|
|
5
|
+
import { withFileLock } from '../bot/file-lock.js';
|
|
5
6
|
|
|
6
7
|
interface QueuedTask {
|
|
7
8
|
id: string;
|
|
@@ -54,38 +55,42 @@ export async function weaverReceiveTask(
|
|
|
54
55
|
} catch { /* fall through to queue check */ }
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
// Check task queue
|
|
58
|
+
// Check task queue (with file locking to prevent race conditions)
|
|
58
59
|
const queuePath = path.join(os.homedir(), '.weaver', 'task-queue.ndjson');
|
|
59
60
|
try {
|
|
60
|
-
|
|
61
|
+
const claimed = await withFileLock(queuePath, () => {
|
|
62
|
+
if (!fs.existsSync(queuePath)) return null;
|
|
61
63
|
const content = fs.readFileSync(queuePath, 'utf-8').trim();
|
|
62
|
-
if (content)
|
|
63
|
-
const tasks: QueuedTask[] = content.split('\n').map(l => JSON.parse(l));
|
|
64
|
-
const pending = tasks
|
|
65
|
-
.filter(t => t.status === 'pending')
|
|
66
|
-
.sort((a, b) => b.priority - a.priority || a.addedAt - b.addedAt);
|
|
64
|
+
if (!content) return null;
|
|
67
65
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
fs.writeFileSync(queuePath, updated.map(t => JSON.stringify(t)).join('\n') + '\n', 'utf-8');
|
|
66
|
+
const tasks: QueuedTask[] = content.split('\n').filter(Boolean).map(l => JSON.parse(l));
|
|
67
|
+
const pending = tasks
|
|
68
|
+
.filter(t => t.status === 'pending')
|
|
69
|
+
.sort((a, b) => b.priority - a.priority || a.addedAt - b.addedAt);
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
if (pending.length === 0) return null;
|
|
72
|
+
|
|
73
|
+
const task = pending[0]!;
|
|
74
|
+
// Atomically mark as running inside the lock
|
|
75
|
+
const updated = tasks.map(t => t.id === task.id ? { ...t, status: 'running' } : t);
|
|
76
|
+
fs.writeFileSync(queuePath, updated.map(t => JSON.stringify(t)).join('\n') + '\n', 'utf-8');
|
|
77
|
+
return task;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (claimed) {
|
|
81
|
+
const botTask = {
|
|
82
|
+
instruction: claimed.instruction,
|
|
83
|
+
mode: claimed.mode ?? 'create',
|
|
84
|
+
targets: claimed.targets,
|
|
85
|
+
options: claimed.options,
|
|
86
|
+
queueId: claimed.id,
|
|
87
|
+
};
|
|
88
|
+
console.log(`\x1b[36m→ Task from queue [${claimed.id}]: ${claimed.instruction.slice(0, 80)}\x1b[0m`);
|
|
89
|
+
context.taskJson = JSON.stringify(botTask);
|
|
90
|
+
context.hasTask = true;
|
|
91
|
+
return { onSuccess: true, onFailure: false, ctx: JSON.stringify(context) };
|
|
87
92
|
}
|
|
88
|
-
} catch {
|
|
93
|
+
} catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[receive-task] queue error:', err); }
|
|
89
94
|
|
|
90
95
|
console.log('\x1b[33m→ No task found\x1b[0m');
|
|
91
96
|
return { onSuccess: false, onFailure: true, ctx: JSON.stringify(context) };
|