reflectt-node 0.1.7 → 0.1.11

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 (220) hide show
  1. package/README.md +13 -0
  2. package/defaults/TEAM-ROLES.yaml +317 -5
  3. package/defaults/gitignore.template +23 -0
  4. package/dist/agent-config.d.ts +51 -0
  5. package/dist/agent-config.d.ts.map +1 -0
  6. package/dist/agent-config.js +129 -0
  7. package/dist/agent-config.js.map +1 -0
  8. package/dist/agent-config.test.d.ts +2 -0
  9. package/dist/agent-config.test.d.ts.map +1 -0
  10. package/dist/agent-config.test.js +91 -0
  11. package/dist/agent-config.test.js.map +1 -0
  12. package/dist/agent-memories.d.ts +58 -0
  13. package/dist/agent-memories.d.ts.map +1 -0
  14. package/dist/agent-memories.js +168 -0
  15. package/dist/agent-memories.js.map +1 -0
  16. package/dist/agent-memories.test.d.ts +2 -0
  17. package/dist/agent-memories.test.d.ts.map +1 -0
  18. package/dist/agent-memories.test.js +327 -0
  19. package/dist/agent-memories.test.js.map +1 -0
  20. package/dist/agent-messaging.d.ts +50 -0
  21. package/dist/agent-messaging.d.ts.map +1 -0
  22. package/dist/agent-messaging.js +103 -0
  23. package/dist/agent-messaging.js.map +1 -0
  24. package/dist/agent-messaging.test.d.ts +2 -0
  25. package/dist/agent-messaging.test.d.ts.map +1 -0
  26. package/dist/agent-messaging.test.js +105 -0
  27. package/dist/agent-messaging.test.js.map +1 -0
  28. package/dist/agent-runs.d.ts +158 -0
  29. package/dist/agent-runs.d.ts.map +1 -0
  30. package/dist/agent-runs.js +514 -0
  31. package/dist/agent-runs.js.map +1 -0
  32. package/dist/agent-runs.test.d.ts +2 -0
  33. package/dist/agent-runs.test.d.ts.map +1 -0
  34. package/dist/agent-runs.test.js +386 -0
  35. package/dist/agent-runs.test.js.map +1 -0
  36. package/dist/approval-queue.test.d.ts +2 -0
  37. package/dist/approval-queue.test.d.ts.map +1 -0
  38. package/dist/approval-queue.test.js +118 -0
  39. package/dist/approval-queue.test.js.map +1 -0
  40. package/dist/artifact-store.d.ts +55 -0
  41. package/dist/artifact-store.d.ts.map +1 -0
  42. package/dist/artifact-store.js +128 -0
  43. package/dist/artifact-store.js.map +1 -0
  44. package/dist/artifact-store.test.d.ts +2 -0
  45. package/dist/artifact-store.test.d.ts.map +1 -0
  46. package/dist/artifact-store.test.js +119 -0
  47. package/dist/artifact-store.test.js.map +1 -0
  48. package/dist/boardHealthWorker.d.ts +32 -0
  49. package/dist/boardHealthWorker.d.ts.map +1 -1
  50. package/dist/boardHealthWorker.js +69 -2
  51. package/dist/boardHealthWorker.js.map +1 -1
  52. package/dist/buildInfo.d.ts.map +1 -1
  53. package/dist/buildInfo.js +47 -10
  54. package/dist/buildInfo.js.map +1 -1
  55. package/dist/canvas-input.test.d.ts +2 -0
  56. package/dist/canvas-input.test.d.ts.map +1 -0
  57. package/dist/canvas-input.test.js +96 -0
  58. package/dist/canvas-input.test.js.map +1 -0
  59. package/dist/canvas-render.test.d.ts +2 -0
  60. package/dist/canvas-render.test.d.ts.map +1 -0
  61. package/dist/canvas-render.test.js +95 -0
  62. package/dist/canvas-render.test.js.map +1 -0
  63. package/dist/capabilities/browser.d.ts +75 -0
  64. package/dist/capabilities/browser.d.ts.map +1 -0
  65. package/dist/capabilities/browser.js +172 -0
  66. package/dist/capabilities/browser.js.map +1 -0
  67. package/dist/channels.d.ts +1 -1
  68. package/dist/chat.d.ts +4 -0
  69. package/dist/chat.d.ts.map +1 -1
  70. package/dist/chat.js +6 -2
  71. package/dist/chat.js.map +1 -1
  72. package/dist/cli.js +41 -14
  73. package/dist/cli.js.map +1 -1
  74. package/dist/cloud.d.ts +2 -0
  75. package/dist/cloud.d.ts.map +1 -1
  76. package/dist/cloud.js +151 -64
  77. package/dist/cloud.js.map +1 -1
  78. package/dist/continuity-loop.d.ts.map +1 -1
  79. package/dist/continuity-loop.js +297 -29
  80. package/dist/continuity-loop.js.map +1 -1
  81. package/dist/cost-enforcement.d.ts +38 -0
  82. package/dist/cost-enforcement.d.ts.map +1 -0
  83. package/dist/cost-enforcement.js +84 -0
  84. package/dist/cost-enforcement.js.map +1 -0
  85. package/dist/db.d.ts.map +1 -1
  86. package/dist/db.js +131 -0
  87. package/dist/db.js.map +1 -1
  88. package/dist/deploy-monitor.d.ts +18 -0
  89. package/dist/deploy-monitor.d.ts.map +1 -0
  90. package/dist/deploy-monitor.js +165 -0
  91. package/dist/deploy-monitor.js.map +1 -0
  92. package/dist/e2e-loop-proof.test.d.ts +2 -0
  93. package/dist/e2e-loop-proof.test.d.ts.map +1 -0
  94. package/dist/e2e-loop-proof.test.js +104 -0
  95. package/dist/e2e-loop-proof.test.js.map +1 -0
  96. package/dist/email-sms-send.test.d.ts +2 -0
  97. package/dist/email-sms-send.test.d.ts.map +1 -0
  98. package/dist/email-sms-send.test.js +96 -0
  99. package/dist/email-sms-send.test.js.map +1 -0
  100. package/dist/events.d.ts +1 -1
  101. package/dist/events.d.ts.map +1 -1
  102. package/dist/events.js +2 -0
  103. package/dist/events.js.map +1 -1
  104. package/dist/executionSweeper.d.ts +1 -0
  105. package/dist/executionSweeper.d.ts.map +1 -1
  106. package/dist/executionSweeper.js +43 -7
  107. package/dist/executionSweeper.js.map +1 -1
  108. package/dist/files.d.ts.map +1 -1
  109. package/dist/files.js +17 -3
  110. package/dist/files.js.map +1 -1
  111. package/dist/fingerprint.d.ts +30 -0
  112. package/dist/fingerprint.d.ts.map +1 -0
  113. package/dist/fingerprint.js +117 -0
  114. package/dist/fingerprint.js.map +1 -0
  115. package/dist/github-webhook-attribution.d.ts +38 -0
  116. package/dist/github-webhook-attribution.d.ts.map +1 -0
  117. package/dist/github-webhook-attribution.js +123 -0
  118. package/dist/github-webhook-attribution.js.map +1 -0
  119. package/dist/github-webhook-chat.d.ts +75 -0
  120. package/dist/github-webhook-chat.d.ts.map +1 -0
  121. package/dist/github-webhook-chat.js +108 -0
  122. package/dist/github-webhook-chat.js.map +1 -0
  123. package/dist/handoff-state.test.d.ts +2 -0
  124. package/dist/handoff-state.test.d.ts.map +1 -0
  125. package/dist/handoff-state.test.js +102 -0
  126. package/dist/handoff-state.test.js.map +1 -0
  127. package/dist/health.d.ts +9 -0
  128. package/dist/health.d.ts.map +1 -1
  129. package/dist/health.js +18 -0
  130. package/dist/health.js.map +1 -1
  131. package/dist/host-error-correlation.d.ts +65 -0
  132. package/dist/host-error-correlation.d.ts.map +1 -0
  133. package/dist/host-error-correlation.js +123 -0
  134. package/dist/host-error-correlation.js.map +1 -0
  135. package/dist/inbox.d.ts.map +1 -1
  136. package/dist/inbox.js +4 -0
  137. package/dist/inbox.js.map +1 -1
  138. package/dist/index.js +76 -11
  139. package/dist/index.js.map +1 -1
  140. package/dist/notificationDedupeGuard.d.ts +4 -0
  141. package/dist/notificationDedupeGuard.d.ts.map +1 -1
  142. package/dist/notificationDedupeGuard.js +8 -4
  143. package/dist/notificationDedupeGuard.js.map +1 -1
  144. package/dist/presence.d.ts +37 -5
  145. package/dist/presence.d.ts.map +1 -1
  146. package/dist/presence.js +127 -16
  147. package/dist/presence.js.map +1 -1
  148. package/dist/pulse.d.ts +7 -0
  149. package/dist/pulse.d.ts.map +1 -1
  150. package/dist/pulse.js +15 -0
  151. package/dist/pulse.js.map +1 -1
  152. package/dist/review-sla.d.ts +9 -0
  153. package/dist/review-sla.d.ts.map +1 -0
  154. package/dist/review-sla.js +51 -0
  155. package/dist/review-sla.js.map +1 -0
  156. package/dist/review-state.d.ts +9 -0
  157. package/dist/review-state.d.ts.map +1 -0
  158. package/dist/review-state.js +17 -0
  159. package/dist/review-state.js.map +1 -0
  160. package/dist/routing-enforcement.test.d.ts +2 -0
  161. package/dist/routing-enforcement.test.d.ts.map +1 -0
  162. package/dist/routing-enforcement.test.js +86 -0
  163. package/dist/routing-enforcement.test.js.map +1 -0
  164. package/dist/run-retention.test.d.ts +2 -0
  165. package/dist/run-retention.test.d.ts.map +1 -0
  166. package/dist/run-retention.test.js +57 -0
  167. package/dist/run-retention.test.js.map +1 -0
  168. package/dist/run-stream.test.d.ts +2 -0
  169. package/dist/run-stream.test.d.ts.map +1 -0
  170. package/dist/run-stream.test.js +70 -0
  171. package/dist/run-stream.test.js.map +1 -0
  172. package/dist/schedule.d.ts +60 -0
  173. package/dist/schedule.d.ts.map +1 -0
  174. package/dist/schedule.js +176 -0
  175. package/dist/schedule.js.map +1 -0
  176. package/dist/server.d.ts.map +1 -1
  177. package/dist/server.js +1714 -88
  178. package/dist/server.js.map +1 -1
  179. package/dist/suppression-ledger.d.ts.map +1 -1
  180. package/dist/suppression-ledger.js +12 -3
  181. package/dist/suppression-ledger.js.map +1 -1
  182. package/dist/system-loop-state.d.ts +1 -1
  183. package/dist/system-loop-state.d.ts.map +1 -1
  184. package/dist/system-loop-state.js +1 -0
  185. package/dist/system-loop-state.js.map +1 -1
  186. package/dist/tasks.d.ts +9 -1
  187. package/dist/tasks.d.ts.map +1 -1
  188. package/dist/tasks.js +238 -41
  189. package/dist/tasks.js.map +1 -1
  190. package/dist/todoHoardingGuard.d.ts +17 -0
  191. package/dist/todoHoardingGuard.d.ts.map +1 -1
  192. package/dist/todoHoardingGuard.js +25 -2
  193. package/dist/todoHoardingGuard.js.map +1 -1
  194. package/dist/types.d.ts +1 -1
  195. package/dist/types.d.ts.map +1 -1
  196. package/dist/usage-tracking.d.ts +26 -0
  197. package/dist/usage-tracking.d.ts.map +1 -1
  198. package/dist/usage-tracking.js +91 -4
  199. package/dist/usage-tracking.js.map +1 -1
  200. package/dist/webhook-storage.d.ts +50 -0
  201. package/dist/webhook-storage.d.ts.map +1 -0
  202. package/dist/webhook-storage.js +102 -0
  203. package/dist/webhook-storage.js.map +1 -0
  204. package/dist/webhook-storage.test.d.ts +2 -0
  205. package/dist/webhook-storage.test.d.ts.map +1 -0
  206. package/dist/webhook-storage.test.js +86 -0
  207. package/dist/webhook-storage.test.js.map +1 -0
  208. package/dist/workflow-templates.d.ts +44 -0
  209. package/dist/workflow-templates.d.ts.map +1 -0
  210. package/dist/workflow-templates.js +154 -0
  211. package/dist/workflow-templates.js.map +1 -0
  212. package/dist/workflow-templates.test.d.ts +2 -0
  213. package/dist/workflow-templates.test.d.ts.map +1 -0
  214. package/dist/workflow-templates.test.js +76 -0
  215. package/dist/workflow-templates.test.js.map +1 -0
  216. package/package.json +3 -1
  217. package/public/dashboard.js +130 -37
  218. package/public/design-tokens-platform.md +118 -0
  219. package/public/design-tokens.css +195 -0
  220. package/public/docs.md +145 -2
