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.
- package/README.md +13 -0
- package/defaults/TEAM-ROLES.yaml +317 -5
- package/defaults/gitignore.template +23 -0
- package/dist/agent-config.d.ts +51 -0
- package/dist/agent-config.d.ts.map +1 -0
- package/dist/agent-config.js +129 -0
- package/dist/agent-config.js.map +1 -0
- package/dist/agent-config.test.d.ts +2 -0
- package/dist/agent-config.test.d.ts.map +1 -0
- package/dist/agent-config.test.js +91 -0
- package/dist/agent-config.test.js.map +1 -0
- package/dist/agent-memories.d.ts +58 -0
- package/dist/agent-memories.d.ts.map +1 -0
- package/dist/agent-memories.js +168 -0
- package/dist/agent-memories.js.map +1 -0
- package/dist/agent-memories.test.d.ts +2 -0
- package/dist/agent-memories.test.d.ts.map +1 -0
- package/dist/agent-memories.test.js +327 -0
- package/dist/agent-memories.test.js.map +1 -0
- package/dist/agent-messaging.d.ts +50 -0
- package/dist/agent-messaging.d.ts.map +1 -0
- package/dist/agent-messaging.js +103 -0
- package/dist/agent-messaging.js.map +1 -0
- package/dist/agent-messaging.test.d.ts +2 -0
- package/dist/agent-messaging.test.d.ts.map +1 -0
- package/dist/agent-messaging.test.js +105 -0
- package/dist/agent-messaging.test.js.map +1 -0
- package/dist/agent-runs.d.ts +158 -0
- package/dist/agent-runs.d.ts.map +1 -0
- package/dist/agent-runs.js +514 -0
- package/dist/agent-runs.js.map +1 -0
- package/dist/agent-runs.test.d.ts +2 -0
- package/dist/agent-runs.test.d.ts.map +1 -0
- package/dist/agent-runs.test.js +386 -0
- package/dist/agent-runs.test.js.map +1 -0
- package/dist/approval-queue.test.d.ts +2 -0
- package/dist/approval-queue.test.d.ts.map +1 -0
- package/dist/approval-queue.test.js +118 -0
- package/dist/approval-queue.test.js.map +1 -0
- package/dist/artifact-store.d.ts +55 -0
- package/dist/artifact-store.d.ts.map +1 -0
- package/dist/artifact-store.js +128 -0
- package/dist/artifact-store.js.map +1 -0
- package/dist/artifact-store.test.d.ts +2 -0
- package/dist/artifact-store.test.d.ts.map +1 -0
- package/dist/artifact-store.test.js +119 -0
- package/dist/artifact-store.test.js.map +1 -0
- package/dist/boardHealthWorker.d.ts +32 -0
- package/dist/boardHealthWorker.d.ts.map +1 -1
- package/dist/boardHealthWorker.js +69 -2
- package/dist/boardHealthWorker.js.map +1 -1
- package/dist/buildInfo.d.ts.map +1 -1
- package/dist/buildInfo.js +47 -10
- package/dist/buildInfo.js.map +1 -1
- package/dist/canvas-input.test.d.ts +2 -0
- package/dist/canvas-input.test.d.ts.map +1 -0
- package/dist/canvas-input.test.js +96 -0
- package/dist/canvas-input.test.js.map +1 -0
- package/dist/canvas-render.test.d.ts +2 -0
- package/dist/canvas-render.test.d.ts.map +1 -0
- package/dist/canvas-render.test.js +95 -0
- package/dist/canvas-render.test.js.map +1 -0
- package/dist/capabilities/browser.d.ts +75 -0
- package/dist/capabilities/browser.d.ts.map +1 -0
- package/dist/capabilities/browser.js +172 -0
- package/dist/capabilities/browser.js.map +1 -0
- package/dist/channels.d.ts +1 -1
- package/dist/chat.d.ts +4 -0
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +6 -2
- package/dist/chat.js.map +1 -1
- package/dist/cli.js +41 -14
- package/dist/cli.js.map +1 -1
- package/dist/cloud.d.ts +2 -0
- package/dist/cloud.d.ts.map +1 -1
- package/dist/cloud.js +151 -64
- package/dist/cloud.js.map +1 -1
- package/dist/continuity-loop.d.ts.map +1 -1
- package/dist/continuity-loop.js +297 -29
- package/dist/continuity-loop.js.map +1 -1
- package/dist/cost-enforcement.d.ts +38 -0
- package/dist/cost-enforcement.d.ts.map +1 -0
- package/dist/cost-enforcement.js +84 -0
- package/dist/cost-enforcement.js.map +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +131 -0
- package/dist/db.js.map +1 -1
- package/dist/deploy-monitor.d.ts +18 -0
- package/dist/deploy-monitor.d.ts.map +1 -0
- package/dist/deploy-monitor.js +165 -0
- package/dist/deploy-monitor.js.map +1 -0
- package/dist/e2e-loop-proof.test.d.ts +2 -0
- package/dist/e2e-loop-proof.test.d.ts.map +1 -0
- package/dist/e2e-loop-proof.test.js +104 -0
- package/dist/e2e-loop-proof.test.js.map +1 -0
- package/dist/email-sms-send.test.d.ts +2 -0
- package/dist/email-sms-send.test.d.ts.map +1 -0
- package/dist/email-sms-send.test.js +96 -0
- package/dist/email-sms-send.test.js.map +1 -0
- package/dist/events.d.ts +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +2 -0
- package/dist/events.js.map +1 -1
- package/dist/executionSweeper.d.ts +1 -0
- package/dist/executionSweeper.d.ts.map +1 -1
- package/dist/executionSweeper.js +43 -7
- package/dist/executionSweeper.js.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +17 -3
- package/dist/files.js.map +1 -1
- package/dist/fingerprint.d.ts +30 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +117 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/github-webhook-attribution.d.ts +38 -0
- package/dist/github-webhook-attribution.d.ts.map +1 -0
- package/dist/github-webhook-attribution.js +123 -0
- package/dist/github-webhook-attribution.js.map +1 -0
- package/dist/github-webhook-chat.d.ts +75 -0
- package/dist/github-webhook-chat.d.ts.map +1 -0
- package/dist/github-webhook-chat.js +108 -0
- package/dist/github-webhook-chat.js.map +1 -0
- package/dist/handoff-state.test.d.ts +2 -0
- package/dist/handoff-state.test.d.ts.map +1 -0
- package/dist/handoff-state.test.js +102 -0
- package/dist/handoff-state.test.js.map +1 -0
- package/dist/health.d.ts +9 -0
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +18 -0
- package/dist/health.js.map +1 -1
- package/dist/host-error-correlation.d.ts +65 -0
- package/dist/host-error-correlation.d.ts.map +1 -0
- package/dist/host-error-correlation.js +123 -0
- package/dist/host-error-correlation.js.map +1 -0
- package/dist/inbox.d.ts.map +1 -1
- package/dist/inbox.js +4 -0
- package/dist/inbox.js.map +1 -1
- package/dist/index.js +76 -11
- package/dist/index.js.map +1 -1
- package/dist/notificationDedupeGuard.d.ts +4 -0
- package/dist/notificationDedupeGuard.d.ts.map +1 -1
- package/dist/notificationDedupeGuard.js +8 -4
- package/dist/notificationDedupeGuard.js.map +1 -1
- package/dist/presence.d.ts +37 -5
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js +127 -16
- package/dist/presence.js.map +1 -1
- package/dist/pulse.d.ts +7 -0
- package/dist/pulse.d.ts.map +1 -1
- package/dist/pulse.js +15 -0
- package/dist/pulse.js.map +1 -1
- package/dist/review-sla.d.ts +9 -0
- package/dist/review-sla.d.ts.map +1 -0
- package/dist/review-sla.js +51 -0
- package/dist/review-sla.js.map +1 -0
- package/dist/review-state.d.ts +9 -0
- package/dist/review-state.d.ts.map +1 -0
- package/dist/review-state.js +17 -0
- package/dist/review-state.js.map +1 -0
- package/dist/routing-enforcement.test.d.ts +2 -0
- package/dist/routing-enforcement.test.d.ts.map +1 -0
- package/dist/routing-enforcement.test.js +86 -0
- package/dist/routing-enforcement.test.js.map +1 -0
- package/dist/run-retention.test.d.ts +2 -0
- package/dist/run-retention.test.d.ts.map +1 -0
- package/dist/run-retention.test.js +57 -0
- package/dist/run-retention.test.js.map +1 -0
- package/dist/run-stream.test.d.ts +2 -0
- package/dist/run-stream.test.d.ts.map +1 -0
- package/dist/run-stream.test.js +70 -0
- package/dist/run-stream.test.js.map +1 -0
- package/dist/schedule.d.ts +60 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +176 -0
- package/dist/schedule.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1714 -88
- package/dist/server.js.map +1 -1
- package/dist/suppression-ledger.d.ts.map +1 -1
- package/dist/suppression-ledger.js +12 -3
- package/dist/suppression-ledger.js.map +1 -1
- package/dist/system-loop-state.d.ts +1 -1
- package/dist/system-loop-state.d.ts.map +1 -1
- package/dist/system-loop-state.js +1 -0
- package/dist/system-loop-state.js.map +1 -1
- package/dist/tasks.d.ts +9 -1
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +238 -41
- package/dist/tasks.js.map +1 -1
- package/dist/todoHoardingGuard.d.ts +17 -0
- package/dist/todoHoardingGuard.d.ts.map +1 -1
- package/dist/todoHoardingGuard.js +25 -2
- package/dist/todoHoardingGuard.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/usage-tracking.d.ts +26 -0
- package/dist/usage-tracking.d.ts.map +1 -1
- package/dist/usage-tracking.js +91 -4
- package/dist/usage-tracking.js.map +1 -1
- package/dist/webhook-storage.d.ts +50 -0
- package/dist/webhook-storage.d.ts.map +1 -0
- package/dist/webhook-storage.js +102 -0
- package/dist/webhook-storage.js.map +1 -0
- package/dist/webhook-storage.test.d.ts +2 -0
- package/dist/webhook-storage.test.d.ts.map +1 -0
- package/dist/webhook-storage.test.js +86 -0
- package/dist/webhook-storage.test.js.map +1 -0
- package/dist/workflow-templates.d.ts +44 -0
- package/dist/workflow-templates.d.ts.map +1 -0
- package/dist/workflow-templates.js +154 -0
- package/dist/workflow-templates.js.map +1 -0
- package/dist/workflow-templates.test.d.ts +2 -0
- package/dist/workflow-templates.test.d.ts.map +1 -0
- package/dist/workflow-templates.test.js +76 -0
- package/dist/workflow-templates.test.js.map +1 -0
- package/package.json +3 -1
- package/public/dashboard.js +130 -37
- package/public/design-tokens-platform.md +118 -0
- package/public/design-tokens.css +195 -0
- 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 @@
|
|
|
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":"
|
|
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 =
|
|
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
|
-
|
|
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
|