@synergenius/flow-weaver-pack-weaver 0.9.201 → 0.9.203

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 (38) hide show
  1. package/dist/bot/preflight.d.ts.map +1 -1
  2. package/dist/bot/preflight.js +26 -0
  3. package/dist/bot/preflight.js.map +1 -1
  4. package/dist/bot/task-create-handler.d.ts +9 -0
  5. package/dist/bot/task-create-handler.d.ts.map +1 -1
  6. package/dist/bot/task-create-handler.js +26 -0
  7. package/dist/bot/task-create-handler.js.map +1 -1
  8. package/dist/node-types/agent-execute.d.ts.map +1 -1
  9. package/dist/node-types/agent-execute.js +26 -9
  10. package/dist/node-types/agent-execute.js.map +1 -1
  11. package/dist/node-types/plan-task.d.ts.map +1 -1
  12. package/dist/node-types/plan-task.js +28 -2
  13. package/dist/node-types/plan-task.js.map +1 -1
  14. package/dist/ui/bot-slot-card.js +10 -0
  15. package/dist/ui/budget-bar.js +5 -3
  16. package/dist/ui/budget-strip.js +156 -0
  17. package/dist/ui/chat-task-result.js +22 -27
  18. package/dist/ui/instance-stream-view.js +36 -0
  19. package/dist/ui/swarm-dashboard.js +1596 -1654
  20. package/dist/ui/task-detail-view.js +973 -485
  21. package/dist/ui/task-editor.js +32 -34
  22. package/dist/ui/task-pool-list.js +11 -3
  23. package/flowweaver.manifest.json +1 -1
  24. package/package.json +3 -2
  25. package/src/bot/preflight.ts +26 -0
  26. package/src/bot/task-create-handler.ts +39 -0
  27. package/src/node-types/agent-execute.ts +27 -10
  28. package/src/node-types/plan-task.ts +25 -2
  29. package/src/ui/bot-slot-card.tsx +23 -0
  30. package/src/ui/budget-bar.tsx +13 -5
  31. package/src/ui/budget-strip.tsx +199 -0
  32. package/src/ui/chat-task-result.tsx +5 -25
  33. package/src/ui/instance-stream-view.tsx +50 -1
  34. package/src/ui/swarm-dashboard.tsx +89 -84
  35. package/src/ui/task-detail-view.tsx +376 -442
  36. package/src/ui/task-editor.tsx +65 -96
  37. package/src/ui/task-pool-list.tsx +3 -12
  38. package/src/ui/task-status.ts +60 -0
@@ -25,6 +25,16 @@ __export(task_editor_exports, {
25
25
  module.exports = __toCommonJS(task_editor_exports);
26
26
  var import_react = require("react");
27
27
  var import_plugin_ui_kit = require("@fw/plugin-ui-kit");
28
+
29
+ // src/ui/task-status.ts
30
+ var TASK_STATUS_COLOR = {
31
+ "open": "color-brand-alt",
32
+ "in-progress": "color-status-info",
33
+ "done": "color-status-positive",
34
+ "cancelled": "color-status-negative"
35
+ };
36
+
37
+ // src/ui/task-editor.tsx
28
38
  var import_jsx_runtime = require("react/jsx-runtime");
29
39
  var PRIORITY_OPTIONS = [
30
40
  { id: "0", label: "0 - None" },
@@ -40,13 +50,7 @@ var COMPLEXITY_OPTIONS = [
40
50
  { id: "moderate", label: "Moderate" },
41
51
  { id: "complex", label: "Complex" }
42
52
  ];
43
- var STATUS_COLOR = {
44
- "open": "color-brand-alt",
45
- "in-progress": "color-status-info",
46
- "done": "color-status-positive",
47
- "cancelled": "color-status-negative"
48
- };
49
- function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
53
+ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete, embedded }) {
50
54
  const ctx = (0, import_plugin_ui_kit.usePackWorkspace)();
51
55
  const { callTool } = ctx;
52
56
  const [title, setTitle] = (0, import_react.useState)("");
@@ -255,7 +259,7 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
255
259
  variant: "column-stretch-start-nowrap-0",
256
260
  style: { width: "100%", height: "100%", overflow: "hidden" },
257
261
  children: [
258
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
262
+ !embedded && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
259
263
  import_plugin_ui_kit.Flex,
260
264
  {
261
265
  variant: "row-center-space-between-nowrap-8",
@@ -299,7 +303,7 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
299
303
  {
300
304
  label: taskData.status,
301
305
  size: "small",
302
- color: STATUS_COLOR[taskData.status] || "color-brand-alt"
306
+ color: TASK_STATUS_COLOR[taskData.status] || "color-brand-alt"
303
307
  }
304
308
  ) }),
305
309
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Field, { label: "Title", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -508,34 +512,11 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
508
512
  children: "Dependencies cannot be changed after creation"
509
513
  }
510
514
  )
