@synergenius/flow-weaver-pack-weaver 0.9.14 → 0.9.16

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 (41) hide show
  1. package/dist/ai-chat-provider.d.ts +34 -0
  2. package/dist/ai-chat-provider.d.ts.map +1 -0
  3. package/dist/ai-chat-provider.js +256 -0
  4. package/dist/ai-chat-provider.js.map +1 -0
  5. package/dist/docs/docs/weaver-bot-usage.md +34 -0
  6. package/dist/docs/docs/weaver-genesis.md +32 -0
  7. package/dist/docs/docs/weaver-task-queue.md +34 -0
  8. package/dist/docs/weaver-bot-usage.md +34 -0
  9. package/dist/docs/weaver-config.md +9 -15
  10. package/dist/docs/weaver-genesis.md +32 -0
  11. package/dist/docs/weaver-task-queue.md +34 -0
  12. package/dist/ui/evolution-panel.js +129 -56
  13. package/dist/ui/insights-widget.js +69 -37
  14. package/flowweaver.manifest.json +281 -4
  15. package/package.json +1 -1
  16. package/src/ai-chat-provider.ts +300 -0
  17. package/src/docs/weaver-bot-usage.md +34 -0
  18. package/src/docs/weaver-genesis.md +32 -0
  19. package/src/docs/weaver-task-queue.md +34 -0
  20. package/src/ui/evolution-panel.tsx +155 -70
  21. package/src/ui/insights-widget.tsx +86 -33
  22. package/dist/bot/agent-loop.d.ts +0 -20
  23. package/dist/bot/agent-loop.d.ts.map +0 -1
  24. package/dist/bot/agent-loop.js +0 -331
  25. package/dist/bot/agent-loop.js.map +0 -1
  26. package/dist/cli.d.ts +0 -3
  27. package/dist/cli.d.ts.map +0 -1
  28. package/dist/cli.js +0 -749
  29. package/dist/cli.js.map +0 -1
  30. package/dist/templates/weaver-template.d.ts +0 -11
  31. package/dist/templates/weaver-template.d.ts.map +0 -1
  32. package/dist/templates/weaver-template.js +0 -53
  33. package/dist/templates/weaver-template.js.map +0 -1
  34. package/dist/workflows/weaver-bot-session.d.ts +0 -65
  35. package/dist/workflows/weaver-bot-session.d.ts.map +0 -1
  36. package/dist/workflows/weaver-bot-session.js +0 -68
  37. package/dist/workflows/weaver-bot-session.js.map +0 -1
  38. package/dist/workflows/weaver.d.ts +0 -24
  39. package/dist/workflows/weaver.d.ts.map +0 -1
  40. package/dist/workflows/weaver.js +0 -28
  41. package/dist/workflows/weaver.js.map +0 -1
