@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.
Files changed (217) hide show
  1. package/dist/ai-chat-provider.d.ts +12 -0
  2. package/dist/ai-chat-provider.d.ts.map +1 -1
  3. package/dist/ai-chat-provider.js +351 -335
  4. package/dist/ai-chat-provider.js.map +1 -1
  5. package/dist/bot/agent-loop.d.ts +20 -0
  6. package/dist/bot/agent-loop.d.ts.map +1 -0
  7. package/dist/bot/agent-loop.js +331 -0
  8. package/dist/bot/agent-loop.js.map +1 -0
  9. package/dist/bot/ai-router.d.ts +19 -0
  10. package/dist/bot/ai-router.d.ts.map +1 -0
  11. package/dist/bot/ai-router.js +104 -0
  12. package/dist/bot/ai-router.js.map +1 -0
  13. package/dist/bot/assistant-tools.d.ts.map +1 -1
  14. package/dist/bot/assistant-tools.js +49 -33
  15. package/dist/bot/assistant-tools.js.map +1 -1
  16. package/dist/bot/async-mutex.d.ts +13 -0
  17. package/dist/bot/async-mutex.d.ts.map +1 -0
  18. package/dist/bot/async-mutex.js +37 -0
  19. package/dist/bot/async-mutex.js.map +1 -0
  20. package/dist/bot/bot-manager.d.ts +2 -2
  21. package/dist/bot/bot-manager.d.ts.map +1 -1
  22. package/dist/bot/bot-manager.js +3 -3
  23. package/dist/bot/bot-manager.js.map +1 -1
  24. package/dist/bot/bot-registry.js +2 -2
  25. package/dist/bot/bot-registry.js.map +1 -1
  26. package/dist/bot/conversation-store.d.ts +1 -0
  27. package/dist/bot/conversation-store.d.ts.map +1 -1
  28. package/dist/bot/conversation-store.js.map +1 -1
  29. package/dist/bot/dashboard.d.ts.map +1 -1
  30. package/dist/bot/dashboard.js +17 -8
  31. package/dist/bot/dashboard.js.map +1 -1
  32. package/dist/bot/improve-loop.js.map +1 -1
  33. package/dist/bot/index.d.ts +2 -4
  34. package/dist/bot/index.d.ts.map +1 -1
  35. package/dist/bot/index.js +1 -2
  36. package/dist/bot/index.js.map +1 -1
  37. package/dist/bot/instance-manager.d.ts +31 -0
  38. package/dist/bot/instance-manager.d.ts.map +1 -0
  39. package/dist/bot/instance-manager.js +115 -0
  40. package/dist/bot/instance-manager.js.map +1 -0
  41. package/dist/bot/orchestrator.d.ts +36 -0
  42. package/dist/bot/orchestrator.d.ts.map +1 -0
  43. package/dist/bot/orchestrator.js +176 -0
  44. package/dist/bot/orchestrator.js.map +1 -0
  45. package/dist/bot/profile-store.d.ts +36 -0
  46. package/dist/bot/profile-store.d.ts.map +1 -0
  47. package/dist/bot/profile-store.js +208 -0
  48. package/dist/bot/profile-store.js.map +1 -0
  49. package/dist/bot/profile-types.d.ts +126 -0
  50. package/dist/bot/profile-types.d.ts.map +1 -0
  51. package/dist/bot/profile-types.js +7 -0
  52. package/dist/bot/profile-types.js.map +1 -0
  53. package/dist/bot/run-store.d.ts.map +1 -1
  54. package/dist/bot/run-store.js +8 -0
  55. package/dist/bot/run-store.js.map +1 -1
  56. package/dist/bot/runner.d.ts +4 -0
  57. package/dist/bot/runner.d.ts.map +1 -1
  58. package/dist/bot/runner.js +5 -1
  59. package/dist/bot/runner.js.map +1 -1
  60. package/dist/bot/swarm-controller.d.ts +109 -0
  61. package/dist/bot/swarm-controller.d.ts.map +1 -0
  62. package/dist/bot/swarm-controller.js +640 -0
  63. package/dist/bot/swarm-controller.js.map +1 -0
  64. package/dist/bot/swarm-event-log.d.ts +28 -0
  65. package/dist/bot/swarm-event-log.d.ts.map +1 -0
  66. package/dist/bot/swarm-event-log.js +54 -0
  67. package/dist/bot/swarm-event-log.js.map +1 -0
  68. package/dist/bot/task-prompt-builder.d.ts +22 -0
  69. package/dist/bot/task-prompt-builder.d.ts.map +1 -0
  70. package/dist/bot/task-prompt-builder.js +240 -0
  71. package/dist/bot/task-prompt-builder.js.map +1 -0
  72. package/dist/bot/task-store.d.ts +21 -0
  73. package/dist/bot/task-store.d.ts.map +1 -0
  74. package/dist/bot/task-store.js +364 -0
  75. package/dist/bot/task-store.js.map +1 -0
  76. package/dist/bot/task-types.d.ts +79 -0
  77. package/dist/bot/task-types.d.ts.map +1 -0
  78. package/dist/bot/task-types.js +6 -0
  79. package/dist/bot/task-types.js.map +1 -0
  80. package/dist/bot/types.d.ts +8 -0
  81. package/dist/bot/types.d.ts.map +1 -1
  82. package/dist/cli-handlers.d.ts.map +1 -1
  83. package/dist/cli-handlers.js +79 -54
  84. package/dist/cli-handlers.js.map +1 -1
  85. package/dist/cli.d.ts +3 -0
  86. package/dist/cli.d.ts.map +1 -0
  87. package/dist/cli.js +749 -0
  88. package/dist/cli.js.map +1 -0
  89. package/dist/docs/docs/weaver-bot-usage.md +35 -18
  90. package/dist/docs/docs/weaver-config.md +20 -0
  91. package/dist/docs/docs/weaver-task-queue.md +31 -19
  92. package/dist/docs/weaver-config.md +15 -9
  93. package/dist/index.d.ts +2 -2
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +1 -1
  96. package/dist/index.js.map +1 -1
  97. package/dist/mcp-tools.d.ts +17 -0
  98. package/dist/mcp-tools.d.ts.map +1 -1
  99. package/dist/mcp-tools.js +98 -279
  100. package/dist/mcp-tools.js.map +1 -1
  101. package/dist/node-types/bot-report.d.ts.map +1 -1
  102. package/dist/node-types/bot-report.js +6 -24
  103. package/dist/node-types/bot-report.js.map +1 -1
  104. package/dist/node-types/orchestrator-dispatch.d.ts +17 -0
  105. package/dist/node-types/orchestrator-dispatch.d.ts.map +1 -0
  106. package/dist/node-types/orchestrator-dispatch.js +63 -0
  107. package/dist/node-types/orchestrator-dispatch.js.map +1 -0
  108. package/dist/node-types/orchestrator-load-state.d.ts +16 -0
  109. package/dist/node-types/orchestrator-load-state.d.ts.map +1 -0
  110. package/dist/node-types/orchestrator-load-state.js +60 -0
  111. package/dist/node-types/orchestrator-load-state.js.map +1 -0
  112. package/dist/node-types/orchestrator-route.d.ts +16 -0
  113. package/dist/node-types/orchestrator-route.d.ts.map +1 -0
  114. package/dist/node-types/orchestrator-route.js +28 -0
  115. package/dist/node-types/orchestrator-route.js.map +1 -0
  116. package/dist/node-types/receive-task.d.ts +2 -3
  117. package/dist/node-types/receive-task.d.ts.map +1 -1
  118. package/dist/node-types/receive-task.js +3 -48
  119. package/dist/node-types/receive-task.js.map +1 -1
  120. package/dist/templates/weaver-template.d.ts +11 -0
  121. package/dist/templates/weaver-template.d.ts.map +1 -0
  122. package/dist/templates/weaver-template.js +53 -0
  123. package/dist/templates/weaver-template.js.map +1 -0
  124. package/dist/ui/bot-activity.js +2 -2
  125. package/dist/ui/bot-constants.d.ts +14 -0
  126. package/dist/ui/bot-constants.d.ts.map +1 -0
  127. package/dist/ui/bot-constants.js +189 -0
  128. package/dist/ui/bot-constants.js.map +1 -0
  129. package/dist/ui/bot-panel.js +207 -245
  130. package/dist/ui/bot-slot-card.js +141 -0
  131. package/dist/ui/budget-bar.js +59 -0
  132. package/dist/ui/chat-task-result.js +178 -0
  133. package/dist/ui/decision-log.js +136 -0
  134. package/dist/ui/profile-card.js +158 -0
  135. package/dist/ui/profile-editor.js +597 -0
  136. package/dist/ui/swarm-controls.js +245 -0
  137. package/dist/ui/swarm-dashboard.js +3012 -0
  138. package/dist/ui/task-create-form.js +98 -0
  139. package/dist/ui/task-detail-view.js +1044 -0
  140. package/dist/ui/task-pool-list.js +156 -0
  141. package/dist/workflows/orchestrator.d.ts +21 -0
  142. package/dist/workflows/orchestrator.d.ts.map +1 -0
  143. package/dist/workflows/orchestrator.js +281 -0
  144. package/dist/workflows/orchestrator.js.map +1 -0
  145. package/dist/workflows/weaver-bot-session.d.ts +65 -0
  146. package/dist/workflows/weaver-bot-session.d.ts.map +1 -0
  147. package/dist/workflows/weaver-bot-session.js +68 -0
  148. package/dist/workflows/weaver-bot-session.js.map +1 -0
  149. package/dist/workflows/weaver.d.ts +24 -0
  150. package/dist/workflows/weaver.d.ts.map +1 -0
  151. package/dist/workflows/weaver.js +28 -0
  152. package/dist/workflows/weaver.js.map +1 -0
  153. package/flowweaver.manifest.json +547 -133
  154. package/package.json +1 -1
  155. package/src/ai-chat-provider.ts +378 -371
  156. package/src/bot/ai-router.ts +132 -0
  157. package/src/bot/assistant-tools.ts +47 -29
  158. package/src/bot/async-mutex.ts +37 -0
  159. package/src/bot/bot-manager.ts +3 -3
  160. package/src/bot/bot-registry.ts +2 -2
  161. package/src/bot/conversation-store.ts +2 -1
  162. package/src/bot/dashboard.ts +17 -8
  163. package/src/bot/improve-loop.ts +6 -6
  164. package/src/bot/index.ts +2 -4
  165. package/src/bot/instance-manager.ts +128 -0
  166. package/src/bot/orchestrator.ts +244 -0
  167. package/src/bot/profile-store.ts +225 -0
  168. package/src/bot/profile-types.ts +141 -0
  169. package/src/bot/run-store.ts +8 -0
  170. package/src/bot/runner.ts +9 -1
  171. package/src/bot/swarm-controller.ts +780 -0
  172. package/src/bot/swarm-event-log.ts +57 -0
  173. package/src/bot/task-prompt-builder.ts +309 -0
  174. package/src/bot/task-store.ts +407 -0
  175. package/src/bot/task-types.ts +100 -0
  176. package/src/bot/types.ts +8 -0
  177. package/src/cli-handlers.ts +78 -53
  178. package/src/docs/weaver-bot-usage.md +35 -18
  179. package/src/docs/weaver-config.md +20 -0
  180. package/src/docs/weaver-task-queue.md +31 -19
  181. package/src/index.ts +5 -4
  182. package/src/mcp-tools.ts +129 -372
  183. package/src/node-types/bot-report.ts +6 -24
  184. package/src/node-types/orchestrator-dispatch.ts +71 -0
  185. package/src/node-types/orchestrator-load-state.ts +66 -0
  186. package/src/node-types/orchestrator-route.ts +33 -0
  187. package/src/node-types/receive-task.ts +3 -57
  188. package/src/ui/bot-activity.tsx +2 -2
  189. package/src/ui/bot-constants.ts +192 -0
  190. package/src/ui/bot-panel.tsx +213 -247
  191. package/src/ui/bot-slot-card.tsx +139 -0
  192. package/src/ui/budget-bar.tsx +30 -0
  193. package/src/ui/chat-task-result.tsx +236 -0
  194. package/src/ui/decision-log.tsx +148 -0
  195. package/src/ui/profile-card.tsx +157 -0
  196. package/src/ui/profile-editor.tsx +384 -0
  197. package/src/ui/swarm-controls.tsx +260 -0
  198. package/src/ui/swarm-dashboard.tsx +647 -0
  199. package/src/ui/task-create-form.tsx +87 -0
  200. package/src/ui/task-detail-view.tsx +841 -0
  201. package/src/ui/task-pool-list.tsx +187 -0
  202. package/src/workflows/orchestrator.ts +302 -0
  203. package/dist/docs/weaver-bot-usage.md +0 -34
  204. package/dist/docs/weaver-genesis.md +0 -32
  205. package/dist/docs/weaver-task-queue.md +0 -34
  206. package/dist/ui/bot-workspace.js +0 -1015
  207. package/dist/ui/chat-bot-result.js +0 -71
  208. package/dist/ui/queue-input.js +0 -82
  209. package/dist/ui/session-bar.js +0 -174
  210. package/src/bot/error-guide.ts +0 -4
  211. package/src/bot/retry-utils.ts +0 -4
  212. package/src/bot/session-state.ts +0 -116
  213. package/src/bot/task-queue.ts +0 -262
  214. package/src/ui/bot-workspace.tsx +0 -442
  215. package/src/ui/chat-bot-result.tsx +0 -81
  216. package/src/ui/queue-input.tsx +0 -56
  217. package/src/ui/session-bar.tsx +0 -157
@@ -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
- }
@@ -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;