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

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 (181) hide show
  1. package/dist/ai-chat-provider.js +5 -5
  2. package/dist/ai-chat-provider.js.map +1 -1
  3. package/dist/bot/acceptance-merge.d.ts +21 -0
  4. package/dist/bot/acceptance-merge.d.ts.map +1 -0
  5. package/dist/bot/acceptance-merge.js +46 -0
  6. package/dist/bot/acceptance-merge.js.map +1 -0
  7. package/dist/bot/ai-client.d.ts +14 -2
  8. package/dist/bot/ai-client.d.ts.map +1 -1
  9. package/dist/bot/ai-client.js +71 -24
  10. package/dist/bot/ai-client.js.map +1 -1
  11. package/dist/bot/assistant-tools.js +3 -3
  12. package/dist/bot/assistant-tools.js.map +1 -1
  13. package/dist/bot/audit-logger.d.ts.map +1 -1
  14. package/dist/bot/audit-logger.js +34 -14
  15. package/dist/bot/audit-logger.js.map +1 -1
  16. package/dist/bot/audit-trail.d.ts +67 -0
  17. package/dist/bot/audit-trail.d.ts.map +1 -0
  18. package/dist/bot/audit-trail.js +153 -0
  19. package/dist/bot/audit-trail.js.map +1 -0
  20. package/dist/bot/behavior-defaults.d.ts +1 -1
  21. package/dist/bot/behavior-defaults.d.ts.map +1 -1
  22. package/dist/bot/behavior-defaults.js +7 -3
  23. package/dist/bot/behavior-defaults.js.map +1 -1
  24. package/dist/bot/capability-registry.d.ts +9 -0
  25. package/dist/bot/capability-registry.d.ts.map +1 -1
  26. package/dist/bot/capability-registry.js +81 -27
  27. package/dist/bot/capability-registry.js.map +1 -1
  28. package/dist/bot/capability-types.d.ts +10 -0
  29. package/dist/bot/capability-types.d.ts.map +1 -1
  30. package/dist/bot/cli-provider.d.ts.map +1 -1
  31. package/dist/bot/cli-provider.js +8 -7
  32. package/dist/bot/cli-provider.js.map +1 -1
  33. package/dist/bot/preflight.d.ts +48 -0
  34. package/dist/bot/preflight.d.ts.map +1 -0
  35. package/dist/bot/preflight.js +247 -0
  36. package/dist/bot/preflight.js.map +1 -0
  37. package/dist/bot/provider-shim.d.ts +74 -0
  38. package/dist/bot/provider-shim.d.ts.map +1 -0
  39. package/dist/bot/provider-shim.js +176 -0
  40. package/dist/bot/provider-shim.js.map +1 -0
  41. package/dist/bot/runner.d.ts +2 -0
  42. package/dist/bot/runner.d.ts.map +1 -1
  43. package/dist/bot/runner.js +60 -17
  44. package/dist/bot/runner.js.map +1 -1
  45. package/dist/bot/step-executor.d.ts.map +1 -1
  46. package/dist/bot/step-executor.js +72 -115
  47. package/dist/bot/step-executor.js.map +1 -1
  48. package/dist/bot/swarm-controller.d.ts +2 -0
  49. package/dist/bot/swarm-controller.d.ts.map +1 -1
  50. package/dist/bot/swarm-controller.js +92 -20
  51. package/dist/bot/swarm-controller.js.map +1 -1
  52. package/dist/bot/task-create-handler.d.ts +37 -0
  53. package/dist/bot/task-create-handler.d.ts.map +1 -0
  54. package/dist/bot/task-create-handler.js +124 -0
  55. package/dist/bot/task-create-handler.js.map +1 -0
  56. package/dist/bot/task-store.d.ts +1 -0
  57. package/dist/bot/task-store.d.ts.map +1 -1
  58. package/dist/bot/task-store.js +67 -0
  59. package/dist/bot/task-store.js.map +1 -1
  60. package/dist/bot/types.d.ts +1 -1
  61. package/dist/bot/types.d.ts.map +1 -1
  62. package/dist/bot/weaver-tools.d.ts.map +1 -1
  63. package/dist/bot/weaver-tools.js +7 -39
  64. package/dist/bot/weaver-tools.js.map +1 -1
  65. package/dist/node-types/agent-execute.d.ts +25 -8
  66. package/dist/node-types/agent-execute.d.ts.map +1 -1
  67. package/dist/node-types/agent-execute.js +89 -23
  68. package/dist/node-types/agent-execute.js.map +1 -1
  69. package/dist/node-types/bot-report.d.ts.map +1 -1
  70. package/dist/node-types/bot-report.js +24 -3
  71. package/dist/node-types/bot-report.js.map +1 -1
  72. package/dist/node-types/plan-task.d.ts +8 -17
  73. package/dist/node-types/plan-task.d.ts.map +1 -1
  74. package/dist/node-types/plan-task.js +217 -256
  75. package/dist/node-types/plan-task.js.map +1 -1
  76. package/dist/node-types/review-result.js +8 -6
  77. package/dist/node-types/review-result.js.map +1 -1
  78. package/dist/palindrome.d.ts +9 -0
  79. package/dist/palindrome.d.ts.map +1 -0
  80. package/dist/palindrome.js +14 -0
  81. package/dist/palindrome.js.map +1 -0
  82. package/dist/ui/approval-card.js +91 -82
  83. package/dist/ui/bot-activity.js +73 -56
  84. package/dist/ui/bot-config.js +48 -31
  85. package/dist/ui/bot-dashboard.js +52 -36
  86. package/dist/ui/bot-panel.js +230 -228
  87. package/dist/ui/bot-slot-card.js +100 -90
  88. package/dist/ui/bot-status.js +37 -15
  89. package/dist/ui/budget-bar.js +57 -31
  90. package/dist/ui/capability-editor.js +447 -378
  91. package/dist/ui/chat-task-result.js +78 -71
  92. package/dist/ui/decision-log.js +68 -81
  93. package/dist/ui/genesis-block.js +86 -95
  94. package/dist/ui/instance-stream-view.js +722 -0
  95. package/dist/ui/profile-card.js +96 -221
  96. package/dist/ui/profile-editor.js +532 -575
  97. package/dist/ui/settings-section.js +41 -45
  98. package/dist/ui/swarm-controls.js +212 -135
  99. package/dist/ui/swarm-dashboard.js +3992 -2715
  100. package/dist/ui/task-detail-view.js +415 -521
  101. package/dist/ui/task-editor.js +339 -390
  102. package/dist/ui/task-pool-list.js +60 -55
  103. package/dist/workflows/src/palindrome.d.ts +11 -0
  104. package/dist/workflows/src/palindrome.d.ts.map +1 -0
  105. package/dist/workflows/src/palindrome.js +16 -0
  106. package/dist/workflows/src/palindrome.js.map +1 -0
  107. package/dist/workflows/tests/palindrome.test.d.ts +2 -0
  108. package/dist/workflows/tests/palindrome.test.d.ts.map +1 -0
  109. package/dist/workflows/tests/palindrome.test.js +41 -0
  110. package/dist/workflows/tests/palindrome.test.js.map +1 -0
  111. package/dist/workflows/weaver-bot-batch.js +1 -1
  112. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  113. package/dist/workflows/weaver-bot.js +1 -1
  114. package/dist/workflows/weaver-bot.js.map +1 -1
  115. package/flowweaver.manifest.json +1 -1
  116. package/package.json +8 -2
  117. package/src/ai-chat-provider.ts +5 -5
  118. package/src/bot/acceptance-merge.ts +62 -0
  119. package/src/bot/ai-client.ts +77 -21
  120. package/src/bot/assistant-tools.ts +3 -3
  121. package/src/bot/audit-logger.ts +42 -14
  122. package/src/bot/audit-trail.ts +211 -0
  123. package/src/bot/behavior-defaults.ts +7 -2
  124. package/src/bot/capability-registry.ts +84 -28
  125. package/src/bot/capability-types.ts +11 -0
  126. package/src/bot/cli-provider.ts +8 -7
  127. package/src/bot/preflight.ts +285 -0
  128. package/src/bot/provider-shim.ts +218 -0
  129. package/src/bot/runner.ts +68 -20
  130. package/src/bot/step-executor.ts +69 -127
  131. package/src/bot/swarm-controller.ts +94 -20
  132. package/src/bot/task-create-handler.ts +164 -0
  133. package/src/bot/task-store.ts +83 -0
  134. package/src/bot/types.ts +4 -1
  135. package/src/bot/weaver-tools.ts +7 -45
  136. package/src/node-types/agent-execute.ts +102 -16
  137. package/src/node-types/bot-report.ts +24 -3
  138. package/src/node-types/plan-task.ts +238 -280
  139. package/src/node-types/review-result.ts +8 -6
  140. package/src/palindrome.ts +14 -0
  141. package/src/ui/approval-card.tsx +78 -62
  142. package/src/ui/bot-activity.tsx +12 -10
  143. package/src/ui/bot-config.tsx +12 -10
  144. package/src/ui/bot-dashboard.tsx +13 -11
  145. package/src/ui/bot-panel.tsx +189 -171
  146. package/src/ui/bot-slot-card.tsx +125 -70
  147. package/src/ui/bot-status.tsx +4 -4
  148. package/src/ui/budget-bar.tsx +86 -25
  149. package/src/ui/capability-editor.tsx +392 -257
  150. package/src/ui/chat-task-result.tsx +81 -78
  151. package/src/ui/decision-log.tsx +76 -73
  152. package/src/ui/genesis-block.tsx +91 -61
  153. package/src/ui/instance-stream-view.tsx +861 -0
  154. package/src/ui/profile-card.tsx +195 -168
  155. package/src/ui/profile-editor.tsx +453 -370
  156. package/src/ui/settings-section.tsx +46 -39
  157. package/src/ui/swarm-controls.tsx +252 -123
  158. package/src/ui/swarm-dashboard.tsx +999 -466
  159. package/src/ui/task-detail-view.tsx +485 -428
  160. package/src/ui/task-editor.tsx +329 -271
  161. package/src/ui/task-pool-list.tsx +68 -62
  162. package/src/workflows/src/palindrome.ts +16 -0
  163. package/src/workflows/tests/palindrome.test.ts +49 -0
  164. package/src/workflows/weaver-bot-batch.ts +1 -1
  165. package/src/workflows/weaver-bot.ts +1 -1
  166. package/dist/ui/bot-constants.d.ts +0 -14
  167. package/dist/ui/bot-constants.d.ts.map +0 -1
  168. package/dist/ui/bot-constants.js +0 -189
  169. package/dist/ui/bot-constants.js.map +0 -1
  170. package/dist/ui/steer-api.d.ts +0 -7
  171. package/dist/ui/steer-api.d.ts.map +0 -1
  172. package/dist/ui/steer-api.js +0 -11
  173. package/dist/ui/steer-api.js.map +0 -1
  174. package/dist/ui/trace-to-timeline.d.ts +0 -91
  175. package/dist/ui/trace-to-timeline.d.ts.map +0 -1
  176. package/dist/ui/trace-to-timeline.js +0 -116
  177. package/dist/ui/trace-to-timeline.js.map +0 -1
  178. package/dist/ui/use-stream-timeline.d.ts +0 -50
  179. package/dist/ui/use-stream-timeline.d.ts.map +0 -1
  180. package/dist/ui/use-stream-timeline.js +0 -245
  181. package/dist/ui/use-stream-timeline.js.map +0 -1
