@synergenius/flow-weaver-pack-weaver 0.8.3 → 0.9.3
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/assistant-core.d.ts +25 -0
- package/dist/bot/assistant-core.d.ts.map +1 -0
- package/dist/bot/assistant-core.js +265 -0
- package/dist/bot/assistant-core.js.map +1 -0
- package/dist/bot/assistant-tools.d.ts +9 -0
- package/dist/bot/assistant-tools.d.ts.map +1 -0
- package/dist/bot/assistant-tools.js +602 -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/audit-store.d.ts.map +1 -1
- package/dist/bot/audit-store.js +3 -11
- package/dist/bot/audit-store.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/cost-store.d.ts.map +1 -1
- package/dist/bot/cost-store.js +10 -14
- package/dist/bot/cost-store.js.map +1 -1
- package/dist/bot/error-guide.d.ts +10 -0
- package/dist/bot/error-guide.d.ts.map +1 -0
- package/dist/bot/error-guide.js +34 -0
- package/dist/bot/error-guide.js.map +1 -0
- package/dist/bot/genesis-store.d.ts.map +1 -1
- package/dist/bot/genesis-store.js +11 -20
- package/dist/bot/genesis-store.js.map +1 -1
- package/dist/bot/index.d.ts +3 -0
- package/dist/bot/index.d.ts.map +1 -1
- package/dist/bot/index.js +3 -0
- package/dist/bot/index.js.map +1 -1
- 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/pipeline-runner.d.ts.map +1 -1
- package/dist/bot/pipeline-runner.js +8 -1
- package/dist/bot/pipeline-runner.js.map +1 -1
- package/dist/bot/retry-utils.d.ts +19 -0
- package/dist/bot/retry-utils.d.ts.map +1 -0
- package/dist/bot/retry-utils.js +64 -0
- package/dist/bot/retry-utils.js.map +1 -0
- package/dist/bot/run-store.d.ts.map +1 -1
- package/dist/bot/run-store.js +2 -10
- package/dist/bot/run-store.js.map +1 -1
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +24 -3
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/safe-json.d.ts +32 -0
- package/dist/bot/safe-json.d.ts.map +1 -0
- package/dist/bot/safe-json.js +56 -0
- package/dist/bot/safe-json.js.map +1 -0
- package/dist/bot/safe-path.d.ts +18 -0
- package/dist/bot/safe-path.d.ts.map +1 -0
- package/dist/bot/safe-path.js +40 -0
- package/dist/bot/safe-path.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 +1 -1
- 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 +102 -14
- 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 +205 -0
- package/dist/bot/terminal-renderer.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 +215 -0
- package/dist/bot/weaver-tools.js.map +1 -0
- package/dist/cli-bridge.d.ts.map +1 -1
- package/dist/cli-bridge.js +10 -3
- package/dist/cli-bridge.js.map +1 -1
- package/dist/cli-handlers.d.ts +15 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +742 -28
- package/dist/cli-handlers.js.map +1 -1
- package/dist/handlers/on-bot-completed.d.ts +21 -0
- package/dist/handlers/on-bot-completed.d.ts.map +1 -0
- package/dist/handlers/on-bot-completed.js +28 -0
- package/dist/handlers/on-bot-completed.js.map +1 -0
- package/dist/handlers/on-execution-failure.d.ts +23 -0
- package/dist/handlers/on-execution-failure.d.ts.map +1 -0
- package/dist/handlers/on-execution-failure.js +28 -0
- package/dist/handlers/on-execution-failure.js.map +1 -0
- package/dist/handlers/scheduled-run.d.ts +24 -0
- package/dist/handlers/scheduled-run.d.ts.map +1 -0
- package/dist/handlers/scheduled-run.js +25 -0
- package/dist/handlers/scheduled-run.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.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 +256 -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 +113 -2
- package/package.json +5 -2
- package/src/bot/ai-client.ts +180 -19
- package/src/bot/assistant-core.ts +306 -0
- package/src/bot/assistant-tools.ts +605 -0
- package/src/bot/audit-logger.ts +6 -5
- package/src/bot/audit-store.ts +3 -12
- 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/cost-store.ts +11 -12
- package/src/bot/error-guide.ts +34 -0
- package/src/bot/genesis-store.ts +11 -17
- package/src/bot/index.ts +5 -0
- package/src/bot/knowledge-store.ts +59 -0
- package/src/bot/pipeline-runner.ts +7 -1
- package/src/bot/retry-utils.ts +76 -0
- package/src/bot/run-store.ts +2 -11
- package/src/bot/runner.ts +26 -3
- package/src/bot/safe-json.ts +76 -0
- package/src/bot/safe-path.ts +44 -0
- package/src/bot/session-state.ts +2 -1
- package/src/bot/steering.ts +1 -1
- 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 +119 -15
- package/src/bot/terminal-renderer.ts +241 -0
- package/src/bot/types.ts +8 -0
- package/src/bot/weaver-tools.ts +225 -0
- package/src/cli-bridge.ts +14 -3
- package/src/cli-handlers.ts +760 -29
- package/src/handlers/on-bot-completed.ts +48 -0
- package/src/handlers/on-execution-failure.ts +42 -0
- package/src/handlers/scheduled-run.ts +42 -0
- package/src/index.ts +5 -0
- package/src/mcp-tools.ts +2 -2
- package/src/node-types/abort-task.ts +5 -4
- package/src/node-types/agent-execute.ts +306 -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
package/src/bot/task-queue.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as crypto from 'node:crypto';
|
|
5
5
|
import { withFileLock } from './file-lock.js';
|
|
6
|
+
import { parseNdjson } from './safe-json.js';
|
|
6
7
|
|
|
7
8
|
export interface QueuedTask {
|
|
8
9
|
id: string;
|
|
@@ -12,19 +13,67 @@ export interface QueuedTask {
|
|
|
12
13
|
options?: Record<string, unknown>;
|
|
13
14
|
priority: number;
|
|
14
15
|
addedAt: number;
|
|
15
|
-
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
16
|
+
status: 'pending' | 'running' | 'completed' | 'no-op' | 'failed' | 'cancelled';
|
|
17
|
+
/** Error reason (set on failure) */
|
|
18
|
+
failureReason?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AddResult {
|
|
22
|
+
id: string;
|
|
23
|
+
duplicate: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Max pending tasks before queue rejects new additions. */
|
|
27
|
+
const MAX_PENDING = 200;
|
|
28
|
+
/** Don't re-queue tasks completed within this window (ms). */
|
|
29
|
+
const CYCLE_DEDUP_WINDOW = 3600_000; // 1 hour
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hash a project directory path into a short filesystem-safe string.
|
|
33
|
+
* Used for per-project queue isolation.
|
|
34
|
+
*/
|
|
35
|
+
function hashDir(dir: string): string {
|
|
36
|
+
return crypto.createHash('sha256').update(dir).digest('hex').slice(0, 8);
|
|
16
37
|
}
|
|
17
38
|
|
|
18
39
|
export class TaskQueue {
|
|
19
|
-
|
|
40
|
+
readonly filePath: string;
|
|
20
41
|
|
|
21
42
|
constructor(dir?: string) {
|
|
22
|
-
|
|
43
|
+
// Priority: explicit dir > env var > project-scoped > global fallback
|
|
44
|
+
const projectDir = process.env.WEAVER_PROJECT_DIR;
|
|
45
|
+
const base = dir
|
|
46
|
+
?? process.env.WEAVER_QUEUE_DIR
|
|
47
|
+
?? (projectDir
|
|
48
|
+
? path.join(os.homedir(), '.weaver', 'projects', hashDir(projectDir))
|
|
49
|
+
: path.join(os.homedir(), '.weaver'));
|
|
23
50
|
this.filePath = path.join(base, 'task-queue.ndjson');
|
|
24
51
|
}
|
|
25
52
|
|
|
26
|
-
async add(task: Omit<QueuedTask, 'id' | 'addedAt' | 'status'>): Promise<
|
|
53
|
+
async add(task: Omit<QueuedTask, 'id' | 'addedAt' | 'status'>): Promise<AddResult> {
|
|
27
54
|
return withFileLock(this.filePath, () => {
|
|
55
|
+
const existing = this.readAll();
|
|
56
|
+
|
|
57
|
+
// Dedup: skip if a pending task with the same instruction exists
|
|
58
|
+
const pendingDup = existing.find(
|
|
59
|
+
t => t.status === 'pending' && t.instruction === task.instruction,
|
|
60
|
+
);
|
|
61
|
+
if (pendingDup) return { id: pendingDup.id, duplicate: true };
|
|
62
|
+
|
|
63
|
+
// Cycle-aware dedup: skip if same instruction was completed recently
|
|
64
|
+
const recentDup = existing.find(
|
|
65
|
+
t => (t.status === 'completed' || t.status === 'no-op')
|
|
66
|
+
&& t.instruction === task.instruction
|
|
67
|
+
&& Date.now() - t.addedAt < CYCLE_DEDUP_WINDOW,
|
|
68
|
+
);
|
|
69
|
+
if (recentDup) return { id: recentDup.id, duplicate: true };
|
|
70
|
+
|
|
71
|
+
// Queue size cap
|
|
72
|
+
const pendingCount = existing.filter(t => t.status === 'pending').length;
|
|
73
|
+
if (pendingCount >= MAX_PENDING) {
|
|
74
|
+
throw new Error(`Queue full (${MAX_PENDING} pending tasks). Clear or process existing tasks first.`);
|
|
75
|
+
}
|
|
76
|
+
|
|
28
77
|
const entry: QueuedTask = {
|
|
29
78
|
...task,
|
|
30
79
|
id: crypto.randomUUID().slice(0, 8),
|
|
@@ -34,7 +83,7 @@ export class TaskQueue {
|
|
|
34
83
|
const dir = path.dirname(this.filePath);
|
|
35
84
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
36
85
|
fs.appendFileSync(this.filePath, JSON.stringify(entry) + '\n', 'utf-8');
|
|
37
|
-
return entry.id;
|
|
86
|
+
return { id: entry.id, duplicate: false };
|
|
38
87
|
});
|
|
39
88
|
}
|
|
40
89
|
|
|
@@ -77,8 +126,66 @@ export class TaskQueue {
|
|
|
77
126
|
await this.updateStatus(id, 'completed');
|
|
78
127
|
}
|
|
79
128
|
|
|
80
|
-
async
|
|
81
|
-
await this.updateStatus(id, '
|
|
129
|
+
async markNoOp(id: string): Promise<void> {
|
|
130
|
+
await this.updateStatus(id, 'no-op');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async markFailed(id: string, reason?: string): Promise<void> {
|
|
134
|
+
return withFileLock(this.filePath, () => {
|
|
135
|
+
const tasks = this.readAll();
|
|
136
|
+
const task = tasks.find(t => t.id === id);
|
|
137
|
+
if (task) {
|
|
138
|
+
task.status = 'failed';
|
|
139
|
+
if (reason) task.failureReason = reason.slice(0, 500);
|
|
140
|
+
this.writeAll(tasks);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Reset a failed or running task back to pending. */
|
|
146
|
+
async retry(id: string): Promise<boolean> {
|
|
147
|
+
return withFileLock(this.filePath, () => {
|
|
148
|
+
const tasks = this.readAll();
|
|
149
|
+
const task = tasks.find(t => t.id === id && (t.status === 'failed' || t.status === 'running'));
|
|
150
|
+
if (!task) return false;
|
|
151
|
+
task.status = 'pending';
|
|
152
|
+
task.failureReason = undefined;
|
|
153
|
+
this.writeAll(tasks);
|
|
154
|
+
return true;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Reset ALL failed tasks back to pending. Returns count reset. */
|
|
159
|
+
async retryAll(): Promise<number> {
|
|
160
|
+
return withFileLock(this.filePath, () => {
|
|
161
|
+
const tasks = this.readAll();
|
|
162
|
+
let count = 0;
|
|
163
|
+
for (const t of tasks) {
|
|
164
|
+
if (t.status === 'failed') {
|
|
165
|
+
t.status = 'pending';
|
|
166
|
+
t.failureReason = undefined;
|
|
167
|
+
count++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (count > 0) this.writeAll(tasks);
|
|
171
|
+
return count;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Reset orphaned "running" tasks to pending (crash recovery). */
|
|
176
|
+
async recoverOrphans(): Promise<number> {
|
|
177
|
+
return withFileLock(this.filePath, () => {
|
|
178
|
+
const tasks = this.readAll();
|
|
179
|
+
let count = 0;
|
|
180
|
+
for (const t of tasks) {
|
|
181
|
+
if (t.status === 'running') {
|
|
182
|
+
t.status = 'pending';
|
|
183
|
+
count++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (count > 0) this.writeAll(tasks);
|
|
187
|
+
return count;
|
|
188
|
+
});
|
|
82
189
|
}
|
|
83
190
|
|
|
84
191
|
private async updateStatus(id: string, status: QueuedTask['status']): Promise<void> {
|
|
@@ -93,14 +200,11 @@ export class TaskQueue {
|
|
|
93
200
|
}
|
|
94
201
|
|
|
95
202
|
private readAll(): QueuedTask[] {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} catch {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
203
|
+
if (!fs.existsSync(this.filePath)) return [];
|
|
204
|
+
const content = fs.readFileSync(this.filePath, 'utf-8').trim();
|
|
205
|
+
if (!content) return [];
|
|
206
|
+
const { records } = parseNdjson<QueuedTask>(content, 'task-queue');
|
|
207
|
+
return records;
|
|
104
208
|
}
|
|
105
209
|
|
|
106
210
|
private writeAll(tasks: QueuedTask[]): void {
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal renderer — centralizes all weaver CLI output through
|
|
3
|
+
* a consistent visual grammar. Pipe-safe (stderr only for decoration).
|
|
4
|
+
*
|
|
5
|
+
* Icons: ✓ success · ✗ error · ⚠ warning · ◆ action · ● running
|
|
6
|
+
* Colors: green/red/yellow/cyan/dim/bold — strict assignments
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { StreamEvent, ToolEvent } from '@synergenius/flow-weaver/agent';
|
|
10
|
+
|
|
11
|
+
// ANSI helpers
|
|
12
|
+
const isTTY = process.stderr.isTTY ?? false;
|
|
13
|
+
const c = {
|
|
14
|
+
green: (s: string) => `\x1b[32m${s}\x1b[0m`,
|
|
15
|
+
red: (s: string) => `\x1b[31m${s}\x1b[0m`,
|
|
16
|
+
yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
|
|
17
|
+
cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
|
|
18
|
+
dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
|
|
19
|
+
bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
|
|
20
|
+
redBold: (s: string) => `\x1b[1;31m${s}\x1b[0m`,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export interface RendererOptions {
|
|
24
|
+
verbose?: boolean;
|
|
25
|
+
quiet?: boolean;
|
|
26
|
+
noColor?: boolean;
|
|
27
|
+
/** Override stderr writer (for testing) */
|
|
28
|
+
write?: (s: string) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TaskEndStats {
|
|
32
|
+
toolCalls: number;
|
|
33
|
+
inputTokens: number;
|
|
34
|
+
outputTokens: number;
|
|
35
|
+
estimatedCost: number;
|
|
36
|
+
filesModified: number;
|
|
37
|
+
elapsed: number;
|
|
38
|
+
gitMessage?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SessionEndStats {
|
|
42
|
+
tasks: number;
|
|
43
|
+
completed: number;
|
|
44
|
+
failed: number;
|
|
45
|
+
totalInputTokens: number;
|
|
46
|
+
totalOutputTokens: number;
|
|
47
|
+
totalCost: number;
|
|
48
|
+
elapsed: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class TerminalRenderer {
|
|
52
|
+
private verbose: boolean;
|
|
53
|
+
private quiet: boolean;
|
|
54
|
+
private out: (s: string) => void;
|
|
55
|
+
private taskStartTime = 0;
|
|
56
|
+
private lastToolStartTime = 0;
|
|
57
|
+
private textBuffer = '';
|
|
58
|
+
private hasActiveText = false;
|
|
59
|
+
|
|
60
|
+
constructor(opts: RendererOptions = {}) {
|
|
61
|
+
this.verbose = opts.verbose ?? false;
|
|
62
|
+
this.quiet = opts.quiet ?? false;
|
|
63
|
+
if (opts.noColor) {
|
|
64
|
+
// Strip all color functions
|
|
65
|
+
for (const key of Object.keys(c) as (keyof typeof c)[]) {
|
|
66
|
+
(c as Record<string, (s: string) => string>)[key] = (s: string) => s;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.out = opts.write ?? ((s: string) => process.stderr.write(s));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- Session lifecycle ---
|
|
73
|
+
|
|
74
|
+
sessionStart(info: { provider: string; parallel?: number; deadline?: string }): void {
|
|
75
|
+
if (this.quiet) return;
|
|
76
|
+
this.out(`${c.bold('[weaver]')} Session started ${c.dim('(Ctrl+C to stop)')}\n`);
|
|
77
|
+
const parts = [`Provider: ${info.provider}`];
|
|
78
|
+
if (info.parallel && info.parallel > 1) parts.push(`Parallel: ${info.parallel}`);
|
|
79
|
+
if (info.deadline) parts.push(`Deadline: ${info.deadline}`);
|
|
80
|
+
this.out(`${c.bold('[weaver]')} ${c.dim(parts.join(' · '))}\n`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
sessionEnd(stats: SessionEndStats): void {
|
|
84
|
+
if (this.quiet) return;
|
|
85
|
+
this.out('\n');
|
|
86
|
+
const parts: string[] = [];
|
|
87
|
+
parts.push(`${stats.tasks} task${stats.tasks === 1 ? '' : 's'}`);
|
|
88
|
+
if (stats.completed > 0) parts.push(c.green(`${stats.completed} completed`));
|
|
89
|
+
if (stats.failed > 0) parts.push(c.red(`${stats.failed} failed`));
|
|
90
|
+
const skipped = stats.tasks - stats.completed - stats.failed;
|
|
91
|
+
if (skipped > 0) parts.push(c.yellow(`${skipped} skipped`));
|
|
92
|
+
this.out(`${c.bold('[weaver]')} Session complete: ${parts.join(' · ')}\n`);
|
|
93
|
+
|
|
94
|
+
const totalTokens = stats.totalInputTokens + stats.totalOutputTokens;
|
|
95
|
+
if (totalTokens > 0) {
|
|
96
|
+
this.out(`${c.bold('[weaver]')} ${c.dim(`Total: ${formatTokens(totalTokens)} tokens · $${stats.totalCost.toFixed(3)} · ${formatElapsed(stats.elapsed)}`)}\n`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- Task lifecycle ---
|
|
101
|
+
|
|
102
|
+
taskStart(index: number, instruction: string): void {
|
|
103
|
+
if (this.quiet) return;
|
|
104
|
+
this.taskStartTime = Date.now();
|
|
105
|
+
this.hasActiveText = false;
|
|
106
|
+
this.textBuffer = '';
|
|
107
|
+
const label = instruction.length > 70 ? instruction.slice(0, 67) + '...' : instruction;
|
|
108
|
+
this.out(`\n${c.cyan('◆')} ${c.bold(`Task ${index}:`)} ${label}\n`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
taskEnd(success: boolean, stats: TaskEndStats): void {
|
|
112
|
+
if (this.quiet) return;
|
|
113
|
+
// Flush any remaining text
|
|
114
|
+
this.flushText();
|
|
115
|
+
|
|
116
|
+
const elapsed = formatElapsed(stats.elapsed);
|
|
117
|
+
const icon = success ? c.green('✓') : c.red('✗');
|
|
118
|
+
const status = success ? 'completed' : 'failed';
|
|
119
|
+
this.out(`${icon} Task ${status} ${c.dim(elapsed)}\n`);
|
|
120
|
+
|
|
121
|
+
// Summary line
|
|
122
|
+
const parts: string[] = [];
|
|
123
|
+
if (stats.toolCalls > 0) parts.push(`${stats.toolCalls} tool calls`);
|
|
124
|
+
const totalTokens = stats.inputTokens + stats.outputTokens;
|
|
125
|
+
if (totalTokens > 0) parts.push(`${formatTokens(totalTokens)} tokens`);
|
|
126
|
+
if (stats.estimatedCost > 0) parts.push(`$${stats.estimatedCost.toFixed(3)}`);
|
|
127
|
+
if (stats.filesModified > 0) parts.push(`${stats.filesModified} file${stats.filesModified === 1 ? '' : 's'} modified`);
|
|
128
|
+
if (parts.length > 0) {
|
|
129
|
+
this.out(` ${c.dim(parts.join(' · '))}\n`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (stats.gitMessage) {
|
|
133
|
+
this.out(` ${c.dim('→ Git: ' + stats.gitMessage)}\n`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --- Stream event handling ---
|
|
138
|
+
|
|
139
|
+
onStreamEvent(event: StreamEvent): void {
|
|
140
|
+
if (this.quiet) return;
|
|
141
|
+
|
|
142
|
+
switch (event.type) {
|
|
143
|
+
case 'thinking_delta':
|
|
144
|
+
if (this.verbose) {
|
|
145
|
+
// In verbose mode, stream thinking as dim text
|
|
146
|
+
this.flushText();
|
|
147
|
+
this.out(` ${c.dim(event.text.replace(/\n/g, '\n '))}`);
|
|
148
|
+
}
|
|
149
|
+
// In normal mode, thinking is completely hidden
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
case 'text_delta':
|
|
153
|
+
if (this.verbose) {
|
|
154
|
+
this.flushText();
|
|
155
|
+
this.out(event.text);
|
|
156
|
+
this.hasActiveText = true;
|
|
157
|
+
}
|
|
158
|
+
// In normal mode, AI text is hidden (tool calls tell the story)
|
|
159
|
+
break;
|
|
160
|
+
|
|
161
|
+
case 'tool_result':
|
|
162
|
+
// CLI internal tool result — show as result line
|
|
163
|
+
break;
|
|
164
|
+
|
|
165
|
+
default:
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
onToolEvent(event: ToolEvent): void {
|
|
171
|
+
if (this.quiet) return;
|
|
172
|
+
|
|
173
|
+
if (event.type === 'tool_call_start') {
|
|
174
|
+
this.flushText();
|
|
175
|
+
this.lastToolStartTime = Date.now();
|
|
176
|
+
const args = event.args ?? {};
|
|
177
|
+
const preview = toolPreview(event.name, args);
|
|
178
|
+
this.out(` ${c.cyan('◆')} ${event.name}${preview ? c.dim(`(${preview})`) : ''}\n`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (event.type === 'tool_call_result') {
|
|
182
|
+
const elapsed = Date.now() - this.lastToolStartTime;
|
|
183
|
+
const result = (event.result ?? '').replace(/\n/g, ' ').slice(0, 120);
|
|
184
|
+
const icon = event.isError ? c.red('✗') : c.dim('→');
|
|
185
|
+
this.out(` ${icon} ${result} ${c.dim(formatElapsed(elapsed))}\n`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- Direct messages ---
|
|
190
|
+
|
|
191
|
+
info(msg: string): void {
|
|
192
|
+
if (!this.quiet) this.out(`${c.bold('[weaver]')} ${msg}\n`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
warn(msg: string): void {
|
|
196
|
+
if (!this.quiet) this.out(`${c.yellow('⚠')} ${msg}\n`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
error(title: string, detail?: string): void {
|
|
200
|
+
this.out(`${c.redBold('✗')} ${c.red(title)}\n`);
|
|
201
|
+
if (detail) this.out(` ${c.red(detail)}\n`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// --- Private ---
|
|
205
|
+
|
|
206
|
+
private flushText(): void {
|
|
207
|
+
if (this.hasActiveText) {
|
|
208
|
+
this.out('\n');
|
|
209
|
+
this.hasActiveText = false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --- Formatting helpers ---
|
|
215
|
+
|
|
216
|
+
export function formatTokens(n: number): string {
|
|
217
|
+
if (n < 1000) return String(n);
|
|
218
|
+
if (n < 1_000_000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
|
219
|
+
return (n / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function formatElapsed(ms: number): string {
|
|
223
|
+
if (ms < 1000) return `${ms}ms`;
|
|
224
|
+
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
|
|
225
|
+
const m = Math.floor(ms / 60_000);
|
|
226
|
+
const s = Math.round((ms % 60_000) / 1000);
|
|
227
|
+
return s > 0 ? `${m}m ${s}s` : `${m}m`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function toolPreview(name: string, args: Record<string, unknown>): string {
|
|
231
|
+
if (args.file) return String(args.file).split('/').pop() ?? '';
|
|
232
|
+
if (args.command) return String(args.command).slice(0, 50);
|
|
233
|
+
if (args.directory) return String(args.directory).split('/').pop() ?? '';
|
|
234
|
+
// For patch_file, show file + patch count
|
|
235
|
+
if (name === 'patch_file' && args.patches) {
|
|
236
|
+
const file = String(args.file ?? '').split('/').pop() ?? '';
|
|
237
|
+
const count = Array.isArray(args.patches) ? args.patches.length : '?';
|
|
238
|
+
return `${file}, ${count} patches`;
|
|
239
|
+
}
|
|
240
|
+
return '';
|
|
241
|
+
}
|
package/src/bot/types.ts
CHANGED
|
@@ -171,6 +171,12 @@ export interface WorkflowResult {
|
|
|
171
171
|
|
|
172
172
|
export type RunOutcome = 'completed' | 'failed' | 'error' | 'skipped';
|
|
173
173
|
|
|
174
|
+
export interface StepLogEntry {
|
|
175
|
+
step: string;
|
|
176
|
+
status: 'ok' | 'blocked' | 'error';
|
|
177
|
+
detail?: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
174
180
|
export interface RunRecord {
|
|
175
181
|
id: string;
|
|
176
182
|
workflowFile: string;
|
|
@@ -187,6 +193,7 @@ export interface RunRecord {
|
|
|
187
193
|
provider?: string;
|
|
188
194
|
pipelineName?: string;
|
|
189
195
|
stageName?: string;
|
|
196
|
+
stepLog?: StepLogEntry[];
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
export interface RunFilter {
|
|
@@ -494,6 +501,7 @@ export interface WeaverContext {
|
|
|
494
501
|
resultJson?: string;
|
|
495
502
|
validationResultJson?: string;
|
|
496
503
|
filesModified?: string;
|
|
504
|
+
stepLogJson?: string;
|
|
497
505
|
allValid?: boolean;
|
|
498
506
|
gitResultJson?: string;
|
|
499
507
|
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weaver-specific tool definitions and executor.
|
|
3
|
+
*
|
|
4
|
+
* These are the tools the weaver bot uses: validate, read_file, patch_file,
|
|
5
|
+
* run_shell, list_files, write_file. Tool execution delegates to step-executor
|
|
6
|
+
* with all safety guards (path traversal, shrink detection, blocked commands).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { executeStep } from './step-executor.js';
|
|
11
|
+
import type { ToolDefinition } from '@synergenius/flow-weaver/agent';
|
|
12
|
+
|
|
13
|
+
export const WEAVER_TOOLS: ToolDefinition[] = [
|
|
14
|
+
{
|
|
15
|
+
name: 'validate',
|
|
16
|
+
description: 'Run flow-weaver validate on a workflow file. Returns JSON with errors and warnings. Use this FIRST to discover issues, and AFTER patching to confirm fixes.',
|
|
17
|
+
inputSchema: { type: 'object', properties: { file: { type: 'string', description: 'Path to the workflow file to validate' } }, required: ['file'] },
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'read_file',
|
|
21
|
+
description: 'Read a file and return its full contents. Use this to understand file structure before patching.',
|
|
22
|
+
inputSchema: { type: 'object', properties: { file: { type: 'string', description: 'Path to the file to read' } }, required: ['file'] },
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'patch_file',
|
|
26
|
+
description: 'Apply surgical find-and-replace patches to a file. Each patch must have exact "find" and "replace" strings. Preferred over write_file for modifications.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
file: { type: 'string', description: 'Path to the file to patch' },
|
|
31
|
+
patches: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
items: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
find: { type: 'string', description: 'Exact string to find' },
|
|
37
|
+
replace: { type: 'string', description: 'String to replace with' },
|
|
38
|
+
},
|
|
39
|
+
required: ['find', 'replace'],
|
|
40
|
+
},
|
|
41
|
+
description: 'Array of find/replace patches',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ['file', 'patches'],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'run_shell',
|
|
49
|
+
description: 'Execute a shell command and return output. Use for: npx flow-weaver validate, git status, etc. Blocked: rm -rf, git push, sudo.',
|
|
50
|
+
inputSchema: { type: 'object', properties: { command: { type: 'string', description: 'Shell command to execute' } }, required: ['command'] },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'list_files',
|
|
54
|
+
description: 'List files in a directory, optionally filtered by regex pattern.',
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
directory: { type: 'string', description: 'Directory to list' },
|
|
59
|
+
pattern: { type: 'string', description: 'Optional regex filter pattern' },
|
|
60
|
+
},
|
|
61
|
+
required: ['directory'],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'write_file',
|
|
66
|
+
description: 'Write content to a file (creates or overwrites). Use patch_file instead for modifications to existing files.',
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
file: { type: 'string', description: 'Path to the file to write' },
|
|
71
|
+
content: { type: 'string', description: 'Complete file content' },
|
|
72
|
+
},
|
|
73
|
+
required: ['file', 'content'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'web_fetch',
|
|
78
|
+
description: 'Fetch HTTP content. Returns text body (max 10KB).',
|
|
79
|
+
inputSchema: { type: 'object', properties: { url: { type: 'string' }, method: { type: 'string', enum: ['GET', 'POST'] } }, required: ['url'] },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'tsc_check',
|
|
83
|
+
description: 'Run TypeScript compiler check (no emit). Returns errors if any.',
|
|
84
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'run_tests',
|
|
88
|
+
description: 'Run project tests. Returns structured results with pass/fail counts.',
|
|
89
|
+
inputSchema: { type: 'object', properties: { pattern: { type: 'string', description: 'Test file pattern (optional)' } }, required: [] },
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'ask_user',
|
|
93
|
+
description: 'Ask the user a question and wait for response. Use when you need a decision.',
|
|
94
|
+
inputSchema: { type: 'object', properties: { question: { type: 'string' } }, required: ['question'] },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'learn',
|
|
98
|
+
description: 'Store a fact for future tasks. Key should be descriptive (e.g. "file:src/agent.ts:port-issue").',
|
|
99
|
+
inputSchema: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' } }, required: ['key', 'value'] },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'recall',
|
|
103
|
+
description: 'Look up stored knowledge. Returns matching entries.',
|
|
104
|
+
inputSchema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
/** Map tool names to step-executor operations. */
|
|
109
|
+
const OPERATION_MAP: Record<string, string> = {
|
|
110
|
+
validate: 'run-shell',
|
|
111
|
+
read_file: 'read-file',
|
|
112
|
+
patch_file: 'patch-file',
|
|
113
|
+
run_shell: 'run-shell',
|
|
114
|
+
list_files: 'list-files',
|
|
115
|
+
write_file: 'write-file',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Execute a weaver tool call. Delegates to step-executor with safety guards.
|
|
120
|
+
* Bound to a specific project directory via closure.
|
|
121
|
+
*/
|
|
122
|
+
export function createWeaverExecutor(projectDir: string) {
|
|
123
|
+
return async (
|
|
124
|
+
name: string,
|
|
125
|
+
args: Record<string, unknown>,
|
|
126
|
+
): Promise<{ result: string; isError: boolean }> => {
|
|
127
|
+
// Handle new tools that bypass step-executor
|
|
128
|
+
switch (name) {
|
|
129
|
+
case 'web_fetch': {
|
|
130
|
+
const url = String(args.url);
|
|
131
|
+
// Safety: block localhost, internal IPs
|
|
132
|
+
if (/localhost|127\.0\.0\.1|0\.0\.0\.0|10\.\d|172\.(1[6-9]|2\d|3[01])\.|192\.168\./i.test(url)) {
|
|
133
|
+
return { result: 'Blocked: cannot fetch internal/localhost URLs.', isError: true };
|
|
134
|
+
}
|
|
135
|
+
const resp = await fetch(url, { method: (args.method as string) ?? 'GET', signal: AbortSignal.timeout(15_000) });
|
|
136
|
+
const text = await resp.text();
|
|
137
|
+
return { result: text.slice(0, 10_000), isError: !resp.ok };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case 'tsc_check': {
|
|
141
|
+
try {
|
|
142
|
+
const output = execFileSync('npx', ['tsc', '--noEmit', '--pretty'], { cwd: projectDir, encoding: 'utf-8', timeout: 60_000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
143
|
+
return { result: output.trim() || 'No TypeScript errors.', isError: false };
|
|
144
|
+
} catch (err: any) {
|
|
145
|
+
return { result: (err.stdout ?? err.message ?? '').slice(0, 5000), isError: true };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
case 'run_tests': {
|
|
150
|
+
try {
|
|
151
|
+
const pattern = args.pattern ? String(args.pattern) : '';
|
|
152
|
+
const testArgs = ['vitest', 'run', '--reporter', 'json'];
|
|
153
|
+
if (pattern) testArgs.push(pattern);
|
|
154
|
+
const output = execFileSync('npx', testArgs, { cwd: projectDir, encoding: 'utf-8', timeout: 120_000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
155
|
+
try {
|
|
156
|
+
const json = JSON.parse(output);
|
|
157
|
+
const passed = json.numPassedTests ?? 0;
|
|
158
|
+
const failed = json.numFailedTests ?? 0;
|
|
159
|
+
const failures = (json.testResults ?? []).filter((t: any) => t.status === 'failed').map((t: any) => t.name).slice(0, 10);
|
|
160
|
+
return { result: JSON.stringify({ passed, failed, total: passed + failed, failures }), isError: failed > 0 };
|
|
161
|
+
} catch {
|
|
162
|
+
return { result: output.slice(0, 5000), isError: false };
|
|
163
|
+
}
|
|
164
|
+
} catch (err: any) {
|
|
165
|
+
return { result: (err.stdout ?? err.stderr ?? err.message ?? '').slice(0, 5000), isError: true };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'ask_user': {
|
|
170
|
+
const question = String(args.question);
|
|
171
|
+
if (process.env.WEAVER_AUTO_APPROVE) {
|
|
172
|
+
return { result: '(Auto-approved — no user input available in autonomous mode)', isError: false };
|
|
173
|
+
}
|
|
174
|
+
// In interactive mode, prompt via readline
|
|
175
|
+
const readline = await import('node:readline');
|
|
176
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
177
|
+
const answer = await new Promise<string>((resolve) => {
|
|
178
|
+
rl.question(`\n Bot asks: ${question}\n > `, (ans) => { rl.close(); resolve(ans); });
|
|
179
|
+
});
|
|
180
|
+
return { result: answer || '(no answer)', isError: false };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
case 'learn': {
|
|
184
|
+
const { KnowledgeStore } = await import('./knowledge-store.js');
|
|
185
|
+
const store = new KnowledgeStore(projectDir);
|
|
186
|
+
store.learn(String(args.key), String(args.value), 'bot');
|
|
187
|
+
return { result: `Learned: ${args.key}`, isError: false };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
case 'recall': {
|
|
191
|
+
const { KnowledgeStore } = await import('./knowledge-store.js');
|
|
192
|
+
const store = new KnowledgeStore(projectDir);
|
|
193
|
+
const entries = store.recall(String(args.query));
|
|
194
|
+
if (entries.length === 0) return { result: 'No knowledge found.', isError: false };
|
|
195
|
+
return { result: entries.map(e => `${e.key}: ${e.value}`).join('\n'), isError: false };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
default:
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Existing step-executor-based tools
|
|
203
|
+
const operation = OPERATION_MAP[name];
|
|
204
|
+
if (!operation) {
|
|
205
|
+
return { result: `Unknown tool: ${name}`, isError: true };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Transform validate tool to run-shell with flow-weaver validate command
|
|
209
|
+
let stepArgs = { ...args };
|
|
210
|
+
if (name === 'validate') {
|
|
211
|
+
stepArgs = { command: `npx flow-weaver validate ${args.file} --json` };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = await executeStep({ operation, args: stepArgs }, projectDir);
|
|
216
|
+
if (result.blocked) {
|
|
217
|
+
return { result: result.blockReason ?? 'Blocked by safety guard', isError: true };
|
|
218
|
+
}
|
|
219
|
+
return { result: result.output ?? 'Done', isError: false };
|
|
220
|
+
} catch (err: unknown) {
|
|
221
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
222
|
+
return { result: msg, isError: true };
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|