511
- ] }) }),
512
- mode === "edit" && taskData && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
513
- import_plugin_ui_kit.Flex,
514
- {
515
- variant: "column-stretch-start-nowrap-10",
516
- style: { marginTop: 8, paddingTop: 12, borderTop: "1px solid var(--color-border-default)" },
517
- children: [
518
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Field, { label: "Created by", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Typography, { variant: "smallCaption-regular", color: "color-text-medium", children: taskData.createdBy || "unknown" }) }),
519
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Field, { label: "Created at", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Typography, { variant: "smallCaption-regular", color: "color-text-medium", children: taskData.createdAt ? new Date(taskData.createdAt).toLocaleString() : "-" }) }),
520
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Field, { label: "Updated at", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Typography, { variant: "smallCaption-regular", color: "color-text-medium", children: taskData.updatedAt ? new Date(taskData.updatedAt).toLocaleString() : "-" }) }),
521
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Field, { label: "Tokens used", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Typography, { variant: "smallCaption-regular", color: "color-text-medium", children: (taskData.tokensUsed ?? 0).toLocaleString() }) }),
522
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Field, { label: "Cost used", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Typography, { variant: "smallCaption-regular", color: "color-text-medium", children: `$${(taskData.costUsed ?? 0).toFixed(3)}` }) }),
523
- taskData.context?.runHistory?.slice(-1)?.[0]?.remainingWork && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_plugin_ui_kit.Field, { label: "Remaining work", align: "start", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
524
- import_plugin_ui_kit.Typography,
525
- {
526
- variant: "smallCaption-regular",
527
- color: "color-status-caution",
528
- style: { fontFamily: "var(--font-mono, monospace)", whiteSpace: "pre-wrap" },
529
- children: taskData.context.runHistory.slice(-1)[0]?.remainingWork
530
- }
531
- ) })
532
- ]
533
- }
534
- )
515
+ ] }) })
535
516
  ]
536
517
  }
537
518
  ),
538
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
519
+ !embedded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
539
520
  import_plugin_ui_kit.Flex,
540
521
  {
541
522
  variant: "row-center-end-nowrap-8",
@@ -552,6 +533,23 @@ function TaskEditor({ mode, taskId, onSave, onCancel, onDelete }) {
552
533
  }
553
534
  )
554
535
  }
536
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
537
+ import_plugin_ui_kit.Flex,
538
+ {
539
+ variant: "row-center-end-nowrap-8",
540
+ style: { padding: "8px 16px", flexShrink: 0 },
541
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
542
+ import_plugin_ui_kit.Button,
543
+ {
544
+ size: "xs",
545
+ variant: "fill",
546
+ color: "primary",
547
+ onClick: handleSave,
548
+ disabled: !title.trim(),
549
+ children: "Save"
550
+ }
551
+ )
552
+ }
555
553
  )
556
554
  ]
557
555
  }