@@ -0,0 +1,55 @@
1
+ export interface Artifact {
2
+ id: string;
3
+ agentId: string;
4
+ teamId: string;
5
+ runId: string | null;
6
+ taskId: string | null;
7
+ name: string;
8
+ mimeType: string;
9
+ sizeBytes: number;
10
+ storagePath: string;
11
+ metadata: Record<string, unknown>;
12
+ createdAt: number;
13
+ }
14
+ /**
15
+ * Store an artifact (writes file to disk + row to DB).
16
+ */
17
+ export declare function storeArtifact(opts: {
18
+ agentId: string;
19
+ teamId?: string;
20
+ runId?: string;
21
+ taskId?: string;
22
+ name: string;
23
+ mimeType?: string;
24
+ content: Buffer | string;
25
+ metadata?: Record<string, unknown>;
26
+ }): Artifact;
27
+ /**
28
+ * Get artifact metadata by ID.
29
+ */
30
+ export declare function getArtifact(id: string): Artifact | null;
31
+ /**
32
+ * Read artifact content from disk.
33
+ */
34
+ export declare function readArtifactContent(id: string): Buffer | null;
35
+ /**
36
+ * List artifacts for an agent, run, or task.
37
+ */
38
+ export declare function listArtifacts(opts: {
39
+ agentId?: string;
40
+ runId?: string;
41
+ taskId?: string;
42
+ limit?: number;
43
+ }): Artifact[];
44
+ /**
45
+ * Delete artifact (removes file + DB row).
46
+ */
47
+ export declare function deleteArtifact(id: string): boolean;
48
+ /**
49
+ * Get total storage used by an agent.
50
+ */
51
+ export declare function getStorageUsage(agentId: string): {
52
+ totalBytes: number;
53
+ count: number;
54
+ };
55
+ //# sourceMappingURL=artifact-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-store.d.ts","sourceRoot":"","sources":["../src/artifact-store.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,SAAS,EAAE,MAAM,CAAA;CAClB;AAwCD;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,GAAG,QAAQ,CAwBX;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAIvD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,GAAG,QAAQ,EAAE,CAab;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAItF"}
@@ -0,0 +1,128 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Host-native artifact store — run/task-linked file storage
3
+ import { getDb } from './db.js';
4
+ import { mkdirSync, writeFileSync, readFileSync, existsSync, unlinkSync } from 'node:fs';
5
+ import { join, dirname } from 'node:path';
6
+ import { homedir } from 'node:os';
7
+ function rowToArtifact(row) {
8
+ return {
9
+ id: row.id,
10
+ agentId: row.agent_id,
11
+ teamId: row.team_id,
12
+ runId: row.run_id,
13
+ taskId: row.task_id,
14
+ name: row.name,
15
+ mimeType: row.mime_type,
16
+ sizeBytes: row.size_bytes,
17
+ storagePath: row.storage_path,
18
+ metadata: JSON.parse(row.metadata || '{}'),
19
+ createdAt: row.created_at,
20
+ };
21
+ }
22
+ function generateId() {
23
+ return `art-${Date.now()}-${Math.random().toString(36).slice(2, 13)}`;
24
+ }
25
+ function getStorageRoot() {
26
+ return join(homedir(), '.reflectt', 'artifacts');
27
+ }
28
+ /**
29
+ * Store an artifact (writes file to disk + row to DB).
30
+ */
31
+ export function storeArtifact(opts) {
32
+ const db = getDb();
33
+ const id = generateId();
34
+ const now = Date.now();
35
+ const teamId = opts.teamId ?? 'default';
36
+ // Storage path: ~/.reflectt/artifacts/<agentId>/<YYYY-MM>/<id>-<name>
37
+ const datePrefix = new Date(now).toISOString().slice(0, 7); // YYYY-MM
38
+ const safeName = opts.name.replace(/[^a-zA-Z0-9._-]/g, '_');
39
+ const storagePath = join(getStorageRoot(), opts.agentId, datePrefix, `${id}-${safeName}`);
40
+ // Write file
41
+ mkdirSync(dirname(storagePath), { recursive: true });
42
+ const content = typeof opts.content === 'string' ? Buffer.from(opts.content) : opts.content;
43
+ writeFileSync(storagePath, content);
44
+ const mimeType = opts.mimeType ?? guessMimeType(opts.name);
45
+ db.prepare(`
46
+ INSERT INTO artifacts (id, agent_id, team_id, run_id, task_id, name, mime_type, size_bytes, storage_path, metadata, created_at)
47
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
48
+ `).run(id, opts.agentId, teamId, opts.runId ?? null, opts.taskId ?? null, opts.name, mimeType, content.length, storagePath, JSON.stringify(opts.metadata ?? {}), now);
49
+ return { id, agentId: opts.agentId, teamId, runId: opts.runId ?? null, taskId: opts.taskId ?? null, name: opts.name, mimeType, sizeBytes: content.length, storagePath, metadata: opts.metadata ?? {}, createdAt: now };
50
+ }
51
+ /**
52
+ * Get artifact metadata by ID.
53
+ */
54
+ export function getArtifact(id) {
55
+ const db = getDb();
56
+ const row = db.prepare('SELECT * FROM artifacts WHERE id = ?').get(id);
57
+ return row ? rowToArtifact(row) : null;
58
+ }
59
+ /**
60
+ * Read artifact content from disk.
61
+ */
62
+ export function readArtifactContent(id) {
63
+ const art = getArtifact(id);
64
+ if (!art || !existsSync(art.storagePath))
65
+ return null;
66
+ return readFileSync(art.storagePath);
67
+ }
68
+ /**
69
+ * List artifacts for an agent, run, or task.
70
+ */
71
+ export function listArtifacts(opts) {
72
+ const db = getDb();
73
+ const conditions = [];
74
+ const params = [];
75
+ if (opts.agentId) {
76
+ conditions.push('agent_id = ?');
77
+ params.push(opts.agentId);
78
+ }
79
+ if (opts.runId) {
80
+ conditions.push('run_id = ?');
81
+ params.push(opts.runId);
82
+ }
83
+ if (opts.taskId) {
84
+ conditions.push('task_id = ?');
85
+ params.push(opts.taskId);
86
+ }
87
+ if (conditions.length === 0)
88
+ conditions.push('1=1');
89
+ const limit = opts.limit ?? 50;
90
+ return db.prepare(`SELECT * FROM artifacts WHERE ${conditions.join(' AND ')} ORDER BY created_at DESC LIMIT ?`)
91
+ .all(...params, limit).map(rowToArtifact);
92
+ }
93
+ /**
94
+ * Delete artifact (removes file + DB row).
95
+ */
96
+ export function deleteArtifact(id) {
97
+ const db = getDb();
98
+ const art = getArtifact(id);
99
+ if (!art)
100
+ return false;
101
+ try {
102
+ if (existsSync(art.storagePath))
103
+ unlinkSync(art.storagePath);
104
+ }
105
+ catch { /* best effort */ }
106
+ db.prepare('DELETE FROM artifacts WHERE id = ?').run(id);
107
+ return true;
108
+ }
109
+ /**
110
+ * Get total storage used by an agent.
111
+ */
112
+ export function getStorageUsage(agentId) {
113
+ const db = getDb();
114
+ const row = db.prepare('SELECT COALESCE(SUM(size_bytes), 0) as totalBytes, COUNT(*) as count FROM artifacts WHERE agent_id = ?').get(agentId);
115
+ return row;
116
+ }
117
+ function guessMimeType(name) {
118
+ const ext = name.split('.').pop()?.toLowerCase();
119
+ const map = {
120
+ 'json': 'application/json', 'md': 'text/markdown', 'txt': 'text/plain',
121
+ 'html': 'text/html', 'css': 'text/css', 'js': 'application/javascript',
122
+ 'ts': 'text/typescript', 'png': 'image/png', 'jpg': 'image/jpeg',
123
+ 'jpeg': 'image/jpeg', 'gif': 'image/gif', 'svg': 'image/svg+xml',
124
+ 'pdf': 'application/pdf', 'zip': 'application/zip',
125
+ };
126
+ return map[ext ?? ''] ?? 'application/octet-stream';
127
+ }
128
+ //# sourceMappingURL=artifact-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-store.js","sourceRoot":"","sources":["../src/artifact-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=artifact-store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-store.test.d.ts","sourceRoot":"","sources":["../src/artifact-store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,119 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { describe, it, beforeEach } from 'node:test';
3
+ import assert from 'node:assert/strict';
4
+ class InMemoryArtifactStore {
5
+ artifacts = [];
6
+ counter = 0;
7
+ store(agentId, name, content, opts) {
8
+ const buf = typeof content === 'string' ? Buffer.from(content) : content;
9
+ const art = {
10
+ id: `art-${++this.counter}`,
11
+ agentId, name,
12
+ runId: opts?.runId ?? null,
13
+ taskId: opts?.taskId ?? null,
14
+ mimeType: opts?.mimeType ?? this.guessMime(name),
15
+ sizeBytes: buf.length,
16
+ content: buf,
17
+ };
18
+ this.artifacts.push(art);
19
+ return art;
20
+ }
21
+ get(id) { return this.artifacts.find(a => a.id === id) ?? null; }
22
+ read(id) { return this.get(id)?.content ?? null; }
23
+ list(opts) {
24
+ return this.artifacts.filter(a => {
25
+ if (opts.agentId && a.agentId !== opts.agentId)
26
+ return false;
27
+ if (opts.runId && a.runId !== opts.runId)
28
+ return false;
29
+ if (opts.taskId && a.taskId !== opts.taskId)
30
+ return false;
31
+ return true;
32
+ });
33
+ }
34
+ delete(id) {
35
+ const idx = this.artifacts.findIndex(a => a.id === id);
36
+ if (idx === -1)
37
+ return false;
38
+ this.artifacts.splice(idx, 1);
39
+ return true;
40
+ }
41
+ usage(agentId) {
42
+ const mine = this.artifacts.filter(a => a.agentId === agentId);
43
+ return { totalBytes: mine.reduce((sum, a) => sum + a.sizeBytes, 0), count: mine.length };
44
+ }
45
+ guessMime(name) {
46
+ const ext = name.split('.').pop()?.toLowerCase();
47
+ if (ext === 'json')
48
+ return 'application/json';
49
+ if (ext === 'md')
50
+ return 'text/markdown';
51
+ if (ext === 'png')
52
+ return 'image/png';
53
+ return 'application/octet-stream';
54
+ }
55
+ clear() { this.artifacts = []; this.counter = 0; }
56
+ }
57
+ describe('artifact store', () => {
58
+ let store;
59
+ beforeEach(() => { store = new InMemoryArtifactStore(); });
60
+ it('stores and retrieves artifact', () => {
61
+ const art = store.store('link', 'report.md', '# Report');
62
+ assert.ok(art.id.startsWith('art-'));
63
+ assert.equal(art.name, 'report.md');
64
+ assert.equal(art.mimeType, 'text/markdown');
65
+ assert.equal(art.sizeBytes, 8);
66
+ });
67
+ it('reads content back', () => {
68
+ const art = store.store('link', 'data.json', '{"key":"value"}');
69
+ const content = store.read(art.id);
70
+ assert.ok(content);
71
+ assert.equal(content.toString(), '{"key":"value"}');
72
+ });
73
+ it('returns null for missing artifact', () => {
74
+ assert.equal(store.get('nonexistent'), null);
75
+ assert.equal(store.read('nonexistent'), null);
76
+ });
77
+ it('links artifact to run', () => {
78
+ store.store('link', 'log.txt', 'run output', { runId: 'arun-123' });
79
+ const results = store.list({ runId: 'arun-123' });
80
+ assert.equal(results.length, 1);
81
+ assert.equal(results[0].runId, 'arun-123');
82
+ });
83
+ it('links artifact to task', () => {
84
+ store.store('link', 'screenshot.png', Buffer.alloc(100), { taskId: 'task-456' });
85
+ const results = store.list({ taskId: 'task-456' });
86
+ assert.equal(results.length, 1);
87
+ });
88
+ it('lists by agent', () => {
89
+ store.store('link', 'a.md', 'aaa');
90
+ store.store('kai', 'b.md', 'bbb');
91
+ store.store('link', 'c.md', 'ccc');
92
+ assert.equal(store.list({ agentId: 'link' }).length, 2);
93
+ assert.equal(store.list({ agentId: 'kai' }).length, 1);
94
+ });
95
+ it('deletes artifact', () => {
96
+ const art = store.store('link', 'temp.txt', 'temp');
97
+ assert.equal(store.delete(art.id), true);
98
+ assert.equal(store.get(art.id), null);
99
+ assert.equal(store.delete(art.id), false);
100
+ });
101
+ it('tracks storage usage', () => {
102
+ store.store('link', 'a.txt', 'hello'); // 5 bytes
103
+ store.store('link', 'b.txt', 'world!'); // 6 bytes
104
+ const usage = store.usage('link');
105
+ assert.equal(usage.count, 2);
106
+ assert.equal(usage.totalBytes, 11);
107
+ });
108
+ it('uses correct MIME types', () => {
109
+ assert.equal(store.store('link', 'file.json', '{}').mimeType, 'application/json');
110
+ assert.equal(store.store('link', 'file.md', '#').mimeType, 'text/markdown');
111
+ assert.equal(store.store('link', 'file.png', Buffer.alloc(1)).mimeType, 'image/png');
112
+ assert.equal(store.store('link', 'file.xyz', 'x').mimeType, 'application/octet-stream');
113
+ });
114
+ it('allows custom MIME type override', () => {
115
+ const art = store.store('link', 'data.bin', 'custom', { mimeType: 'application/x-custom' });
116
+ assert.equal(art.mimeType, 'application/x-custom');
117
+ });
118
+ });
119
+ //# sourceMappingURL=artifact-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-store.test.js","sourceRoot":"","sources":["../src/artifact-store.test.ts"],"names":[],"mappings":"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"}
@@ -48,7 +48,18 @@ export interface BoardHealthWorkerConfig {
48
48
  reviewSlaThresholdMin: number;
49
49
  /** Fallback reviewer when no active agent is available (default: 'ryan') */
50
50
  reviewEscalationTarget: string;
51
+ /**
52
+ * Post-restart quiet window in milliseconds (default: 5 min).
53
+ * Ready-queue alerts are suppressed for this duration after process start
54
+ * to give the continuity loop time to replenish queues before watchdog fires.
55
+ * Set to 0 in tests that need immediate breach detection.
56
+ */
57
+ restartQuietWindowMs: number;
51
58
  }
