@synergenius/flowweaver-pack-weaver 0.6.0 → 0.7.0

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 (196) hide show
  1. package/dist/bot/ai-client.d.ts +1 -0
  2. package/dist/bot/ai-client.d.ts.map +1 -1
  3. package/dist/bot/ai-client.js +52 -1
  4. package/dist/bot/ai-client.js.map +1 -1
  5. package/dist/bot/audit-logger.d.ts +5 -0
  6. package/dist/bot/audit-logger.d.ts.map +1 -0
  7. package/dist/bot/audit-logger.js +42 -0
  8. package/dist/bot/audit-logger.js.map +1 -0
  9. package/dist/bot/audit-store.d.ts +13 -0
  10. package/dist/bot/audit-store.d.ts.map +1 -0
  11. package/dist/bot/audit-store.js +59 -0
  12. package/dist/bot/audit-store.js.map +1 -0
  13. package/dist/bot/cli-provider.d.ts +1 -0
  14. package/dist/bot/cli-provider.d.ts.map +1 -1
  15. package/dist/bot/cli-provider.js +86 -22
  16. package/dist/bot/cli-provider.js.map +1 -1
  17. package/dist/bot/cli-stream-parser.d.ts +11 -0
  18. package/dist/bot/cli-stream-parser.d.ts.map +1 -0
  19. package/dist/bot/cli-stream-parser.js +53 -0
  20. package/dist/bot/cli-stream-parser.js.map +1 -0
  21. package/dist/bot/design-checker.d.ts +24 -0
  22. package/dist/bot/design-checker.d.ts.map +1 -0
  23. package/dist/bot/design-checker.js +269 -0
  24. package/dist/bot/design-checker.js.map +1 -0
  25. package/dist/bot/file-validator.d.ts +5 -2
  26. package/dist/bot/file-validator.d.ts.map +1 -1
  27. package/dist/bot/file-validator.js +14 -7
  28. package/dist/bot/file-validator.js.map +1 -1
  29. package/dist/bot/fw-api.d.ts +8 -0
  30. package/dist/bot/fw-api.d.ts.map +1 -0
  31. package/dist/bot/fw-api.js +12 -0
  32. package/dist/bot/fw-api.js.map +1 -0
  33. package/dist/bot/runner.d.ts +2 -1
  34. package/dist/bot/runner.d.ts.map +1 -1
  35. package/dist/bot/runner.js +8 -0
  36. package/dist/bot/runner.js.map +1 -1
  37. package/dist/bot/step-executor.d.ts +3 -2
  38. package/dist/bot/step-executor.d.ts.map +1 -1
  39. package/dist/bot/step-executor.js +9 -30
  40. package/dist/bot/step-executor.js.map +1 -1
  41. package/dist/bot/system-prompt.d.ts +13 -1
  42. package/dist/bot/system-prompt.d.ts.map +1 -1
  43. package/dist/bot/system-prompt.js +28 -22
  44. package/dist/bot/system-prompt.js.map +1 -1
  45. package/dist/bot/types.d.ts +9 -1
  46. package/dist/bot/types.d.ts.map +1 -1
  47. package/dist/cli-bridge.d.ts.map +1 -1
  48. package/dist/cli-bridge.js +2 -1
  49. package/dist/cli-bridge.js.map +1 -1
  50. package/dist/cli-handlers.d.ts +4 -2
  51. package/dist/cli-handlers.d.ts.map +1 -1
  52. package/dist/cli-handlers.js +199 -92
  53. package/dist/cli-handlers.js.map +1 -1
  54. package/dist/docs/docs/weaver-config.md +8 -14
  55. package/dist/docs/weaver-config.md +8 -14
  56. package/dist/node-types/approval-gate.d.ts.map +1 -1
  57. package/dist/node-types/approval-gate.js +4 -0
  58. package/dist/node-types/approval-gate.js.map +1 -1
  59. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  60. package/dist/node-types/exec-validate-retry.js +33 -7
  61. package/dist/node-types/exec-validate-retry.js.map +1 -1
  62. package/dist/node-types/execute-plan.js +1 -1
  63. package/dist/node-types/execute-plan.js.map +1 -1
  64. package/dist/node-types/genesis-validate-proposal.d.ts.map +1 -1
  65. package/dist/node-types/genesis-validate-proposal.js +23 -0
  66. package/dist/node-types/genesis-validate-proposal.js.map +1 -1
  67. package/dist/node-types/git-ops.d.ts.map +1 -1
  68. package/dist/node-types/git-ops.js +2 -0
  69. package/dist/node-types/git-ops.js.map +1 -1
  70. package/dist/node-types/plan-task.d.ts.map +1 -1
  71. package/dist/node-types/plan-task.js +9 -1
  72. package/dist/node-types/plan-task.js.map +1 -1
  73. package/dist/node-types/send-notify.d.ts.map +1 -1
  74. package/dist/node-types/send-notify.js +4 -1
  75. package/dist/node-types/send-notify.js.map +1 -1
  76. package/dist/node-types/validate-result.d.ts +2 -2
  77. package/dist/node-types/validate-result.d.ts.map +1 -1
  78. package/dist/node-types/validate-result.js +2 -2
  79. package/dist/node-types/validate-result.js.map +1 -1
  80. package/dist/templates/index.d.ts +2 -4
  81. package/dist/templates/index.d.ts.map +1 -1
  82. package/dist/templates/index.js +1 -3
  83. package/dist/templates/index.js.map +1 -1
  84. package/dist/templates/weaver-bot-template.d.ts +9 -1
  85. package/dist/templates/weaver-bot-template.d.ts.map +1 -1
  86. package/dist/templates/weaver-bot-template.js.map +1 -1
  87. package/dist/workflows/index.d.ts +0 -1
  88. package/dist/workflows/index.d.ts.map +1 -1
  89. package/dist/workflows/index.js +0 -1
  90. package/dist/workflows/index.js.map +1 -1
  91. package/dist/workflows/weaver-bot-batch.d.ts +4 -1
  92. package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
  93. package/dist/workflows/weaver-bot-batch.js +1 -1
  94. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  95. package/dist/workflows/weaver-bot.d.ts +4 -1
  96. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  97. package/dist/workflows/weaver-bot.js +1 -1
  98. package/dist/workflows/weaver-bot.js.map +1 -1
  99. package/flowweaver.manifest.json +10 -10
  100. package/package.json +4 -3
  101. package/src/bot/agent-provider.ts +273 -0
  102. package/src/bot/ai-client.ts +109 -0
  103. package/src/bot/approvals.ts +273 -0
  104. package/src/bot/audit-logger.ts +45 -0
  105. package/src/bot/audit-store.ts +69 -0
  106. package/src/bot/bot-agent-channel.ts +99 -0
  107. package/src/bot/cli-provider.ts +169 -0
  108. package/src/bot/cli-stream-parser.ts +59 -0
  109. package/src/bot/cost-store.ts +92 -0
  110. package/src/bot/cost-tracker.ts +72 -0
  111. package/src/bot/cron-parser.ts +153 -0
  112. package/src/bot/cron-scheduler.ts +48 -0
  113. package/src/bot/dashboard.ts +658 -0
  114. package/src/bot/design-checker.ts +327 -0
  115. package/src/bot/file-lock.ts +73 -0
  116. package/src/bot/file-validator.ts +41 -0
  117. package/src/bot/file-watcher.ts +103 -0
  118. package/src/bot/fw-api.ts +18 -0
  119. package/src/bot/genesis-prompt-context.ts +135 -0
  120. package/src/bot/genesis-store.ts +180 -0
  121. package/src/bot/index.ts +127 -0
  122. package/src/bot/notifications.ts +263 -0
  123. package/src/bot/pipeline-runner.ts +324 -0
  124. package/src/bot/provider-registry.ts +236 -0
  125. package/src/bot/run-store.ts +169 -0
  126. package/src/bot/runner.ts +311 -0
  127. package/src/bot/session-state.ts +73 -0
  128. package/src/bot/steering.ts +44 -0
  129. package/src/bot/step-executor.ts +34 -0
  130. package/src/bot/system-prompt.ts +280 -0
  131. package/src/bot/task-queue.ts +111 -0
  132. package/src/bot/types.ts +571 -0
  133. package/src/bot/utils.ts +17 -0
  134. package/src/bot/watch-daemon.ts +203 -0
  135. package/src/bot/web-approval.ts +240 -0
  136. package/src/cli-bridge.ts +41 -0
  137. package/src/cli-handlers.ts +1271 -0
  138. package/src/docs/weaver-config.md +135 -0
  139. package/src/index.ts +173 -0
  140. package/src/mcp-tools.ts +274 -0
  141. package/src/node-types/abort-task.ts +31 -0
  142. package/src/node-types/approval-gate.ts +75 -0
  143. package/src/node-types/bot-report.ts +82 -0
  144. package/src/node-types/build-context.ts +65 -0
  145. package/src/node-types/detect-provider.ts +75 -0
  146. package/src/node-types/exec-validate-retry.ts +175 -0
  147. package/src/node-types/execute-plan.ts +130 -0
  148. package/src/node-types/execute-target.ts +267 -0
  149. package/src/node-types/fix-errors.ts +68 -0
  150. package/src/node-types/genesis-apply-retry.ts +138 -0
  151. package/src/node-types/genesis-apply.ts +96 -0
  152. package/src/node-types/genesis-approve.ts +73 -0
  153. package/src/node-types/genesis-check-stabilize.ts +37 -0
  154. package/src/node-types/genesis-check-threshold.ts +34 -0
  155. package/src/node-types/genesis-commit.ts +71 -0
  156. package/src/node-types/genesis-compile-validate.ts +77 -0
  157. package/src/node-types/genesis-diff-fingerprint.ts +67 -0
  158. package/src/node-types/genesis-diff-workflow.ts +71 -0
  159. package/src/node-types/genesis-escrow-grace.ts +62 -0
  160. package/src/node-types/genesis-escrow-migrate.ts +138 -0
  161. package/src/node-types/genesis-escrow-recover.ts +99 -0
  162. package/src/node-types/genesis-escrow-stage.ts +104 -0
  163. package/src/node-types/genesis-escrow-validate.ts +120 -0
  164. package/src/node-types/genesis-load-config.ts +44 -0
  165. package/src/node-types/genesis-observe.ts +119 -0
  166. package/src/node-types/genesis-propose.ts +97 -0
  167. package/src/node-types/genesis-report.ts +95 -0
  168. package/src/node-types/genesis-snapshot.ts +30 -0
  169. package/src/node-types/genesis-try-apply.ts +165 -0
  170. package/src/node-types/genesis-update-history.ts +72 -0
  171. package/src/node-types/genesis-validate-proposal.ts +124 -0
  172. package/src/node-types/git-ops.ts +72 -0
  173. package/src/node-types/index.ts +36 -0
  174. package/src/node-types/load-config.ts +27 -0
  175. package/src/node-types/plan-task.ts +77 -0
  176. package/src/node-types/read-workflow.ts +68 -0
  177. package/src/node-types/receive-task.ts +92 -0
  178. package/src/node-types/report.ts +25 -0
  179. package/src/node-types/resolve-target.ts +64 -0
  180. package/src/node-types/route-task.ts +25 -0
  181. package/src/node-types/send-notify.ts +75 -0
  182. package/src/node-types/validate-result.ts +49 -0
  183. package/src/templates/index.ts +5 -0
  184. package/src/templates/weaver-bot-template.ts +106 -0
  185. package/src/workflows/genesis-task.ts +91 -0
  186. package/src/workflows/index.ts +3 -0
  187. package/src/workflows/weaver-bot-batch.ts +65 -0
  188. package/src/workflows/weaver-bot.ts +79 -0
  189. package/dist/templates/weaver-template.d.ts +0 -11
  190. package/dist/templates/weaver-template.d.ts.map +0 -1
  191. package/dist/templates/weaver-template.js +0 -53
  192. package/dist/templates/weaver-template.js.map +0 -1
  193. package/dist/workflows/weaver.d.ts +0 -24
  194. package/dist/workflows/weaver.d.ts.map +0 -1
  195. package/dist/workflows/weaver.js +0 -28
  196. package/dist/workflows/weaver.js.map +0 -1