@@ -26,13 +26,20 @@ __export(task_pool_list_exports, {
26
26
  module.exports = __toCommonJS(task_pool_list_exports);
27
27
  var import_react = require("react");
28
28
  var import_plugin_ui_kit = require("@fw/plugin-ui-kit");
29
- var import_jsx_runtime = require("react/jsx-runtime");
30
- var statusToIcon = {
29
+
30
+ // src/ui/task-status.ts
31
+ var TASK_STATUS_ICON = {
31
32
  "open": "pending",
32
33
  "in-progress": "running",
33
34
  "done": "completed",
34
35
  "cancelled": "failed"
35
36
  };
37
+ var TASK_STATUS_ICON_OVERRIDE = {
38
+ "in-progress": "pendingActions"
39
+ };
40
+
41
+ // src/ui/task-pool-list.tsx
42
+ var import_jsx_runtime = require("react/jsx-runtime");
36
43
  var rowBaseStyle = {
37
44
  padding: "8px 12px",
38
45
  cursor: "pointer",
@@ -95,7 +102,8 @@ function TaskRowItem({ task, indent, onClick }) {
95
102
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
96
103
  import_plugin_ui_kit.StatusIcon,
97
104
  {
98
- status: statusToIcon[task.status] || "pending",
105
+ status: TASK_STATUS_ICON[task.status] || "pending",
106
+ icon: TASK_STATUS_ICON_OVERRIDE[task.status],
99
107
  size: "sm"
100
108
  }
101
109
  ),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifestVersion": 2,
3
3
  "name": "@synergenius/flow-weaver-pack-weaver",
4
- "version": "0.9.201",
4
+ "version": "0.9.203",
5
5
  "description": "AI bot for Flow Weaver. Execute tasks, run workflows, evolve autonomously.",
6
6
  "engineVersion": ">=0.22.10",
7
7
  "categories": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver-pack-weaver",
3
- "version": "0.9.201",
3
+ "version": "0.9.203",
4
4
  "description": "AI bot for Flow Weaver. Execute tasks, run workflows, evolve autonomously.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -75,8 +75,9 @@
75
75
  "dev:install": "npm run dev && node -e \"const p=process.argv[1]; if(!p){console.error('Usage: npm run dev:install -- /path/to/project');process.exit(1)} require('child_process').execSync('npm install --no-save /tmp/synergenius-flow-weaver-pack-weaver-'+require('./package.json').version+'.tgz',{cwd:p,stdio:'inherit'}); require('child_process').execSync('find '+p+' -name fw-exec-\\* -type f -delete',{stdio:'pipe'}); console.log('✓ Installed in '+p+' (cache cleaned)')\" --"
76
76
  },
77
77
  "devDependencies": {
78
- "@synergenius/flow-weaver": "^0.26.2",
78
+ "@synergenius/flow-weaver": "^0.26.7",
79
79
  "@types/node": "^25.3.5",
80
+ "@types/react": "^19.2.14",
80
81
  "@vitest/coverage-v8": "^4.1.2",
81
82
  "esbuild": "^0.27.4",
82
83
  "tsx": "^4.0.0",
@@ -238,6 +238,32 @@ export async function runBridgePreflight(): Promise<PreflightResult> {
238
238
  bridge?.cleanup();
239
239
  }
240
240
 
241
+ // 6. Feature check — verify installed core has costUsd support in StreamJsonParser
242
+ // This catches stale core that was installed before the cost feature was added.
243
+ // More reliable than version comparison — tests actual behavior.
244
+ // This catches stale core that was installed before the costUsd feature was added.
245
+ try {
246
+ const { StreamJsonParser } = await import('@synergenius/flow-weaver/agent');
247
+ const testEvents: Array<{ type: string; costUsd?: number }> = [];
248
+ const testParser = new StreamJsonParser((e: any) => testEvents.push(e));
249
+ testParser.feed(JSON.stringify({
250
+ type: 'result',
251
+ is_error: false,
252
+ total_cost_usd: 0.042,
253
+ usage: { input_tokens: 100, output_tokens: 200, cache_read_input_tokens: 50 },
254
+ }));
255
+ const usageEvent = testEvents.find(e => e.type === 'usage');
256
+ const hasCostUsd = usageEvent && typeof (usageEvent as any).costUsd === 'number' && (usageEvent as any).costUsd > 0;
257
+ if (!hasCostUsd) {
258
+ errors.push({ check: 'core-cost-feature', tool: '', message: 'Installed flow-weaver does not extract costUsd from CLI result events. Update to >=0.26.3: npm update @synergenius/flow-weaver' });
259
+ }
260
+ checks.push({ name: 'core-cost-feature', passed: !!hasCostUsd, count: hasCostUsd ? 1 : 0 });
261
+ } catch (err) {
262
+ const msg = err instanceof Error ? err.message : String(err);
263
+ errors.push({ check: 'core-cost-feature', tool: '', message: `Cost feature check failed: ${msg}` });
264
+ checks.push({ name: 'core-cost-feature', passed: false, count: 0 });
265
+ }
266
+
241
267
  return {
242
268
  passed: errors.length === 0,
243
269
  errors,
@@ -8,6 +8,14 @@
8
8
  import type { CreateTaskInput } from './task-types.js';
9
9
  import { TaskStore } from './task-store.js';
10
10
 
11
+ export interface SubtaskArgs {
12
+ title: string;
13
+ description?: string;
14
+ assignedProfile?: string;
15
+ dependsOn?: string[];
16
+ symbolicId?: string;
17
+ }
18
+
11
19
  export interface TaskCreateArgs {
12
20
  title?: string;
13
21
  description?: string;
@@ -18,6 +26,8 @@ export interface TaskCreateArgs {
18
26
  dependsOn?: string[];
19
27
  acceptance?: { checks?: Array<{ name: string; command: string }> };
20
28
  files?: string[];
29
+ subtasks?: SubtaskArgs[];
30
+ symbolicId?: string;
21
31
  }
22
32
 
23
33
  export interface TaskCreateResult {
@@ -148,6 +158,9 @@ export async function handleTaskCreate(
148
158
  // Track in symbolicIdMap for subsequent task_create calls
149
159
  if (symbolicIdMap) {
150
160
  symbolicIdMap[title] = task.id;
161
+ if (args.symbolicId) {
162
+ symbolicIdMap[args.symbolicId] = task.id;
163
+ }
151
164
  }
152
165
 
153
166
  // Set files if provided
@@ -157,6 +170,32 @@ export async function handleTaskCreate(
157
170
  });
158
171
  }
159
172
 
173
+ // Create inline subtasks
174
+ if (args.subtasks && Array.isArray(args.subtasks)) {
175
+ for (const sub of args.subtasks) {
176
+ const subTitle = String(sub.title ?? '').trim();
177
+ if (!subTitle) continue;
178
+
179
+ const subDeps = sub.dependsOn ?? [];
180
+ const resolvedSubDeps = symbolicIdMap
181
+ ? subDeps.map(dep => symbolicIdMap[dep] ?? dep)
182
+ : subDeps;
183
+
184
+ const subTask = await store.create({
185
+ title: subTitle,
186
+ description: String(sub.description ?? subTitle),
187
+ parentId: task.id,
188
+ assignedProfile: sub.assignedProfile ?? assignedProfile ?? 'developer',
189
+ dependsOn: resolvedSubDeps,
190
+ createdBy: 'ai',
191
+ });
192
+
193
+ if (symbolicIdMap) {
194
+ symbolicIdMap[subTitle] = subTask.id;
195
+ }
196
+ }
197
+ }
198
+
160
199
  return {
161
200
  output: JSON.stringify({ id: task.id, title: task.title, status: task.status }),
162
201
  taskId: task.id,
@@ -22,7 +22,12 @@ import { withRetry, getErrorGuidance } from '../bot/error-classifier.js';
22
22
  import { CostTracker } from '../bot/cost-tracker.js';
23
23
  import { PostTurnHookRunner, CostCheckpointHook, ProgressReportHook } from '../bot/post-turn-hooks.js';
24
24
 
25
- // Track MCP bridges for cleanup alongside CLI sessions
25
+ // Track MCP bridges for cleanup alongside CLI sessions.
26
+ // Bridges are cached by projectDir key — one bridge per project, reused across
27
+ // all createSessionProvider calls. This prevents creating a new bridge (with a
28
+ // new temp socket path) on every call, which would cause fingerprint mismatches
29
+ // in getOrCreateCliSession and kill active sessions mid-turn.
30
+ const bridgeCache = new Map<string, McpBridge>();
26
31
  const activeBridges: McpBridge[] = [];
27
32
 
28
33
  // Clean up persistent sessions and MCP bridges on process exit
@@ -208,7 +213,7 @@ export async function weaverAgentExecute(
208
213
  : env.providerInfo;
209
214
  const { projectDir } = env;
210
215
 
211
- let task: { instruction?: string; targets?: string[]; mode?: string; options?: Record<string, unknown> };
216
+ let task: { id?: string; assignedProfile?: string; instruction?: string; targets?: string[]; mode?: string; options?: Record<string, unknown> };
212
217
  try {
213
218
  task = JSON.parse(context.taskJson ?? '{}');
214
219
  } catch {
@@ -251,7 +256,7 @@ export async function weaverAgentExecute(
251
256
  const { TerminalRenderer } = await import('../bot/terminal-renderer.js');
252
257
  const renderer = new TerminalRenderer({ verbose: !!process.env.WEAVER_VERBOSE });
253
258
 
254
- auditEmit('run-start', { task: task.instruction });
259
+ auditEmit('run-start', { nodeType: 'agent-execute', taskId: task.id, profile: task.assignedProfile, task: task.instruction });
255
260
 
256
261
  const filesCreated: string[] = [];
257
262
  const filesModified: string[] = [];
@@ -342,7 +347,11 @@ export async function weaverAgentExecute(
342
347
  );
343
348
 
344
349
  const usage = result.usage;
345
- // Use real cost from CLI if available, fall back to estimate
350
+ // Use real cost from CLI if available, fall back to estimate.
351
+ // KNOWN ISSUE: On long runs (many MCP tool calls), the CLI process can die
352
+ // before emitting the `result` event, leaving usage.costUsd=0. The estimate
353
+ // from CostTracker is inaccurate (doesn't account for cache reads/writes)
354
+ // but better than reporting $0. See markDead() in cli-session.ts.
346
355
  const estimatedCost = usage.costUsd > 0
347
356
  ? usage.costUsd
348
357
  : CostTracker.estimateCost(model, {
@@ -414,6 +423,9 @@ export async function weaverAgentExecute(
414
423
  (context as unknown as Record<string, unknown>).runProgressJson = runProgressJson;
415
424
 
416
425
  auditEmit('run-complete', {
426
+ nodeType: 'agent-execute',
427
+ taskId: task.id,
428
+ profile: task.assignedProfile,
417
429
  success: outcome === 'completed',
418
430
  outcome,
419
431
  toolCalls: result.toolCallCount,
@@ -543,12 +555,17 @@ async function createSessionProvider(model?: string, projectDir?: string): Promi
543
555
  const key = projectDir ?? process.cwd();
544
556
  const resolvedModel = model ?? 'claude-sonnet-4-6';
545
557
 
546
- // Create MCP bridge so the CLI session has pack tools (read_file, write_file,
547
- // run_shell, etc.) registered. The bridge uses a placeholder executor that is
548
- // swapped per-request via setHandlers() in CliSessionProvider.stream().
549
- const executor = createWeaverExecutor(key);
550
- const bridge = await createMcpBridge(WEAVER_TOOLS, executor);
551
- activeBridges.push(bridge);
558
+ // Reuse existing bridge for this project one bridge per projectDir.
559
+ // Creating a new bridge on every call generates a new temp socket path,
560
+ // which changes the mcpConfigPath in session options, causing fingerprint
561
+ // mismatches in getOrCreateCliSession that kill active sessions mid-turn.
562
+ let bridge = bridgeCache.get(key);
563
+ if (!bridge) {
564
+ const executor = createWeaverExecutor(key);
565
+ bridge = await createMcpBridge(WEAVER_TOOLS, executor);
566
+ bridgeCache.set(key, bridge);
567
+ activeBridges.push(bridge);
568
+ }
552
569
 
553
570
  // NOTE: auditEmit from this module is unreliable under symlinks (dual module
554
571
  // instance — currentRunId may be null). Observability is handled by the caller
@@ -343,7 +343,7 @@ Rules:
343
343
  grantedToolNames.add('complete');
344
344
  const loopTools = WEAVER_TOOLS.filter(t => grantedToolNames.has(t.name));
345
345
 
346
- auditEmit('run-start', { task: task.instruction, mode: 'agent-loop', packVersion: PACK_VERSION, profile: task.assignedProfile });
346
+ auditEmit('run-start', { nodeType: 'plan-task', taskId: task.id, task: task.instruction, mode: 'agent-loop', packVersion: PACK_VERSION, profile: task.assignedProfile });
347
347
  auditEmit('ai-request', {
348
348
  systemPrompt,
349
349
  userPrompt,
@@ -388,6 +388,9 @@ Rules:
388
388
  const finalStats = (provider as unknown as { stats?: typeof providerStats }).stats;
389
389
 
390
390
  auditEmit('run-complete', {
391
+ nodeType: 'plan-task',
392
+ taskId: task.id,
393
+ profile: task.assignedProfile,
391
394
  success: true,
392
395
  toolCalls: toolCallCount,
393
396
  filesCreated: uniqueCreated.length,
@@ -415,7 +418,27 @@ Rules:
415
418
  }
416
419
  }
417
420
 
418
- const costStr = usage.costUsd > 0 ? ` | $${usage.costUsd.toFixed(4)}` : '';
421
+ // KNOWN ISSUE: On long runs (many MCP tool calls), the CLI process can die
422
+ // before emitting the `result` event, leaving usage.costUsd=0. When this
423
+ // happens, fall back to the global usage callback's cost tracker which
424
+ // accumulates cost per API call independently of the result event.
425
+ // See markDead() in cli-session.ts for the root cause explanation.
426
+ let reportedCost = usage.costUsd;
427
+ if (reportedCost === 0) {
428
+ // Try the global cost tracker as fallback
429
+ const trackers = (globalThis as Record<string, unknown>).__fw_usage_trackers__ as Map<string, { costTracker: { getRunSummary: () => { totalCost: number } } }> | undefined;
430
+ if (trackers) {
431
+ let fallbackCost = 0;
432
+ for (const [, t] of trackers) {
433
+ try { fallbackCost += t.costTracker.getRunSummary().totalCost; } catch { /* non-fatal */ }
434
+ }
435
+ if (fallbackCost > 0) {
436
+ reportedCost = fallbackCost;
437
+ console.warn(`\x1b[33m ⚠ costUsd=0 from CLI (process died before result event) — using fallback: $${fallbackCost.toFixed(4)}\x1b[0m`);
438
+ }
439
+ }
440
+ }
441
+ const costStr = reportedCost > 0 ? ` | $${reportedCost.toFixed(4)}` : '';
419
442
  console.log(`\x1b[36m→ Agent loop: ${toolCallCount} tool calls, ${uniqueCreated.length + uniqueModified.length} files changed${costStr}\x1b[0m`);
420
443
  return { onSuccess: true, onFailure: false, ctx: JSON.stringify(context) };
421
444
  } catch (err: unknown) {
@@ -22,6 +22,13 @@ interface InstanceSlot {
22
22
  startedAt?: string;
23
23
  tokensUsed: number;
24
24
  cost: number;
25
+ /** MCP bridge health — populated from run-complete bridge stats */
26
+ bridge?: {
27
+ active: boolean;
28
+ toolUseFiltered: number;
29
+ toolResultPassthrough: number;
30
+ textToolCallDetected: number;
31
+ };
25
32
  }
26
33
 
27
34
  interface BotSlotCardProps {
@@ -66,6 +73,7 @@ const BotCol = styled.div({ width: '110px', flexShrink: 0, display: 'flex', alig
66
73
  const StatusCol = styled.div({ width: '70px', flexShrink: 0 });
67
74
  const TaskCol = styled.div({ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' });
68
75
  const StatCol = styled.div({ width: '50px', flexShrink: 0, textAlign: 'right' });
76
+ const HealthCol = styled.div({ width: '24px', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' });
69
77
  const ActionsCol = styled.div({ width: '50px', flexShrink: 0 });
70
78
 
71
79
  // ---------------------------------------------------------------------------
@@ -151,6 +159,21 @@ function BotSlotCard({ bot, currentTaskTitle, profileName, botDisplayName, botIc
151
159
  </Typography>
152
160
  </StatCol>
153
161
 
162
+ <HealthCol>
163
+ {bot.bridge && (
164
+ <Icon
165
+ name={bot.bridge.textToolCallDetected > 0 ? 'warning' : bot.bridge.active ? 'checkCircle' : 'removeCircle'}
166
+ size={12}
167
+ color={bot.bridge.textToolCallDetected > 0 ? 'color-danger-main' : bot.bridge.active ? 'color-success-main' : 'color-text-subtle'}
168
+ title={bot.bridge.textToolCallDetected > 0
169
+ ? `MCP disconnected — ${bot.bridge.textToolCallDetected} tool calls as text`
170
+ : bot.bridge.active
171
+ ? `Bridge OK — ${bot.bridge.toolResultPassthrough} tools executed`
172
+ : 'Bridge inactive'}
173
+ />
174
+ )}
175
+ </HealthCol>
176
+
154
177
  <ActionsCol>
155
178
  <Flex
156
179
  variant="row-center-end-nowrap-1"
@@ -10,11 +10,13 @@ interface BudgetBarProps {
10
10
  used: number;
11
11
  limit: number;
12
12
  unit: string;
13
+ /** Source of the cost data: 'cli' = real from Claude CLI, 'estimated' = token-based estimate */
14
+ costSource?: 'cli' | 'estimated';
13
15
  /** Called when the user edits the limit. If not provided, limit is read-only. */
14
16
  onLimitChange?: (newLimit: number) => void;
15
17
  }
16
18
 
17
- function BudgetBar({ label, used, limit, unit, onLimitChange }: BudgetBarProps) {
19
+ function BudgetBar({ label, used, limit, unit, costSource, onLimitChange }: BudgetBarProps) {
18
20
  const [editing, setEditing] = useState(false);
19
21
  const [editValue, setEditValue] = useState('');
20
22
 
@@ -37,9 +39,16 @@ function BudgetBar({ label, used, limit, unit, onLimitChange }: BudgetBarProps)
37
39
  return (
38
40
  <Flex variant="column-start-start-nowrap-2">
39
41
  <Flex variant="row-center-space-between-nowrap-4">
40
- <Typography variant="smallCaption-regular" color="color-text-subtle">
41
- {label}
42
- </Typography>
42
+ <Flex variant="row-center-start-nowrap-4">
43
+ <Typography variant="smallCaption-regular" color="color-text-subtle">
44
+ {label}
45
+ </Typography>
46
+ {costSource && (
47
+ <Typography variant="smallCaption-regular" color={costSource === 'cli' ? 'color-success-main' : 'color-text-subtle'}>
48
+ {costSource === 'cli' ? '(actual)' : '(est.)'}
49
+ </Typography>
50
+ )}
51
+ </Flex>
43
52
 
44
53
  <Flex variant="row-center-start-nowrap-4">
45
54
  {editing ? (
@@ -56,7 +65,6 @@ function BudgetBar({ label, used, limit, unit, onLimitChange }: BudgetBarProps)
56
65
  if (e.key === 'Escape') setEditing(false);
57
66
  }}
58
67
  onBlur={handleCommit}
59
- type="number"
60
68
  size="small"
61
69
  autoFocus={true}
62
70
  />