@soleri/core 7.0.0 → 8.0.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.
- package/dist/agency/agency-manager.d.ts +27 -1
- package/dist/agency/agency-manager.d.ts.map +1 -1
- package/dist/agency/agency-manager.js +180 -9
- package/dist/agency/agency-manager.js.map +1 -1
- package/dist/agency/default-rules.d.ts +7 -0
- package/dist/agency/default-rules.d.ts.map +1 -0
- package/dist/agency/default-rules.js +79 -0
- package/dist/agency/default-rules.js.map +1 -0
- package/dist/agency/types.d.ts +48 -0
- package/dist/agency/types.d.ts.map +1 -1
- package/dist/brain/brain.d.ts +17 -2
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +118 -8
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/knowledge-synthesizer.d.ts +37 -0
- package/dist/brain/knowledge-synthesizer.d.ts.map +1 -0
- package/dist/brain/knowledge-synthesizer.js +161 -0
- package/dist/brain/knowledge-synthesizer.js.map +1 -0
- package/dist/brain/learning-radar.d.ts +96 -0
- package/dist/brain/learning-radar.d.ts.map +1 -0
- package/dist/brain/learning-radar.js +202 -0
- package/dist/brain/learning-radar.js.map +1 -0
- package/dist/brain/types.d.ts +15 -0
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/context/context-engine.d.ts.map +1 -1
- package/dist/context/context-engine.js +82 -17
- package/dist/context/context-engine.js.map +1 -1
- package/dist/context/types.d.ts +5 -0
- package/dist/context/types.d.ts.map +1 -1
- package/dist/control/intent-router.d.ts +12 -1
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +68 -0
- package/dist/control/intent-router.js.map +1 -1
- package/dist/control/types.d.ts +17 -0
- package/dist/control/types.d.ts.map +1 -1
- package/dist/curator/classifier.d.ts +18 -0
- package/dist/curator/classifier.d.ts.map +1 -0
- package/dist/curator/classifier.js +61 -0
- package/dist/curator/classifier.js.map +1 -0
- package/dist/curator/quality-gate.d.ts +29 -0
- package/dist/curator/quality-gate.d.ts.map +1 -0
- package/dist/curator/quality-gate.js +88 -0
- package/dist/curator/quality-gate.js.map +1 -0
- package/dist/engine/bin/soleri-engine.js +1 -0
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/events/event-bus.d.ts +30 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +51 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/flows/chain-runner.d.ts +46 -0
- package/dist/flows/chain-runner.d.ts.map +1 -0
- package/dist/flows/chain-runner.js +271 -0
- package/dist/flows/chain-runner.js.map +1 -0
- package/dist/flows/chain-types.d.ts +103 -0
- package/dist/flows/chain-types.d.ts.map +1 -0
- package/dist/flows/chain-types.js +23 -0
- package/dist/flows/chain-types.js.map +1 -0
- package/dist/health/doctor-checks.d.ts +15 -0
- package/dist/health/doctor-checks.d.ts.map +1 -0
- package/dist/health/doctor-checks.js +98 -0
- package/dist/health/doctor-checks.js.map +1 -0
- package/dist/intake/text-ingester.d.ts +52 -0
- package/dist/intake/text-ingester.d.ts.map +1 -0
- package/dist/intake/text-ingester.js +181 -0
- package/dist/intake/text-ingester.js.map +1 -0
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +37 -1
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/llm/oauth-discovery.d.ts +26 -0
- package/dist/llm/oauth-discovery.d.ts.map +1 -0
- package/dist/llm/oauth-discovery.js +149 -0
- package/dist/llm/oauth-discovery.js.map +1 -0
- package/dist/planning/evidence-collector.d.ts +41 -0
- package/dist/planning/evidence-collector.d.ts.map +1 -0
- package/dist/planning/evidence-collector.js +194 -0
- package/dist/planning/evidence-collector.js.map +1 -0
- package/dist/planning/planner.d.ts +4 -0
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +11 -0
- package/dist/planning/planner.js.map +1 -1
- package/dist/queue/job-queue.d.ts +92 -0
- package/dist/queue/job-queue.d.ts.map +1 -0
- package/dist/queue/job-queue.js +180 -0
- package/dist/queue/job-queue.js.map +1 -0
- package/dist/queue/pipeline-runner.d.ts +62 -0
- package/dist/queue/pipeline-runner.d.ts.map +1 -0
- package/dist/queue/pipeline-runner.js +126 -0
- package/dist/queue/pipeline-runner.js.map +1 -0
- package/dist/runtime/admin-setup-ops.d.ts +20 -0
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -0
- package/dist/runtime/admin-setup-ops.js +583 -0
- package/dist/runtime/admin-setup-ops.js.map +1 -0
- package/dist/runtime/chain-ops.d.ts +9 -0
- package/dist/runtime/chain-ops.d.ts.map +1 -0
- package/dist/runtime/chain-ops.js +107 -0
- package/dist/runtime/chain-ops.js.map +1 -0
- package/dist/runtime/claude-md-helpers.d.ts +65 -0
- package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
- package/dist/runtime/claude-md-helpers.js +173 -0
- package/dist/runtime/claude-md-helpers.js.map +1 -0
- package/dist/runtime/curator-extra-ops.d.ts +3 -2
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
- package/dist/runtime/curator-extra-ops.js +81 -3
- package/dist/runtime/curator-extra-ops.js.map +1 -1
- package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
- package/dist/runtime/facades/admin-facade.js +4 -0
- package/dist/runtime/facades/admin-facade.js.map +1 -1
- package/dist/runtime/facades/agency-facade.d.ts.map +1 -1
- package/dist/runtime/facades/agency-facade.js +64 -0
- package/dist/runtime/facades/agency-facade.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +122 -1
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/control-facade.d.ts.map +1 -1
- package/dist/runtime/facades/control-facade.js +42 -0
- package/dist/runtime/facades/control-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +20 -2
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +2 -0
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +25 -5
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/intake-ops.d.ts +7 -5
- package/dist/runtime/intake-ops.d.ts.map +1 -1
- package/dist/runtime/intake-ops.js +98 -5
- package/dist/runtime/intake-ops.js.map +1 -1
- package/dist/runtime/memory-extra-ops.d.ts +6 -3
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
- package/dist/runtime/memory-extra-ops.js +292 -4
- package/dist/runtime/memory-extra-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +85 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/playbook-ops.js +1 -1
- package/dist/runtime/playbook-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +143 -2
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts +23 -0
- package/dist/runtime/session-briefing.d.ts.map +1 -0
- package/dist/runtime/session-briefing.js +140 -0
- package/dist/runtime/session-briefing.js.map +1 -0
- package/dist/runtime/types.d.ts +23 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
- package/dist/runtime/vault-linking-ops.js +1 -3
- package/dist/runtime/vault-linking-ops.js.map +1 -1
- package/dist/vault/vault.d.ts +25 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +67 -3
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/admin-setup-ops.test.ts +355 -0
- package/src/__tests__/async-infrastructure.test.ts +307 -0
- package/src/__tests__/cognee-client-gaps.test.ts +6 -2
- package/src/__tests__/cognee-hybrid-search.test.ts +49 -35
- package/src/__tests__/cognee-sync-manager-deep.test.ts +89 -65
- package/src/__tests__/curator-extra-ops.test.ts +6 -2
- package/src/__tests__/curator-pipeline-e2e.test.ts +358 -0
- package/src/__tests__/memory-extra-ops.test.ts +2 -2
- package/src/__tests__/planning-extra-ops.test.ts +2 -2
- package/src/__tests__/second-brain-features.test.ts +583 -0
- package/src/agency/agency-manager.ts +217 -9
- package/src/agency/default-rules.ts +83 -0
- package/src/agency/types.ts +61 -0
- package/src/brain/brain.ts +110 -8
- package/src/brain/knowledge-synthesizer.ts +218 -0
- package/src/brain/learning-radar.ts +340 -0
- package/src/brain/types.ts +16 -0
- package/src/context/context-engine.ts +114 -15
- package/src/context/types.ts +5 -0
- package/src/control/intent-router.ts +107 -0
- package/src/control/types.ts +10 -0
- package/src/curator/classifier.ts +88 -0
- package/src/curator/quality-gate.ts +129 -0
- package/src/engine/bin/soleri-engine.ts +1 -0
- package/src/events/event-bus.ts +58 -0
- package/src/flows/chain-runner.ts +369 -0
- package/src/flows/chain-types.ts +57 -0
- package/src/health/doctor-checks.ts +115 -0
- package/src/intake/text-ingester.ts +234 -0
- package/src/llm/llm-client.ts +38 -1
- package/src/llm/oauth-discovery.ts +169 -0
- package/src/planning/evidence-collector.ts +247 -0
- package/src/planning/planner.ts +11 -0
- package/src/queue/job-queue.ts +281 -0
- package/src/queue/pipeline-runner.ts +149 -0
- package/src/runtime/admin-setup-ops.ts +664 -0
- package/src/runtime/chain-ops.ts +121 -0
- package/src/runtime/claude-md-helpers.ts +236 -0
- package/src/runtime/curator-extra-ops.ts +86 -3
- package/src/runtime/facades/admin-facade.ts +4 -0
- package/src/runtime/facades/agency-facade.ts +68 -0
- package/src/runtime/facades/brain-facade.ts +142 -1
- package/src/runtime/facades/control-facade.ts +45 -0
- package/src/runtime/facades/memory-facade.ts +20 -2
- package/src/runtime/facades/plan-facade.ts +2 -0
- package/src/runtime/facades/vault-facade.ts +28 -5
- package/src/runtime/intake-ops.ts +107 -5
- package/src/runtime/memory-extra-ops.ts +312 -4
- package/src/runtime/planning-extra-ops.ts +94 -0
- package/src/runtime/playbook-ops.ts +1 -1
- package/src/runtime/runtime.ts +138 -2
- package/src/runtime/session-briefing.ts +161 -0
- package/src/runtime/types.ts +23 -0
- package/src/runtime/vault-linking-ops.ts +1 -3
- package/src/vault/vault.ts +79 -4
package/src/planning/planner.ts
CHANGED
|
@@ -385,6 +385,17 @@ export class Planner {
|
|
|
385
385
|
return [...this.store.plans];
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Permanently remove a plan by ID. Returns true if found and removed.
|
|
390
|
+
*/
|
|
391
|
+
remove(planId: string): boolean {
|
|
392
|
+
const idx = this.store.plans.findIndex((p) => p.id === planId);
|
|
393
|
+
if (idx < 0) return false;
|
|
394
|
+
this.store.plans.splice(idx, 1);
|
|
395
|
+
this.save();
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
388
399
|
/**
|
|
389
400
|
* Transition a plan to a new status using the typed FSM.
|
|
390
401
|
* Validates that the transition is allowed before applying it.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Job Queue — SQLite-backed FIFO queue with DAG dependencies and retries.
|
|
3
|
+
*
|
|
4
|
+
* Generic infrastructure — not curator-specific. Reusable by agency, intake, etc.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Persistent jobs (survive process restarts)
|
|
8
|
+
* - DAG dependency resolution (job B waits for job A)
|
|
9
|
+
* - Pipeline grouping (group related jobs under one ID)
|
|
10
|
+
* - Configurable retries with max limit
|
|
11
|
+
* - Status tracking: pending → running → completed | failed
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
15
|
+
import type { PersistenceProvider } from '../persistence/types.js';
|
|
16
|
+
|
|
17
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export type JobStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
20
|
+
|
|
21
|
+
export interface Job {
|
|
22
|
+
id: string;
|
|
23
|
+
type: string;
|
|
24
|
+
status: JobStatus;
|
|
25
|
+
entryId: string | null;
|
|
26
|
+
payload: Record<string, unknown>;
|
|
27
|
+
dependsOn: string[];
|
|
28
|
+
pipelineId: string | null;
|
|
29
|
+
retryCount: number;
|
|
30
|
+
maxRetries: number;
|
|
31
|
+
result: unknown | null;
|
|
32
|
+
error: string | null;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
startedAt: string | null;
|
|
35
|
+
completedAt: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface JobQueueStats {
|
|
39
|
+
pending: number;
|
|
40
|
+
running: number;
|
|
41
|
+
completed: number;
|
|
42
|
+
failed: number;
|
|
43
|
+
total: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface EnqueueOptions {
|
|
47
|
+
entryId?: string;
|
|
48
|
+
payload?: Record<string, unknown>;
|
|
49
|
+
dependsOn?: string[];
|
|
50
|
+
pipelineId?: string;
|
|
51
|
+
maxRetries?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Class ───────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export class JobQueue {
|
|
57
|
+
private provider: PersistenceProvider;
|
|
58
|
+
|
|
59
|
+
constructor(provider: PersistenceProvider) {
|
|
60
|
+
this.provider = provider;
|
|
61
|
+
this.initializeTable();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private initializeTable(): void {
|
|
65
|
+
this.provider.execSql(`
|
|
66
|
+
CREATE TABLE IF NOT EXISTS job_queue (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
type TEXT NOT NULL,
|
|
69
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
70
|
+
entry_id TEXT,
|
|
71
|
+
payload TEXT DEFAULT '{}',
|
|
72
|
+
depends_on TEXT DEFAULT '[]',
|
|
73
|
+
pipeline_id TEXT,
|
|
74
|
+
retry_count INTEGER DEFAULT 0,
|
|
75
|
+
max_retries INTEGER DEFAULT 3,
|
|
76
|
+
result TEXT,
|
|
77
|
+
error TEXT,
|
|
78
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
79
|
+
started_at TEXT,
|
|
80
|
+
completed_at TEXT
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_job_queue_status ON job_queue(status);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_job_queue_pipeline ON job_queue(pipeline_id);
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Enqueue a new job. Returns the job ID.
|
|
90
|
+
*/
|
|
91
|
+
enqueue(type: string, options?: EnqueueOptions): string {
|
|
92
|
+
const id = randomUUID().slice(0, 12);
|
|
93
|
+
this.provider.run(
|
|
94
|
+
`INSERT INTO job_queue (id, type, entry_id, payload, depends_on, pipeline_id, max_retries)
|
|
95
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
96
|
+
[
|
|
97
|
+
id,
|
|
98
|
+
type,
|
|
99
|
+
options?.entryId ?? null,
|
|
100
|
+
JSON.stringify(options?.payload ?? {}),
|
|
101
|
+
JSON.stringify(options?.dependsOn ?? []),
|
|
102
|
+
options?.pipelineId ?? null,
|
|
103
|
+
options?.maxRetries ?? 3,
|
|
104
|
+
],
|
|
105
|
+
);
|
|
106
|
+
return id;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Dequeue the oldest pending job with all dependencies completed.
|
|
111
|
+
* Marks it as running. Returns null if no ready jobs.
|
|
112
|
+
*/
|
|
113
|
+
dequeue(): Job | null {
|
|
114
|
+
const ready = this.dequeueReady(1);
|
|
115
|
+
return ready.length > 0 ? ready[0] : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Dequeue up to `limit` pending jobs whose dependencies are all completed.
|
|
120
|
+
*/
|
|
121
|
+
dequeueReady(limit: number = 10): Job[] {
|
|
122
|
+
const rows = this.provider.all<JobRow>(
|
|
123
|
+
"SELECT * FROM job_queue WHERE status = 'pending' ORDER BY created_at ASC LIMIT ?",
|
|
124
|
+
[limit * 3], // Over-fetch to filter by deps
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const ready: Job[] = [];
|
|
128
|
+
for (const row of rows) {
|
|
129
|
+
if (ready.length >= limit) break;
|
|
130
|
+
const deps = JSON.parse(row.depends_on) as string[];
|
|
131
|
+
if (deps.length === 0 || this.allDepsCompleted(deps)) {
|
|
132
|
+
this.provider.run(
|
|
133
|
+
"UPDATE job_queue SET status = 'running', started_at = datetime('now') WHERE id = ?",
|
|
134
|
+
[row.id],
|
|
135
|
+
);
|
|
136
|
+
const job = rowToJob(row);
|
|
137
|
+
job.status = 'running';
|
|
138
|
+
ready.push(job);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return ready;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Mark a job as completed with an optional result.
|
|
146
|
+
*/
|
|
147
|
+
complete(jobId: string, result?: unknown): void {
|
|
148
|
+
this.provider.run(
|
|
149
|
+
"UPDATE job_queue SET status = 'completed', completed_at = datetime('now'), result = ? WHERE id = ?",
|
|
150
|
+
[result !== undefined ? JSON.stringify(result) : null, jobId],
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Mark a job as failed with an error message.
|
|
156
|
+
*/
|
|
157
|
+
fail(jobId: string, error: string): void {
|
|
158
|
+
this.provider.run(
|
|
159
|
+
"UPDATE job_queue SET status = 'failed', completed_at = datetime('now'), error = ? WHERE id = ?",
|
|
160
|
+
[error, jobId],
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Retry a failed job (resets to pending). Returns false if max retries exceeded.
|
|
166
|
+
*/
|
|
167
|
+
retry(jobId: string): boolean {
|
|
168
|
+
const row = this.provider.get<JobRow>('SELECT * FROM job_queue WHERE id = ?', [jobId]);
|
|
169
|
+
if (!row) return false;
|
|
170
|
+
if (row.retry_count >= row.max_retries) return false;
|
|
171
|
+
|
|
172
|
+
this.provider.run(
|
|
173
|
+
"UPDATE job_queue SET status = 'pending', retry_count = retry_count + 1, error = NULL, started_at = NULL, completed_at = NULL WHERE id = ?",
|
|
174
|
+
[jobId],
|
|
175
|
+
);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get queue statistics.
|
|
181
|
+
*/
|
|
182
|
+
getStats(): JobQueueStats {
|
|
183
|
+
const rows = this.provider.all<{ status: string; count: number }>(
|
|
184
|
+
'SELECT status, COUNT(*) as count FROM job_queue GROUP BY status',
|
|
185
|
+
);
|
|
186
|
+
const byStatus: Record<string, number> = {};
|
|
187
|
+
let total = 0;
|
|
188
|
+
for (const row of rows) {
|
|
189
|
+
byStatus[row.status] = row.count;
|
|
190
|
+
total += row.count;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
pending: byStatus['pending'] ?? 0,
|
|
194
|
+
running: byStatus['running'] ?? 0,
|
|
195
|
+
completed: byStatus['completed'] ?? 0,
|
|
196
|
+
failed: byStatus['failed'] ?? 0,
|
|
197
|
+
total,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get all jobs for a pipeline.
|
|
203
|
+
*/
|
|
204
|
+
getByPipeline(pipelineId: string): Job[] {
|
|
205
|
+
const rows = this.provider.all<JobRow>(
|
|
206
|
+
'SELECT * FROM job_queue WHERE pipeline_id = ? ORDER BY created_at ASC',
|
|
207
|
+
[pipelineId],
|
|
208
|
+
);
|
|
209
|
+
return rows.map(rowToJob);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get a single job by ID.
|
|
214
|
+
*/
|
|
215
|
+
get(jobId: string): Job | null {
|
|
216
|
+
const row = this.provider.get<JobRow>('SELECT * FROM job_queue WHERE id = ?', [jobId]);
|
|
217
|
+
return row ? rowToJob(row) : null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Purge completed/failed jobs older than N days.
|
|
222
|
+
*/
|
|
223
|
+
purge(olderThanDays: number = 30): number {
|
|
224
|
+
const result = this.provider.run(
|
|
225
|
+
"DELETE FROM job_queue WHERE status IN ('completed', 'failed') AND completed_at < datetime('now', ?)",
|
|
226
|
+
[`-${olderThanDays} days`],
|
|
227
|
+
);
|
|
228
|
+
return result.changes;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─── Internal ──────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
private allDepsCompleted(deps: string[]): boolean {
|
|
234
|
+
for (const depId of deps) {
|
|
235
|
+
const row = this.provider.get<{ status: string }>(
|
|
236
|
+
'SELECT status FROM job_queue WHERE id = ?',
|
|
237
|
+
[depId],
|
|
238
|
+
);
|
|
239
|
+
if (!row || row.status !== 'completed') return false;
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── Row Types ───────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
interface JobRow {
|
|
248
|
+
id: string;
|
|
249
|
+
type: string;
|
|
250
|
+
status: string;
|
|
251
|
+
entry_id: string | null;
|
|
252
|
+
payload: string;
|
|
253
|
+
depends_on: string;
|
|
254
|
+
pipeline_id: string | null;
|
|
255
|
+
retry_count: number;
|
|
256
|
+
max_retries: number;
|
|
257
|
+
result: string | null;
|
|
258
|
+
error: string | null;
|
|
259
|
+
created_at: string;
|
|
260
|
+
started_at: string | null;
|
|
261
|
+
completed_at: string | null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function rowToJob(row: JobRow): Job {
|
|
265
|
+
return {
|
|
266
|
+
id: row.id,
|
|
267
|
+
type: row.type,
|
|
268
|
+
status: row.status as JobStatus,
|
|
269
|
+
entryId: row.entry_id,
|
|
270
|
+
payload: JSON.parse(row.payload) as Record<string, unknown>,
|
|
271
|
+
dependsOn: JSON.parse(row.depends_on) as string[],
|
|
272
|
+
pipelineId: row.pipeline_id,
|
|
273
|
+
retryCount: row.retry_count,
|
|
274
|
+
maxRetries: row.max_retries,
|
|
275
|
+
result: row.result ? JSON.parse(row.result) : null,
|
|
276
|
+
error: row.error,
|
|
277
|
+
createdAt: row.created_at,
|
|
278
|
+
startedAt: row.started_at,
|
|
279
|
+
completedAt: row.completed_at,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Runner — background polling loop for DAG job execution.
|
|
3
|
+
*
|
|
4
|
+
* Polls the job queue for ready jobs (dependencies completed),
|
|
5
|
+
* dispatches them to registered handlers, and marks them complete/failed.
|
|
6
|
+
*
|
|
7
|
+
* Generic — handlers are registered by type, any module can add its own.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { JobQueue, Job } from './job-queue.js';
|
|
11
|
+
|
|
12
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export type JobHandler = (job: Job) => Promise<unknown>;
|
|
15
|
+
|
|
16
|
+
export interface PipelineRunnerStatus {
|
|
17
|
+
running: boolean;
|
|
18
|
+
pollIntervalMs: number;
|
|
19
|
+
tickCount: number;
|
|
20
|
+
jobsProcessed: number;
|
|
21
|
+
jobsFailed: number;
|
|
22
|
+
jobsRetried: number;
|
|
23
|
+
lastTickAt: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Class ───────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export class PipelineRunner {
|
|
29
|
+
private queue: JobQueue;
|
|
30
|
+
private handlers = new Map<string, JobHandler>();
|
|
31
|
+
private pollIntervalMs: number;
|
|
32
|
+
private running = false;
|
|
33
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
34
|
+
private tickCount = 0;
|
|
35
|
+
private jobsProcessed = 0;
|
|
36
|
+
private jobsFailed = 0;
|
|
37
|
+
private jobsRetried = 0;
|
|
38
|
+
private lastTickAt: string | null = null;
|
|
39
|
+
private processing = false;
|
|
40
|
+
|
|
41
|
+
constructor(queue: JobQueue, pollIntervalMs: number = 5000) {
|
|
42
|
+
this.queue = queue;
|
|
43
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Register a handler for a job type. When a job of this type is dequeued,
|
|
48
|
+
* the handler is called. Return value is stored as the job result.
|
|
49
|
+
*/
|
|
50
|
+
registerHandler(type: string, handler: JobHandler): void {
|
|
51
|
+
this.handlers.set(type, handler);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Start background polling. Idempotent — calling start() twice is safe.
|
|
56
|
+
*/
|
|
57
|
+
start(): void {
|
|
58
|
+
if (this.running) return;
|
|
59
|
+
this.running = true;
|
|
60
|
+
this.timer = setInterval(() => this.tick(), this.pollIntervalMs);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Stop background polling.
|
|
65
|
+
*/
|
|
66
|
+
stop(): void {
|
|
67
|
+
this.running = false;
|
|
68
|
+
if (this.timer) {
|
|
69
|
+
clearInterval(this.timer);
|
|
70
|
+
this.timer = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Process one batch of ready jobs immediately (without waiting for poll).
|
|
76
|
+
* Useful for testing or manual triggering.
|
|
77
|
+
*/
|
|
78
|
+
async processOnce(batchSize: number = 5): Promise<number> {
|
|
79
|
+
return this.processBatch(batchSize);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get runner status.
|
|
84
|
+
*/
|
|
85
|
+
getStatus(): PipelineRunnerStatus {
|
|
86
|
+
return {
|
|
87
|
+
running: this.running,
|
|
88
|
+
pollIntervalMs: this.pollIntervalMs,
|
|
89
|
+
tickCount: this.tickCount,
|
|
90
|
+
jobsProcessed: this.jobsProcessed,
|
|
91
|
+
jobsFailed: this.jobsFailed,
|
|
92
|
+
jobsRetried: this.jobsRetried,
|
|
93
|
+
lastTickAt: this.lastTickAt,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if a handler is registered for a job type.
|
|
99
|
+
*/
|
|
100
|
+
hasHandler(type: string): boolean {
|
|
101
|
+
return this.handlers.has(type);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Internal ──────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
private tick(): void {
|
|
107
|
+
if (this.processing) return; // Skip if previous tick still running
|
|
108
|
+
this.tickCount++;
|
|
109
|
+
this.lastTickAt = new Date().toISOString();
|
|
110
|
+
this.processBatch(5).catch(() => {
|
|
111
|
+
/* best-effort */
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async processBatch(batchSize: number): Promise<number> {
|
|
116
|
+
this.processing = true;
|
|
117
|
+
let processed = 0;
|
|
118
|
+
try {
|
|
119
|
+
const jobs = this.queue.dequeueReady(batchSize);
|
|
120
|
+
for (const job of jobs) {
|
|
121
|
+
const handler = this.handlers.get(job.type);
|
|
122
|
+
if (!handler) {
|
|
123
|
+
this.queue.fail(job.id, `No handler registered for job type: ${job.type}`);
|
|
124
|
+
this.jobsFailed++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const result = await handler(job);
|
|
130
|
+
this.queue.complete(job.id, result);
|
|
131
|
+
this.jobsProcessed++;
|
|
132
|
+
processed++;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
135
|
+
const retried = this.queue.retry(job.id);
|
|
136
|
+
if (retried) {
|
|
137
|
+
this.jobsRetried++;
|
|
138
|
+
} else {
|
|
139
|
+
this.queue.fail(job.id, errorMsg);
|
|
140
|
+
this.jobsFailed++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
this.processing = false;
|
|
146
|
+
}
|
|
147
|
+
return processed;
|
|
148
|
+
}
|
|
149
|
+
}
|