@@ -0,0 +1,300 @@
1
+ /**
2
+ * AI Chat Provider for the Weaver pack.
3
+ *
4
+ * Thin adapter that bridges Weaver's existing capabilities into the
5
+ * platform's generic pack AI chat extension system.
6
+ *
7
+ * - executeTool() delegates to the same handlers used by MCP tools
8
+ * - getSystemPromptSections() composes from existing bot infrastructure
9
+ */
10
+
11
+ import { runWorkflow } from './bot/runner.js';
12
+ import { RunStore } from './bot/run-store.js';
13
+ import { CostStore } from './bot/cost-store.js';
14
+ import { defaultRegistry, discoverProviders } from './bot/provider-registry.js';
15
+
16
+ interface AiChatToolContext {
17
+ workspacePath: string;
18
+ userId: string;
19
+ }
20
+
21
+ interface AiChatToolResult {
22
+ result: string;
23
+ isError: boolean;
24
+ }
25
+
26
+ interface AiChatPromptSection {
27
+ id: string;
28
+ title: string;
29
+ content: string;
30
+ priority: number;
31
+ }
32
+
33
+ interface AiChatPromptContext {
34
+ workspacePath: string;
35
+ conversationId: string;
36
+ messageCount: number;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Tool handlers — reuse the same logic as MCP tools
41
+ // ---------------------------------------------------------------------------
42
+
43
+ const toolHandlers: Record<
44
+ string,
45
+ (args: Record<string, unknown>, ctx: AiChatToolContext) => Promise<string>
46
+ > = {
47
+ async fw_weaver_run(args, ctx) {
48
+ const result = await runWorkflow(args.file as string, {
49
+ params: args.params as Record<string, unknown> | undefined,
50
+ verbose: args.verbose as boolean | undefined,
51
+ dryRun: args.dryRun as boolean | undefined,
52
+ });
53
+ return JSON.stringify(result, null, 2);
54
+ },
55
+
56
+ async fw_weaver_history(args) {
57
+ const store = new RunStore();
58
+ if (args.id) {
59
+ const record = store.get(args.id as string);
60
+ return record ? JSON.stringify(record, null, 2) : `No run found matching "${args.id}"`;
61
+ }
62
+ const records = store.list({
63
+ outcome: args.outcome as 'completed' | 'failed' | 'error' | 'skipped' | undefined,
64
+ workflowFile: args.workflowFile as string | undefined,
65
+ limit: (args.limit as number | undefined) ?? 20,
66
+ });
67
+ return records.length === 0 ? 'No runs recorded yet.' : JSON.stringify(records, null, 2);
68
+ },
69
+
70
+ async fw_weaver_costs(args) {
71
+ const store = new CostStore();
72
+ let sinceTs: number | undefined;
73
+ if (args.since) {
74
+ const spec = args.since as string;
75
+ const match = spec.match(/^(\d+)([dhm])$/);
76
+ if (match) {
77
+ const n = parseInt(match[1]!, 10);
78
+ const unit = match[2];
79
+ const ms = unit === 'd' ? n * 86_400_000 : unit === 'h' ? n * 3_600_000 : n * 60_000;
80
+ sinceTs = Date.now() - ms;
81
+ } else {
82
+ const ts = new Date(spec).getTime();
83
+ if (!isNaN(ts)) sinceTs = ts;
84
+ }
85
+ }
86
+ const summary = store.summarize({ since: sinceTs, model: args.model as string | undefined });
87
+ return JSON.stringify(summary, null, 2);
88
+ },
89
+
90
+ async fw_weaver_providers() {
91
+ await discoverProviders(defaultRegistry);
92
+ const providers = defaultRegistry.list();
93
+ const result = providers.map(({ name, metadata }) => ({
94
+ name,
95
+ source: metadata.source,
96
+ description: metadata.description,
97
+ requiredEnvVars: metadata.requiredEnvVars,
98
+ envVarsSet: metadata.requiredEnvVars?.every((v: string) => process.env[v]) ?? false,
99
+ detectCliCommand: metadata.detectCliCommand,
100
+ }));
101
+ return JSON.stringify(result, null, 2);
102
+ },
103
+
104
+ async fw_weaver_bot(args, ctx) {
105
+ const { fileURLToPath } = await import('node:url');
106
+ const { existsSync } = await import('node:fs');
107
+ const task = {
108
+ instruction: args.task as string,
109
+ mode: (args.mode as string) ?? 'create',
110
+ targets: args.targets as string[] | undefined,
111
+ options: {
112
+ template: args.template as string | undefined,
113
+ dryRun: args.dryRun as boolean | undefined,
114
+ autoApprove: (args.autoApprove as boolean | undefined) ?? true,
115
+ },
116
+ };
117
+ const packRoot = new URL('..', import.meta.url);
118
+ let workflowPath = fileURLToPath(new URL('src/workflows/weaver-bot.ts', packRoot));
119
+ if (!existsSync(workflowPath)) {
120
+ workflowPath = fileURLToPath(new URL('dist/workflows/weaver-bot.js', packRoot));
121
+ }
122
+ const result = await runWorkflow(workflowPath, {
123
+ params: { taskJson: JSON.stringify(task), projectDir: ctx.workspacePath || process.cwd() },
124
+ dryRun: args.dryRun as boolean | undefined,
125
+ });
126
+ return JSON.stringify(result, null, 2);
127
+ },
128
+
129
+ async fw_weaver_steer(args) {
130
+ const { SteeringController } = await import('./bot/steering.js');
131
+ const controller = new SteeringController();
132
+ await controller.write({
133
+ command: args.command as 'pause' | 'resume' | 'cancel' | 'redirect' | 'queue',
134
+ payload: args.payload as string | undefined,
135
+ timestamp: Date.now(),
136
+ });
137
+ return `Steering command sent: ${args.command}`;
138
+ },
139
+
140
+ async fw_weaver_queue(args) {
141
+ const { TaskQueue } = await import('./bot/task-queue.js');
142
+ const queue = new TaskQueue();
143
+ switch (args.action) {
144
+ case 'add': {
145
+ if (!args.task) return 'Error: task instruction required';
146
+ const { id, duplicate } = await queue.add({ instruction: args.task as string, priority: 0 });
147
+ return duplicate ? `Task already queued (${id})` : `Task added: ${id}`;
148
+ }
149
+ case 'list':
150
+ return JSON.stringify(await queue.list(), null, 2);
151
+ case 'clear': {
152
+ const count = await queue.clear();
153
+ return `Cleared ${count} task(s)`;
154
+ }
155
+ case 'remove': {
156
+ if (!args.id) return 'Error: task ID required';
157
+ const removed = await queue.remove(args.id as string);
158
+ return removed ? `Removed ${args.id}` : `Not found: ${args.id}`;
159
+ }
160
+ default:
161
+ return `Unknown action: ${args.action}`;
162
+ }
163
+ },
164
+
165
+ async fw_weaver_status() {
166
+ const { SessionStore } = await import('./bot/session-state.js');
167
+ const store = new SessionStore();
168
+ const state = store.load();
169
+ return state ? JSON.stringify(state, null, 2) : JSON.stringify({ status: 'no active session' }, null, 2);
170
+ },
171
+
172
+ async fw_weaver_genesis(args, ctx) {
173
+ const { fileURLToPath } = await import('node:url');
174
+ const { existsSync } = await import('node:fs');
175
+ const packRoot = new URL('..', import.meta.url);
176
+ let workflowPath = fileURLToPath(new URL('src/workflows/genesis-task.ts', packRoot));
177
+ if (!existsSync(workflowPath)) {
178
+ workflowPath = fileURLToPath(new URL('dist/workflows/genesis-task.js', packRoot));
179
+ }
180
+ const result = await runWorkflow(workflowPath, {
181
+ params: { projectDir: ctx.workspacePath || process.cwd() },
182
+ dryRun: args.dryRun as boolean | undefined,
183
+ });
184
+ return JSON.stringify(result, null, 2);
185
+ },
186
+
187
+ async fw_weaver_insights(args, ctx) {
188
+ const { ProjectModelStore } = await import('./bot/project-model.js');
189
+ const { InsightEngine } = await import('./bot/insight-engine.js');
190
+ const projectDir = (args.projectDir as string) || ctx.workspacePath;
191
+ const pms = new ProjectModelStore(projectDir);
192
+ const model = await pms.getOrBuild();
193
+ const engine = new InsightEngine();
194
+ const insights = engine.analyze(model);
195
+ return JSON.stringify({
196
+ health: model.health,
197
+ bots: model.bots,
198
+ insights: insights.slice(0, 10),
199
+ trust: model.trust,
200
+ cost: model.cost,
201
+ evolution: {
202
+ totalCycles: model.evolution.totalCycles,
203
+ successRate: model.evolution.successRate,
204
+ },
205
+ }, null, 2);
206
+ },
207
+ };
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // Provider implementation
211
+ // ---------------------------------------------------------------------------
212
+
213
+ export default {
214
+ async executeTool(
215
+ name: string,
216
+ args: Record<string, unknown>,
217
+ context: AiChatToolContext,
218
+ ): Promise<AiChatToolResult> {
219
+ const handler = toolHandlers[name];
220
+ if (!handler) {
221
+ return { result: `Unknown weaver tool: ${name}`, isError: true };
222
+ }
223
+ try {
224
+ const result = await handler(args, context);
225
+ return { result, isError: false };
226
+ } catch (err) {
227
+ return { result: err instanceof Error ? err.message : String(err), isError: true };
228
+ }
229
+ },
230
+
231
+ async getSystemPromptSections(
232
+ context: AiChatPromptContext,
233
+ ): Promise<AiChatPromptSection[]> {
234
+ const sections: AiChatPromptSection[] = [];
235
+
236
+ // Weaver behavioral instructions
237
+ try {
238
+ const { buildBotSystemPrompt } = await import('./bot/system-prompt.js');
239
+ const prompt = buildBotSystemPrompt();
240
+ if (prompt) {
241
+ sections.push({
242
+ id: 'weaver-identity',
243
+ title: 'Weaver Behavioral Instructions',
244
+ content: prompt,
245
+ priority: 10,
246
+ });
247
+ }
248
+ } catch {
249
+ // system-prompt module not available — skip
250
+ }
251
+
252
+ // Project intelligence
253
+ if (context.workspacePath) {
254
+ try {
255
+ const { ProjectModelStore } = await import('./bot/project-model.js');
256
+ const pms = new ProjectModelStore(context.workspacePath);
257
+ const model = await pms.getOrBuild();
258
+ if (model && (model.health.workflows.length > 0 || model.bots.length > 0)) {
259
+ sections.push({
260
+ id: 'project-intelligence',
261
+ title: 'Project Intelligence',
262
+ content: pms.formatSummary(model) +
263
+ '\n\nBe proactive: mention relevant insights when they relate to what the user is working on.',
264
+ priority: 20,
265
+ });
266
+ }
267
+ } catch {
268
+ // ProjectModelStore not available — skip
269
+ }
270
+ }
271
+
272
+ // Proactive insights
273
+ if (context.workspacePath) {
274
+ try {
275
+ const { InsightEngine } = await import('./bot/insight-engine.js');
276
+ const engine = new InsightEngine();
277
+ const { ProjectModelStore } = await import('./bot/project-model.js');
278
+ const pms = new ProjectModelStore(context.workspacePath);
279
+ const model = await pms.getOrBuild();
280
+ if (model) {
281
+ const insights = engine.analyze(model).slice(0, 3);
282
+ if (insights.length > 0) {
283
+ sections.push({
284
+ id: 'proactive-insights',
285
+ title: 'Proactive Insights',
286
+ content: insights.map((i) =>
287
+ `- [${i.severity}] ${i.title}: ${i.description}`
288
+ ).join('\n'),
289
+ priority: 30,
290
+ });
291
+ }
292
+ }
293
+ } catch {
294
+ // InsightEngine not available — skip
295
+ }
296
+ }
297
+
298
+ return sections;
299
+ },
300
+ };
@@ -0,0 +1,34 @@
1
+ ## Weaver Bot
2
+
3
+ The Weaver bot is an autonomous AI agent that creates and modifies Flow Weaver workflows from natural language instructions.
4
+
5
+ ### Running the bot
6
+
7
+ Use `fw_weaver_bot` with a task description:
8
+ - `task`: Natural language instruction (required)
9
+ - `mode`: `create` (new workflow), `modify` (edit existing), `read` (analyze), `batch` (multiple tasks)
10
+ - `targets`: File paths for modify/read mode
11
+ - `autoApprove`: Skip the approval gate (default: true in studio)
12
+
13
+ ### Execution flow
14
+
15
+ 1. **Receive task** — parses instruction and determines mode
16
+ 2. **Build context** — gathers project state, templates, and relevant files
17
+ 3. **Plan** — AI generates a step-by-step execution plan
18
+ 4. **Approval gate** — plan shown for review (skipped if autoApprove)
19
+ 5. **Execute + validate + retry** — runs steps, validates output, retries on errors (up to 3 attempts)
20
+ 6. **Git ops** — commits changes if successful
21
+ 7. **Report** — returns summary with outcome
22
+
23
+ ### Steering a running bot
24
+
25
+ Use `fw_weaver_steer` to control execution:
26
+ - `pause` — pause at next safe point
27
+ - `resume` — continue after pause
28
+ - `cancel` — abort execution
29
+ - `redirect` — change task mid-execution
30
+ - `queue` — add a follow-up task
31
+
32
+ ### Checking status
33
+
34
+ Use `fw_weaver_status` to see the current bot session state, active task, and completion count.
@@ -0,0 +1,32 @@
1
+ ## Genesis Self-Evolution
2
+
3
+ Genesis is Weaver's self-evolution protocol. It autonomously observes, proposes, applies, and validates changes to workflows.
4
+
5
+ ### Running a Genesis cycle
6
+
7
+ Use `fw_weaver_genesis` to trigger a single cycle:
8
+ - `projectDir`: Project directory (defaults to workspace)
9
+ - `dryRun`: Preview changes without applying
10
+
11
+ ### How it works
12
+
13
+ 1. **Observe** — scans project for workflows and their health
14
+ 2. **Fingerprint** — detects if workflows changed since last cycle (avoids duplicate proposals)
15
+ 3. **Stabilize check** — prevents rapid churn (cooldown between cycles)
16
+ 4. **Propose** — AI analyzes project state and proposes modifications within a budget
17
+ 5. **Validate proposal** — checks proposed changes are safe
18
+ 6. **Snapshot** — creates backup before applying
19
+ 7. **Apply** — executes proposed operations via `flow-weaver modify`
20
+ 8. **Validate result** — compiles and validates the modified workflow
21
+ 9. **Threshold check** — decides if changes meet the quality bar
22
+ 10. **Approve** — human approval gate (configurable)
23
+ 11. **Commit** — git commit with rollback capability
24
+ 12. **Escrow** — data safety pipeline for recovery
25
+
26
+ ### Safety features
27
+
28
+ - Pre-apply snapshots for rollback
29
+ - Escrow recovery from previous cycles
30
+ - Fingerprinting prevents duplicate proposals
31
+ - Threshold checking before committing
32
+ - Budget limits on proposed changes
@@ -0,0 +1,34 @@
1
+ ## Task Queue & Steering
2
+
3
+ Weaver supports queuing tasks for background processing and steering running bots in real-time.
4
+
5
+ ### Task queue
6
+
7
+ Use `fw_weaver_queue` to manage tasks:
8
+ - `add` — add a task to the queue (requires `task` instruction)
9
+ - `list` — show all queued tasks
10
+ - `clear` — remove all pending tasks
11
+ - `remove` — remove a specific task by ID
12
+
13
+ Tasks are stored in NDJSON format and processed sequentially by the bot session.
14
+
15
+ ### Steering commands
16
+
17
+ Use `fw_weaver_steer` to control a running bot:
18
+ - `pause` — pause execution at the next safe point
19
+ - `resume` — continue after a pause
20
+ - `cancel` — abort the current task
21
+ - `redirect` — change the task instruction mid-execution (requires `payload` with new instruction)
22
+ - `queue` — add a follow-up task without interrupting the current one
23
+
24
+ ### Batch mode
25
+
26
+ For multiple related tasks, use `fw_weaver_bot` with `mode: "batch"` to process them in sequence through the same bot session, sharing context between tasks.
27
+
28
+ ### Monitoring
29
+
30
+ Use `fw_weaver_status` to check the current session state:
31
+ - Session phase (idle, planning, executing, validating)
32
+ - Current task instruction
33
+ - Completed task count
34
+ - Error state if any
@@ -1,94 +1,179 @@
1
1
  /**
2
- * Weaver Evolution Panel — shows Genesis cycle history and operation effectiveness.
3
- * Loaded dynamically by the platform's pack UI contributions loader.
2
+ * Weaver Evolution Panel — shows Genesis cycle history, trust level, improve status.
3
+ * Fetches data from the connected device via platform relay endpoints.
4
4
  */
5
5
 
6
- import React, { useEffect, useState } from 'react';
6
+ import React, { useEffect, useState, useCallback } from 'react';
7
7
 
8
- interface EvolutionData {
9
- evolution: {
10
- totalCycles: number;
11
- successRate: number;
12
- byOperationType: Record<string, { proposed: number; applied: number; effectiveness: number }>;
13
- recentCycles: Array<{ id: string; outcome: string; proposal?: { summary: string; impactLevel: string } }>;
8
+ interface ImproveStatus {
9
+ running: boolean;
10
+ lastRun?: {
11
+ successes: number;
12
+ failures: number;
13
+ skips: number;
14
+ cycles: Array<{ cycle: number; outcome: string; description: string; commitHash?: string }>;
14
15
  };
15
- trust: { phase: number; score: number };
16
16
  }
17
17
 
18
- export function WeaverEvolutionPanel({ projectDir }: { projectDir?: string }) {
19
- const [data, setData] = useState<EvolutionData | null>(null);
18
+ interface HealthData {
19
+ health?: { overall: number };
20
+ trust?: { phase: number; score: number };
21
+ cost?: { last7Days: number; trend: string };
22
+ }
23
+
24
+ interface Device {
25
+ id: string;
26
+ name: string;
27
+ capabilities: string[];
28
+ }
29
+
30
+ export function WeaverEvolutionPanel() {
31
+ const [health, setHealth] = useState<HealthData | null>(null);
32
+ const [improve, setImprove] = useState<ImproveStatus | null>(null);
20
33
  const [error, setError] = useState<string | null>(null);
34
+ const [loading, setLoading] = useState(true);
35
+ const [deviceId, setDeviceId] = useState<string | null>(null);
36
+ const [deviceName, setDeviceName] = useState('');
37
+ const [improving, setImproving] = useState(false);
38
+
39
+ const fetchData = useCallback(async () => {
40
+ try {
41
+ const devRes = await fetch('/api/devices', { credentials: 'include' });
42
+ if (!devRes.ok) { setError('Not connected'); setLoading(false); return; }
43
+ const devices: Device[] = await devRes.json();
44
+ const device = devices.find(d => d.capabilities?.includes('improve'));
45
+ if (!device) { setError('No device with improve capability'); setLoading(false); return; }
46
+ setDeviceId(device.id);
47
+ setDeviceName(device.name);
48
+
49
+ const [healthRes, improveRes] = await Promise.allSettled([
50
+ fetch(`/api/devices/${device.id}/health`, { credentials: 'include' }),
51
+ fetch(`/api/devices/${device.id}/improve/status`, { credentials: 'include' }),
52
+ ]);
53
+
54
+ if (healthRes.status === 'fulfilled' && healthRes.value.ok) {
55
+ setHealth(await healthRes.value.json());
56
+ }
57
+ if (improveRes.status === 'fulfilled' && improveRes.value.ok) {
58
+ const data = await improveRes.value.json();
59
+ setImprove(data);
60
+ setImproving(data.running ?? false);
61
+ }
62
+ setError(null);
63
+ } catch (e) {
64
+ setError(e instanceof Error ? e.message : 'Failed to load');
65
+ } finally {
66
+ setLoading(false);
67
+ }
68
+ }, []);
21
69
 
22
70
  useEffect(() => {
23
- if (!projectDir) return;
24
- fetch(`/api/mcp/fw_weaver_insights?projectDir=${encodeURIComponent(projectDir)}`)
25
- .then(r => r.json())
26
- .then(setData)
27
- .catch(e => setError(e.message));
28
- }, [projectDir]);
29
-
30
- if (error) return <div style={{ padding: 16, color: '#ef4444' }}>Error: {error}</div>;
31
- if (!data) return <div style={{ padding: 16, opacity: 0.5 }}>Loading evolution data...</div>;
32
-
33
- const { evolution, trust } = data;
34
- const outcomeColor: Record<string, string> = {
35
- applied: '#22c55e',
36
- 'rolled-back': '#ef4444',
37
- rejected: '#f59e0b',
38
- 'no-change': '#6b7280',
39
- error: '#ef4444',
71
+ fetchData();
72
+ const interval = setInterval(fetchData, 15_000);
73
+ return () => clearInterval(interval);
74
+ }, [fetchData]);
75
+
76
+ const startImprove = async () => {
77
+ if (!deviceId) return;
78
+ setImproving(true);
79
+ try {
80
+ await fetch(`/api/devices/${deviceId}/improve`, {
81
+ method: 'POST',
82
+ headers: { 'Content-Type': 'application/json' },
83
+ credentials: 'include',
84
+ body: JSON.stringify({ maxCycles: 5 }),
85
+ });
86
+ } catch { /* ignore */ }
40
87
  };
41
88
 
89
+ if (loading) return <div style={{ padding: 16, opacity: 0.5 }}>Loading...</div>;
90
+ if (error) return (
91
+ <div style={{ padding: 16 }}>
92
+ <div style={{ color: '#ef4444', marginBottom: 8 }}>{error}</div>
93
+ <button onClick={fetchData} style={{ fontSize: 12, color: '#6b7280', background: 'none', border: '1px solid #333', borderRadius: 4, padding: '4px 8px', cursor: 'pointer' }}>Retry</button>
94
+ </div>
95
+ );
96
+
97
+ const trustPhase = health?.trust?.phase ?? 1;
98
+ const trustScore = health?.trust?.score ?? 0;
99
+ const healthScore = health?.health?.overall ?? 0;
100
+
101
+ const outcomeIcon: Record<string, string> = { success: '\u2713', failure: '\u2717' };
102
+ const outcomeColor: Record<string, string> = { success: '#22c55e', failure: '#ef4444' };
103
+
42
104
  return (
43
- <div style={{ padding: 16, fontFamily: 'system-ui, sans-serif', fontSize: 13 }}>
44
- <div style={{ marginBottom: 16 }}>
45
- <div style={{ fontWeight: 600, marginBottom: 4 }}>Trust Level</div>
46
- <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
47
- <span style={{ fontSize: 20, fontWeight: 700 }}>Phase {trust.phase}</span>
48
- <span style={{ opacity: 0.5 }}>Score: {trust.score}/100</span>
49
- </div>
105
+ <div style={{ padding: 16, fontFamily: 'system-ui, sans-serif', fontSize: 13, color: 'var(--color-text-high, #e5e5e5)' }}>
106
+ <div style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.05em', opacity: 0.5, marginBottom: 12 }}>
107
+ {deviceName}
50
108
  </div>
51
109
 
52
- <div style={{ marginBottom: 16 }}>
53
- <div style={{ fontWeight: 600, marginBottom: 4 }}>
54
- Genesis Cycles: {evolution.totalCycles} ({evolution.totalCycles > 0 ? Math.round(evolution.successRate * 100) : 0}% success)
110
+ {/* Trust + Health */}
111
+ <div style={{ display: 'flex', gap: 16, marginBottom: 16 }}>
112
+ <div style={{ flex: 1, padding: 12, border: '1px solid rgba(128,128,128,0.2)', borderRadius: 6 }}>
113
+ <div style={{ fontSize: 20, fontWeight: 700 }}>P{trustPhase}</div>
114
+ <div style={{ fontSize: 10, opacity: 0.5, textTransform: 'uppercase', marginTop: 2 }}>Trust · {trustScore}/100</div>
115
+ </div>
116
+ <div style={{ flex: 1, padding: 12, border: '1px solid rgba(128,128,128,0.2)', borderRadius: 6 }}>
117
+ <div style={{ fontSize: 20, fontWeight: 700 }}>{healthScore}</div>
118
+ <div style={{ fontSize: 10, opacity: 0.5, textTransform: 'uppercase', marginTop: 2 }}>Health</div>
55
119
  </div>
56
120
  </div>
57
121
 
58
- {Object.keys(evolution.byOperationType).length > 0 && (
59
- <div style={{ marginBottom: 16 }}>
60
- <div style={{ fontWeight: 600, marginBottom: 4 }}>Operation Effectiveness</div>
61
- {Object.entries(evolution.byOperationType).map(([op, stats]) => (
62
- <div key={op} style={{ padding: '2px 0', display: 'flex', justifyContent: 'space-between' }}>
63
- <span>{op}</span>
64
- <span style={{ opacity: 0.7 }}>{Math.round(stats.effectiveness * 100)}% ({stats.applied}/{stats.proposed})</span>
65
- </div>
66
- ))}
122
+ {/* Improve */}
123
+ <div style={{ marginBottom: 16 }}>
124
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
125
+ <div style={{ fontWeight: 600, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.04em', opacity: 0.6 }}>
126
+ Improve
127
+ </div>
128
+ <button
129
+ onClick={startImprove}
130
+ disabled={improving}
131
+ style={{
132
+ fontSize: 11,
133
+ fontWeight: 500,
134
+ padding: '3px 10px',
135
+ borderRadius: 4,
136
+ border: '1px solid rgba(128,128,128,0.3)',
137
+ background: improving ? 'rgba(34,197,94,0.1)' : 'transparent',
138
+ color: improving ? '#22c55e' : 'var(--color-text-medium, #999)',
139
+ cursor: improving ? 'default' : 'pointer',
140
+ opacity: improving ? 0.8 : 1,
141
+ }}
142
+ >
143
+ {improving ? 'Running...' : 'Start Improve'}
144
+ </button>
67
145
  </div>
68
- )}
69
-
70
- {evolution.recentCycles.length > 0 && (
71
- <div>
72
- <div style={{ fontWeight: 600, marginBottom: 4 }}>Recent Cycles</div>
73
- {evolution.recentCycles.slice(-5).reverse().map((cycle) => (
74
- <div key={cycle.id} style={{ padding: '4px 0', borderBottom: '1px solid rgba(128,128,128,0.1)' }}>
75
- <span style={{ color: outcomeColor[cycle.outcome] ?? '#6b7280', fontWeight: 600, marginRight: 8 }}>
76
- {cycle.outcome}
77
- </span>
78
- <span style={{ opacity: 0.7 }}>{cycle.id}</span>
79
- {cycle.proposal && (
80
- <div style={{ opacity: 0.6, paddingLeft: 8 }}>{cycle.proposal.summary}</div>
81
- )}
146
+
147
+ {improve?.lastRun && (
148
+ <>
149
+ <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
150
+ <span style={{ color: '#22c55e', fontWeight: 600 }}>{improve.lastRun.successes} committed</span>
151
+ <span style={{ color: '#ef4444', fontWeight: 600 }}>{improve.lastRun.failures} rolled back</span>
152
+ {improve.lastRun.skips > 0 && <span style={{ opacity: 0.5 }}>{improve.lastRun.skips} skipped</span>}
82
153
  </div>
83
- ))}
84
- </div>
85
- )}
86
154
 
87
- {evolution.totalCycles === 0 && (
88
- <div style={{ opacity: 0.5, textAlign: 'center', padding: 20 }}>
89
- No genesis cycles yet. Use /genesis in the assistant to start evolving bot workflows.
90
- </div>
91
- )}
155
+ {improve.lastRun.cycles.slice(-5).reverse().map(cy => (
156
+ <div key={cy.cycle} style={{ padding: '4px 0', borderBottom: '1px solid rgba(128,128,128,0.1)', display: 'flex', alignItems: 'flex-start', gap: 6 }}>
157
+ <span style={{ color: outcomeColor[cy.outcome] ?? '#6b7280', fontWeight: 700, fontSize: 12, width: 14, textAlign: 'center', flexShrink: 0 }}>
158
+ {outcomeIcon[cy.outcome] ?? '\u25CB'}
159
+ </span>
160
+ <span style={{ flex: 1, opacity: 0.8 }}>{cy.description.slice(0, 80)}</span>
161
+ {cy.commitHash && <span style={{ fontFamily: 'monospace', fontSize: 10, opacity: 0.4 }}>{cy.commitHash}</span>}
162
+ </div>
163
+ ))}
164
+ </>
165
+ )}
166
+
167
+ {!improve?.lastRun && (
168
+ <div style={{ opacity: 0.5, textAlign: 'center', padding: 16 }}>
169
+ No improve runs yet. Click "Start Improve" to begin.
170
+ </div>
171
+ )}
172
+ </div>
173
+
174
+ <div style={{ textAlign: 'right' }}>
175
+ <button onClick={fetchData} style={{ fontSize: 11, color: '#6b7280', background: 'none', border: 'none', cursor: 'pointer' }}>Refresh</button>
176
+ </div>
92
177
  </div>
93
178
  );
94
179
  }