59
+ /** Read persisted lastDigestAt from the kv table (survives process restarts). */
60
+ export declare function readPersistedDigestAt(): number;
61
+ /** Persist lastDigestAt so it survives process restarts. */
62
+ export declare function writePersistedDigestAt(ts: number): void;
52
63
  export declare class BoardHealthWorker {
53
64
  private config;
54
65
  private auditLog;
@@ -59,6 +70,13 @@ export declare class BoardHealthWorker {
59
70
  constructor(config?: Partial<BoardHealthWorkerConfig>);
60
71
  start(): void;
61
72
  stop(): void;
73
+ /**
74
+ * Reset the quiet window — suppresses ready-queue alerts for
75
+ * `restartQuietWindowMs` from now. Call when a gateway restart or
76
+ * reconnection is detected so agents have time to re-establish presence
77
+ * before idle alerts fire.
78
+ */
79
+ resetQuietWindow(): void;
62
80
  updateConfig(patch: Partial<BoardHealthWorkerConfig>): void;
63
81
  getConfig(): BoardHealthWorkerConfig;
64
82
  tick(options?: {
@@ -80,6 +98,20 @@ export declare class BoardHealthWorker {
80
98
  private readyQueueLastState;
81
99
  /** Track when each agent's queue first went empty (for idle escalation) */
82
100
  private idleQueueSince;
101
+ /**
102
+ * Quiet-window anchor — used to enforce a post-restart quiet window.
103
+ * For the first `restartQuietWindowMs` after this timestamp, ready-queue
104
+ * alerts are suppressed so agents have time to reconnect and replenish queues.
105
+ *
106
+ * Initially set to process start (node restart). Also reset by
107
+ * `resetQuietWindow()` when an external event (e.g. gateway restart)
108
+ * warrants a fresh suppression window.
109
+ *
110
+ * Tests that need immediate breach detection should set restartQuietWindowMs: 0
111
+ * in the constructor config.
112
+ */
113
+ private readonly startedAt;
114
+ private lastQuietReset;
83
115
  private checkReadyQueueFloor;
84
116
  /** Track last replenish time per agent to enforce cooldown between auto-creates */
85
117
  private replenishLastAt;
@@ -1 +1 @@
1
- {"version":3,"file":"boardHealthWorker.d.ts","sourceRoot":"","sources":["../src/boardHealthWorker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"boardHealthWorker.d.ts","sourceRoot":"","sources":["../src/boardHealthWorker.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,8 @@ import { presenceManager } from './presence.js';
19
19
  import { suggestReviewer, getAgentRoles } from './assignment.js';
20
20
  import { isTestHarnessTask } from './test-task-filter.js';
21
21
  import { recordSystemLoopTick } from './system-loop-state.js';
22
+ import { isWaitingOnAuthor } from './review-state.js';
23
+ import { getDb } from './db.js';
22
24
  const DEFAULT_CONFIG = {
23
25
  enabled: true,
24
26
  intervalMs: 5 * 60 * 1000, // 5 minutes
@@ -34,12 +36,34 @@ const DEFAULT_CONFIG = {
34
36
  inactiveAgentThresholdMin: 1440, // 24 hours
35
37
  reviewSlaThresholdMin: 480, // 8 hours
36
38
  reviewEscalationTarget: 'ryan',
39
+ restartQuietWindowMs: 5 * 60_000, // 5 minutes — suppress post-restart thundering herd
37
40
  };
38
41
  // ── Worker ─────────────────────────────────────────────────────────────────
42
+ const KV_DIGEST_KEY = 'board_health_last_digest_at';
43
+ /** Read persisted lastDigestAt from the kv table (survives process restarts). */
44
+ export function readPersistedDigestAt() {
45
+ try {
46
+ const row = getDb().prepare('SELECT value FROM kv WHERE key = ?').get(KV_DIGEST_KEY);
47
+ const parsed = row ? parseInt(row.value, 10) : 0;
48
+ return isNaN(parsed) ? 0 : parsed;
49
+ }
50
+ catch {
51
+ return 0;
52
+ }
53
+ }
54
+ /** Persist lastDigestAt so it survives process restarts. */
55
+ export function writePersistedDigestAt(ts) {
56
+ try {
57
+ getDb().prepare('INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)').run(KV_DIGEST_KEY, String(ts));
58
+ }
59
+ catch {
60
+ // Non-fatal — fall back to in-memory only
61
+ }
62
+ }
39
63
  export class BoardHealthWorker {
40
64
  config;
41
65
  auditLog = [];
42
- lastDigestAt = 0;
66
+ lastDigestAt = readPersistedDigestAt();
43
67
  lastTickAt = 0;
44
68
  tickCount = 0;
45
69
  timer = null;
@@ -63,6 +87,15 @@ export class BoardHealthWorker {
63
87
  this.timer = null;
64
88
  }
65
89
  }
90
+ /**
91
+ * Reset the quiet window — suppresses ready-queue alerts for
92
+ * `restartQuietWindowMs` from now. Call when a gateway restart or
93
+ * reconnection is detected so agents have time to re-establish presence
94
+ * before idle alerts fire.
95
+ */
96
+ resetQuietWindow() {
97
+ this.lastQuietReset = Date.now();
98
+ }
66
99
  updateConfig(patch) {
67
100
  const wasEnabled = this.config.enabled;
68
101
  this.config = { ...this.config, ...patch };
@@ -178,11 +211,18 @@ export class BoardHealthWorker {
178
211
  catch { /* continuity loop may not be loaded */ }
179
212
  }
180
213
  // 4. Emit digest if interval elapsed
214
+ // Re-read persisted value each tick in case another process updated it.
215
+ if (!dryRun) {
216
+ const persisted = readPersistedDigestAt();
217
+ if (persisted > this.lastDigestAt)
218
+ this.lastDigestAt = persisted;
219
+ }
181
220
  let digest = null;
182
221
  if (force || now - this.lastDigestAt >= this.config.digestIntervalMs) {
183
222
  digest = await this.emitDigest(now, actions, dryRun);
184
223
  if (!dryRun) {
185
224
  this.lastDigestAt = now;
225
+ writePersistedDigestAt(now);
186
226
  }
187
227
  }
188
228
  return { actions, digest, skipped: false };
@@ -323,6 +363,20 @@ export class BoardHealthWorker {
323
363
  readyQueueLastState = {};
324
364
  /** Track when each agent's queue first went empty (for idle escalation) */
325
365
  idleQueueSince = {};
366
+ /**
367
+ * Quiet-window anchor — used to enforce a post-restart quiet window.
368
+ * For the first `restartQuietWindowMs` after this timestamp, ready-queue
369
+ * alerts are suppressed so agents have time to reconnect and replenish queues.
370
+ *
371
+ * Initially set to process start (node restart). Also reset by
372
+ * `resetQuietWindow()` when an external event (e.g. gateway restart)
373
+ * warrants a fresh suppression window.
374
+ *
375
+ * Tests that need immediate breach detection should set restartQuietWindowMs: 0
376
+ * in the constructor config.
377
+ */
378
+ startedAt = Date.now();
379
+ lastQuietReset = Date.now();
326
380
  async checkReadyQueueFloor(now, dryRun) {
327
381
  const policy = policyManager.get();
328
382
  const rqf = policy.readyQueueFloor;
@@ -363,7 +417,15 @@ export class BoardHealthWorker {
363
417
  let lastAlert = this.readyQueueLastAlertAt[agent] || 0;
364
418
  const cooldownMs = (rqf.cooldownMin || 30) * 60_000;
365
419
  // Ready-queue floor check (breach vs info)
366
- if (belowFloor && now - lastAlert > cooldownMs) {
420
+ // Post-restart quiet window: suppress alerts for restartQuietWindowMs after startup.
421
+ // Prevents thundering-herd — all agents with no prior alert record (lastAlertAt=0)
422
+ // would otherwise fire simultaneously on the first post-restart sweep.
423
+ // restartQuietWindowMs can be set to 0 in tests for immediate breach detection.
424
+ // Quiet window: suppress alerts shortly after any restart (node start OR gateway reconnect).
425
+ // Uses the most recent of startedAt and lastQuietReset as the anchor.
426
+ const quietAnchor = Math.max(this.startedAt, this.lastQuietReset);
427
+ const inStartupQuiet = lastAlert === 0 && (now - quietAnchor) < this.config.restartQuietWindowMs;
428
+ if (belowFloor && now - lastAlert > cooldownMs && !inStartupQuiet) {
367
429
  const deficit = rqf.minReady - readyCount;
368
430
  // State fingerprint: suppress if identical to last alert
369
431
  const blockedTasks = todoTasks.filter(t => !unblockedTodo.includes(t));
@@ -568,6 +630,11 @@ export class BoardHealthWorker {
568
630
  continue;
569
631
  // Check reviewer activity on this task using the review_last_activity_at metadata field
570
632
  const meta = (task.metadata || {});
633
+ // ── Skip tasks where reviewer has already acted ──
634
+ // Canonical precedence rule: reviewer_decision suppresses reviewer-facing
635
+ // SLA paging even when review_state was not populated.
636
+ if (isWaitingOnAuthor(meta))
637
+ continue;
571
638
  const reviewEnteredAt = normalizeEpochMs(meta.entered_validating_at) || (task.updatedAt ?? task.createdAt);
572
639
  const reviewLastActivityAt = normalizeEpochMs(meta.review_last_activity_at) || reviewEnteredAt;
573
640
  // Use the more recent of entered_validating and review_last_activity