@synergenius/flow-weaver-pack-weaver 0.9.53 → 0.9.55

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.
@@ -0,0 +1,1087 @@
1
+ "use strict";
2
+
3
+ // src/ui/session-bar.tsx
4
+ var React = require("react");
5
+ var { useState, useEffect, useCallback, useRef } = React;
6
+ var { Flex, Typography, Icon, Button, Badge, toast, formatDuration, formatCost } = require("@fw/plugin-ui-kit");
7
+ function SessionBar({ callTool, dispatchEvent }) {
8
+ const [session, setSession] = useState(null);
9
+ const [elapsed, setElapsed] = useState(0);
10
+ const [starting, setStarting] = useState(false);
11
+ const [stopping, setStopping] = useState(false);
12
+ const [pausing, setPausing] = useState(false);
13
+ const prevActiveRef = useRef(false);
14
+ const pollStatus = useCallback(async () => {
15
+ try {
16
+ const result = await callTool("fw_weaver_session", { action: "status" });
17
+ const isActive2 = result.status !== "no active session" && result.status !== "idle";
18
+ setSession(isActive2 ? result : null);
19
+ if (prevActiveRef.current && !isActive2) {
20
+ dispatchEvent("fw:refresh-bot-workspace");
21
+ }
22
+ prevActiveRef.current = isActive2;
23
+ } catch {
24
+ }
25
+ }, [callTool, dispatchEvent]);
26
+ useEffect(() => {
27
+ pollStatus();
28
+ const interval = setInterval(pollStatus, 5e3);
29
+ return () => clearInterval(interval);
30
+ }, [pollStatus]);
31
+ useEffect(() => {
32
+ if (!session?.startedAt || session.status === "idle") return;
33
+ const tick = () => setElapsed(Date.now() - (session.startedAt ?? Date.now()));
34
+ tick();
35
+ const interval = setInterval(tick, 1e3);
36
+ return () => clearInterval(interval);
37
+ }, [session?.startedAt, session?.status]);
38
+ const [startError, setStartError] = useState(null);
39
+ const handleStart = useCallback(async () => {
40
+ setStarting(true);
41
+ setStartError(null);
42
+ try {
43
+ const result = await callTool("fw_weaver_session", { action: "start" });
44
+ const data = result;
45
+ if (data?.error) {
46
+ setStartError(data.error);
47
+ toast(data.error, { type: "error" });
48
+ setStarting(false);
49
+ return;
50
+ }
51
+ toast("Session started", { type: "success" });
52
+ dispatchEvent("fw:refresh-bot-workspace");
53
+ await pollStatus();
54
+ } catch (err) {
55
+ const msg = err instanceof Error ? err.message : "Failed to start session";
56
+ setStartError(msg);
57
+ toast(msg, { type: "error" });
58
+ }
59
+ setStarting(false);
60
+ }, [callTool, pollStatus, dispatchEvent]);
61
+ const handleStop = useCallback(async () => {
62
+ setStopping(true);
63
+ try {
64
+ await callTool("fw_weaver_session", { action: "stop" });
65
+ toast("Session ended", { type: "info" });
66
+ await pollStatus();
67
+ } catch (err) {
68
+ toast(err instanceof Error ? err.message : "Failed to stop session", { type: "error" });
69
+ }
70
+ setStopping(false);
71
+ }, [callTool, pollStatus]);
72
+ const handlePause = useCallback(async () => {
73
+ setPausing(true);
74
+ try {
75
+ await callTool("fw_weaver_steer", { command: "pause" });
76
+ toast("Session paused", { type: "info" });
77
+ } catch (err) {
78
+ toast(err instanceof Error ? err.message : "Failed to pause session", { type: "error" });
79
+ }
80
+ setPausing(false);
81
+ }, [callTool]);
82
+ const isActive = session && session.status !== "idle" && session.status !== "no active session";
83
+ if (!isActive) {
84
+ return React.createElement(
85
+ Flex,
86
+ {
87
+ variant: "row-center-space-between-nowrap-10",
88
+ style: { flexShrink: 0, padding: "8px 16px", backgroundColor: "transparent", borderBottom: "1px solid var(--color-border-default)" }
89
+ },
90
+ React.createElement(Icon, { name: "smartToy", size: 14, color: "color-text-subtle" }),
91
+ React.createElement(
92
+ Flex,
93
+ { variant: "row-center-start-nowrap-12", style: { flex: 1, fontSize: "12px" } },
94
+ React.createElement(Typography, {
95
+ variant: "caption-regular",
96
+ color: startError ? "color-status-negative" : "color-text-subtle"
97
+ }, startError ?? "No active session")
98
+ ),
99
+ React.createElement(
100
+ Flex,
101
+ { variant: "row-center-start-nowrap-6", style: { flexShrink: 0 } },
102
+ React.createElement(Button, {
103
+ size: "sm",
104
+ variant: "clear",
105
+ onClick: handleStart,
106
+ loading: starting,
107
+ disabled: starting
108
+ }, "Start Session")
109
+ )
110
+ );
111
+ }
112
+ return React.createElement(
113
+ Flex,
114
+ {
115
+ variant: "row-center-space-between-nowrap-10",
116
+ style: { flexShrink: 0, padding: "8px 16px", backgroundColor: "var(--color-brand-main-alpha-10)", borderBottom: "1px solid var(--color-brand-main)" }
117
+ },
118
+ React.createElement(Badge, { variant: "success" }, "Session"),
119
+ React.createElement(
120
+ Flex,
121
+ {
122
+ variant: "row-center-start-nowrap-12",
123
+ style: { flex: 1, fontSize: "12px", color: "var(--color-text-medium)" }
124
+ },
125
+ session.completedTasks != null && React.createElement("span", null, `${session.completedTasks} tasks done`),
126
+ session.totalCost != null && session.totalCost > 0 && React.createElement("span", null, formatCost(session.totalCost)),
127
+ session.startedAt && React.createElement("span", null, `${formatDuration(elapsed)} elapsed`),
128
+ session.currentTask && React.createElement("span", { style: { opacity: 0.7 } }, `\xB7 ${session.currentTask}`)
129
+ ),
130
+ React.createElement(
131
+ Flex,
132
+ { variant: "row-center-start-nowrap-6", style: { flexShrink: 0 } },
133
+ React.createElement(Button, {
134
+ size: "sm",
135
+ variant: "clear",
136
+ onClick: handlePause,
137
+ loading: pausing,
138
+ disabled: pausing || stopping
139
+ }, "Pause"),
140
+ React.createElement(Button, {
141
+ size: "sm",
142
+ variant: "clear",
143
+ color: "danger",
144
+ onClick: handleStop,
145
+ loading: stopping,
146
+ disabled: stopping || pausing
147
+ }, "End")
148
+ )
149
+ );
150
+ }
151
+
152
+ // src/ui/settings-section.tsx
153
+ var React2 = require("react");
154
+ var { useState: useState2, useEffect: useEffect2, useCallback: useCallback2 } = React2;
155
+ var { Flex: Flex2, Typography: Typography2, Button: Button2, CollapsibleSection, KeyValueRow, toast: toast2 } = require("@fw/plugin-ui-kit");
156
+ function SettingsSection({ callTool, dispatchEvent }) {
157
+ const [providers, setProviders] = useState2([]);
158
+ const [insights, setInsights] = useState2(null);
159
+ const handleClear = useCallback2(() => {
160
+ if (!confirm("Clear all bot run history? This action cannot be undone.")) return;
161
+ callTool("fw_weaver_history", { clear: true }).then(() => {
162
+ dispatchEvent("fw:refresh-bot-workspace");
163
+ toast2("Run history cleared", { type: "success" });
164
+ }).catch(() => {
165
+ toast2("Failed to clear history", { type: "error" });
166
+ });
167
+ }, [callTool, dispatchEvent]);
168
+ useEffect2(() => {
169
+ Promise.all([
170
+ callTool("fw_weaver_providers"),
171
+ callTool("fw_weaver_insights")
172
+ ]).then(([prov, ins]) => {
173
+ if (prov) setProviders(Array.isArray(prov) ? prov : []);
174
+ if (ins) setInsights(ins);
175
+ });
176
+ }, [callTool]);
177
+ const activeProvider = providers.find((p) => p.envVarsSet) ?? providers[0];
178
+ const trustScore = insights?.trust?.score;
179
+ const trustDisplay = trustScore != null ? (trustScore <= 1 ? (trustScore * 100).toFixed(0) : Math.round(trustScore)) + "%" : "\u2014";
180
+ return React2.createElement(
181
+ CollapsibleSection,
182
+ {
183
+ title: "Settings",
184
+ variant: "list",
185
+ defaultExpanded: false
186
+ },
187
+ React2.createElement(
188
+ Flex2,
189
+ { variant: "column-start-start-nowrap-4", style: { width: "100%" } },
190
+ activeProvider && React2.createElement(KeyValueRow, {
191
+ keyName: "Provider",
192
+ value: `${activeProvider.name} (${activeProvider.source})`,
193
+ size: "small"
194
+ }),
195
+ insights?.trust && React2.createElement(KeyValueRow, {
196
+ keyName: "Trust",
197
+ value: `Phase ${insights.trust.phase} \xB7 ${trustDisplay}`,
198
+ size: "small"
199
+ }),
200
+ insights?.health && React2.createElement(KeyValueRow, {
201
+ keyName: "Health",
202
+ value: `${insights.health.overall}/100`,
203
+ size: "small"
204
+ }),
205
+ insights?.cost && React2.createElement(KeyValueRow, {
206
+ keyName: "Cost (7d)",
207
+ value: `$${insights.cost.last7Days?.toFixed(2) ?? "0.00"} \xB7 ${insights.cost.trend ?? "stable"}`,
208
+ size: "small"
209
+ }),
210
+ React2.createElement(
211
+ Flex2,
212
+ {
213
+ variant: "row-center-space-between-nowrap-8",
214
+ style: { width: "100%", marginTop: "4px" }
215
+ },
216
+ React2.createElement(Button2, {
217
+ size: "xs",
218
+ variant: "outlined",
219
+ color: "danger",
220
+ onClick: handleClear
221
+ }, "Clear Run History"),
222
+ React2.createElement(Typography2, {
223
+ variant: "smallCaption-regular",
224
+ color: "color-text-subtle"
225
+ }, "Edit settings in .weaver.json")
226
+ )
227
+ )
228
+ );
229
+ }
230
+
231
+ // src/ui/queue-input.tsx
232
+ var React3 = require("react");
233
+ var { useState: useState3, useCallback: useCallback3 } = React3;
234
+ var { Flex: Flex3, IconButton, Input, toast: toast3 } = require("@fw/plugin-ui-kit");
235
+ function QueueInput({ callTool, onTaskAdded }) {
236
+ const [newTask, setNewTask] = useState3("");
237
+ const [adding, setAdding] = useState3(false);
238
+ const handleAdd = useCallback3(async () => {
239
+ const instruction = newTask.trim();
240
+ if (!instruction) return;
241
+ setAdding(true);
242
+ try {
243
+ await callTool("fw_weaver_queue", { action: "add", task: instruction });
244
+ setNewTask("");
245
+ onTaskAdded?.();
246
+ toast3("Task added to queue", { type: "success" });
247
+ } catch (err) {
248
+ toast3(err instanceof Error ? err.message : "Failed to add task", { type: "error" });
249
+ }
250
+ setAdding(false);
251
+ }, [callTool, newTask, onTaskAdded]);
252
+ return React3.createElement(
253
+ Flex3,
254
+ {
255
+ variant: "row-center-start-nowrap-6",
256
+ style: {
257
+ flexShrink: 0,
258
+ padding: "8px 16px",
259
+ borderTop: "1px solid var(--color-border-default)",
260
+ width: "100%",
261
+ boxSizing: "border-box"
262
+ }
263
+ },
264
+ React3.createElement(Input, {
265
+ type: "text",
266
+ size: "medium",
267
+ placeholder: "Add a task...",
268
+ value: newTask,
269
+ onChange: setNewTask,
270
+ onEnter: handleAdd,
271
+ disabled: adding,
272
+ defaultBoxStyle: { flex: 1, minWidth: 0 },
273
+ inputBoxStyle: { maxWidth: "none" }
274
+ }),
275
+ React3.createElement(IconButton, {
276
+ icon: "add",
277
+ size: "md",
278
+ variant: "fill",
279
+ color: "primary",
280
+ "aria-label": "Add task",
281
+ onClick: handleAdd,
282
+ loading: adding,
283
+ disabled: adding || !newTask.trim()
284
+ })
285
+ );
286
+ }
287
+
288
+ // src/ui/genesis-block.tsx
289
+ var React4 = require("react");
290
+ var { useState: useState4, useCallback: useCallback4 } = React4;
291
+ var { CollapsibleBlock, Flex: Flex4, Typography: Typography3, Icon: Icon2, Badge: Badge2, Tag, Button: Button3, toast: toast4, formatTimestamp } = require("@fw/plugin-ui-kit");
292
+ var OUTCOME_BADGE_VARIANTS = {
293
+ applied: "success",
294
+ "rolled-back": "error",
295
+ rejected: "warning",
296
+ stabilized: "success",
297
+ "no-change": "default",
298
+ error: "error"
299
+ };
300
+ var OUTCOME_STATUS = {
301
+ applied: "completed",
302
+ stabilized: "completed",
303
+ "rolled-back": "error",
304
+ rejected: "error",
305
+ error: "error",
306
+ "no-change": "completed"
307
+ };
308
+ function GenesisBlock({ cycle, callTool }) {
309
+ const [expanded, setExpanded] = useState4(false);
310
+ const [rollingBack, setRollingBack] = useState4(false);
311
+ const handleRollback = useCallback4(async () => {
312
+ setRollingBack(true);
313
+ try {
314
+ await callTool("fw_weaver_genesis_rollback", { cycleId: cycle.id });
315
+ toast4("Genesis cycle rolled back", { type: "success" });
316
+ } catch (err) {
317
+ toast4(err instanceof Error ? err.message : "Failed to rollback", { type: "error" });
318
+ }
319
+ setRollingBack(false);
320
+ }, [callTool, cycle.id]);
321
+ const summary = cycle.proposal?.summary ?? `Genesis cycle ${cycle.id.slice(0, 8)}`;
322
+ const badgeVariant = OUTCOME_BADGE_VARIANTS[cycle.outcome] ?? "default";
323
+ const status = OUTCOME_STATUS[cycle.outcome] ?? "pending";
324
+ return React4.createElement(
325
+ CollapsibleBlock,
326
+ {
327
+ status,
328
+ icon: React4.createElement(Icon2, { name: "autorenew", size: 14, color: "color-brand-main" }),
329
+ label: React4.createElement(Typography3, {
330
+ variant: "caption-thick",
331
+ color: "color-text-high",
332
+ style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 }
333
+ }, summary),
334
+ headerSuffix: React4.createElement(
335
+ Flex4,
336
+ { variant: "row-center-start-nowrap-8" },
337
+ React4.createElement(Typography3, { variant: "smallCaption-regular", color: "color-text-subtle" }, formatTimestamp(cycle.timestamp)),
338
+ React4.createElement(Badge2, { variant: badgeVariant }, cycle.outcome)
339
+ ),
340
+ expanded,
341
+ onToggle: () => setExpanded((v) => !v)
342
+ },
343
+ // Proposal
344
+ cycle.proposal && React4.createElement(
345
+ Flex4,
346
+ {
347
+ variant: "column-start-start-nowrap-6",
348
+ style: { padding: "10px 12px", borderBottom: "1px solid var(--color-border-default)" }
349
+ },
350
+ React4.createElement(
351
+ Typography3,
352
+ { variant: "caption-bold", color: "color-text-subtle" },
353
+ `Proposal \xB7 ${cycle.proposal.impactLevel}`
354
+ ),
355
+ React4.createElement(
356
+ Typography3,
357
+ { variant: "smallCaption-regular", color: "color-text-medium" },
358
+ cycle.proposal.rationale
359
+ ),
360
+ ...cycle.proposal.operations.map(
361
+ (op, i) => React4.createElement(
362
+ Flex4,
363
+ { key: i, variant: "row-center-start-nowrap-6", style: { fontSize: "11px" } },
364
+ React4.createElement(Tag, { size: "small", color: "info" }, op.type),
365
+ React4.createElement(Typography3, { variant: "smallCaption-regular", color: "color-text-medium" }, op.rationale)
366
+ )
367
+ )
368
+ ),
369
+ // Diff
370
+ cycle.diffSummary && React4.createElement(
371
+ Flex4,
372
+ {
373
+ variant: "column-start-start-nowrap-6",
374
+ style: { padding: "10px 12px", borderBottom: "1px solid var(--color-border-default)" }
375
+ },
376
+ React4.createElement(Typography3, { variant: "caption-bold", color: "color-text-subtle" }, "Changes"),
377
+ React4.createElement("pre", {
378
+ style: {
379
+ fontSize: "11px",
380
+ fontFamily: "monospace",
381
+ lineHeight: 1.4,
382
+ padding: "8px",
383
+ borderRadius: "var(--border-radius-compact, 4px)",
384
+ backgroundColor: "var(--color-surface-raised)",
385
+ color: "var(--color-text-medium)",
386
+ whiteSpace: "pre-wrap",
387
+ wordBreak: "break-word",
388
+ maxHeight: "200px",
389
+ overflow: "auto",
390
+ margin: 0,
391
+ width: "100%"
392
+ }
393
+ }, cycle.diffSummary)
394
+ ),
395
+ // Error
396
+ cycle.error && React4.createElement("div", {
397
+ style: { padding: "10px 12px", borderBottom: "1px solid var(--color-border-default)" }
398
+ }, React4.createElement(Typography3, { variant: "smallCaption-regular", color: "color-status-negative" }, cycle.error)),
399
+ // Rollback action
400
+ cycle.outcome === "applied" && React4.createElement(
401
+ Flex4,
402
+ {
403
+ variant: "row-center-start-nowrap-8",
404
+ style: { padding: "10px 12px" }
405
+ },
406
+ React4.createElement(Button3, {
407
+ size: "xs",
408
+ variant: "outlined",
409
+ color: "danger",
410
+ onClick: handleRollback,
411
+ loading: rollingBack,
412
+ disabled: rollingBack
413
+ }, "Rollback")
414
+ )
415
+ );
416
+ }
417
+
418
+ // src/ui/approval-card.tsx
419
+ var React5 = require("react");
420
+ var { useState: useState5, useCallback: useCallback5 } = React5;
421
+ var {
422
+ CollapsibleBlock: CollapsibleBlock2,
423
+ Flex: Flex5,
424
+ Typography: Typography4,
425
+ Badge: Badge3,
426
+ Tag: Tag2,
427
+ Button: Button4,
428
+ toast: toast5
429
+ } = require("@fw/plugin-ui-kit");
430
+ function ApprovalCard({ plan, callTool, onDecision }) {
431
+ const [deciding, setDeciding] = useState5(false);
432
+ const [decided, setDecided] = useState5(null);
433
+ const handleDecision = useCallback5(
434
+ async (approved) => {
435
+ setDeciding(true);
436
+ try {
437
+ await callTool("fw_weaver_approve", { approved });
438
+ setDecided(approved);
439
+ onDecision?.(approved);
440
+ toast5(approved ? "Plan approved" : "Plan rejected", {
441
+ type: approved ? "success" : "info"
442
+ });
443
+ } catch (err) {
444
+ toast5(err instanceof Error ? err.message : "Failed to send decision", { type: "error" });
445
+ } finally {
446
+ setDeciding(false);
447
+ }
448
+ },
449
+ [callTool, onDecision]
450
+ );
451
+ if (decided !== null) {
452
+ return React5.createElement(CollapsibleBlock2, {
453
+ status: decided ? "completed" : "error",
454
+ label: React5.createElement(Typography4, {
455
+ variant: "caption-bold",
456
+ color: decided ? "color-status-positive" : "color-status-negative"
457
+ }, decided ? "Plan Approved" : "Plan Rejected"),
458
+ expanded: false,
459
+ canExpand: false
460
+ });
461
+ }
462
+ return React5.createElement(
463
+ CollapsibleBlock2,
464
+ {
465
+ status: "pending",
466
+ label: React5.createElement(
467
+ Flex5,
468
+ { variant: "row-center-start-nowrap-8" },
469
+ React5.createElement(Badge3, { variant: "warning" }, "Approval Needed"),
470
+ React5.createElement(
471
+ Typography4,
472
+ { variant: "caption-bold", color: "color-text-high" },
473
+ plan.summary || `${plan.steps.length} step plan`
474
+ )
475
+ ),
476
+ expanded: true,
477
+ canExpand: false
478
+ },
479
+ React5.createElement(
480
+ Flex5,
481
+ {
482
+ variant: "column-start-start-nowrap-6",
483
+ style: { padding: "10px 12px" }
484
+ },
485
+ ...plan.steps.map(
486
+ (step, i) => React5.createElement(
487
+ Flex5,
488
+ {
489
+ key: step.id || i,
490
+ variant: "row-center-start-nowrap-6",
491
+ style: { fontSize: "11px" }
492
+ },
493
+ React5.createElement(Tag2, { size: "small", color: "info" }, step.operation),
494
+ React5.createElement(Typography4, {
495
+ variant: "smallCaption-regular",
496
+ color: "color-text-medium"
497
+ }, step.description)
498
+ )
499
+ )
500
+ ),
501
+ React5.createElement(
502
+ Flex5,
503
+ {
504
+ variant: "row-center-start-nowrap-8",
505
+ style: { padding: "10px 12px", borderTop: "1px solid var(--color-border-default)" }
506
+ },
507
+ React5.createElement(Button4, {
508
+ variant: "fill",
509
+ color: "primary",
510
+ size: "xs",
511
+ onClick: () => handleDecision(true),
512
+ loading: deciding,
513
+ disabled: deciding
514
+ }, "Approve"),
515
+ React5.createElement(Button4, {
516
+ variant: "outlined",
517
+ color: "danger",
518
+ size: "xs",
519
+ onClick: () => handleDecision(false),
520
+ loading: deciding,
521
+ disabled: deciding
522
+ }, "Reject")
523
+ )
524
+ );
525
+ }
526
+
527
+ // src/ui/use-stream-timeline.ts
528
+ var React6 = require("react");
529
+ var { useMemo, useState: useState6, useEffect: useEffect3, useRef: useRef2 } = React6;
530
+ function useStreamTimeline(events, isDone) {
531
+ const [elapsed, setElapsed] = useState6(0);
532
+ const startTimeRef = useRef2(null);
533
+ useEffect3(() => {
534
+ if (events.length === 0) {
535
+ startTimeRef.current = null;
536
+ setElapsed(0);
537
+ } else if (startTimeRef.current === null) {
538
+ startTimeRef.current = events[0].timestamp;
539
+ }
540
+ }, [events.length]);
541
+ useEffect3(() => {
542
+ if (isDone || !startTimeRef.current) return;
543
+ const tick = () => setElapsed(Date.now() - (startTimeRef.current ?? Date.now()));
544
+ tick();
545
+ const interval = setInterval(tick, 1e3);
546
+ return () => clearInterval(interval);
547
+ }, [isDone, events.length]);
548
+ const timeline = useMemo(() => {
549
+ const entries = [];
550
+ const nodeEntryIndex = /* @__PURE__ */ new Map();
551
+ const nodeStarts = /* @__PURE__ */ new Map();
552
+ let idCounter = 0;
553
+ for (const event of events) {
554
+ const d = event.data ?? {};
555
+ switch (event.type) {
556
+ case "bot-started":
557
+ entries.push({
558
+ id: `s-${idCounter++}`,
559
+ timestamp: new Date(event.timestamp),
560
+ type: "task-started",
561
+ label: "Task started",
562
+ detail: d.instruction
563
+ });
564
+ break;
565
+ case "node-start": {
566
+ const nodeId = d.nodeId;
567
+ if (nodeId) nodeStarts.set(nodeId, event.timestamp);
568
+ const idx = entries.length;
569
+ nodeEntryIndex.set(nodeId, idx);
570
+ entries.push({
571
+ id: `s-${idCounter++}`,
572
+ timestamp: new Date(event.timestamp),
573
+ type: "node-started",
574
+ nodeId,
575
+ label: d.label ?? d.nodeType ?? nodeId ?? "Node"
576
+ });
577
+ break;
578
+ }
579
+ case "node-complete": {
580
+ const nodeId = d.nodeId;
581
+ const startTs = nodeStarts.get(nodeId);
582
+ const duration = d.durationMs ?? (startTs ? event.timestamp - startTs : void 0);
583
+ if (nodeId) nodeStarts.delete(nodeId);
584
+ const completed = {
585
+ id: `s-${idCounter++}`,
586
+ timestamp: new Date(startTs ?? event.timestamp),
587
+ type: "node-completed",
588
+ nodeId,
589
+ label: d.label ?? d.nodeType ?? nodeId ?? "Node",
590
+ duration
591
+ };
592
+ const existingIdx = nodeEntryIndex.get(nodeId);
593
+ if (existingIdx != null && entries[existingIdx]) {
594
+ entries[existingIdx] = completed;
595
+ nodeEntryIndex.delete(nodeId);
596
+ } else {
597
+ entries.push(completed);
598
+ }
599
+ break;
600
+ }
601
+ case "node-error": {
602
+ const nodeId = d.nodeId;
603
+ const startTs = nodeStarts.get(nodeId);
604
+ const duration = d.durationMs ?? (startTs ? event.timestamp - startTs : void 0);
605
+ if (nodeId) nodeStarts.delete(nodeId);
606
+ const failed = {
607
+ id: `s-${idCounter++}`,
608
+ timestamp: new Date(startTs ?? event.timestamp),
609
+ type: "node-failed",
610
+ nodeId,
611
+ label: d.label ?? d.nodeType ?? nodeId ?? "Node",
612
+ detail: d.error,
613
+ duration
614
+ };
615
+ const existingIdx = nodeEntryIndex.get(nodeId);
616
+ if (existingIdx != null && entries[existingIdx]) {
617
+ entries[existingIdx] = failed;
618
+ nodeEntryIndex.delete(nodeId);
619
+ } else {
620
+ entries.push(failed);
621
+ }
622
+ break;
623
+ }
624
+ case "bot-completed":
625
+ entries.push({
626
+ id: `s-${idCounter++}`,
627
+ timestamp: new Date(event.timestamp),
628
+ type: "task-completed",
629
+ label: d.success ? "Task completed" : "Task finished",
630
+ detail: d.summary
631
+ });
632
+ break;
633
+ case "bot-failed":
634
+ entries.push({
635
+ id: `s-${idCounter++}`,
636
+ timestamp: new Date(event.timestamp),
637
+ type: "task-failed",
638
+ label: "Task failed",
639
+ detail: d.error
640
+ });
641
+ break;
642
+ // Skip audit, cost-update, done — they're used for metadata, not timeline
643
+ default:
644
+ break;
645
+ }
646
+ }
647
+ return entries;
648
+ }, [events]);
649
+ const { phase, instruction, cost, plan, awaitingApproval } = useMemo(() => {
650
+ let phase2 = "idle";
651
+ let instruction2 = null;
652
+ let cost2 = null;
653
+ let plan2 = null;
654
+ let awaitingApproval2 = false;
655
+ for (const event of events) {
656
+ const d = event.data ?? {};
657
+ if (event.type === "bot-started") {
658
+ phase2 = "planning";
659
+ instruction2 = d.instruction ?? null;
660
+ } else if (event.type === "node-start" && phase2 !== "completed" && phase2 !== "failed") {
661
+ phase2 = "executing";
662
+ } else if (event.type === "bot-completed") {
663
+ phase2 = d.success ? "completed" : "failed";
664
+ } else if (event.type === "bot-failed") {
665
+ phase2 = "failed";
666
+ } else if (event.type === "cost-update") {
667
+ cost2 = d.totalCost ?? cost2;
668
+ } else if (event.type === "audit:plan-created" || event.type === "plan-created") {
669
+ const planData = d.plan ?? d;
670
+ const summary = planData.summary ?? "";
671
+ const steps = planData.steps ?? [];
672
+ if (summary || steps.length > 0) {
673
+ plan2 = { summary, steps };
674
+ }
675
+ } else if (event.type === "approval-needed") {
676
+ awaitingApproval2 = true;
677
+ } else if (event.type === "audit:approval-decision") {
678
+ awaitingApproval2 = false;
679
+ }
680
+ }
681
+ if (isDone && phase2 !== "completed" && phase2 !== "failed") {
682
+ phase2 = "completed";
683
+ }
684
+ return { phase: phase2, instruction: instruction2, cost: cost2, plan: plan2, awaitingApproval: awaitingApproval2 };
685
+ }, [events, isDone]);
686
+ return { timeline, phase, instruction, elapsed, cost, plan, awaitingApproval };
687
+ }
688
+
689
+ // src/ui/trace-to-timeline.ts
690
+ function traceToTimeline(run) {
691
+ const entries = [];
692
+ let idCounter = 0;
693
+ const meta = run.nodeMeta ?? {};
694
+ if (run.trace && run.trace.length > 0) {
695
+ const nodeStarts = /* @__PURE__ */ new Map();
696
+ for (const event of run.trace) {
697
+ if (event.type === "node-start") {
698
+ nodeStarts.set(event.nodeId, event);
699
+ } else if (event.type === "node-complete" || event.type === "node-error") {
700
+ const start = nodeStarts.get(event.nodeId);
701
+ const duration = event.durationMs ?? (start ? event.timestamp - start.timestamp : void 0);
702
+ const nm = meta[event.nodeId] ?? (event.nodeType ? meta[event.nodeType] : void 0);
703
+ entries.push({
704
+ id: `trace-${idCounter++}`,
705
+ timestamp: new Date(event.timestamp),
706
+ type: event.type === "node-complete" ? "node-completed" : "node-failed",
707
+ nodeId: event.nodeId,
708
+ label: nm?.label ?? event.nodeType ?? event.nodeId,
709
+ detail: event.error,
710
+ duration,
711
+ color: nm?.color,
712
+ icon: nm?.icon
713
+ });
714
+ nodeStarts.delete(event.nodeId);
715
+ }
716
+ }
717
+ for (const [nodeId, event] of nodeStarts) {
718
+ const nm = meta[nodeId] ?? (event.nodeType ? meta[event.nodeType] : void 0);
719
+ entries.push({
720
+ id: `trace-${idCounter++}`,
721
+ timestamp: new Date(event.timestamp),
722
+ type: "node-started",
723
+ nodeId,
724
+ label: nm?.label ?? event.nodeType ?? nodeId,
725
+ color: nm?.color,
726
+ icon: nm?.icon
727
+ });
728
+ }
729
+ } else if (run.stepLog && run.stepLog.length > 0) {
730
+ for (const step of run.stepLog) {
731
+ const type = step.status === "ok" ? "node-completed" : step.status === "error" ? "node-failed" : "node-completed";
732
+ entries.push({
733
+ id: `step-${idCounter++}`,
734
+ timestamp: new Date(run.startedAt ?? Date.now()),
735
+ type,
736
+ label: step.step,
737
+ detail: step.detail,
738
+ icon: step.status === "ok" ? "success" : step.status === "error" ? "error" : "warning"
739
+ });
740
+ }
741
+ }
742
+ if (run.outcome === "failed" || run.outcome === "error") {
743
+ let errorDetail = "";
744
+ if (run.auditTrail) {
745
+ for (const a of run.auditTrail) {
746
+ if (a.type === "step-complete" && a.data) {
747
+ const errors = a.data.errors;
748
+ if (errors && errors.length > 0) {
749
+ errorDetail = errors.map((e) => e.length > 120 ? e.slice(0, 117) + "..." : e).join("\n");
750
+ break;
751
+ }
752
+ }
753
+ }
754
+ }
755
+ if (!errorDetail && run.summary) {
756
+ const parts = run.summary.split("|").map((p) => p.trim());
757
+ const outcomePart = parts.find((p) => p.startsWith("Outcome:"));
758
+ const summaryPart = parts.find((p) => p.startsWith("Summary:"));
759
+ errorDetail = summaryPart?.replace("Summary:", "").trim() ?? outcomePart ?? run.summary;
760
+ }
761
+ entries.push({
762
+ id: `result-${idCounter++}`,
763
+ timestamp: new Date(run.finishedAt ?? run.startedAt ?? Date.now()),
764
+ type: "task-failed",
765
+ label: "Task failed",
766
+ detail: errorDetail || "Unknown failure",
767
+ icon: "error"
768
+ });
769
+ } else if (run.outcome === "completed" && run.success) {
770
+ entries.push({
771
+ id: `result-${idCounter++}`,
772
+ timestamp: new Date(run.finishedAt ?? run.startedAt ?? Date.now()),
773
+ type: "task-completed",
774
+ label: "Task completed",
775
+ detail: run.summary?.includes("|") ? run.summary.split("|").find((p) => p.trim().startsWith("Summary:"))?.replace("Summary:", "").trim() : void 0
776
+ });
777
+ }
778
+ entries.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
779
+ return entries;
780
+ }
781
+
782
+ // src/ui/steer-api.ts
783
+ async function sendSteerCommand(callTool, command, payload) {
784
+ await callTool("fw_weaver_steer", {
785
+ command,
786
+ ...payload ? { payload } : {}
787
+ });
788
+ }
789
+
790
+ // src/ui/bot-workspace.tsx
791
+ var React7 = require("react");
792
+ var { useRef: useRef3, useEffect: useEffect4, useState: useState7, useCallback: useCallback6, useMemo: useMemo2 } = React7;
793
+ var {
794
+ Flex: Flex6,
795
+ ScrollArea,
796
+ EmptyState,
797
+ TaskBlock,
798
+ toast: toast6,
799
+ usePackWorkspace,
800
+ useEventStream
801
+ } = require("@fw/plugin-ui-kit");
802
+ function BotWorkspace() {
803
+ const ctx = usePackWorkspace();
804
+ const { callTool, windowData, dispatchEvent, onRefresh } = ctx;
805
+ const packId = ctx.packId;
806
+ const isLive = windowData?.live === true && !!windowData?.runId;
807
+ const highlightRunId = windowData?.runId;
808
+ const stream = useEventStream();
809
+ const {
810
+ timeline: liveTimeline,
811
+ phase: livePhase,
812
+ instruction: liveInstruction,
813
+ elapsed,
814
+ cost,
815
+ plan,
816
+ awaitingApproval
817
+ } = useStreamTimeline(stream.events, stream.isDone);
818
+ useEffect4(() => {
819
+ if (!isLive || !windowData?.runId) return;
820
+ stream.start(packId, "fw_weaver_events", windowData.runId);
821
+ return () => stream.stop();
822
+ }, [isLive, packId, windowData?.runId]);
823
+ const [sessionRunId, setSessionRunId] = useState7(null);
824
+ useEffect4(() => {
825
+ if (isLive) return;
826
+ const poll = async () => {
827
+ try {
828
+ const status = await callTool("fw_weaver_status");
829
+ const runId = status?.currentRunId ?? null;
830
+ const alive = status?.alive;
831
+ if (runId && alive === false) {
832
+ setSessionRunId(null);
833
+ } else {
834
+ setSessionRunId(runId);
835
+ }
836
+ } catch {
837
+ }
838
+ };
839
+ poll();
840
+ const interval = setInterval(poll, 3e3);
841
+ return () => clearInterval(interval);
842
+ }, [isLive, callTool]);
843
+ useEffect4(() => {
844
+ if (!sessionRunId || isLive) return;
845
+ stream.start(packId, "fw_weaver_events", sessionRunId);
846
+ return () => stream.stop();
847
+ }, [sessionRunId, packId, isLive]);
848
+ const scrollRef = useRef3(null);
849
+ useEffect4(() => {
850
+ const el = scrollRef.current;
851
+ if (el) el.scrollTop = el.scrollHeight;
852
+ }, [liveTimeline.length, stream.events.length]);
853
+ const [pausing, setPausing] = useState7(false);
854
+ const [stopping, setStopping] = useState7(false);
855
+ const handlePause = useCallback6(async () => {
856
+ setPausing(true);
857
+ try {
858
+ await sendSteerCommand(callTool, "pause");
859
+ toast6("Pause signal sent", { type: "info" });
860
+ } catch (err) {
861
+ toast6(err instanceof Error ? err.message : "Failed to pause", { type: "error" });
862
+ }
863
+ setPausing(false);
864
+ }, [callTool]);
865
+ const handleStop = useCallback6(async () => {
866
+ setStopping(true);
867
+ try {
868
+ await sendSteerCommand(callTool, "cancel");
869
+ toast6("Stopping \u2014 will take effect after current node completes", { type: "info" });
870
+ } catch (err) {
871
+ toast6(err instanceof Error ? err.message : "Failed to stop", { type: "error" });
872
+ setStopping(false);
873
+ }
874
+ }, [callTool]);
875
+ const [history, setHistory] = useState7([]);
876
+ const [genesisCycles, setGenesisCycles] = useState7([]);
877
+ const [queuedTasks, setQueuedTasks] = useState7([]);
878
+ const [removingIds, setRemovingIds] = useState7(/* @__PURE__ */ new Set());
879
+ const [expandedRunId, setExpandedRunId] = useState7(
880
+ highlightRunId && !isLive ? highlightRunId : null
881
+ );
882
+ const [liveExpanded, setLiveExpanded] = useState7(true);
883
+ const refreshData = useCallback6(() => {
884
+ callTool("fw_weaver_history", { limit: 20 }).then((data) => {
885
+ if (Array.isArray(data)) {
886
+ setHistory(
887
+ data.map((r) => {
888
+ const costObj = r.cost && typeof r.cost === "object" ? r.cost : void 0;
889
+ return { ...r, costDetail: costObj, cost: costObj?.totalCost };
890
+ })
891
+ );
892
+ } else {
893
+ setHistory([]);
894
+ }
895
+ });
896
+ callTool("fw_weaver_insights").then((data) => {
897
+ const d = data;
898
+ if (d?.evolution) {
899
+ const evo = d.evolution;
900
+ if (Array.isArray(evo.recentCycles)) {
901
+ setGenesisCycles(evo.recentCycles.slice(0, 10));
902
+ }
903
+ }
904
+ });
905
+ callTool("fw_weaver_queue", { action: "list" }).then((data) => {
906
+ const visible = (Array.isArray(data) ? data : []).filter(
907
+ (t) => t.status === "pending" || t.status === "running"
908
+ );
909
+ setQueuedTasks(visible);
910
+ });
911
+ }, [callTool]);
912
+ useEffect4(() => {
913
+ refreshData();
914
+ }, [refreshData]);
915
+ useEffect4(() => {
916
+ const interval = setInterval(() => {
917
+ callTool("fw_weaver_queue", { action: "list" }).then((data) => {
918
+ const visible = (Array.isArray(data) ? data : []).filter(
919
+ (t) => t.status === "pending" || t.status === "running"
920
+ );
921
+ setQueuedTasks(visible);
922
+ });
923
+ }, 1e4);
924
+ return () => clearInterval(interval);
925
+ }, [callTool]);
926
+ useEffect4(() => {
927
+ return onRefresh(() => refreshData());
928
+ }, [refreshData, onRefresh]);
929
+ useEffect4(() => {
930
+ if (sessionRunId !== null) refreshData();
931
+ }, [sessionRunId, refreshData]);
932
+ const handleRemoveTask = useCallback6(async (id) => {
933
+ setRemovingIds((prev) => new Set(prev).add(id));
934
+ try {
935
+ await callTool("fw_weaver_queue", { action: "remove", id });
936
+ refreshData();
937
+ toast6("Task removed", { type: "warning" });
938
+ } catch (err) {
939
+ toast6(err instanceof Error ? err.message : "Failed to remove task", { type: "error" });
940
+ }
941
+ setRemovingIds((prev) => {
942
+ const next = new Set(prev);
943
+ next.delete(id);
944
+ return next;
945
+ });
946
+ }, [callTool, refreshData]);
947
+ const timelineItems = useMemo2(() => {
948
+ const items = [];
949
+ for (const run of history) {
950
+ items.push({ kind: "run", timestamp: new Date(run.startedAt ?? 0).getTime(), run, runTimeline: traceToTimeline(run) });
951
+ }
952
+ for (const cycle of genesisCycles) {
953
+ items.push({ kind: "genesis", timestamp: new Date(cycle.timestamp).getTime(), genesis: cycle });
954
+ }
955
+ items.sort((a, b) => a.timestamp - b.timestamp);
956
+ return items;
957
+ }, [history, genesisCycles]);
958
+ const toggleExpand = useCallback6((id) => {
959
+ setExpandedRunId((prev) => prev === id ? null : id);
960
+ }, []);
961
+ const isStreaming = isLive || !!sessionRunId;
962
+ const hasContent = isStreaming || timelineItems.length > 0 || queuedTasks.length > 0;
963
+ function extractInstruction(run) {
964
+ let text = "";
965
+ if (run.params) {
966
+ try {
967
+ const taskJson = run.params.taskJson;
968
+ if (taskJson) {
969
+ const task = JSON.parse(taskJson);
970
+ if (task.instruction) text = task.instruction;
971
+ }
972
+ } catch {
973
+ }
974
+ }
975
+ if (!text && run.summary) {
976
+ const summary = run.summary;
977
+ if (summary.includes("|")) {
978
+ const parts = summary.split("|").map((p) => p.trim());
979
+ const taskPart = parts.find((p) => p.startsWith("Task:"));
980
+ const summaryPart = parts.find((p) => p.startsWith("Summary:"));
981
+ text = taskPart?.replace("Task:", "").trim() ?? summaryPart?.replace("Summary:", "").trim() ?? parts[0] ?? "";
982
+ } else {
983
+ text = summary;
984
+ }
985
+ }
986
+ if (!text) {
987
+ const wf = run.workflowFile;
988
+ if (wf) {
989
+ const parts = wf.split("/");
990
+ text = parts[parts.length - 1] ?? "Bot run";
991
+ } else {
992
+ text = "Bot run";
993
+ }
994
+ }
995
+ if (text.length > 120) text = text.slice(0, 117) + "...";
996
+ return text;
997
+ }
998
+ const approvalSlot = awaitingApproval && plan ? React7.createElement(ApprovalCard, { plan, callTool }) : null;
999
+ return React7.createElement(
1000
+ Flex6,
1001
+ {
1002
+ variant: "column-stretch-start-nowrap-0",
1003
+ style: { width: "100%", height: "100%", overflow: "hidden" }
1004
+ },
1005
+ // Session bar
1006
+ React7.createElement(SessionBar, { callTool, dispatchEvent }),
1007
+ // Settings
1008
+ React7.createElement(SettingsSection, { callTool, dispatchEvent }),
1009
+ // Main scrollable timeline
1010
+ React7.createElement(
1011
+ Flex6,
1012
+ { variant: "column-stretch-start-nowrap-0", style: { flex: 1, minHeight: 0 } },
1013
+ React7.createElement(
1014
+ ScrollArea,
1015
+ { ref: scrollRef },
1016
+ React7.createElement(
1017
+ Flex6,
1018
+ { variant: "column-stretch-start-nowrap-8", style: { padding: "12px 16px" } },
1019
+ // Empty state
1020
+ !hasContent && React7.createElement(EmptyState, {
1021
+ icon: "smartToy",
1022
+ message: "No bot runs yet",
1023
+ description: "Ask the AI assistant to run a task, or add tasks below."
1024
+ }),
1025
+ ...timelineItems.map((item) => {
1026
+ if (item.kind === "genesis" && item.genesis) {
1027
+ return React7.createElement(GenesisBlock, {
1028
+ key: `genesis-${item.genesis.id}`,
1029
+ cycle: item.genesis,
1030
+ callTool
1031
+ });
1032
+ }
1033
+ if (item.kind === "run" && item.run) {
1034
+ const run = item.run;
1035
+ const runId = run.id;
1036
+ const isExpanded = expandedRunId === runId;
1037
+ const isSuccess = run.outcome === "completed" || run.success === true;
1038
+ return React7.createElement(TaskBlock, {
1039
+ key: `run-${runId}`,
1040
+ state: isSuccess ? "completed" : "failed",
1041
+ instruction: extractInstruction(run),
1042
+ timeline: item.runTimeline ?? [],
1043
+ cost: typeof run.cost === "number" ? run.cost : run.costDetail?.totalCost ?? null,
1044
+ plan: run.plan,
1045
+ startedAt: run.startedAt,
1046
+ durationMs: run.durationMs ?? run.duration,
1047
+ expanded: isExpanded,
1048
+ onToggleExpand: () => toggleExpand(runId)
1049
+ });
1050
+ }
1051
+ return null;
1052
+ }),
1053
+ // Live execution
1054
+ isStreaming && React7.createElement(TaskBlock, {
1055
+ state: "running",
1056
+ instruction: liveInstruction ?? windowData?.instruction ?? "Running...",
1057
+ timeline: liveTimeline,
1058
+ phase: livePhase,
1059
+ elapsed,
1060
+ cost,
1061
+ plan,
1062
+ error: stream.error,
1063
+ approvalSlot,
1064
+ onPause: handlePause,
1065
+ onStop: handleStop,
1066
+ pauseLoading: pausing,
1067
+ stopLoading: stopping,
1068
+ expanded: liveExpanded,
1069
+ onToggleExpand: () => setLiveExpanded((v) => !v)
1070
+ }),
1071
+ ...(isStreaming ? queuedTasks.filter((t) => t.status !== "running") : queuedTasks).map(
1072
+ (task) => React7.createElement(TaskBlock, {
1073
+ key: task.id,
1074
+ state: "pending",
1075
+ instruction: task.instruction,
1076
+ onRemove: () => handleRemoveTask(task.id),
1077
+ removeLoading: removingIds.has(task.id)
1078
+ })
1079
+ )
1080
+ )
1081
+ )
1082
+ ),
1083
+ // Input bar
1084
+ React7.createElement(QueueInput, { callTool, onTaskAdded: refreshData })
1085
+ );
1086
+ }
1087
+ module.exports = BotWorkspace;