@@ -67,7 +67,23 @@ export function extractCliJsonResult(raw: string): string {
67
67
  return trimmed;
68
68
  }
69
69
 
70
- export async function callCliAsync(provider: string, prompt: string, model?: string, systemPrompt?: string, cliBinPath?: string): Promise<string> {
70
+ /** Callback for streaming conversation events to the EventLog. */
71
+ export type StreamEventCallback = (event: { type: string; timestamp: number; data?: Record<string, unknown> }) => void;
72
+
73
+ /**
74
+ * Set/clear the global stream event callback. Called by the runner before/after
75
+ * workflow execution so all callAI call sites automatically emit conversation events.
76
+ * Same pattern as __fw_ai_usage_callback__.
77
+ */
78
+ export function setStreamEventCallback(cb: StreamEventCallback | undefined): void {
79
+ (globalThis as Record<string, unknown>).__fw_stream_event_callback__ = cb;
80
+ }
81
+
82
+ function getStreamEventCallback(): StreamEventCallback | undefined {
83
+ return (globalThis as Record<string, unknown>).__fw_stream_event_callback__ as StreamEventCallback | undefined;
84
+ }
85
+
86
+ export async function callCliAsync(provider: string, prompt: string, model?: string, systemPrompt?: string, cliBinPath?: string, onStreamEvent?: StreamEventCallback): Promise<string> {
71
87
  if (provider === 'copilot-cli') {
72
88
  return callCli(provider, prompt, model);
73
89
  }
@@ -75,19 +91,21 @@ export async function callCliAsync(provider: string, prompt: string, model?: str
75
91
  throw new Error(`Unknown CLI provider: ${provider}`);
76
92
  }
77
93
 
78
- // Flags matching the platform's proven CLI integration:
79
- // --verbose + --output-format stream-json + --include-partial-messages for incremental streaming
80
- // --json-schema for structured JSON output enforcement
81
- // --dangerously-skip-permissions to prevent permission prompt hangs
94
+ // Use explicit callback or fall back to global (set by runner)
95
+ const emitStreamEvent = onStreamEvent ?? getStreamEventCallback();
96
+
97
+ // Use centralized CLI config enforces --tools "" and --strict-mcp-config
98
+ const { getCliBaseArgs } = await import('@synergenius/flow-weaver/agent');
82
99
  const args = [
83
- '-p', '--verbose',
84
- '--output-format', 'stream-json',
85
- '--include-partial-messages',
86
- '--json-schema', PLAN_JSON_SCHEMA,
87
- '--dangerously-skip-permissions',
100
+ ...getCliBaseArgs({
101
+ model,
102
+ systemPrompt,
103
+ outputFormat: 'stream-json',
104
+ includePartialMessages: true,
105
+ jsonSchema: PLAN_JSON_SCHEMA,
106
+ }),
107
+ '--verbose',
88
108
  ];
89
- if (model) args.push('--model', model);
90
- if (systemPrompt) args.push('--system-prompt', systemPrompt);
91
109
 
92
110
  const bin = cliBinPath || 'claude';
93
111
  const child = spawn(bin, args, {
@@ -130,28 +148,62 @@ export async function callCliAsync(provider: string, prompt: string, model?: str
130
148
  inThinkingBlock = true;
131
149
  process.stderr.write('\x1b[90m thinking...\x1b[0m');
132
150
  }
133
- // Incremental thinking — dim text, no emoji per token
151
+ // Incremental thinking — emit to stream
134
152
  else if (event.type === 'content_block_delta' && event.delta?.type === 'thinking_delta' && event.delta?.thinking) {
135
- // Don't stream every thinking token just show we're thinking
153
+ emitStreamEvent?.({ type: 'thinking_delta', timestamp: Date.now(), data: { text: event.delta.thinking } });
154
+ }
155
+ // Thinking block end
156
+ else if (event.type === 'content_block_stop' && inThinkingBlock) {
157
+ process.stderr.write('\n');
158
+ inThinkingBlock = false;
159
+ emitStreamEvent?.({ type: 'thinking_done', timestamp: Date.now() });
136
160
  }
137
161
  // Text block start
138
162
  else if (event.type === 'content_block_start' && event.content_block?.type === 'text') {
139
163
  if (inThinkingBlock) {
140
164
  process.stderr.write('\n');
141
165
  inThinkingBlock = false;
166
+ emitStreamEvent?.({ type: 'thinking_done', timestamp: Date.now() });
142
167
  }
143
168
  }
144
- // Incremental text stream to stderr (chatbot-like output)
169
+ // Tool use block start
170
+ else if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
171
+ emitStreamEvent?.({ type: 'tool_use_start', timestamp: Date.now(), data: {
172
+ id: event.content_block.id, name: event.content_block.name,
173
+ }});
174
+ }
175
+ // Incremental text — stream to stderr + emit to stream
145
176
  else if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta' && event.delta?.text) {
146
177
  hasStreamedText = true;
147
178
  process.stderr.write(`\x1b[36m${event.delta.text}\x1b[0m`);
179
+ emitStreamEvent?.({ type: 'text_delta', timestamp: Date.now(), data: { text: event.delta.text } });
180
+ }
181
+ // Tool result — from partial messages showing tool execution results
182
+ else if (event.type === 'content_block_start' && event.content_block?.type === 'tool_result') {
183
+ emitStreamEvent?.({ type: 'tool_result', timestamp: Date.now(), data: {
184
+ id: event.content_block.tool_use_id,
185
+ result: typeof event.content_block.content === 'string'
186
+ ? event.content_block.content
187
+ : JSON.stringify(event.content_block.content ?? ''),
188
+ isError: event.content_block.is_error ?? false,
189
+ }});
190
+ }
191
+ // Subagent tool events — --include-partial-messages may emit these
192
+ else if (event.type === 'tool_use' || event.type === 'tool_start') {
193
+ emitStreamEvent?.({ type: 'tool_use_start', timestamp: Date.now(), data: {
194
+ id: event.id ?? event.tool_use_id, name: event.name ?? event.tool_name ?? 'tool',
195
+ }});
196
+ }
197
+ else if (event.type === 'tool_result') {
198
+ emitStreamEvent?.({ type: 'tool_result', timestamp: Date.now(), data: {
199
+ id: event.tool_use_id ?? event.id,
200
+ result: typeof event.content === 'string' ? event.content : JSON.stringify(event.content ?? ''),
201
+ isError: event.is_error ?? false,
202
+ }});
148
203
  }
149
204
  // Block boundary — newline
150
205
  else if (event.type === 'content_block_stop') {
151
- if (inThinkingBlock) {
152
- process.stderr.write('\n');
153
- inThinkingBlock = false;
154
- }
206
+ // handled above for thinking blocks
155
207
  }
156
208
  // Result event — contains structured_output with --json-schema
157
209
  else if (event.type === 'result') {
@@ -398,6 +450,7 @@ export async function callAI(
398
450
  systemPrompt: string,
399
451
  userPrompt: string,
400
452
  defaultMaxTokens = 4096,
453
+ onStreamEvent?: StreamEventCallback,
401
454
  ): Promise<string> {
402
455
  if (pInfo.type === 'platform') {
403
456
  return callPlatform(systemPrompt, userPrompt, pInfo.model, pInfo.maxTokens ?? defaultMaxTokens);
@@ -414,7 +467,7 @@ export async function callAI(
414
467
  // Async-only path: uses spawn() so Ctrl+C can kill child processes.
415
468
  // Also uses --output-format json + --json-schema to enforce JSON output
416
469
  // (eliminates hallucination entirely — no retry needed).
417
- return callCliAsync(pInfo.type, userPrompt, pInfo.model, systemPrompt, pInfo.cliBinPath);
470
+ return callCliAsync(pInfo.type, userPrompt, pInfo.model, systemPrompt, pInfo.cliBinPath, onStreamEvent);
418
471
  }
419
472
 
420
473
  /**
@@ -456,7 +509,10 @@ export async function callAIWithMessages(
456
509
  const systemMsg = messages.find(m => m.role === 'system');
457
510
  const userMsg = [...messages].reverse().find(m => m.role === 'user');
458
511
  const systemPrompt = systemMsg?.content ?? '';
459
- const userPrompt = typeof userMsg?.content === 'string' ? userMsg.content : JSON.stringify(userMsg?.content ?? '');
512
+ const rawContent = userMsg?.content;
513
+ const userPrompt = typeof rawContent === 'string' ? rawContent
514
+ : rawContent != null ? JSON.stringify(rawContent)
515
+ : '';
460
516
  return callAIWithTools(pInfo, systemPrompt, userPrompt, tools, defaultMaxTokens);
461
517
  }
462
518
 
@@ -115,13 +115,13 @@ export function createAssistantExecutor(projectDir: string, steeringEngine?: imp
115
115
  }
116
116
  case 'queue_retry': {
117
117
  const store = mgr.getTaskStore(String(args.bot));
118
- const tasks = await store.list({ status: 'cancelled' });
118
+ const tasks = await store.list({ status: ['cancelled', 'done'] });
119
119
  let count = 0;
120
120
  for (const t of tasks) {
121
- await store.update(t.id, { status: 'open' });
121
+ await store.retry(t.id);
122
122
  count++;
123
123
  }
124
- return { result: `Reset ${count} cancelled task(s) to open.`, isError: false };
124
+ return { result: `Reset ${count} terminal task(s) to open.`, isError: false };
125
125
  }
126
126
 
127
127
  // Flow-weaver tools
@@ -1,46 +1,74 @@
1
1
  import type { AuditEventType, AuditEventCallback } from './types.js';
2
2
  import { AuditStore } from './audit-store.js';
3
3
 
4
- let store: AuditStore | null = null;
5
- let currentRunId: string | null = null;
6
- let onEvent: AuditEventCallback | undefined;
4
+ // ---------------------------------------------------------------------------
5
+ // State stored on globalThis to survive dual module instances.
6
+ //
7
+ // When the pack is symlinked (benchmarks, dev), Node.js may load
8
+ // audit-logger.js from two different paths, creating separate module
9
+ // instances with separate module-level variables. The runner calls
10
+ // initAuditLogger on instance A, but plan-task calls auditEmit on
11
+ // instance B — which has currentRunId=null, silently dropping events.
12
+ //
13
+ // Using globalThis ensures both instances share the same state.
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const GLOBAL_KEY = '__fw_audit_logger__';
17
+
18
+ interface AuditState {
19
+ store: AuditStore | null;
20
+ currentRunId: string | null;
21
+ onEvent: AuditEventCallback | undefined;
22
+ }
23
+
24
+ function getState(): AuditState {
25
+ let state = (globalThis as Record<string, unknown>)[GLOBAL_KEY] as AuditState | undefined;
26
+ if (!state) {
27
+ state = { store: null, currentRunId: null, onEvent: undefined };
28
+ (globalThis as Record<string, unknown>)[GLOBAL_KEY] = state;
29
+ }
30
+ return state;
31
+ }
7
32
 
8
33
  export function initAuditLogger(runId: string, callback?: AuditEventCallback): void {
34
+ const state = getState();
9
35
  try {
10
- store = new AuditStore();
36
+ state.store = new AuditStore();
11
37
  } catch (err) {
12
38
  if (process.env.WEAVER_VERBOSE) process.stderr.write(`[weaver] audit store init failed: ${err}\n`);
13
- store = null;
39
+ state.store = null;
14
40
  }
15
- currentRunId = runId;
16
- onEvent = callback;
41
+ state.currentRunId = runId;
42
+ state.onEvent = callback;
17
43
  }
18
44
 
19
45
  export function auditEmit(type: AuditEventType, data?: Record<string, unknown>): void {
20
- if (!currentRunId) return;
46
+ const state = getState();
47
+ if (!state.currentRunId) return;
21
48
 
22
49
  const event = {
23
50
  type,
24
51
  timestamp: new Date().toISOString(),
25
- runId: currentRunId,
52
+ runId: state.currentRunId,
26
53
  data,
27
54
  };
28
55
 
29
56
  try {
30
- store?.emit(event);
57
+ state.store?.emit(event);
31
58
  } catch (err) {
32
59
  if (process.env.WEAVER_VERBOSE) process.stderr.write(`[weaver] audit emit failed: ${err}\n`);
33
60
  }
34
61
 
35
62
  try {
36
- onEvent?.(event);
63
+ state.onEvent?.(event);
37
64
  } catch (err) {
38
65
  if (process.env.WEAVER_VERBOSE) process.stderr.write(`[weaver] audit callback failed: ${err}\n`);
39
66
  }
40
67
  }
41
68
 
42
69
  export function teardownAuditLogger(): void {
43
- store = null;
44
- currentRunId = null;
45
- onEvent = undefined;
70
+ const state = getState();
71
+ state.store = null;
72
+ state.currentRunId = null;
73
+ state.onEvent = undefined;
46
74
  }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Audit Trail Assembler — reads .weaver/ event files and produces
3
+ * a single structured object: the complete story of a swarm session.
4
+ *
5
+ * Usage:
6
+ * import { assembleAuditTrail } from './audit-trail.js';
7
+ * const trail = assembleAuditTrail('/path/to/project');
8
+ * fs.writeFileSync('.weaver/audit-trail.json', JSON.stringify(trail, null, 2));
9
+ *
10
+ * The trail includes:
11
+ * - Session metadata (provider, budgets, pack version)
12
+ * - All tasks with final state and acceptance results
13
+ * - Per-run event logs (full prompts, full responses, tool calls)
14
+ * - All swarm events (decisions, claims, releases)
15
+ * - Everything merged and sorted by timestamp
16
+ *
17
+ * Streaming noise (thinking_delta, text_delta) is filtered out —
18
+ * the full thinking/content is already in the audit:ai-response event.
19
+ */
20
+
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Types
26
+ // ---------------------------------------------------------------------------
27
+
28
+ export interface AuditEvent {
29
+ type: string;
30
+ timestamp: number;
31
+ data: Record<string, unknown>;
32
+ source?: string; // 'swarm' | runId
33
+ }
34
+
35
+ export interface AuditRun {
36
+ runId: string;
37
+ events: AuditEvent[];
38
+ }
39
+
40
+ export interface AuditTask {
41
+ id: string;
42
+ title: string;
43
+ description: string;
44
+ status: string;
45
+ assignedProfile?: string;
46
+ dependsOn: string[];
47
+ tokensUsed: number;
48
+ costUsed: number;
49
+ runs: number;
50
+ acceptance?: {
51
+ met: boolean;
52
+ results: Array<{ name: string; pass: boolean; detail?: string }>;
53
+ };
54
+ }
55
+
56
+ export interface AuditSession {
57
+ packVersion?: string;
58
+ status?: string;
59
+ maxConcurrent?: number;
60
+ totalCost?: number;
61
+ totalTokensUsed?: number;
62
+ tasksCompleted?: number;
63
+ budgets?: Record<string, unknown>;
64
+ }
65
+
66
+ export interface AuditTrail {
67
+ assembledAt: string;
68
+ projectDir: string;
69
+ session: AuditSession;
70
+ tasks: AuditTask[];
71
+ runs: AuditRun[];
72
+ events: AuditEvent[]; // all events merged and sorted by timestamp
73
+ }
74
+
75
+ // Streaming noise — these are redundant because full content is
76
+ // captured in audit:ai-response. Filtering them keeps the trail readable.
77
+ const NOISE_TYPES = new Set([
78
+ 'thinking_delta',
79
+ 'text_delta',
80
+ 'thinking_done',
81
+ 'cost-update', // per-second cost updates — final cost is in swarm/task state
82
+ ]);
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Assembler
86
+ // ---------------------------------------------------------------------------
87
+
88
+ export function assembleAuditTrail(projectDir: string): AuditTrail {
89
+ const weaverDir = path.join(projectDir, '.weaver');
90
+
91
+ if (!fs.existsSync(weaverDir)) {
92
+ return emptyTrail(projectDir);
93
+ }
94
+
95
+ // 1. Read session metadata
96
+ const session = readSession(weaverDir);
97
+
98
+ // 2. Read tasks
99
+ const tasks = readTasks(weaverDir);
100
+
101
+ // 3. Read swarm events
102
+ const swarmEvents = readNdjsonSafe(path.join(weaverDir, 'swarm-events.ndjson'))
103
+ .map(e => ({ ...e, source: 'swarm' } as AuditEvent));
104
+
105
+ // 4. Read per-run event files
106
+ const runs: AuditRun[] = [];
107
+ const runEvents: AuditEvent[] = [];
108
+
109
+ const files = fs.readdirSync(weaverDir).filter(f => f.startsWith('events-') && f.endsWith('.ndjson'));
110
+ for (const file of files.sort()) {
111
+ const runId = file.replace('events-', '').replace('.ndjson', '');
112
+ const raw = readNdjsonSafe(path.join(weaverDir, file));
113
+ const filtered = raw.filter(e => !NOISE_TYPES.has(e.type));
114
+ const events = filtered.map(e => ({ ...e, source: runId } as AuditEvent));
115
+ runs.push({ runId, events });
116
+ runEvents.push(...events);
117
+ }
118
+
119
+ // 5. Merge all events sorted by timestamp
120
+ const allEvents = [...swarmEvents, ...runEvents].sort((a, b) => a.timestamp - b.timestamp);
121
+
122
+ return {
123
+ assembledAt: new Date().toISOString(),
124
+ projectDir,
125
+ session,
126
+ tasks,
127
+ runs,
128
+ events: allEvents,
129
+ };
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Helpers
134
+ // ---------------------------------------------------------------------------
135
+
136
+ function emptyTrail(projectDir: string): AuditTrail {
137
+ return {
138
+ assembledAt: new Date().toISOString(),
139
+ projectDir,
140
+ session: {},
141
+ tasks: [],
142
+ runs: [],
143
+ events: [],
144
+ };
145
+ }
146
+
147
+ function readSession(weaverDir: string): AuditSession {
148
+ const swarmPath = path.join(weaverDir, 'swarm.json');
149
+ if (!fs.existsSync(swarmPath)) return {};
150
+ try {
151
+ const raw = JSON.parse(fs.readFileSync(swarmPath, 'utf-8'));
152
+ return {
153
+ packVersion: raw.packVersion,
154
+ status: raw.status,
155
+ maxConcurrent: raw.maxConcurrent,
156
+ totalCost: raw.totalCost,
157
+ totalTokensUsed: raw.totalTokensUsed,
158
+ tasksCompleted: raw.tasksCompleted,
159
+ budgets: raw.budgets,
160
+ };
161
+ } catch {
162
+ return {};
163
+ }
164
+ }
165
+
166
+ function readTasks(weaverDir: string): AuditTask[] {
167
+ const tasksPath = path.join(weaverDir, 'tasks.json');
168
+ if (!fs.existsSync(tasksPath)) return [];
169
+ try {
170
+ const raw = JSON.parse(fs.readFileSync(tasksPath, 'utf-8'));
171
+ if (!Array.isArray(raw)) return [];
172
+ return raw.map((t: Record<string, unknown>) => ({
173
+ id: t.id as string,
174
+ title: t.title as string,
175
+ description: (t.description as string) ?? '',
176
+ status: t.status as string,
177
+ assignedProfile: t.assignedProfile as string | undefined,
178
+ dependsOn: (t.dependsOn as string[]) ?? [],
179
+ tokensUsed: (t.tokensUsed as number) ?? 0,
180
+ costUsed: (t.costUsed as number) ?? 0,
181
+ runs: ((t.context as Record<string, unknown>)?.runHistory as unknown[])?.length ?? 0,
182
+ acceptance: t.lastAcceptanceCheck as AuditTask['acceptance'],
183
+ }));
184
+ } catch {
185
+ return [];
186
+ }
187
+ }
188
+
189
+ function readNdjsonSafe(filePath: string): AuditEvent[] {
190
+ if (!fs.existsSync(filePath)) return [];
191
+ try {
192
+ const content = fs.readFileSync(filePath, 'utf-8');
193
+ const events: AuditEvent[] = [];
194
+ for (const line of content.split('\n')) {
195
+ if (!line.trim()) continue;
196
+ try {
197
+ const parsed = JSON.parse(line);
198
+ events.push({
199
+ type: parsed.type ?? 'unknown',
200
+ timestamp: parsed.timestamp ?? 0,
201
+ data: parsed.data ?? parsed,
202
+ });
203
+ } catch {
204
+ // Skip malformed lines
205
+ }
206
+ }
207
+ return events;
208
+ } catch {
209
+ return [];
210
+ }
211
+ }
@@ -224,6 +224,7 @@ export function buildDefaultBehavior(
224
224
  export function adjustBehaviorForComplexity(
225
225
  behavior: ProfileBehavior,
226
226
  complexity: 'trivial' | 'simple' | 'moderate' | 'complex' | undefined,
227
+ profileRole?: string,
227
228
  ): ProfileBehavior {
228
229
  if (!complexity || complexity === 'moderate') {
229
230
  return behavior;
@@ -233,8 +234,12 @@ export function adjustBehaviorForComplexity(
233
234
  const adjusted: ProfileBehavior = JSON.parse(JSON.stringify(behavior));
234
235
 
235
236
  if (complexity === 'complex') {
236
- // Complex: upgrade to powerful tier for plan phase (the critical thinking step)
237
- if (adjusted.phases['plan']?.tier) adjusted.phases['plan'].tier = 'powerful';
237
+ // Complex: upgrade to powerful tier for plan phase (the critical thinking step).
238
+ // Exception: orchestrator profile caps at standard — decomposition is
239
+ // pattern matching (break into subtasks), not deep reasoning. Using Opus
240
+ // for orchestration burns 30%+ of budget on a single decomposition call.
241
+ const planTier = profileRole === 'orchestrator' ? 'standard' : 'powerful';
242
+ if (adjusted.phases['plan']?.tier) adjusted.phases['plan'].tier = planTier as ModelTier;
238
243
  } else if (complexity === 'trivial') {
239
244
  // Trivial: fast tier everywhere, skip review, 1 attempt, no evidence
240
245
  for (const phase of Object.values(adjusted.phases)) {