@synergenius/flowweaver-pack-weaver 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bot/ai-client.d.ts +1 -0
- package/dist/bot/ai-client.d.ts.map +1 -1
- package/dist/bot/ai-client.js +52 -1
- package/dist/bot/ai-client.js.map +1 -1
- package/dist/bot/audit-logger.d.ts +5 -0
- package/dist/bot/audit-logger.d.ts.map +1 -0
- package/dist/bot/audit-logger.js +42 -0
- package/dist/bot/audit-logger.js.map +1 -0
- package/dist/bot/audit-store.d.ts +13 -0
- package/dist/bot/audit-store.d.ts.map +1 -0
- package/dist/bot/audit-store.js +59 -0
- package/dist/bot/audit-store.js.map +1 -0
- package/dist/bot/cli-provider.d.ts +1 -0
- package/dist/bot/cli-provider.d.ts.map +1 -1
- package/dist/bot/cli-provider.js +86 -22
- package/dist/bot/cli-provider.js.map +1 -1
- package/dist/bot/cli-stream-parser.d.ts +11 -0
- package/dist/bot/cli-stream-parser.d.ts.map +1 -0
- package/dist/bot/cli-stream-parser.js +53 -0
- package/dist/bot/cli-stream-parser.js.map +1 -0
- package/dist/bot/design-checker.d.ts +24 -0
- package/dist/bot/design-checker.d.ts.map +1 -0
- package/dist/bot/design-checker.js +269 -0
- package/dist/bot/design-checker.js.map +1 -0
- package/dist/bot/file-validator.d.ts +5 -2
- package/dist/bot/file-validator.d.ts.map +1 -1
- package/dist/bot/file-validator.js +14 -7
- package/dist/bot/file-validator.js.map +1 -1
- package/dist/bot/fw-api.d.ts +8 -0
- package/dist/bot/fw-api.d.ts.map +1 -0
- package/dist/bot/fw-api.js +12 -0
- package/dist/bot/fw-api.js.map +1 -0
- package/dist/bot/runner.d.ts +2 -1
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +8 -0
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/step-executor.d.ts +3 -2
- package/dist/bot/step-executor.d.ts.map +1 -1
- package/dist/bot/step-executor.js +9 -30
- package/dist/bot/step-executor.js.map +1 -1
- package/dist/bot/system-prompt.d.ts +13 -1
- package/dist/bot/system-prompt.d.ts.map +1 -1
- package/dist/bot/system-prompt.js +28 -22
- package/dist/bot/system-prompt.js.map +1 -1
- package/dist/bot/types.d.ts +9 -1
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/cli-bridge.d.ts.map +1 -1
- package/dist/cli-bridge.js +2 -1
- package/dist/cli-bridge.js.map +1 -1
- package/dist/cli-handlers.d.ts +4 -2
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +199 -92
- package/dist/cli-handlers.js.map +1 -1
- package/dist/docs/docs/weaver-config.md +8 -14
- package/dist/docs/weaver-config.md +8 -14
- package/dist/node-types/approval-gate.d.ts.map +1 -1
- package/dist/node-types/approval-gate.js +4 -0
- package/dist/node-types/approval-gate.js.map +1 -1
- package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
- package/dist/node-types/exec-validate-retry.js +33 -7
- package/dist/node-types/exec-validate-retry.js.map +1 -1
- package/dist/node-types/execute-plan.js +1 -1
- package/dist/node-types/execute-plan.js.map +1 -1
- package/dist/node-types/genesis-validate-proposal.d.ts.map +1 -1
- package/dist/node-types/genesis-validate-proposal.js +23 -0
- package/dist/node-types/genesis-validate-proposal.js.map +1 -1
- package/dist/node-types/git-ops.d.ts.map +1 -1
- package/dist/node-types/git-ops.js +2 -0
- package/dist/node-types/git-ops.js.map +1 -1
- package/dist/node-types/plan-task.d.ts.map +1 -1
- package/dist/node-types/plan-task.js +9 -1
- package/dist/node-types/plan-task.js.map +1 -1
- package/dist/node-types/send-notify.d.ts.map +1 -1
- package/dist/node-types/send-notify.js +4 -1
- package/dist/node-types/send-notify.js.map +1 -1
- package/dist/node-types/validate-result.d.ts +2 -2
- package/dist/node-types/validate-result.d.ts.map +1 -1
- package/dist/node-types/validate-result.js +2 -2
- package/dist/node-types/validate-result.js.map +1 -1
- package/dist/templates/index.d.ts +2 -4
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +1 -3
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/weaver-bot-template.d.ts +9 -1
- package/dist/templates/weaver-bot-template.d.ts.map +1 -1
- package/dist/templates/weaver-bot-template.js.map +1 -1
- package/dist/workflows/index.d.ts +0 -1
- package/dist/workflows/index.d.ts.map +1 -1
- package/dist/workflows/index.js +0 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/weaver-bot-batch.d.ts +4 -1
- package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
- package/dist/workflows/weaver-bot-batch.js +1 -1
- package/dist/workflows/weaver-bot-batch.js.map +1 -1
- package/dist/workflows/weaver-bot.d.ts +4 -1
- package/dist/workflows/weaver-bot.d.ts.map +1 -1
- package/dist/workflows/weaver-bot.js +1 -1
- package/dist/workflows/weaver-bot.js.map +1 -1
- package/flowweaver.manifest.json +10 -10
- package/package.json +4 -3
- package/src/bot/agent-provider.ts +273 -0
- package/src/bot/ai-client.ts +109 -0
- package/src/bot/approvals.ts +273 -0
- package/src/bot/audit-logger.ts +45 -0
- package/src/bot/audit-store.ts +69 -0
- package/src/bot/bot-agent-channel.ts +99 -0
- package/src/bot/cli-provider.ts +169 -0
- package/src/bot/cli-stream-parser.ts +59 -0
- package/src/bot/cost-store.ts +92 -0
- package/src/bot/cost-tracker.ts +72 -0
- package/src/bot/cron-parser.ts +153 -0
- package/src/bot/cron-scheduler.ts +48 -0
- package/src/bot/dashboard.ts +658 -0
- package/src/bot/design-checker.ts +327 -0
- package/src/bot/file-lock.ts +73 -0
- package/src/bot/file-validator.ts +41 -0
- package/src/bot/file-watcher.ts +103 -0
- package/src/bot/fw-api.ts +18 -0
- package/src/bot/genesis-prompt-context.ts +135 -0
- package/src/bot/genesis-store.ts +180 -0
- package/src/bot/index.ts +127 -0
- package/src/bot/notifications.ts +263 -0
- package/src/bot/pipeline-runner.ts +324 -0
- package/src/bot/provider-registry.ts +236 -0
- package/src/bot/run-store.ts +169 -0
- package/src/bot/runner.ts +311 -0
- package/src/bot/session-state.ts +73 -0
- package/src/bot/steering.ts +44 -0
- package/src/bot/step-executor.ts +34 -0
- package/src/bot/system-prompt.ts +280 -0
- package/src/bot/task-queue.ts +111 -0
- package/src/bot/types.ts +571 -0
- package/src/bot/utils.ts +17 -0
- package/src/bot/watch-daemon.ts +203 -0
- package/src/bot/web-approval.ts +240 -0
- package/src/cli-bridge.ts +41 -0
- package/src/cli-handlers.ts +1271 -0
- package/src/docs/weaver-config.md +135 -0
- package/src/index.ts +173 -0
- package/src/mcp-tools.ts +274 -0
- package/src/node-types/abort-task.ts +31 -0
- package/src/node-types/approval-gate.ts +75 -0
- package/src/node-types/bot-report.ts +82 -0
- package/src/node-types/build-context.ts +65 -0
- package/src/node-types/detect-provider.ts +75 -0
- package/src/node-types/exec-validate-retry.ts +175 -0
- package/src/node-types/execute-plan.ts +130 -0
- package/src/node-types/execute-target.ts +267 -0
- package/src/node-types/fix-errors.ts +68 -0
- package/src/node-types/genesis-apply-retry.ts +138 -0
- package/src/node-types/genesis-apply.ts +96 -0
- package/src/node-types/genesis-approve.ts +73 -0
- package/src/node-types/genesis-check-stabilize.ts +37 -0
- package/src/node-types/genesis-check-threshold.ts +34 -0
- package/src/node-types/genesis-commit.ts +71 -0
- package/src/node-types/genesis-compile-validate.ts +77 -0
- package/src/node-types/genesis-diff-fingerprint.ts +67 -0
- package/src/node-types/genesis-diff-workflow.ts +71 -0
- package/src/node-types/genesis-escrow-grace.ts +62 -0
- package/src/node-types/genesis-escrow-migrate.ts +138 -0
- package/src/node-types/genesis-escrow-recover.ts +99 -0
- package/src/node-types/genesis-escrow-stage.ts +104 -0
- package/src/node-types/genesis-escrow-validate.ts +120 -0
- package/src/node-types/genesis-load-config.ts +44 -0
- package/src/node-types/genesis-observe.ts +119 -0
- package/src/node-types/genesis-propose.ts +97 -0
- package/src/node-types/genesis-report.ts +95 -0
- package/src/node-types/genesis-snapshot.ts +30 -0
- package/src/node-types/genesis-try-apply.ts +165 -0
- package/src/node-types/genesis-update-history.ts +72 -0
- package/src/node-types/genesis-validate-proposal.ts +124 -0
- package/src/node-types/git-ops.ts +72 -0
- package/src/node-types/index.ts +36 -0
- package/src/node-types/load-config.ts +27 -0
- package/src/node-types/plan-task.ts +77 -0
- package/src/node-types/read-workflow.ts +68 -0
- package/src/node-types/receive-task.ts +92 -0
- package/src/node-types/report.ts +25 -0
- package/src/node-types/resolve-target.ts +64 -0
- package/src/node-types/route-task.ts +25 -0
- package/src/node-types/send-notify.ts +75 -0
- package/src/node-types/validate-result.ts +49 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/weaver-bot-template.ts +106 -0
- package/src/workflows/genesis-task.ts +91 -0
- package/src/workflows/index.ts +3 -0
- package/src/workflows/weaver-bot-batch.ts +65 -0
- package/src/workflows/weaver-bot.ts +79 -0
- package/dist/templates/weaver-template.d.ts +0 -11
- package/dist/templates/weaver-template.d.ts.map +0 -1
- package/dist/templates/weaver-template.js +0 -53
- package/dist/templates/weaver-template.js.map +0 -1
- package/dist/workflows/weaver.d.ts +0 -24
- package/dist/workflows/weaver.d.ts.map +0 -1
- package/dist/workflows/weaver.js +0 -28
- package/dist/workflows/weaver.js.map +0 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import type { TriggerSource, WatchDaemonOptions, WatchDaemonState, WorkflowResult, ExecutionEvent } from './types.js';
|
|
4
|
+
import { runWorkflow } from './runner.js';
|
|
5
|
+
import { parseCron } from './cron-parser.js';
|
|
6
|
+
import { FileWatcher } from './file-watcher.js';
|
|
7
|
+
import { CronScheduler } from './cron-scheduler.js';
|
|
8
|
+
|
|
9
|
+
export class WatchDaemon {
|
|
10
|
+
private options: WatchDaemonOptions;
|
|
11
|
+
private fileWatcher: FileWatcher | null = null;
|
|
12
|
+
private cronScheduler: CronScheduler | null = null;
|
|
13
|
+
private logStream: fs.WriteStream | null = null;
|
|
14
|
+
private state: WatchDaemonState;
|
|
15
|
+
private stopping = false;
|
|
16
|
+
private forceExit = false;
|
|
17
|
+
|
|
18
|
+
constructor(options: WatchDaemonOptions) {
|
|
19
|
+
this.options = options;
|
|
20
|
+
this.state = {
|
|
21
|
+
running: false,
|
|
22
|
+
lastRun: null,
|
|
23
|
+
lastTrigger: null,
|
|
24
|
+
lastResult: null,
|
|
25
|
+
runCount: 0,
|
|
26
|
+
errorCount: 0,
|
|
27
|
+
startedAt: new Date(),
|
|
28
|
+
queued: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async start(): Promise<void> {
|
|
33
|
+
const absPath = path.resolve(this.options.filePath);
|
|
34
|
+
if (!fs.existsSync(absPath)) {
|
|
35
|
+
throw new Error(`Workflow file not found: ${absPath}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (this.options.logFile) {
|
|
39
|
+
this.logStream = fs.createWriteStream(this.options.logFile, { flags: 'a' });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.log(`Watching ${absPath}`);
|
|
43
|
+
|
|
44
|
+
if (this.options.watchFile) {
|
|
45
|
+
this.fileWatcher = new FileWatcher({
|
|
46
|
+
filePath: absPath,
|
|
47
|
+
debounceMs: this.options.debounceMs,
|
|
48
|
+
});
|
|
49
|
+
this.fileWatcher.on('change', () => this.onTrigger('file-change'));
|
|
50
|
+
this.fileWatcher.start();
|
|
51
|
+
this.log('File watcher started');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (this.options.cron) {
|
|
55
|
+
const parsed = parseCron(this.options.cron);
|
|
56
|
+
this.cronScheduler = new CronScheduler(parsed);
|
|
57
|
+
this.cronScheduler.on('tick', () => this.onTrigger('cron'));
|
|
58
|
+
this.cronScheduler.start();
|
|
59
|
+
this.log(`Cron scheduler started: ${this.options.cron}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.setupSignalHandlers();
|
|
63
|
+
|
|
64
|
+
// Keep alive
|
|
65
|
+
await new Promise<void>((resolve) => {
|
|
66
|
+
const check = setInterval(() => {
|
|
67
|
+
if (this.forceExit) {
|
|
68
|
+
clearInterval(check);
|
|
69
|
+
resolve();
|
|
70
|
+
}
|
|
71
|
+
}, 500);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.cleanup();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private onTrigger(source: TriggerSource): void {
|
|
78
|
+
if (this.stopping) return;
|
|
79
|
+
|
|
80
|
+
if (this.state.running) {
|
|
81
|
+
this.state.queued = true;
|
|
82
|
+
this.log(`Run in progress, queuing trigger (${source})`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.executeRun(source);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private async executeRun(source: TriggerSource): Promise<void> {
|
|
90
|
+
this.state.running = true;
|
|
91
|
+
this.state.queued = false;
|
|
92
|
+
const runNum = this.state.runCount + 1;
|
|
93
|
+
|
|
94
|
+
this.log(`\nRun #${runNum} triggered by ${source}`);
|
|
95
|
+
|
|
96
|
+
const onEvent = this.options.quiet
|
|
97
|
+
? undefined
|
|
98
|
+
: (event: ExecutionEvent) => {
|
|
99
|
+
if (event.type === 'node-complete') {
|
|
100
|
+
this.log(` + ${event.nodeId}${event.nodeType ? ` (${event.nodeType})` : ''}`);
|
|
101
|
+
} else if (event.type === 'node-error') {
|
|
102
|
+
this.log(` x ${event.nodeId}: ${event.error ?? 'unknown error'}`);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await runWorkflow(path.resolve(this.options.filePath), {
|
|
108
|
+
params: this.options.params,
|
|
109
|
+
verbose: this.options.verbose,
|
|
110
|
+
config: this.options.config,
|
|
111
|
+
onEvent,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.state.lastResult = result;
|
|
115
|
+
this.state.runCount++;
|
|
116
|
+
this.state.lastRun = new Date();
|
|
117
|
+
this.state.lastTrigger = source;
|
|
118
|
+
|
|
119
|
+
if (!result.success) this.state.errorCount++;
|
|
120
|
+
|
|
121
|
+
const statusColor = result.success ? '\x1b[32m' : '\x1b[31m';
|
|
122
|
+
this.log(`${statusColor}Run #${runNum}: ${result.outcome}\x1b[0m - ${result.summary}`);
|
|
123
|
+
} catch (err: unknown) {
|
|
124
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
125
|
+
this.state.errorCount++;
|
|
126
|
+
this.state.runCount++;
|
|
127
|
+
this.state.lastRun = new Date();
|
|
128
|
+
this.log(`\x1b[31mRun #${runNum}: fatal error\x1b[0m - ${msg}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.state.running = false;
|
|
132
|
+
|
|
133
|
+
if (this.state.queued && !this.stopping) {
|
|
134
|
+
this.executeRun(source);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private setupSignalHandlers(): void {
|
|
139
|
+
const handler = () => {
|
|
140
|
+
if (this.stopping) {
|
|
141
|
+
// Second signal: force exit after 2s
|
|
142
|
+
this.log('\nForce exit in 2s...');
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
this.forceExit = true;
|
|
145
|
+
}, 2000);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.stopping = true;
|
|
150
|
+
this.log('\nStopping daemon...');
|
|
151
|
+
|
|
152
|
+
if (this.fileWatcher) this.fileWatcher.stop();
|
|
153
|
+
if (this.cronScheduler) this.cronScheduler.stop();
|
|
154
|
+
|
|
155
|
+
if (this.state.running) {
|
|
156
|
+
this.log('Waiting for current run to finish (30s timeout, Ctrl+C to force)...');
|
|
157
|
+
const timeout = setTimeout(() => {
|
|
158
|
+
this.forceExit = true;
|
|
159
|
+
}, 30_000);
|
|
160
|
+
|
|
161
|
+
const check = setInterval(() => {
|
|
162
|
+
if (!this.state.running) {
|
|
163
|
+
clearTimeout(timeout);
|
|
164
|
+
clearInterval(check);
|
|
165
|
+
this.printSummary();
|
|
166
|
+
this.forceExit = true;
|
|
167
|
+
}
|
|
168
|
+
}, 500);
|
|
169
|
+
} else {
|
|
170
|
+
this.printSummary();
|
|
171
|
+
this.forceExit = true;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
process.on('SIGINT', handler);
|
|
176
|
+
process.on('SIGTERM', handler);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private printSummary(): void {
|
|
180
|
+
const uptime = Date.now() - this.state.startedAt.getTime();
|
|
181
|
+
const uptimeStr = uptime < 60_000
|
|
182
|
+
? `${(uptime / 1000).toFixed(0)}s`
|
|
183
|
+
: `${(uptime / 60_000).toFixed(1)}m`;
|
|
184
|
+
|
|
185
|
+
this.log(`\nDaemon summary: ${this.state.runCount} runs, ${this.state.errorCount} errors, uptime ${uptimeStr}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private log(msg: string): void {
|
|
189
|
+
const ts = new Date().toISOString().slice(11, 19);
|
|
190
|
+
const line = `[${ts}] ${msg}`;
|
|
191
|
+
console.log(line);
|
|
192
|
+
if (this.logStream) {
|
|
193
|
+
this.logStream.write(line + '\n');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private cleanup(): void {
|
|
198
|
+
if (this.logStream) {
|
|
199
|
+
this.logStream.end();
|
|
200
|
+
this.logStream = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import type { NotificationEvent } from './types.js';
|
|
3
|
+
import type { ApprovalHandler, ApprovalRequest, ApprovalResult } from './approvals.js';
|
|
4
|
+
import type { DashboardServer } from './dashboard.js';
|
|
5
|
+
import { openBrowser } from './utils.js';
|
|
6
|
+
|
|
7
|
+
export class WebApprovalHandler implements ApprovalHandler {
|
|
8
|
+
private timeoutSeconds: number;
|
|
9
|
+
private shouldOpen: boolean;
|
|
10
|
+
private notifier: (event: NotificationEvent) => Promise<void>;
|
|
11
|
+
private dashboardServer?: DashboardServer;
|
|
12
|
+
|
|
13
|
+
constructor(options: {
|
|
14
|
+
timeoutSeconds: number;
|
|
15
|
+
open?: boolean;
|
|
16
|
+
notifier: (event: NotificationEvent) => Promise<void>;
|
|
17
|
+
dashboardServer?: DashboardServer;
|
|
18
|
+
}) {
|
|
19
|
+
this.timeoutSeconds = options.timeoutSeconds;
|
|
20
|
+
this.shouldOpen = options.open ?? true;
|
|
21
|
+
this.notifier = options.notifier;
|
|
22
|
+
this.dashboardServer = options.dashboardServer;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async handle(
|
|
26
|
+
request: ApprovalRequest,
|
|
27
|
+
event: NotificationEvent,
|
|
28
|
+
): Promise<ApprovalResult> {
|
|
29
|
+
await this.notifier(event);
|
|
30
|
+
|
|
31
|
+
// Dashboard mode: register with existing dashboard
|
|
32
|
+
if (this.dashboardServer) {
|
|
33
|
+
return this.handleViaDashboard(request);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Standalone mode: spin up a temporary server
|
|
37
|
+
return this.handleStandalone(request);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private handleViaDashboard(request: ApprovalRequest): Promise<ApprovalResult> {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
const id = this.dashboardServer!.registerApproval(
|
|
43
|
+
{ prompt: request.prompt, context: request.context },
|
|
44
|
+
resolve,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const url = `${this.dashboardServer!.getUrl()}`;
|
|
48
|
+
console.log(`[weaver] Approval pending at ${url}`);
|
|
49
|
+
|
|
50
|
+
// Timeout auto-approves
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
this.dashboardServer!.removeApproval(id);
|
|
53
|
+
resolve({
|
|
54
|
+
approved: true,
|
|
55
|
+
reason: `auto-approved after ${this.timeoutSeconds}s timeout`,
|
|
56
|
+
});
|
|
57
|
+
}, this.timeoutSeconds * 1000);
|
|
58
|
+
|
|
59
|
+
// Wrap resolve to also clear timeout
|
|
60
|
+
const originalResolve = resolve;
|
|
61
|
+
const wrappedResolve = (result: ApprovalResult) => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
originalResolve(result);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Re-register with wrapped resolve
|
|
67
|
+
this.dashboardServer!.removeApproval(id);
|
|
68
|
+
this.dashboardServer!.registerApproval(
|
|
69
|
+
{ prompt: request.prompt, context: request.context },
|
|
70
|
+
wrappedResolve,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private handleStandalone(request: ApprovalRequest): Promise<ApprovalResult> {
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
let resolved = false;
|
|
78
|
+
let server: http.Server;
|
|
79
|
+
|
|
80
|
+
const doResolve = (result: ApprovalResult) => {
|
|
81
|
+
if (resolved) return;
|
|
82
|
+
resolved = true;
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
server.close();
|
|
85
|
+
resolve(result);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const timer = setTimeout(() => {
|
|
89
|
+
doResolve({
|
|
90
|
+
approved: true,
|
|
91
|
+
reason: `auto-approved after ${this.timeoutSeconds}s timeout`,
|
|
92
|
+
});
|
|
93
|
+
}, this.timeoutSeconds * 1000);
|
|
94
|
+
|
|
95
|
+
server = http.createServer((req, res) => {
|
|
96
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
97
|
+
|
|
98
|
+
if (req.method === 'GET' && url.pathname === '/') {
|
|
99
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
100
|
+
res.end(getApprovalHtml(request, this.timeoutSeconds));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (req.method === 'POST' && url.pathname === '/api/approve') {
|
|
105
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
106
|
+
res.end(JSON.stringify({ ok: true }));
|
|
107
|
+
doResolve({ approved: true, reason: 'approved via web UI' });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (req.method === 'POST' && url.pathname === '/api/reject') {
|
|
112
|
+
let body = '';
|
|
113
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
114
|
+
req.on('end', () => {
|
|
115
|
+
let reason = 'rejected via web UI';
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(body);
|
|
118
|
+
if (parsed.reason) reason = parsed.reason;
|
|
119
|
+
} catch { /* use default */ }
|
|
120
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
121
|
+
res.end(JSON.stringify({ ok: true }));
|
|
122
|
+
doResolve({ approved: false, reason });
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
res.writeHead(404);
|
|
128
|
+
res.end('Not found');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Port 0 = OS picks a free port
|
|
132
|
+
server.listen(0, '127.0.0.1', () => {
|
|
133
|
+
const addr = server.address();
|
|
134
|
+
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
135
|
+
const url = `http://127.0.0.1:${port}`;
|
|
136
|
+
console.log(`[weaver] Approval page: ${url}`);
|
|
137
|
+
if (this.shouldOpen) openBrowser(url);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getApprovalHtml(request: ApprovalRequest, timeoutSeconds: number): string {
|
|
144
|
+
// Safe injection: double-encode JSON to prevent XSS
|
|
145
|
+
const safeContext = JSON.stringify(JSON.stringify(request.context, null, 2));
|
|
146
|
+
const safePrompt = JSON.stringify(request.prompt);
|
|
147
|
+
|
|
148
|
+
return `<!DOCTYPE html>
|
|
149
|
+
<html lang="en">
|
|
150
|
+
<head>
|
|
151
|
+
<meta charset="utf-8">
|
|
152
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
153
|
+
<title>Weaver Approval</title>
|
|
154
|
+
<style>
|
|
155
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
156
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f6f8fa; color: #24292f; display: flex; justify-content: center; padding: 40px 16px; }
|
|
157
|
+
.card { background: #fff; border: 1px solid #d0d7de; border-radius: 12px; max-width: 640px; width: 100%; padding: 32px; }
|
|
158
|
+
h1 { font-size: 20px; margin-bottom: 8px; }
|
|
159
|
+
.timer { color: #656d76; font-size: 14px; margin-bottom: 20px; }
|
|
160
|
+
.prompt { background: #f6f8fa; border: 1px solid #d0d7de; border-radius: 8px; padding: 16px; margin-bottom: 20px; font-size: 14px; line-height: 1.5; }
|
|
161
|
+
.context { background: #0d1117; color: #c9d1d9; border-radius: 8px; padding: 16px; margin-bottom: 24px; font-family: 'SF Mono', monospace; font-size: 13px; line-height: 1.5; overflow-x: auto; max-height: 400px; overflow-y: auto; white-space: pre-wrap; }
|
|
162
|
+
.actions { display: flex; gap: 12px; }
|
|
163
|
+
.btn { padding: 10px 24px; border: none; border-radius: 8px; font-size: 15px; font-weight: 500; cursor: pointer; flex: 1; }
|
|
164
|
+
.btn-approve { background: #2da44e; color: #fff; }
|
|
165
|
+
.btn-approve:hover { background: #218838; }
|
|
166
|
+
.btn-reject { background: #cf222e; color: #fff; }
|
|
167
|
+
.btn-reject:hover { background: #a40e26; }
|
|
168
|
+
.btn:disabled { opacity: 0.5; cursor: default; }
|
|
169
|
+
.reason { width: 100%; margin-bottom: 12px; padding: 8px 12px; border: 1px solid #d0d7de; border-radius: 6px; font-size: 14px; font-family: inherit; display: none; }
|
|
170
|
+
.done { text-align: center; padding: 20px; font-size: 16px; }
|
|
171
|
+
.done.approved { color: #2da44e; }
|
|
172
|
+
.done.rejected { color: #cf222e; }
|
|
173
|
+
</style>
|
|
174
|
+
</head>
|
|
175
|
+
<body>
|
|
176
|
+
<div class="card">
|
|
177
|
+
<div id="form">
|
|
178
|
+
<h1>Approval Required</h1>
|
|
179
|
+
<div class="timer" id="timer">Auto-approves in ${timeoutSeconds}s</div>
|
|
180
|
+
<div class="prompt" id="prompt"></div>
|
|
181
|
+
<div class="context" id="context"></div>
|
|
182
|
+
<textarea class="reason" id="reason" placeholder="Rejection reason (optional)" rows="2"></textarea>
|
|
183
|
+
<div class="actions">
|
|
184
|
+
<button class="btn btn-approve" id="approveBtn" onclick="doApprove()">Approve</button>
|
|
185
|
+
<button class="btn btn-reject" id="rejectBtn" onclick="showReject()">Reject</button>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
<div id="result" class="done" style="display:none"></div>
|
|
189
|
+
</div>
|
|
190
|
+
<script>
|
|
191
|
+
var deadline = Date.now() + ${timeoutSeconds * 1000};
|
|
192
|
+
document.getElementById('prompt').textContent = ${safePrompt};
|
|
193
|
+
document.getElementById('context').textContent = JSON.parse(${safeContext});
|
|
194
|
+
var submitted = false;
|
|
195
|
+
|
|
196
|
+
function doApprove() {
|
|
197
|
+
if (submitted) return;
|
|
198
|
+
submitted = true;
|
|
199
|
+
fetch('/api/approve', { method: 'POST' }).then(function() {
|
|
200
|
+
document.getElementById('form').style.display = 'none';
|
|
201
|
+
document.getElementById('result').style.display = 'block';
|
|
202
|
+
document.getElementById('result').className = 'done approved';
|
|
203
|
+
document.getElementById('result').textContent = 'Approved. You can close this tab.';
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function showReject() {
|
|
208
|
+
document.getElementById('reason').style.display = 'block';
|
|
209
|
+
document.getElementById('rejectBtn').textContent = 'Confirm Reject';
|
|
210
|
+
document.getElementById('rejectBtn').onclick = doReject;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function doReject() {
|
|
214
|
+
if (submitted) return;
|
|
215
|
+
submitted = true;
|
|
216
|
+
var reason = document.getElementById('reason').value || 'rejected via web UI';
|
|
217
|
+
fetch('/api/reject', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reason: reason }) }).then(function() {
|
|
218
|
+
document.getElementById('form').style.display = 'none';
|
|
219
|
+
document.getElementById('result').style.display = 'block';
|
|
220
|
+
document.getElementById('result').className = 'done rejected';
|
|
221
|
+
document.getElementById('result').textContent = 'Rejected. You can close this tab.';
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
setInterval(function() {
|
|
226
|
+
if (submitted) return;
|
|
227
|
+
var remaining = Math.max(0, Math.ceil((deadline - Date.now()) / 1000));
|
|
228
|
+
document.getElementById('timer').textContent = 'Auto-approves in ' + remaining + 's';
|
|
229
|
+
if (remaining <= 0) {
|
|
230
|
+
document.getElementById('form').style.display = 'none';
|
|
231
|
+
document.getElementById('result').style.display = 'block';
|
|
232
|
+
document.getElementById('result').className = 'done approved';
|
|
233
|
+
document.getElementById('result').textContent = 'Auto-approved (timeout). You can close this tab.';
|
|
234
|
+
submitted = true;
|
|
235
|
+
}
|
|
236
|
+
}, 1000);
|
|
237
|
+
</script>
|
|
238
|
+
</body>
|
|
239
|
+
</html>`;
|
|
240
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ParsedArgs } from './cli-handlers.js';
|
|
2
|
+
import {
|
|
3
|
+
handleRun, handleHistory, handleCosts, handleWatch,
|
|
4
|
+
handleCron, handlePipeline, handleDashboard, handleProviders,
|
|
5
|
+
handleEject, handleBot, handleSession, handleSteer, handleQueue,
|
|
6
|
+
handleGenesis, handleAudit,
|
|
7
|
+
} from './cli-handlers.js';
|
|
8
|
+
|
|
9
|
+
const handlers: Record<string, (opts: ParsedArgs) => Promise<void>> = {
|
|
10
|
+
run: handleRun,
|
|
11
|
+
history: handleHistory,
|
|
12
|
+
costs: handleCosts,
|
|
13
|
+
watch: handleWatch,
|
|
14
|
+
cron: handleCron,
|
|
15
|
+
pipeline: handlePipeline,
|
|
16
|
+
dashboard: handleDashboard,
|
|
17
|
+
providers: handleProviders,
|
|
18
|
+
eject: handleEject,
|
|
19
|
+
bot: handleBot,
|
|
20
|
+
session: handleSession,
|
|
21
|
+
steer: handleSteer,
|
|
22
|
+
queue: handleQueue,
|
|
23
|
+
genesis: handleGenesis,
|
|
24
|
+
audit: handleAudit,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export async function handleCommand(
|
|
28
|
+
name: string,
|
|
29
|
+
args: string[],
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
const handler = handlers[name];
|
|
32
|
+
if (!handler) {
|
|
33
|
+
throw new Error(`Unknown weaver command: ${name}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Import parseArgs to handle the remaining flags
|
|
37
|
+
const { parseArgs } = await import('./cli-handlers.js');
|
|
38
|
+
// Prepend dummy entries so parseArgs skips argv[0] and argv[1]
|
|
39
|
+
const opts = parseArgs(['node', 'weaver', name, ...args]);
|
|
40
|
+
await handler(opts);
|
|
41
|
+
}
|