@@ -0,0 +1,273 @@
1
+ import * as readline from 'node:readline';
2
+ import type { ApprovalMode, NotificationEvent } from './types.js';
3
+ import type { DashboardServer } from './dashboard.js'; // type-only, no circular runtime dep
4
+
5
+ export interface ApprovalRequest {
6
+ context: Record<string, unknown>;
7
+ prompt: string;
8
+ }
9
+
10
+ export interface ApprovalResult {
11
+ approved: boolean;
12
+ reason: string;
13
+ }
14
+
15
+ export interface ApprovalHandler {
16
+ handle(
17
+ request: ApprovalRequest,
18
+ event: NotificationEvent,
19
+ ): Promise<ApprovalResult>;
20
+ }
21
+
22
+ export interface ApprovalHandlerOptions {
23
+ timeoutSeconds: number;
24
+ webhookUrl?: string;
25
+ notifier: (event: NotificationEvent) => Promise<void>;
26
+ webOpen?: boolean;
27
+ dashboardServer?: DashboardServer;
28
+ }
29
+
30
+ export function createApprovalHandler(
31
+ mode: ApprovalMode,
32
+ options: ApprovalHandlerOptions,
33
+ ): ApprovalHandler {
34
+ switch (mode) {
35
+ case 'auto':
36
+ return new AutoApproval(options.notifier);
37
+ case 'prompt':
38
+ return new PromptApproval(options.timeoutSeconds, options.notifier);
39
+ case 'webhook':
40
+ return new WebhookApproval(
41
+ options.webhookUrl ?? '',
42
+ options.timeoutSeconds,
43
+ options.notifier,
44
+ );
45
+ case 'timeout-auto':
46
+ return new TimeoutAutoApproval(options.timeoutSeconds, options.notifier);
47
+ case 'web':
48
+ return new LazyWebApproval(options);
49
+ }
50
+ }
51
+
52
+ class AutoApproval implements ApprovalHandler {
53
+ constructor(private notifier: (event: NotificationEvent) => Promise<void>) {}
54
+
55
+ async handle(
56
+ _request: ApprovalRequest,
57
+ event: NotificationEvent,
58
+ ): Promise<ApprovalResult> {
59
+ await this.notifier(event);
60
+ return { approved: true, reason: 'auto-approved by Weaver' };
61
+ }
62
+ }
63
+
64
+ class PromptApproval implements ApprovalHandler {
65
+ constructor(
66
+ private timeoutSeconds: number,
67
+ private notifier: (event: NotificationEvent) => Promise<void>,
68
+ ) {}
69
+
70
+ async handle(
71
+ request: ApprovalRequest,
72
+ event: NotificationEvent,
73
+ ): Promise<ApprovalResult> {
74
+ await this.notifier(event);
75
+
76
+ // Non-TTY: fall back to timeout-auto
77
+ if (!process.stdin.isTTY) {
78
+ console.log(
79
+ `[weaver] No TTY available, auto-approving after ${this.timeoutSeconds}s`,
80
+ );
81
+ await new Promise((r) => setTimeout(r, this.timeoutSeconds * 1000));
82
+ return { approved: true, reason: 'auto-approved (no TTY)' };
83
+ }
84
+
85
+ const summary =
86
+ typeof request.context === 'string'
87
+ ? request.context
88
+ : JSON.stringify(request.context, null, 2);
89
+
90
+ console.log('\n[weaver] Approval required:');
91
+ console.log(summary.slice(0, 500));
92
+ if (summary.length > 500) console.log(' ... (truncated)');
93
+
94
+ const answer = await this.askWithTimeout(
95
+ `\n[weaver] Approve? (y/n, auto-approves in ${this.timeoutSeconds}s): `,
96
+ this.timeoutSeconds,
97
+ );
98
+
99
+ if (answer === null) {
100
+ return {
101
+ approved: true,
102
+ reason: `auto-approved after ${this.timeoutSeconds}s timeout`,
103
+ };
104
+ }
105
+
106
+ const approved = answer.trim().toLowerCase().startsWith('y');
107
+ return {
108
+ approved,
109
+ reason: approved ? 'approved by user' : 'rejected by user',
110
+ };
111
+ }
112
+
113
+ private askWithTimeout(
114
+ prompt: string,
115
+ timeoutSeconds: number,
116
+ ): Promise<string | null> {
117
+ return new Promise((resolve) => {
118
+ const rl = readline.createInterface({
119
+ input: process.stdin,
120
+ output: process.stdout,
121
+ });
122
+
123
+ const timer = setTimeout(() => {
124
+ console.log('\n[weaver] Timeout reached, auto-approving');
125
+ rl.close();
126
+ resolve(null);
127
+ }, timeoutSeconds * 1000);
128
+
129
+ rl.question(prompt, (answer) => {
130
+ clearTimeout(timer);
131
+ rl.close();
132
+ resolve(answer);
133
+ });
134
+ });
135
+ }
136
+ }
137
+
138
+ class WebhookApproval implements ApprovalHandler {
139
+ constructor(
140
+ private webhookUrl: string,
141
+ private timeoutSeconds: number,
142
+ private notifier: (event: NotificationEvent) => Promise<void>,
143
+ ) {}
144
+
145
+ async handle(
146
+ request: ApprovalRequest,
147
+ event: NotificationEvent,
148
+ ): Promise<ApprovalResult> {
149
+ await this.notifier(event);
150
+
151
+ if (!this.webhookUrl) {
152
+ console.error(
153
+ '[weaver] Webhook approval requires webhookUrl in approval config, falling back to timeout-auto',
154
+ );
155
+ await new Promise((r) => setTimeout(r, this.timeoutSeconds * 1000));
156
+ return { approved: true, reason: 'auto-approved (no webhookUrl)' };
157
+ }
158
+
159
+ // POST the proposal to the webhook
160
+ try {
161
+ const resp = await fetch(this.webhookUrl, {
162
+ method: 'POST',
163
+ headers: { 'Content-Type': 'application/json' },
164
+ body: JSON.stringify({
165
+ type: 'approval-request',
166
+ proposal: request.context,
167
+ prompt: request.prompt,
168
+ timeoutSeconds: this.timeoutSeconds,
169
+ }),
170
+ });
171
+
172
+ if (!resp.ok) {
173
+ console.error(
174
+ `[weaver] Webhook POST failed: ${resp.status}, falling back to timeout-auto`,
175
+ );
176
+ await new Promise((r) => setTimeout(r, this.timeoutSeconds * 1000));
177
+ return { approved: true, reason: 'auto-approved (webhook error)' };
178
+ }
179
+
180
+ // Poll for response
181
+ const pollUrl =
182
+ resp.headers.get('location') ?? this.webhookUrl + '/status';
183
+ return await this.pollForDecision(pollUrl);
184
+ } catch (err: unknown) {
185
+ const msg = err instanceof Error ? err.message : String(err);
186
+ console.error(
187
+ `[weaver] Webhook error: ${msg}, falling back to timeout-auto`,
188
+ );
189
+ await new Promise((r) => setTimeout(r, this.timeoutSeconds * 1000));
190
+ return { approved: true, reason: 'auto-approved (webhook error)' };
191
+ }
192
+ }
193
+
194
+ private async pollForDecision(url: string): Promise<ApprovalResult> {
195
+ const deadline = Date.now() + this.timeoutSeconds * 1000;
196
+ const interval = 5000;
197
+
198
+ while (Date.now() < deadline) {
199
+ await new Promise((r) => setTimeout(r, interval));
200
+
201
+ try {
202
+ const resp = await fetch(url);
203
+ if (!resp.ok) continue;
204
+
205
+ const body = (await resp.json()) as {
206
+ approved?: boolean;
207
+ reason?: string;
208
+ pending?: boolean;
209
+ };
210
+ if (body.pending) continue;
211
+
212
+ return {
213
+ approved: body.approved ?? true,
214
+ reason: body.reason ?? (body.approved ? 'approved via webhook' : 'rejected via webhook'),
215
+ };
216
+ } catch {
217
+ continue;
218
+ }
219
+ }
220
+
221
+ return {
222
+ approved: true,
223
+ reason: `auto-approved after ${this.timeoutSeconds}s webhook timeout`,
224
+ };
225
+ }
226
+ }
227
+
228
+ class TimeoutAutoApproval implements ApprovalHandler {
229
+ constructor(
230
+ private timeoutSeconds: number,
231
+ private notifier: (event: NotificationEvent) => Promise<void>,
232
+ ) {}
233
+
234
+ async handle(
235
+ _request: ApprovalRequest,
236
+ event: NotificationEvent,
237
+ ): Promise<ApprovalResult> {
238
+ await this.notifier(event);
239
+ console.log(
240
+ `[weaver] Waiting ${this.timeoutSeconds}s before auto-approving...`,
241
+ );
242
+ await new Promise((r) => setTimeout(r, this.timeoutSeconds * 1000));
243
+ return {
244
+ approved: true,
245
+ reason: `auto-approved after ${this.timeoutSeconds}s timeout`,
246
+ };
247
+ }
248
+ }
249
+
250
+ class LazyWebApproval implements ApprovalHandler {
251
+ private inner: ApprovalHandler | null = null;
252
+ private options: ApprovalHandlerOptions;
253
+
254
+ constructor(options: ApprovalHandlerOptions) {
255
+ this.options = options;
256
+ }
257
+
258
+ async handle(
259
+ request: ApprovalRequest,
260
+ event: NotificationEvent,
261
+ ): Promise<ApprovalResult> {
262
+ if (!this.inner) {
263
+ const { WebApprovalHandler } = await import('./web-approval.js');
264
+ this.inner = new WebApprovalHandler({
265
+ timeoutSeconds: this.options.timeoutSeconds,
266
+ open: this.options.webOpen ?? true,
267
+ notifier: this.options.notifier,
268
+ dashboardServer: this.options.dashboardServer,
269
+ });
270
+ }
271
+ return this.inner.handle(request, event);
272
+ }
273
+ }
@@ -0,0 +1,45 @@
1
+ import type { AuditEventType, AuditEventCallback } from './types.js';
2
+ import { AuditStore } from './audit-store.js';
3
+
4
+ let store: AuditStore | null = null;
5
+ let currentRunId: string | null = null;
6
+ let onEvent: AuditEventCallback | undefined;
7
+
8
+ export function initAuditLogger(runId: string, callback?: AuditEventCallback): void {
9
+ try {
10
+ store = new AuditStore();
11
+ } catch {
12
+ store = null;
13
+ }
14
+ currentRunId = runId;
15
+ onEvent = callback;
16
+ }
17
+
18
+ export function auditEmit(type: AuditEventType, data?: Record<string, unknown>): void {
19
+ if (!currentRunId) return;
20
+
21
+ const event = {
22
+ type,
23
+ timestamp: new Date().toISOString(),
24
+ runId: currentRunId,
25
+ data,
26
+ };
27
+
28
+ try {
29
+ store?.emit(event);
30
+ } catch {
31
+ // non-fatal
32
+ }
33
+
34
+ try {
35
+ onEvent?.(event);
36
+ } catch {
37
+ // non-fatal
38
+ }
39
+ }
40
+
41
+ export function teardownAuditLogger(): void {
42
+ store = null;
43
+ currentRunId = null;
44
+ onEvent = undefined;
45
+ }
@@ -0,0 +1,69 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import type { AuditEvent } from './types.js';
5
+
6
+ export class AuditStore {
7
+ private readonly dir: string;
8
+ private readonly filePath: string;
9
+
10
+ constructor(storeDir?: string) {
11
+ this.dir = storeDir ?? process.env.WEAVER_HISTORY_DIR ?? path.join(os.homedir(), '.weaver');
12
+ fs.mkdirSync(this.dir, { recursive: true });
13
+ this.filePath = path.join(this.dir, 'audit.ndjson');
14
+ }
15
+
16
+ emit(event: AuditEvent): void {
17
+ fs.appendFileSync(this.filePath, JSON.stringify(event) + '\n', 'utf-8');
18
+ }
19
+
20
+ queryByRun(runId: string): AuditEvent[] {
21
+ return this.readAll().filter((e) => e.runId === runId);
22
+ }
23
+
24
+ queryRecent(limit = 50): AuditEvent[] {
25
+ const all = this.readAll();
26
+ return all.slice(-limit);
27
+ }
28
+
29
+ prune(maxAgeDays: number): number {
30
+ const all = this.readAll();
31
+ if (all.length === 0) return 0;
32
+
33
+ const cutoff = new Date(Date.now() - maxAgeDays * 86_400_000).toISOString();
34
+ const kept = all.filter((e) => e.timestamp >= cutoff);
35
+ const pruned = all.length - kept.length;
36
+
37
+ if (pruned === 0) return 0;
38
+
39
+ const tmpPath = this.filePath + '.tmp';
40
+ fs.writeFileSync(tmpPath, kept.map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
41
+ fs.renameSync(tmpPath, this.filePath);
42
+
43
+ return pruned;
44
+ }
45
+
46
+ clear(): boolean {
47
+ if (!fs.existsSync(this.filePath)) return false;
48
+ fs.unlinkSync(this.filePath);
49
+ return true;
50
+ }
51
+
52
+ private readAll(): AuditEvent[] {
53
+ if (!fs.existsSync(this.filePath)) return [];
54
+
55
+ const content = fs.readFileSync(this.filePath, 'utf-8');
56
+ const lines = content.split('\n').filter((line) => line.trim().length > 0);
57
+ const events: AuditEvent[] = [];
58
+
59
+ for (const line of lines) {
60
+ try {
61
+ events.push(JSON.parse(line) as AuditEvent);
62
+ } catch {
63
+ // skip corrupt line
64
+ }
65
+ }
66
+
67
+ return events;
68
+ }
69
+ }
@@ -0,0 +1,99 @@
1
+ import type { BotAgentProvider } from './agent-provider.js';
2
+ import type { ApprovalMode, NotificationEvent, StreamChunk, ToolDefinition, ToolUseResult } from './types.js';
3
+ import { createApprovalHandler } from './approvals.js';
4
+ import type { DashboardServer } from './dashboard.js';
5
+
6
+ export interface BotChannelContext {
7
+ projectDir: string;
8
+ workflowFile?: string;
9
+ cycle?: number;
10
+ }
11
+
12
+ export class BotAgentChannel {
13
+ private provider: BotAgentProvider;
14
+ private approvalHandler: ReturnType<typeof createApprovalHandler>;
15
+ private notifier: (event: NotificationEvent) => Promise<void>;
16
+ private context: BotChannelContext;
17
+
18
+ constructor(
19
+ provider: BotAgentProvider,
20
+ options: {
21
+ approvalMode: ApprovalMode;
22
+ approvalTimeoutSeconds: number;
23
+ approvalWebhookUrl?: string;
24
+ approvalWebOpen?: boolean;
25
+ dashboardServer?: DashboardServer;
26
+ notifier: (event: NotificationEvent) => Promise<void>;
27
+ context: BotChannelContext;
28
+ },
29
+ ) {
30
+ this.provider = provider;
31
+ this.notifier = options.notifier;
32
+ this.context = options.context;
33
+ this.approvalHandler = createApprovalHandler(options.approvalMode, {
34
+ timeoutSeconds: options.approvalTimeoutSeconds,
35
+ webhookUrl: options.approvalWebhookUrl,
36
+ notifier: options.notifier,
37
+ webOpen: options.approvalWebOpen,
38
+ dashboardServer: options.dashboardServer,
39
+ });
40
+ }
41
+
42
+ async request(agentRequest: {
43
+ agentId: string;
44
+ context: Record<string, unknown>;
45
+ prompt: string;
46
+ }): Promise<object> {
47
+ if (agentRequest.agentId.includes('approval')) {
48
+ return this.handleApproval(agentRequest);
49
+ }
50
+ return this.provider.decide(agentRequest);
51
+ }
52
+
53
+ private async handleApproval(request: {
54
+ context: Record<string, unknown>;
55
+ prompt: string;
56
+ }): Promise<object> {
57
+ const event: NotificationEvent = {
58
+ type: 'approval-needed',
59
+ cycle: this.context.cycle,
60
+ projectDir: this.context.projectDir,
61
+ workflowFile: this.context.workflowFile,
62
+ proposal: request.context,
63
+ };
64
+
65
+ return this.approvalHandler.handle(request, event);
66
+ }
67
+
68
+ async requestWithTools(
69
+ agentRequest: { agentId: string; context: Record<string, unknown>; prompt: string },
70
+ tools: ToolDefinition[],
71
+ ): Promise<{ result: Record<string, unknown>; toolCalls?: ToolUseResult[] }> {
72
+ if (this.provider.decideWithTools) {
73
+ return this.provider.decideWithTools({ ...agentRequest, tools });
74
+ }
75
+ const result = await this.provider.decide(agentRequest);
76
+ return { result };
77
+ }
78
+
79
+ async *streamRequest(agentRequest: {
80
+ agentId: string;
81
+ context: Record<string, unknown>;
82
+ prompt: string;
83
+ }): AsyncIterable<StreamChunk> {
84
+ if (this.provider.stream) {
85
+ yield* this.provider.stream(agentRequest);
86
+ return;
87
+ }
88
+ const result = await this.provider.decide(agentRequest);
89
+ yield { type: 'text', text: JSON.stringify(result) };
90
+ yield { type: 'done' };
91
+ }
92
+
93
+ // Compat stubs for AgentChannel interface (used by executor, not by nodes)
94
+ onPause(): Promise<object> {
95
+ return new Promise(() => {});
96
+ }
97
+ resume(_result: object): void {}
98
+ fail(_reason: string): void {}
99
+ }
@@ -0,0 +1,169 @@
1
+ import { execSync, spawn } from 'node:child_process';
2
+ import type { BotAgentProvider, OnUsageCallback, StreamChunk, ToolDefinition, ToolUseResult } from './types.js';
3
+ import { buildSystemPrompt } from './system-prompt.js';
4
+ import { parseStreamLine, extractTextFromChunks } from './cli-stream-parser.js';
5
+
6
+ // Strip CLAUDECODE from child env so nested claude CLI invocations work.
7
+ const childEnv = { ...process.env };
8
+ delete childEnv.CLAUDECODE;
9
+
10
+ export class CliAgentProvider implements BotAgentProvider {
11
+ private cli: 'claude-cli' | 'copilot-cli';
12
+ private model?: string;
13
+ onUsage?: OnUsageCallback;
14
+
15
+ constructor(cli: 'claude-cli' | 'copilot-cli', model?: string) {
16
+ this.cli = cli;
17
+ this.model = model;
18
+ }
19
+
20
+ async decide(request: {
21
+ agentId: string;
22
+ context: Record<string, unknown>;
23
+ prompt: string;
24
+ }): Promise<Record<string, unknown>> {
25
+ const systemPrompt = await buildSystemPrompt();
26
+
27
+ const contextStr =
28
+ typeof request.context === 'string'
29
+ ? request.context
30
+ : JSON.stringify(request.context, null, 2);
31
+
32
+ const fullPrompt = `${systemPrompt}\n\nContext:\n${contextStr}\n\nInstructions:\n${request.prompt}`;
33
+
34
+ if (this.cli === 'claude-cli') {
35
+ const chunks: StreamChunk[] = [];
36
+ for await (const chunk of this.streamRaw(fullPrompt)) {
37
+ chunks.push(chunk);
38
+ }
39
+ const raw = extractTextFromChunks(chunks);
40
+ return this.parseJson(raw);
41
+ }
42
+
43
+ // copilot-cli: no stream-json support, keep execSync
44
+ const raw = execSync('copilot -p --silent --allow-all-tools', {
45
+ input: fullPrompt,
46
+ encoding: 'utf-8',
47
+ stdio: ['pipe', 'pipe', 'pipe'],
48
+ timeout: 120_000,
49
+ env: childEnv,
50
+ }).trim();
51
+
52
+ return this.parseJson(raw);
53
+ }
54
+
55
+ async *stream(request: {
56
+ agentId: string;
57
+ context: Record<string, unknown>;
58
+ prompt: string;
59
+ }): AsyncIterable<StreamChunk> {
60
+ const systemPrompt = await buildSystemPrompt();
61
+
62
+ const contextStr =
63
+ typeof request.context === 'string'
64
+ ? request.context
65
+ : JSON.stringify(request.context, null, 2);
66
+
67
+ const fullPrompt = `${systemPrompt}\n\nContext:\n${contextStr}\n\nInstructions:\n${request.prompt}`;
68
+
69
+ if (this.cli === 'claude-cli') {
70
+ yield* this.streamRaw(fullPrompt);
71
+ } else {
72
+ const result = await this.decide(request);
73
+ yield { type: 'text', text: JSON.stringify(result) };
74
+ yield { type: 'done' };
75
+ }
76
+ }
77
+
78
+ async decideWithTools(request: {
79
+ agentId: string;
80
+ context: Record<string, unknown>;
81
+ prompt: string;
82
+ tools: ToolDefinition[];
83
+ }): Promise<{ result: Record<string, unknown>; toolCalls?: ToolUseResult[] }> {
84
+ const result = await this.decide(request);
85
+ return { result };
86
+ }
87
+
88
+ private async *streamRaw(fullPrompt: string): AsyncGenerator<StreamChunk> {
89
+ const args = ['-p', '--output-format', 'stream-json', '--include-partial-messages'];
90
+ if (this.model) {
91
+ args.push('--model', this.model);
92
+ }
93
+
94
+ const child = spawn('claude', args, {
95
+ stdio: ['pipe', 'pipe', 'pipe'],
96
+ env: childEnv,
97
+ });
98
+
99
+ child.stdin.write(fullPrompt);
100
+ child.stdin.end();
101
+
102
+ const timeout = setTimeout(() => {
103
+ child.kill('SIGTERM');
104
+ }, 120_000);
105
+
106
+ let buffer = '';
107
+
108
+ try {
109
+ for await (const data of child.stdout) {
110
+ buffer += data.toString();
111
+ const lines = buffer.split('\n');
112
+ buffer = lines.pop()!;
113
+
114
+ for (const line of lines) {
115
+ const chunk = parseStreamLine(line);
116
+ if (!chunk) continue;
117
+
118
+ if (chunk.type === 'usage' && chunk.usage && this.onUsage) {
119
+ this.onUsage('stream', this.model ?? 'unknown', chunk.usage);
120
+ }
121
+
122
+ yield chunk;
123
+ }
124
+ }
125
+
126
+ // Process remaining buffer
127
+ if (buffer.trim()) {
128
+ const chunk = parseStreamLine(buffer);
129
+ if (chunk) {
130
+ if (chunk.type === 'usage' && chunk.usage && this.onUsage) {
131
+ this.onUsage('stream', this.model ?? 'unknown', chunk.usage);
132
+ }
133
+ yield chunk;
134
+ }
135
+ }
136
+ } finally {
137
+ clearTimeout(timeout);
138
+ }
139
+
140
+ // Wait for process exit
141
+ await new Promise<void>((resolve, reject) => {
142
+ child.on('close', (code) => {
143
+ if (code && code !== 0) {
144
+ reject(new Error(`claude CLI exited with code ${code}`));
145
+ } else {
146
+ resolve();
147
+ }
148
+ });
149
+ child.on('error', reject);
150
+ });
151
+ }
152
+
153
+ private parseJson(text: string): Record<string, unknown> {
154
+ let cleaned = text.trim();
155
+ if (cleaned.startsWith('```')) {
156
+ cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```\s*$/, '');
157
+ }
158
+
159
+ try {
160
+ return JSON.parse(cleaned);
161
+ } catch {
162
+ const match = cleaned.match(/\{[\s\S]*\}/);
163
+ if (match) {
164
+ return JSON.parse(match[0]);
165
+ }
166
+ throw new Error(`Failed to parse CLI response as JSON: ${text.slice(0, 200)}`);
167
+ }
168
+ }
169
+ }