@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.
- package/dist/bot/preflight.d.ts.map +1 -1
- package/dist/bot/preflight.js +26 -0
- package/dist/bot/preflight.js.map +1 -1
- package/dist/bot/task-create-handler.d.ts +9 -0
- package/dist/bot/task-create-handler.d.ts.map +1 -1
- package/dist/bot/task-create-handler.js +26 -0
- package/dist/bot/task-create-handler.js.map +1 -1
- package/dist/node-types/agent-execute.d.ts.map +1 -1
- package/dist/node-types/agent-execute.js +26 -9
- package/dist/node-types/agent-execute.js.map +1 -1
- package/dist/node-types/plan-task.d.ts.map +1 -1
- package/dist/node-types/plan-task.js +28 -2
- package/dist/node-types/plan-task.js.map +1 -1
- package/dist/ui/bot-slot-card.js +10 -0
- package/dist/ui/budget-bar.js +5 -3
- package/dist/ui/budget-strip.js +156 -0
- package/dist/ui/chat-task-result.js +22 -27
- package/dist/ui/instance-stream-view.js +36 -0
- package/dist/ui/swarm-dashboard.js +1596 -1654
- package/dist/ui/task-detail-view.js +973 -485
- package/dist/ui/task-editor.js +32 -34
- package/dist/ui/task-pool-list.js +11 -3
- package/flowweaver.manifest.json +1 -1
- package/package.json +3 -2
- package/src/bot/preflight.ts +26 -0
- package/src/bot/task-create-handler.ts +39 -0
- package/src/node-types/agent-execute.ts +27 -10
- package/src/node-types/plan-task.ts +25 -2
- package/src/ui/bot-slot-card.tsx +23 -0
- package/src/ui/budget-bar.tsx +13 -5
- package/src/ui/budget-strip.tsx +199 -0
- package/src/ui/chat-task-result.tsx +5 -25
- package/src/ui/instance-stream-view.tsx +50 -1
- package/src/ui/swarm-dashboard.tsx +89 -84
- package/src/ui/task-detail-view.tsx +376 -442
- package/src/ui/task-editor.tsx +65 -96
- package/src/ui/task-pool-list.tsx +3 -12
- package/src/ui/task-status.ts +60 -0
package/dist/ui/task-editor.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
30
|
-
|
|
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:
|
|
105
|
+
status: TASK_STATUS_ICON[task.status] || "pending",
|
|
106
|
+
icon: TASK_STATUS_ICON_OVERRIDE[task.status],
|
|
99
107
|
size: "sm"
|
|
100
108
|
}
|
|
101
109
|
),
|
package/flowweaver.manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": 2,
|
|
3
3
|
"name": "@synergenius/flow-weaver-pack-weaver",
|
|
4
|
-
"version": "0.9.
|
|
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.
|
|
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.
|
|
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",
|
package/src/bot/preflight.ts
CHANGED
|
@@ -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
|
-
//
|
|
547
|
-
//
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
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) {
|
package/src/ui/bot-slot-card.tsx
CHANGED
|
@@ -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"
|
package/src/ui/budget-bar.tsx
CHANGED
|
@@ -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
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
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
|
/>
|