@synergenius/flow-weaver-pack-weaver 0.9.59 → 0.9.77
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/ai-chat-provider.d.ts +12 -0
- package/dist/ai-chat-provider.d.ts.map +1 -1
- package/dist/ai-chat-provider.js +351 -335
- package/dist/ai-chat-provider.js.map +1 -1
- package/dist/bot/agent-loop.d.ts +20 -0
- package/dist/bot/agent-loop.d.ts.map +1 -0
- package/dist/bot/agent-loop.js +331 -0
- package/dist/bot/agent-loop.js.map +1 -0
- package/dist/bot/ai-router.d.ts +19 -0
- package/dist/bot/ai-router.d.ts.map +1 -0
- package/dist/bot/ai-router.js +104 -0
- package/dist/bot/ai-router.js.map +1 -0
- package/dist/bot/assistant-tools.d.ts.map +1 -1
- package/dist/bot/assistant-tools.js +49 -33
- package/dist/bot/assistant-tools.js.map +1 -1
- package/dist/bot/async-mutex.d.ts +13 -0
- package/dist/bot/async-mutex.d.ts.map +1 -0
- package/dist/bot/async-mutex.js +37 -0
- package/dist/bot/async-mutex.js.map +1 -0
- package/dist/bot/bot-manager.d.ts +2 -2
- package/dist/bot/bot-manager.d.ts.map +1 -1
- package/dist/bot/bot-manager.js +3 -3
- package/dist/bot/bot-manager.js.map +1 -1
- package/dist/bot/bot-registry.js +2 -2
- package/dist/bot/bot-registry.js.map +1 -1
- package/dist/bot/conversation-store.d.ts +1 -0
- package/dist/bot/conversation-store.d.ts.map +1 -1
- package/dist/bot/conversation-store.js.map +1 -1
- package/dist/bot/dashboard.d.ts.map +1 -1
- package/dist/bot/dashboard.js +17 -8
- package/dist/bot/dashboard.js.map +1 -1
- package/dist/bot/improve-loop.js.map +1 -1
- package/dist/bot/index.d.ts +2 -4
- package/dist/bot/index.d.ts.map +1 -1
- package/dist/bot/index.js +1 -2
- package/dist/bot/index.js.map +1 -1
- package/dist/bot/instance-manager.d.ts +31 -0
- package/dist/bot/instance-manager.d.ts.map +1 -0
- package/dist/bot/instance-manager.js +115 -0
- package/dist/bot/instance-manager.js.map +1 -0
- package/dist/bot/orchestrator.d.ts +36 -0
- package/dist/bot/orchestrator.d.ts.map +1 -0
- package/dist/bot/orchestrator.js +176 -0
- package/dist/bot/orchestrator.js.map +1 -0
- package/dist/bot/profile-store.d.ts +36 -0
- package/dist/bot/profile-store.d.ts.map +1 -0
- package/dist/bot/profile-store.js +208 -0
- package/dist/bot/profile-store.js.map +1 -0
- package/dist/bot/profile-types.d.ts +126 -0
- package/dist/bot/profile-types.d.ts.map +1 -0
- package/dist/bot/profile-types.js +7 -0
- package/dist/bot/profile-types.js.map +1 -0
- package/dist/bot/run-store.d.ts.map +1 -1
- package/dist/bot/run-store.js +8 -0
- package/dist/bot/run-store.js.map +1 -1
- package/dist/bot/runner.d.ts +4 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +5 -1
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/swarm-controller.d.ts +109 -0
- package/dist/bot/swarm-controller.d.ts.map +1 -0
- package/dist/bot/swarm-controller.js +640 -0
- package/dist/bot/swarm-controller.js.map +1 -0
- package/dist/bot/swarm-event-log.d.ts +28 -0
- package/dist/bot/swarm-event-log.d.ts.map +1 -0
- package/dist/bot/swarm-event-log.js +54 -0
- package/dist/bot/swarm-event-log.js.map +1 -0
- package/dist/bot/task-prompt-builder.d.ts +22 -0
- package/dist/bot/task-prompt-builder.d.ts.map +1 -0
- package/dist/bot/task-prompt-builder.js +240 -0
- package/dist/bot/task-prompt-builder.js.map +1 -0
- package/dist/bot/task-store.d.ts +21 -0
- package/dist/bot/task-store.d.ts.map +1 -0
- package/dist/bot/task-store.js +364 -0
- package/dist/bot/task-store.js.map +1 -0
- package/dist/bot/task-types.d.ts +79 -0
- package/dist/bot/task-types.d.ts.map +1 -0
- package/dist/bot/task-types.js +6 -0
- package/dist/bot/task-types.js.map +1 -0
- package/dist/bot/types.d.ts +8 -0
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +79 -54
- package/dist/cli-handlers.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/docs/docs/weaver-bot-usage.md +35 -18
- package/dist/docs/docs/weaver-config.md +20 -0
- package/dist/docs/docs/weaver-task-queue.md +31 -19
- package/dist/docs/weaver-config.md +15 -9
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-tools.d.ts +17 -0
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +98 -279
- package/dist/mcp-tools.js.map +1 -1
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +6 -24
- package/dist/node-types/bot-report.js.map +1 -1
- package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
- package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
- package/dist/node-types/orchestrator-dispatch.js +63 -0
- package/dist/node-types/orchestrator-dispatch.js.map +1 -0
- package/dist/node-types/orchestrator-load-state.d.ts +16 -0
- package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
- package/dist/node-types/orchestrator-load-state.js +60 -0
- package/dist/node-types/orchestrator-load-state.js.map +1 -0
- package/dist/node-types/orchestrator-route.d.ts +16 -0
- package/dist/node-types/orchestrator-route.d.ts.map +1 -0
- package/dist/node-types/orchestrator-route.js +28 -0
- package/dist/node-types/orchestrator-route.js.map +1 -0
- package/dist/node-types/receive-task.d.ts +2 -3
- package/dist/node-types/receive-task.d.ts.map +1 -1
- package/dist/node-types/receive-task.js +3 -48
- package/dist/node-types/receive-task.js.map +1 -1
- package/dist/templates/weaver-template.d.ts +11 -0
- package/dist/templates/weaver-template.d.ts.map +1 -0
- package/dist/templates/weaver-template.js +53 -0
- package/dist/templates/weaver-template.js.map +1 -0
- package/dist/ui/bot-activity.js +2 -2
- package/dist/ui/bot-constants.d.ts +14 -0
- package/dist/ui/bot-constants.d.ts.map +1 -0
- package/dist/ui/bot-constants.js +189 -0
- package/dist/ui/bot-constants.js.map +1 -0
- package/dist/ui/bot-panel.js +207 -245
- package/dist/ui/bot-slot-card.js +141 -0
- package/dist/ui/budget-bar.js +59 -0
- package/dist/ui/chat-task-result.js +178 -0
- package/dist/ui/decision-log.js +136 -0
- package/dist/ui/profile-card.js +158 -0
- package/dist/ui/profile-editor.js +597 -0
- package/dist/ui/swarm-controls.js +245 -0
- package/dist/ui/swarm-dashboard.js +3012 -0
- package/dist/ui/task-create-form.js +98 -0
- package/dist/ui/task-detail-view.js +1044 -0
- package/dist/ui/task-pool-list.js +156 -0
- package/dist/workflows/orchestrator.d.ts +21 -0
- package/dist/workflows/orchestrator.d.ts.map +1 -0
- package/dist/workflows/orchestrator.js +281 -0
- package/dist/workflows/orchestrator.js.map +1 -0
- package/dist/workflows/weaver-bot-session.d.ts +65 -0
- package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
- package/dist/workflows/weaver-bot-session.js +68 -0
- package/dist/workflows/weaver-bot-session.js.map +1 -0
- package/dist/workflows/weaver.d.ts +24 -0
- package/dist/workflows/weaver.d.ts.map +1 -0
- package/dist/workflows/weaver.js +28 -0
- package/dist/workflows/weaver.js.map +1 -0
- package/flowweaver.manifest.json +547 -133
- package/package.json +1 -1
- package/src/ai-chat-provider.ts +378 -371
- package/src/bot/ai-router.ts +132 -0
- package/src/bot/assistant-tools.ts +47 -29
- package/src/bot/async-mutex.ts +37 -0
- package/src/bot/bot-manager.ts +3 -3
- package/src/bot/bot-registry.ts +2 -2
- package/src/bot/conversation-store.ts +2 -1
- package/src/bot/dashboard.ts +17 -8
- package/src/bot/improve-loop.ts +6 -6
- package/src/bot/index.ts +2 -4
- package/src/bot/instance-manager.ts +128 -0
- package/src/bot/orchestrator.ts +244 -0
- package/src/bot/profile-store.ts +225 -0
- package/src/bot/profile-types.ts +141 -0
- package/src/bot/run-store.ts +8 -0
- package/src/bot/runner.ts +9 -1
- package/src/bot/swarm-controller.ts +780 -0
- package/src/bot/swarm-event-log.ts +57 -0
- package/src/bot/task-prompt-builder.ts +309 -0
- package/src/bot/task-store.ts +407 -0
- package/src/bot/task-types.ts +100 -0
- package/src/bot/types.ts +8 -0
- package/src/cli-handlers.ts +78 -53
- package/src/docs/weaver-bot-usage.md +35 -18
- package/src/docs/weaver-config.md +20 -0
- package/src/docs/weaver-task-queue.md +31 -19
- package/src/index.ts +5 -4
- package/src/mcp-tools.ts +129 -372
- package/src/node-types/bot-report.ts +6 -24
- package/src/node-types/orchestrator-dispatch.ts +71 -0
- package/src/node-types/orchestrator-load-state.ts +66 -0
- package/src/node-types/orchestrator-route.ts +33 -0
- package/src/node-types/receive-task.ts +3 -57
- package/src/ui/bot-activity.tsx +2 -2
- package/src/ui/bot-constants.ts +192 -0
- package/src/ui/bot-panel.tsx +213 -247
- package/src/ui/bot-slot-card.tsx +139 -0
- package/src/ui/budget-bar.tsx +30 -0
- package/src/ui/chat-task-result.tsx +236 -0
- package/src/ui/decision-log.tsx +148 -0
- package/src/ui/profile-card.tsx +157 -0
- package/src/ui/profile-editor.tsx +384 -0
- package/src/ui/swarm-controls.tsx +260 -0
- package/src/ui/swarm-dashboard.tsx +647 -0
- package/src/ui/task-create-form.tsx +87 -0
- package/src/ui/task-detail-view.tsx +841 -0
- package/src/ui/task-pool-list.tsx +187 -0
- package/src/workflows/orchestrator.ts +302 -0
- package/dist/docs/weaver-bot-usage.md +0 -34
- package/dist/docs/weaver-genesis.md +0 -32
- package/dist/docs/weaver-task-queue.md +0 -34
- package/dist/ui/bot-workspace.js +0 -1015
- package/dist/ui/chat-bot-result.js +0 -71
- package/dist/ui/queue-input.js +0 -82
- package/dist/ui/session-bar.js +0 -174
- package/src/bot/error-guide.ts +0 -4
- package/src/bot/retry-utils.ts +0 -4
- package/src/bot/session-state.ts +0 -116
- package/src/bot/task-queue.ts +0 -262
- package/src/ui/bot-workspace.tsx +0 -442
- package/src/ui/chat-bot-result.tsx +0 -81
- package/src/ui/queue-input.tsx +0 -56
- package/src/ui/session-bar.tsx +0 -157
package/src/bot/task-queue.ts
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as crypto from 'node:crypto';
|
|
4
|
-
import { withFileLock } from './file-lock.js';
|
|
5
|
-
import { parseNdjson } from './safe-json.js';
|
|
6
|
-
import { resolveWeaverDir } from './paths.js';
|
|
7
|
-
|
|
8
|
-
export interface QueuedTask {
|
|
9
|
-
id: string;
|
|
10
|
-
instruction: string;
|
|
11
|
-
mode?: 'create' | 'modify' | 'read' | 'batch';
|
|
12
|
-
targets?: string[];
|
|
13
|
-
options?: Record<string, unknown>;
|
|
14
|
-
priority: number;
|
|
15
|
-
addedAt: number;
|
|
16
|
-
status: 'pending' | 'running' | 'completed' | 'no-op' | 'failed' | 'cancelled';
|
|
17
|
-
/** PID of the process running this task (set when status becomes 'running'). */
|
|
18
|
-
runnerId?: number;
|
|
19
|
-
/** Error reason (set on failure) */
|
|
20
|
-
failureReason?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface AddResult {
|
|
24
|
-
id: string;
|
|
25
|
-
duplicate: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Max pending tasks before queue rejects new additions. */
|
|
29
|
-
const MAX_PENDING = 200;
|
|
30
|
-
/** Don't re-queue tasks completed within this window (ms). */
|
|
31
|
-
const CYCLE_DEDUP_WINDOW = 3600_000; // 1 hour
|
|
32
|
-
|
|
33
|
-
export class TaskQueue {
|
|
34
|
-
readonly filePath: string;
|
|
35
|
-
|
|
36
|
-
constructor(dir?: string) {
|
|
37
|
-
const base = dir ?? resolveWeaverDir();
|
|
38
|
-
this.filePath = path.join(base, 'task-queue.ndjson');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async add(task: Omit<QueuedTask, 'id' | 'addedAt' | 'status'>): Promise<AddResult> {
|
|
42
|
-
return withFileLock(this.filePath, () => {
|
|
43
|
-
const existing = this.readAll();
|
|
44
|
-
|
|
45
|
-
// Dedup: skip if a pending task with the same instruction exists
|
|
46
|
-
const pendingDup = existing.find(
|
|
47
|
-
t => t.status === 'pending' && t.instruction === task.instruction,
|
|
48
|
-
);
|
|
49
|
-
if (pendingDup) return { id: pendingDup.id, duplicate: true };
|
|
50
|
-
|
|
51
|
-
// Cycle-aware dedup: skip if same instruction was completed recently
|
|
52
|
-
const recentDup = existing.find(
|
|
53
|
-
t => (t.status === 'completed' || t.status === 'no-op')
|
|
54
|
-
&& t.instruction === task.instruction
|
|
55
|
-
&& Date.now() - t.addedAt < CYCLE_DEDUP_WINDOW,
|
|
56
|
-
);
|
|
57
|
-
if (recentDup) return { id: recentDup.id, duplicate: true };
|
|
58
|
-
|
|
59
|
-
// Queue size cap
|
|
60
|
-
const pendingCount = existing.filter(t => t.status === 'pending').length;
|
|
61
|
-
if (pendingCount >= MAX_PENDING) {
|
|
62
|
-
throw new Error(`Queue full (${MAX_PENDING} pending tasks). Use queue_retry to resume failed tasks, or queue_list to review.`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const entry: QueuedTask = {
|
|
66
|
-
...task,
|
|
67
|
-
id: crypto.randomUUID().slice(0, 8),
|
|
68
|
-
addedAt: Date.now(),
|
|
69
|
-
status: 'pending',
|
|
70
|
-
};
|
|
71
|
-
const dir = path.dirname(this.filePath);
|
|
72
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
73
|
-
fs.appendFileSync(this.filePath, JSON.stringify(entry) + '\n', 'utf-8');
|
|
74
|
-
return { id: entry.id, duplicate: false };
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async next(): Promise<QueuedTask | null> {
|
|
79
|
-
return withFileLock(this.filePath, () => {
|
|
80
|
-
const tasks = this.readAll().filter(t => t.status === 'pending');
|
|
81
|
-
tasks.sort((a, b) => b.priority - a.priority || a.addedAt - b.addedAt);
|
|
82
|
-
return tasks[0] ?? null;
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async list(): Promise<QueuedTask[]> {
|
|
87
|
-
return withFileLock(this.filePath, () => this.readAll());
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async remove(id: string): Promise<boolean> {
|
|
91
|
-
return withFileLock(this.filePath, () => {
|
|
92
|
-
const tasks = this.readAll();
|
|
93
|
-
const filtered = tasks.filter(t => t.id !== id);
|
|
94
|
-
if (filtered.length === tasks.length) return false;
|
|
95
|
-
this.writeAll(filtered);
|
|
96
|
-
return true;
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async clear(): Promise<number> {
|
|
101
|
-
return withFileLock(this.filePath, () => {
|
|
102
|
-
const tasks = this.readAll();
|
|
103
|
-
if (tasks.length === 0) return 0;
|
|
104
|
-
try { fs.unlinkSync(this.filePath); } catch { /* ignore */ }
|
|
105
|
-
return tasks.length;
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async markRunning(id: string): Promise<void> {
|
|
110
|
-
return withFileLock(this.filePath, () => {
|
|
111
|
-
const tasks = this.readAll();
|
|
112
|
-
const task = tasks.find(t => t.id === id);
|
|
113
|
-
if (task) {
|
|
114
|
-
task.status = 'running';
|
|
115
|
-
task.runnerId = process.pid;
|
|
116
|
-
this.writeAll(tasks);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async markComplete(id: string): Promise<void> {
|
|
122
|
-
await this.updateStatus(id, 'completed');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async markNoOp(id: string): Promise<void> {
|
|
126
|
-
await this.updateStatus(id, 'no-op');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async markFailed(id: string, reason?: string): Promise<void> {
|
|
130
|
-
return withFileLock(this.filePath, () => {
|
|
131
|
-
const tasks = this.readAll();
|
|
132
|
-
const task = tasks.find(t => t.id === id);
|
|
133
|
-
if (task) {
|
|
134
|
-
task.status = 'failed';
|
|
135
|
-
if (reason) task.failureReason = reason.slice(0, 500);
|
|
136
|
-
this.writeAll(tasks);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Reset a failed or running task back to pending. */
|
|
142
|
-
async retry(id: string): Promise<boolean> {
|
|
143
|
-
return withFileLock(this.filePath, () => {
|
|
144
|
-
const tasks = this.readAll();
|
|
145
|
-
const task = tasks.find(t => t.id === id && (t.status === 'failed' || t.status === 'running'));
|
|
146
|
-
if (!task) return false;
|
|
147
|
-
task.status = 'pending';
|
|
148
|
-
task.failureReason = undefined;
|
|
149
|
-
this.writeAll(tasks);
|
|
150
|
-
return true;
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Reset ALL failed tasks back to pending. Returns count reset. */
|
|
155
|
-
async retryAll(): Promise<number> {
|
|
156
|
-
return withFileLock(this.filePath, () => {
|
|
157
|
-
const tasks = this.readAll();
|
|
158
|
-
let count = 0;
|
|
159
|
-
for (const t of tasks) {
|
|
160
|
-
if (t.status === 'failed') {
|
|
161
|
-
t.status = 'pending';
|
|
162
|
-
t.failureReason = undefined;
|
|
163
|
-
count++;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (count > 0) this.writeAll(tasks);
|
|
167
|
-
return count;
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Reset orphaned "running" tasks to pending (crash recovery).
|
|
172
|
-
* Resets tasks whose runner PID is dead, OR whose PID is current process
|
|
173
|
-
* but the run is no longer tracked in the in-memory registry. */
|
|
174
|
-
async recoverOrphans(activeRunIds?: Set<string>): Promise<number> {
|
|
175
|
-
return withFileLock(this.filePath, () => {
|
|
176
|
-
const tasks = this.readAll();
|
|
177
|
-
let count = 0;
|
|
178
|
-
const currentPid = process.pid;
|
|
179
|
-
for (const t of tasks) {
|
|
180
|
-
if (t.status === 'running') {
|
|
181
|
-
// If we have a registry, check it — definitive alive/dead
|
|
182
|
-
if (activeRunIds && t.runnerId === currentPid) {
|
|
183
|
-
// Same process but run not in registry = dead
|
|
184
|
-
// (We don't have the runId on the task, so if this process's
|
|
185
|
-
// registry is empty, all its running tasks are orphans)
|
|
186
|
-
if (activeRunIds.size === 0) {
|
|
187
|
-
t.status = 'pending';
|
|
188
|
-
t.runnerId = undefined;
|
|
189
|
-
count++;
|
|
190
|
-
}
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
// Different process or no registry — check PID
|
|
194
|
-
if (t.runnerId != null) {
|
|
195
|
-
let alive = false;
|
|
196
|
-
try { process.kill(t.runnerId, 0); alive = true; } catch { /* process gone */ }
|
|
197
|
-
if (alive) continue;
|
|
198
|
-
}
|
|
199
|
-
t.status = 'pending';
|
|
200
|
-
t.runnerId = undefined;
|
|
201
|
-
count++;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (count > 0) this.writeAll(tasks);
|
|
205
|
-
return count;
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async reorder(id: string, priority: number): Promise<boolean> {
|
|
210
|
-
return withFileLock(this.filePath, () => {
|
|
211
|
-
const tasks = this.readAll();
|
|
212
|
-
const task = tasks.find(t => t.id === id);
|
|
213
|
-
if (!task) return false;
|
|
214
|
-
task.priority = priority;
|
|
215
|
-
this.writeAll(tasks);
|
|
216
|
-
return true;
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private async updateStatus(id: string, status: QueuedTask['status']): Promise<void> {
|
|
221
|
-
return withFileLock(this.filePath, () => {
|
|
222
|
-
const tasks = this.readAll();
|
|
223
|
-
const task = tasks.find(t => t.id === id);
|
|
224
|
-
if (task) {
|
|
225
|
-
task.status = status;
|
|
226
|
-
this.writeAll(tasks);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private readAll(): QueuedTask[] {
|
|
232
|
-
if (!fs.existsSync(this.filePath)) return [];
|
|
233
|
-
const content = fs.readFileSync(this.filePath, 'utf-8').trim();
|
|
234
|
-
if (!content) return [];
|
|
235
|
-
const { records } = parseNdjson<QueuedTask>(content, 'task-queue');
|
|
236
|
-
return records;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/** Atomically claim the next pending task: selects highest-priority and marks it running in one lock. */
|
|
240
|
-
async claimNext(): Promise<QueuedTask | null> {
|
|
241
|
-
return withFileLock(this.filePath, () => {
|
|
242
|
-
const tasks = this.readAll();
|
|
243
|
-
const pending = tasks.filter(t => t.status === 'pending');
|
|
244
|
-
pending.sort((a, b) => b.priority - a.priority || a.addedAt - b.addedAt);
|
|
245
|
-
const chosen = pending[0];
|
|
246
|
-
if (!chosen) return null;
|
|
247
|
-
chosen.status = 'running';
|
|
248
|
-
chosen.runnerId = process.pid;
|
|
249
|
-
this.writeAll(tasks);
|
|
250
|
-
return chosen;
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private writeAll(tasks: QueuedTask[]): void {
|
|
255
|
-
const dir = path.dirname(this.filePath);
|
|
256
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
257
|
-
// Atomic write: write to temp file then rename to avoid partial reads on crash
|
|
258
|
-
const tmpPath = this.filePath + '.tmp.' + process.pid;
|
|
259
|
-
fs.writeFileSync(tmpPath, tasks.map(t => JSON.stringify(t)).join('\n') + (tasks.length > 0 ? '\n' : ''), 'utf-8');
|
|
260
|
-
fs.renameSync(tmpPath, this.filePath);
|
|
261
|
-
}
|
|
262
|
-
}
|
package/src/ui/bot-workspace.tsx
DELETED
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Weaver Bot Workspace — center dock execution workspace.
|
|
3
|
-
*
|
|
4
|
-
* Pack-contributed component loaded via botUI.workspace manifest slot.
|
|
5
|
-
* Receives PackWorkspaceContext from platform — uses it for all tool calls,
|
|
6
|
-
* event streaming, and window management. Zero platform imports.
|
|
7
|
-
*
|
|
8
|
-
* Single chronological timeline (oldest → newest, like a conversation):
|
|
9
|
-
* - Completed runs at the top
|
|
10
|
-
* - Live execution streaming in the middle
|
|
11
|
-
* - Queued tasks at the bottom
|
|
12
|
-
* - Genesis cycles interleaved with history
|
|
13
|
-
*
|
|
14
|
-
* Layout:
|
|
15
|
-
* - Session bar (top)
|
|
16
|
-
* - Settings (collapsed)
|
|
17
|
-
* - Scrollable timeline (main area)
|
|
18
|
-
* - Input bar (pinned bottom)
|
|
19
|
-
*/
|
|
20
|
-
const React = require('react');
|
|
21
|
-
const { useRef, useEffect, useState, useCallback, useMemo } = React;
|
|
22
|
-
const {
|
|
23
|
-
Flex, ScrollArea, EmptyState, TaskBlock, toast, usePackWorkspace, useEventStream,
|
|
24
|
-
} = require('@fw/plugin-ui-kit');
|
|
25
|
-
|
|
26
|
-
// Local pack-specific components and utilities (bundled by esbuild)
|
|
27
|
-
import { SessionBar } from './session-bar';
|
|
28
|
-
import { SettingsSection } from './settings-section';
|
|
29
|
-
import { QueueInput } from './queue-input';
|
|
30
|
-
import { GenesisBlock } from './genesis-block';
|
|
31
|
-
// ApprovalCard no longer needed — TaskBlock handles approval UI natively
|
|
32
|
-
import { useStreamTimeline } from './use-stream-timeline';
|
|
33
|
-
import { traceToTimeline } from './trace-to-timeline';
|
|
34
|
-
import { sendSteerCommand } from './steer-api';
|
|
35
|
-
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
// Types
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
interface QueuedTask {
|
|
41
|
-
id: string;
|
|
42
|
-
instruction: string;
|
|
43
|
-
status: string;
|
|
44
|
-
priority: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface GenesisCycleData {
|
|
48
|
-
id: string;
|
|
49
|
-
timestamp: string;
|
|
50
|
-
durationMs: number;
|
|
51
|
-
proposal: Record<string, unknown> | null;
|
|
52
|
-
outcome: string;
|
|
53
|
-
diffSummary: string | null;
|
|
54
|
-
error: string | null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface HistoricalRun {
|
|
58
|
-
id: string;
|
|
59
|
-
outcome: string;
|
|
60
|
-
success?: boolean;
|
|
61
|
-
summary?: string;
|
|
62
|
-
startedAt?: string;
|
|
63
|
-
durationMs?: number;
|
|
64
|
-
duration?: number;
|
|
65
|
-
cost?: number;
|
|
66
|
-
costDetail?: Record<string, unknown>;
|
|
67
|
-
workflowFile?: string;
|
|
68
|
-
plan?: Record<string, unknown>;
|
|
69
|
-
trace?: Array<Record<string, unknown>>;
|
|
70
|
-
nodeMeta?: Record<string, Record<string, unknown>>;
|
|
71
|
-
auditTrail?: Array<Record<string, unknown>>;
|
|
72
|
-
params?: Record<string, unknown>;
|
|
73
|
-
[key: string]: unknown;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface TimelineItem {
|
|
77
|
-
kind: 'run' | 'genesis';
|
|
78
|
-
timestamp: number;
|
|
79
|
-
run?: HistoricalRun;
|
|
80
|
-
runTimeline?: Array<Record<string, unknown>>;
|
|
81
|
-
genesis?: GenesisCycleData;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// Component
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
function BotWorkspace() {
|
|
89
|
-
const ctx = usePackWorkspace();
|
|
90
|
-
const { callTool, windowData, dispatchEvent, onRefresh } = ctx;
|
|
91
|
-
const packId = ctx.packId;
|
|
92
|
-
|
|
93
|
-
const isLive = windowData?.live === true && !!windowData?.runId;
|
|
94
|
-
const highlightRunId = windowData?.runId as string | undefined;
|
|
95
|
-
|
|
96
|
-
// ── Live execution (SSE stream) ──────────────────────────────
|
|
97
|
-
const stream = useEventStream();
|
|
98
|
-
const {
|
|
99
|
-
timeline: liveTimeline,
|
|
100
|
-
phase: livePhase,
|
|
101
|
-
instruction: liveInstruction,
|
|
102
|
-
elapsed,
|
|
103
|
-
cost,
|
|
104
|
-
plan,
|
|
105
|
-
awaitingApproval,
|
|
106
|
-
} = useStreamTimeline(stream.events, stream.isDone);
|
|
107
|
-
|
|
108
|
-
// Start SSE stream for direct live run (from AI chat)
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
if (!isLive || !windowData?.runId) return;
|
|
111
|
-
stream.start(packId, 'fw_weaver_events', windowData.runId as string);
|
|
112
|
-
return () => stream.stop();
|
|
113
|
-
}, [isLive, packId, windowData?.runId]);
|
|
114
|
-
|
|
115
|
-
// Auto-detect active session task
|
|
116
|
-
const [sessionRunId, setSessionRunId] = useState<string | null>(null);
|
|
117
|
-
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
if (isLive) return;
|
|
120
|
-
const poll = async () => {
|
|
121
|
-
try {
|
|
122
|
-
const status = (await callTool('fw_weaver_status')) as Record<string, unknown> | null;
|
|
123
|
-
const runId = (status?.currentRunId as string) ?? null;
|
|
124
|
-
const alive = status?.alive as boolean | null;
|
|
125
|
-
if (runId && alive === false) {
|
|
126
|
-
setSessionRunId(null);
|
|
127
|
-
} else {
|
|
128
|
-
setSessionRunId(runId);
|
|
129
|
-
}
|
|
130
|
-
} catch { /* non-fatal */ }
|
|
131
|
-
};
|
|
132
|
-
poll();
|
|
133
|
-
const interval = setInterval(poll, 3_000);
|
|
134
|
-
return () => clearInterval(interval);
|
|
135
|
-
}, [isLive, callTool]);
|
|
136
|
-
|
|
137
|
-
// Start SSE for session's current task
|
|
138
|
-
useEffect(() => {
|
|
139
|
-
if (!sessionRunId || isLive) return;
|
|
140
|
-
stream.start(packId, 'fw_weaver_events', sessionRunId);
|
|
141
|
-
return () => stream.stop();
|
|
142
|
-
}, [sessionRunId, packId, isLive]);
|
|
143
|
-
|
|
144
|
-
// Auto-scroll
|
|
145
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
const el = scrollRef.current;
|
|
148
|
-
if (el) el.scrollTop = el.scrollHeight;
|
|
149
|
-
}, [liveTimeline.length, stream.events.length]);
|
|
150
|
-
|
|
151
|
-
// Steer controls
|
|
152
|
-
const [pausing, setPausing] = useState(false);
|
|
153
|
-
const [stopping, setStopping] = useState(false);
|
|
154
|
-
|
|
155
|
-
const handlePause = useCallback(async () => {
|
|
156
|
-
setPausing(true);
|
|
157
|
-
try {
|
|
158
|
-
await sendSteerCommand(callTool, 'pause');
|
|
159
|
-
toast('Pause signal sent', { type: 'info' });
|
|
160
|
-
} catch (err: unknown) {
|
|
161
|
-
toast(err instanceof Error ? err.message : 'Failed to pause', { type: 'error' });
|
|
162
|
-
}
|
|
163
|
-
setPausing(false);
|
|
164
|
-
}, [callTool]);
|
|
165
|
-
|
|
166
|
-
const handleStop = useCallback(async () => {
|
|
167
|
-
setStopping(true);
|
|
168
|
-
try {
|
|
169
|
-
await sendSteerCommand(callTool, 'cancel');
|
|
170
|
-
toast('Stopping — will take effect after current node completes', { type: 'info' });
|
|
171
|
-
} catch (err: unknown) {
|
|
172
|
-
toast(err instanceof Error ? err.message : 'Failed to stop', { type: 'error' });
|
|
173
|
-
setStopping(false);
|
|
174
|
-
}
|
|
175
|
-
}, [callTool]);
|
|
176
|
-
|
|
177
|
-
// ── History + Genesis + Queue ────────────────────────────────
|
|
178
|
-
const [history, setHistory] = useState<HistoricalRun[]>([]);
|
|
179
|
-
const [genesisCycles, setGenesisCycles] = useState<GenesisCycleData[]>([]);
|
|
180
|
-
const [queuedTasks, setQueuedTasks] = useState<QueuedTask[]>([]);
|
|
181
|
-
const [removingIds, setRemovingIds] = useState<Set<string>>(new Set());
|
|
182
|
-
const [expandedRunId, setExpandedRunId] = useState<string | null>(
|
|
183
|
-
highlightRunId && !isLive ? highlightRunId : null,
|
|
184
|
-
);
|
|
185
|
-
const [liveExpanded, setLiveExpanded] = useState(true);
|
|
186
|
-
|
|
187
|
-
const refreshData = useCallback(() => {
|
|
188
|
-
callTool('fw_weaver_history', { limit: 20 }).then((data: unknown) => {
|
|
189
|
-
if (Array.isArray(data)) {
|
|
190
|
-
setHistory(
|
|
191
|
-
data.map((r: Record<string, unknown>) => {
|
|
192
|
-
const costObj = r.cost && typeof r.cost === 'object' ? (r.cost as Record<string, unknown>) : undefined;
|
|
193
|
-
return { ...r, costDetail: costObj, cost: costObj?.totalCost } as unknown as HistoricalRun;
|
|
194
|
-
}),
|
|
195
|
-
);
|
|
196
|
-
} else {
|
|
197
|
-
setHistory([]);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
callTool('fw_weaver_insights').then((data: unknown) => {
|
|
201
|
-
const d = data as Record<string, unknown> | null;
|
|
202
|
-
if (d?.evolution) {
|
|
203
|
-
const evo = d.evolution as Record<string, unknown>;
|
|
204
|
-
if (Array.isArray(evo.recentCycles)) {
|
|
205
|
-
setGenesisCycles(evo.recentCycles.slice(0, 10) as GenesisCycleData[]);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
callTool('fw_weaver_queue', { action: 'list' }).then((data: unknown) => {
|
|
210
|
-
const visible = (Array.isArray(data) ? (data as QueuedTask[]) : []).filter(
|
|
211
|
-
(t: QueuedTask) => t.status === 'pending' || t.status === 'running',
|
|
212
|
-
);
|
|
213
|
-
setQueuedTasks(visible);
|
|
214
|
-
});
|
|
215
|
-
}, [callTool]);
|
|
216
|
-
|
|
217
|
-
useEffect(() => { refreshData(); }, [refreshData]);
|
|
218
|
-
|
|
219
|
-
// Poll queue every 10s
|
|
220
|
-
useEffect(() => {
|
|
221
|
-
const interval = setInterval(() => {
|
|
222
|
-
callTool('fw_weaver_queue', { action: 'list' }).then((data: unknown) => {
|
|
223
|
-
const visible = (Array.isArray(data) ? (data as QueuedTask[]) : []).filter(
|
|
224
|
-
(t: QueuedTask) => t.status === 'pending' || t.status === 'running',
|
|
225
|
-
);
|
|
226
|
-
setQueuedTasks(visible);
|
|
227
|
-
});
|
|
228
|
-
}, 10_000);
|
|
229
|
-
return () => clearInterval(interval);
|
|
230
|
-
}, [callTool]);
|
|
231
|
-
|
|
232
|
-
// Listen for refresh events
|
|
233
|
-
useEffect(() => {
|
|
234
|
-
return onRefresh(() => refreshData());
|
|
235
|
-
}, [refreshData, onRefresh]);
|
|
236
|
-
|
|
237
|
-
// Refresh when session task changes
|
|
238
|
-
useEffect(() => {
|
|
239
|
-
if (sessionRunId !== null) refreshData();
|
|
240
|
-
}, [sessionRunId, refreshData]);
|
|
241
|
-
|
|
242
|
-
const handleRemoveTask = useCallback(async (id: string) => {
|
|
243
|
-
setRemovingIds((prev: Set<string>) => new Set(prev).add(id));
|
|
244
|
-
try {
|
|
245
|
-
await callTool('fw_weaver_queue', { action: 'remove', id });
|
|
246
|
-
refreshData();
|
|
247
|
-
toast('Task removed', { type: 'warning' });
|
|
248
|
-
} catch (err: unknown) {
|
|
249
|
-
toast(err instanceof Error ? err.message : 'Failed to remove task', { type: 'error' });
|
|
250
|
-
}
|
|
251
|
-
setRemovingIds((prev: Set<string>) => {
|
|
252
|
-
const next = new Set(prev);
|
|
253
|
-
next.delete(id);
|
|
254
|
-
return next;
|
|
255
|
-
});
|
|
256
|
-
}, [callTool, refreshData]);
|
|
257
|
-
|
|
258
|
-
// Build unified timeline
|
|
259
|
-
const timelineItems = useMemo(() => {
|
|
260
|
-
const items: TimelineItem[] = [];
|
|
261
|
-
for (const run of history) {
|
|
262
|
-
items.push({ kind: 'run', timestamp: new Date(run.startedAt ?? 0).getTime(), run, runTimeline: traceToTimeline(run) });
|
|
263
|
-
}
|
|
264
|
-
for (const cycle of genesisCycles) {
|
|
265
|
-
items.push({ kind: 'genesis', timestamp: new Date(cycle.timestamp).getTime(), genesis: cycle });
|
|
266
|
-
}
|
|
267
|
-
items.sort((a: TimelineItem, b: TimelineItem) => a.timestamp - b.timestamp);
|
|
268
|
-
return items;
|
|
269
|
-
}, [history, genesisCycles]);
|
|
270
|
-
|
|
271
|
-
const toggleExpand = useCallback((id: string) => {
|
|
272
|
-
setExpandedRunId((prev: string | null) => (prev === id ? null : id));
|
|
273
|
-
}, []);
|
|
274
|
-
|
|
275
|
-
// ── Render ────────────────────────────────────────────────────
|
|
276
|
-
|
|
277
|
-
const isStreaming = isLive || !!sessionRunId;
|
|
278
|
-
const hasContent = isStreaming || timelineItems.length > 0 || queuedTasks.length > 0;
|
|
279
|
-
|
|
280
|
-
// Helper to extract instruction text from a run
|
|
281
|
-
function extractInstruction(run: HistoricalRun): string {
|
|
282
|
-
let text = '';
|
|
283
|
-
if (run.params) {
|
|
284
|
-
try {
|
|
285
|
-
const taskJson = (run.params as Record<string, unknown>).taskJson as string;
|
|
286
|
-
if (taskJson) {
|
|
287
|
-
const task = JSON.parse(taskJson) as Record<string, unknown>;
|
|
288
|
-
if (task.instruction) text = task.instruction as string;
|
|
289
|
-
}
|
|
290
|
-
} catch { /* not JSON */ }
|
|
291
|
-
}
|
|
292
|
-
if (!text && run.summary) {
|
|
293
|
-
const summary = run.summary;
|
|
294
|
-
if (summary.includes('|')) {
|
|
295
|
-
const parts = summary.split('|').map((p: string) => p.trim());
|
|
296
|
-
const taskPart = parts.find((p: string) => p.startsWith('Task:'));
|
|
297
|
-
const summaryPart = parts.find((p: string) => p.startsWith('Summary:'));
|
|
298
|
-
text = taskPart?.replace('Task:', '').trim() ?? summaryPart?.replace('Summary:', '').trim() ?? parts[0] ?? '';
|
|
299
|
-
} else {
|
|
300
|
-
text = summary;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if (!text) {
|
|
304
|
-
const wf = run.workflowFile;
|
|
305
|
-
if (wf) {
|
|
306
|
-
const parts = wf.split('/');
|
|
307
|
-
text = parts[parts.length - 1] ?? 'Bot run';
|
|
308
|
-
} else {
|
|
309
|
-
text = 'Bot run';
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
if (text.length > 120) text = text.slice(0, 117) + '...';
|
|
313
|
-
return text;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Approval state for running TaskBlock
|
|
317
|
-
const [approvalStatus, setApprovalStatus] = useState<'pending' | 'approved' | 'rejected' | null>(null);
|
|
318
|
-
const [approvalLoading, setApprovalLoading] = useState(false);
|
|
319
|
-
|
|
320
|
-
// Sync approval status with stream events
|
|
321
|
-
useEffect(() => {
|
|
322
|
-
if (awaitingApproval) setApprovalStatus('pending');
|
|
323
|
-
}, [awaitingApproval]);
|
|
324
|
-
|
|
325
|
-
// Reset approval when stream restarts (new run)
|
|
326
|
-
useEffect(() => {
|
|
327
|
-
if (stream.events.length === 0) setApprovalStatus(null);
|
|
328
|
-
}, [stream.events.length]);
|
|
329
|
-
|
|
330
|
-
const handleApprove = useCallback(async () => {
|
|
331
|
-
setApprovalLoading(true);
|
|
332
|
-
try {
|
|
333
|
-
await callTool('fw_weaver_approve', { approved: true });
|
|
334
|
-
setApprovalStatus('approved');
|
|
335
|
-
toast('Plan approved', { type: 'success' });
|
|
336
|
-
} catch (err: unknown) {
|
|
337
|
-
toast(err instanceof Error ? err.message : 'Failed to approve', { type: 'error' });
|
|
338
|
-
}
|
|
339
|
-
setApprovalLoading(false);
|
|
340
|
-
}, [callTool]);
|
|
341
|
-
|
|
342
|
-
const handleReject = useCallback(async () => {
|
|
343
|
-
setApprovalLoading(true);
|
|
344
|
-
try {
|
|
345
|
-
await callTool('fw_weaver_approve', { approved: false });
|
|
346
|
-
setApprovalStatus('rejected');
|
|
347
|
-
toast('Plan rejected', { type: 'info' });
|
|
348
|
-
} catch (err: unknown) {
|
|
349
|
-
toast(err instanceof Error ? err.message : 'Failed to reject', { type: 'error' });
|
|
350
|
-
}
|
|
351
|
-
setApprovalLoading(false);
|
|
352
|
-
}, [callTool]);
|
|
353
|
-
|
|
354
|
-
return React.createElement(Flex, {
|
|
355
|
-
variant: 'column-stretch-start-nowrap-0',
|
|
356
|
-
style: { width: '100%', height: '100%', overflow: 'hidden' },
|
|
357
|
-
},
|
|
358
|
-
// Session bar
|
|
359
|
-
React.createElement(SessionBar, { callTool, dispatchEvent }),
|
|
360
|
-
|
|
361
|
-
// Settings
|
|
362
|
-
React.createElement(SettingsSection, { callTool, dispatchEvent }),
|
|
363
|
-
|
|
364
|
-
// Main scrollable timeline
|
|
365
|
-
React.createElement(Flex, { variant: 'column-stretch-start-nowrap-0', style: { flex: 1, minHeight: 0 } },
|
|
366
|
-
React.createElement(ScrollArea, { ref: scrollRef },
|
|
367
|
-
React.createElement(Flex, { variant: 'column-stretch-start-nowrap-8', style: { padding: '12px 16px' } },
|
|
368
|
-
// Empty state
|
|
369
|
-
!hasContent && React.createElement(EmptyState, {
|
|
370
|
-
icon: 'smartToy', message: 'No bot runs yet',
|
|
371
|
-
description: 'Ask the AI assistant to run a task, or add tasks below.',
|
|
372
|
-
}),
|
|
373
|
-
|
|
374
|
-
// History + Genesis
|
|
375
|
-
...timelineItems.map((item: TimelineItem) => {
|
|
376
|
-
if (item.kind === 'genesis' && item.genesis) {
|
|
377
|
-
return React.createElement(GenesisBlock, {
|
|
378
|
-
key: `genesis-${item.genesis.id}`, cycle: item.genesis, callTool,
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
if (item.kind === 'run' && item.run) {
|
|
382
|
-
const run = item.run;
|
|
383
|
-
const runId = run.id;
|
|
384
|
-
const isExpanded = expandedRunId === runId;
|
|
385
|
-
const isSuccess = run.outcome === 'completed' || run.success === true;
|
|
386
|
-
return React.createElement(TaskBlock, {
|
|
387
|
-
key: `run-${runId}`,
|
|
388
|
-
state: isSuccess ? 'completed' : 'failed',
|
|
389
|
-
instruction: extractInstruction(run),
|
|
390
|
-
timeline: item.runTimeline ?? [],
|
|
391
|
-
cost: typeof run.cost === 'number' ? run.cost : ((run.costDetail?.totalCost as number) ?? null),
|
|
392
|
-
plan: run.plan,
|
|
393
|
-
startedAt: run.startedAt,
|
|
394
|
-
durationMs: run.durationMs ?? run.duration,
|
|
395
|
-
expanded: isExpanded,
|
|
396
|
-
onToggleExpand: () => toggleExpand(runId),
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
return null;
|
|
400
|
-
}),
|
|
401
|
-
|
|
402
|
-
// Live execution
|
|
403
|
-
isStreaming && React.createElement(TaskBlock, {
|
|
404
|
-
state: 'running',
|
|
405
|
-
instruction: liveInstruction ?? (windowData?.instruction as string) ?? 'Running...',
|
|
406
|
-
timeline: liveTimeline,
|
|
407
|
-
phase: livePhase,
|
|
408
|
-
elapsed,
|
|
409
|
-
cost,
|
|
410
|
-
plan,
|
|
411
|
-
error: stream.error,
|
|
412
|
-
approval: approvalStatus ?? undefined,
|
|
413
|
-
approvalLoading,
|
|
414
|
-
onApprove: handleApprove,
|
|
415
|
-
onReject: handleReject,
|
|
416
|
-
onPause: handlePause,
|
|
417
|
-
onStop: handleStop,
|
|
418
|
-
pauseLoading: pausing,
|
|
419
|
-
stopLoading: stopping,
|
|
420
|
-
expanded: liveExpanded,
|
|
421
|
-
onToggleExpand: () => setLiveExpanded((v: boolean) => !v),
|
|
422
|
-
}),
|
|
423
|
-
|
|
424
|
-
// Queued tasks
|
|
425
|
-
...(isStreaming ? queuedTasks.filter((t: QueuedTask) => t.status !== 'running') : queuedTasks)
|
|
426
|
-
.map((task: QueuedTask) =>
|
|
427
|
-
React.createElement(TaskBlock, {
|
|
428
|
-
key: task.id, state: 'pending', instruction: task.instruction,
|
|
429
|
-
onRemove: () => handleRemoveTask(task.id),
|
|
430
|
-
removeLoading: removingIds.has(task.id),
|
|
431
|
-
}),
|
|
432
|
-
),
|
|
433
|
-
),
|
|
434
|
-
),
|
|
435
|
-
),
|
|
436
|
-
|
|
437
|
-
// Input bar
|
|
438
|
-
React.createElement(QueueInput, { callTool, onTaskAdded: refreshData }),
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
module.exports = BotWorkspace;
|