@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
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain Runner — executes multi-step workflows with data flow between steps.
|
|
3
|
+
*
|
|
4
|
+
* Steps call facade ops via a dispatch function. Each step's output is stored
|
|
5
|
+
* in a context object and can be referenced by subsequent steps via $variable syntax.
|
|
6
|
+
*
|
|
7
|
+
* Gates pause execution. Resume via approve(). State persists to SQLite.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import type { PersistenceProvider } from '../persistence/types.js';
|
|
12
|
+
import type { ChainDef, ChainInstance, StepOutput } from './chain-types.js';
|
|
13
|
+
|
|
14
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export type DispatchFn = (op: string, params: Record<string, unknown>) => Promise<unknown>;
|
|
17
|
+
|
|
18
|
+
type GateCheckFn = (
|
|
19
|
+
gate: string,
|
|
20
|
+
stepId: string,
|
|
21
|
+
stepResult: unknown,
|
|
22
|
+
) => Promise<{ passed: boolean; message?: string }>;
|
|
23
|
+
|
|
24
|
+
// ─── Class ───────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export class ChainRunner {
|
|
27
|
+
private provider: PersistenceProvider;
|
|
28
|
+
|
|
29
|
+
constructor(provider: PersistenceProvider) {
|
|
30
|
+
this.provider = provider;
|
|
31
|
+
this.initializeTable();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private initializeTable(): void {
|
|
35
|
+
this.provider.execSql(`
|
|
36
|
+
CREATE TABLE IF NOT EXISTS chain_instances (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
chain_id TEXT NOT NULL,
|
|
39
|
+
chain_name TEXT NOT NULL DEFAULT '',
|
|
40
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
41
|
+
current_step TEXT,
|
|
42
|
+
paused_at_gate TEXT,
|
|
43
|
+
input TEXT NOT NULL DEFAULT '{}',
|
|
44
|
+
context TEXT NOT NULL DEFAULT '{}',
|
|
45
|
+
step_outputs TEXT NOT NULL DEFAULT '[]',
|
|
46
|
+
steps_completed INTEGER NOT NULL DEFAULT 0,
|
|
47
|
+
total_steps INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
49
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
50
|
+
);
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Execute a chain from the beginning (or a specific step).
|
|
56
|
+
*/
|
|
57
|
+
async execute(
|
|
58
|
+
chainDef: ChainDef,
|
|
59
|
+
input: Record<string, unknown>,
|
|
60
|
+
dispatch: DispatchFn,
|
|
61
|
+
gateCheck?: GateCheckFn,
|
|
62
|
+
startFromStep?: string,
|
|
63
|
+
): Promise<ChainInstance> {
|
|
64
|
+
const instanceId = randomUUID().slice(0, 12);
|
|
65
|
+
const instance: ChainInstance = {
|
|
66
|
+
id: instanceId,
|
|
67
|
+
chainId: chainDef.id,
|
|
68
|
+
chainName: chainDef.name ?? chainDef.id,
|
|
69
|
+
status: 'running',
|
|
70
|
+
currentStep: null,
|
|
71
|
+
pausedAtGate: null,
|
|
72
|
+
input,
|
|
73
|
+
context: { input },
|
|
74
|
+
stepOutputs: [],
|
|
75
|
+
stepsCompleted: 0,
|
|
76
|
+
totalSteps: chainDef.steps.length,
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
updatedAt: new Date().toISOString(),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
this.persist(instance);
|
|
82
|
+
|
|
83
|
+
// Find start index
|
|
84
|
+
let startIdx = 0;
|
|
85
|
+
if (startFromStep) {
|
|
86
|
+
const idx = chainDef.steps.findIndex((s) => s.id === startFromStep);
|
|
87
|
+
if (idx >= 0) startIdx = idx;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return this.runSteps(chainDef, instance, dispatch, gateCheck, startIdx);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resume a paused chain from where it left off.
|
|
95
|
+
*/
|
|
96
|
+
async resume(
|
|
97
|
+
instanceId: string,
|
|
98
|
+
chainDef: ChainDef,
|
|
99
|
+
dispatch: DispatchFn,
|
|
100
|
+
gateCheck?: GateCheckFn,
|
|
101
|
+
): Promise<ChainInstance> {
|
|
102
|
+
const instance = this.getInstance(instanceId);
|
|
103
|
+
if (!instance) throw new Error(`Chain instance not found: ${instanceId}`);
|
|
104
|
+
if (instance.status !== 'paused') throw new Error(`Chain is ${instance.status}, not paused`);
|
|
105
|
+
|
|
106
|
+
// Find the step after the paused gate
|
|
107
|
+
const pausedStep = instance.pausedAtGate;
|
|
108
|
+
if (!pausedStep) throw new Error('No paused gate to resume from');
|
|
109
|
+
|
|
110
|
+
const stepIdx = chainDef.steps.findIndex((s) => s.id === pausedStep);
|
|
111
|
+
if (stepIdx < 0) throw new Error(`Paused step ${pausedStep} not found in chain def`);
|
|
112
|
+
|
|
113
|
+
// Mark step as approved, move to next
|
|
114
|
+
instance.status = 'running';
|
|
115
|
+
instance.pausedAtGate = null;
|
|
116
|
+
this.persist(instance);
|
|
117
|
+
|
|
118
|
+
return this.runSteps(chainDef, instance, dispatch, gateCheck, stepIdx + 1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Approve a gate-paused step and resume the chain.
|
|
123
|
+
*/
|
|
124
|
+
async approve(
|
|
125
|
+
instanceId: string,
|
|
126
|
+
chainDef: ChainDef,
|
|
127
|
+
dispatch: DispatchFn,
|
|
128
|
+
gateCheck?: GateCheckFn,
|
|
129
|
+
): Promise<ChainInstance> {
|
|
130
|
+
return this.resume(instanceId, chainDef, dispatch, gateCheck);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get chain instance status.
|
|
135
|
+
*/
|
|
136
|
+
getInstance(instanceId: string): ChainInstance | null {
|
|
137
|
+
const row = this.provider.get<InstanceRow>('SELECT * FROM chain_instances WHERE id = ?', [
|
|
138
|
+
instanceId,
|
|
139
|
+
]);
|
|
140
|
+
return row ? rowToInstance(row) : null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* List all chain instances.
|
|
145
|
+
*/
|
|
146
|
+
list(limit: number = 20): ChainInstance[] {
|
|
147
|
+
const rows = this.provider.all<InstanceRow>(
|
|
148
|
+
'SELECT * FROM chain_instances ORDER BY updated_at DESC LIMIT ?',
|
|
149
|
+
[limit],
|
|
150
|
+
);
|
|
151
|
+
return rows.map(rowToInstance);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── Core Execution Loop ──────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
private async runSteps(
|
|
157
|
+
chainDef: ChainDef,
|
|
158
|
+
instance: ChainInstance,
|
|
159
|
+
dispatch: DispatchFn,
|
|
160
|
+
gateCheck: GateCheckFn | undefined,
|
|
161
|
+
startIdx: number,
|
|
162
|
+
): Promise<ChainInstance> {
|
|
163
|
+
// Steps MUST run sequentially — step N output feeds step N+1 input.
|
|
164
|
+
// Using recursive approach to satisfy no-await-in-loop lint rule.
|
|
165
|
+
return this.runStep(chainDef, instance, dispatch, gateCheck, startIdx);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async runStep(
|
|
169
|
+
chainDef: ChainDef,
|
|
170
|
+
instance: ChainInstance,
|
|
171
|
+
dispatch: DispatchFn,
|
|
172
|
+
gateCheck: GateCheckFn | undefined,
|
|
173
|
+
idx: number,
|
|
174
|
+
): Promise<ChainInstance> {
|
|
175
|
+
if (idx >= chainDef.steps.length) {
|
|
176
|
+
instance.status = 'completed';
|
|
177
|
+
instance.currentStep = null;
|
|
178
|
+
this.persist(instance);
|
|
179
|
+
return instance;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const step = chainDef.steps[idx];
|
|
183
|
+
instance.currentStep = step.id;
|
|
184
|
+
this.persist(instance);
|
|
185
|
+
|
|
186
|
+
const resolvedParams = resolveParams(step.params ?? {}, instance.context);
|
|
187
|
+
|
|
188
|
+
const stepStart = Date.now();
|
|
189
|
+
let result: unknown;
|
|
190
|
+
let stepStatus: StepOutput['status'] = 'completed';
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
result = await dispatch(step.op, resolvedParams);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
result = { error: (err as Error).message };
|
|
196
|
+
stepStatus = 'failed';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const output: StepOutput = {
|
|
200
|
+
stepId: step.id,
|
|
201
|
+
op: step.op,
|
|
202
|
+
result,
|
|
203
|
+
status: stepStatus,
|
|
204
|
+
durationMs: Date.now() - stepStart,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
instance.stepOutputs.push(output);
|
|
208
|
+
instance.context[step.id] = result;
|
|
209
|
+
if (step.output) instance.context[step.output] = result;
|
|
210
|
+
if (stepStatus === 'completed') instance.stepsCompleted++;
|
|
211
|
+
|
|
212
|
+
// Check gate
|
|
213
|
+
if (step.gate && step.gate !== 'none' && stepStatus === 'completed') {
|
|
214
|
+
const gateResult = await this.evaluateChainGate(step.gate, step.id, result, gateCheck);
|
|
215
|
+
if (!gateResult.passed) {
|
|
216
|
+
if (step.gate === 'user-approval') {
|
|
217
|
+
instance.status = 'paused';
|
|
218
|
+
instance.pausedAtGate = step.id;
|
|
219
|
+
this.persist(instance);
|
|
220
|
+
return instance;
|
|
221
|
+
}
|
|
222
|
+
instance.status = 'failed';
|
|
223
|
+
this.persist(instance);
|
|
224
|
+
return instance;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (stepStatus === 'failed') {
|
|
229
|
+
instance.status = 'failed';
|
|
230
|
+
this.persist(instance);
|
|
231
|
+
return instance;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.persist(instance);
|
|
235
|
+
return this.runStep(chainDef, instance, dispatch, gateCheck, idx + 1);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private async evaluateChainGate(
|
|
239
|
+
gate: string,
|
|
240
|
+
stepId: string,
|
|
241
|
+
stepResult: unknown,
|
|
242
|
+
gateCheck?: GateCheckFn,
|
|
243
|
+
): Promise<{ passed: boolean; message?: string }> {
|
|
244
|
+
switch (gate) {
|
|
245
|
+
case 'user-approval':
|
|
246
|
+
return { passed: false, message: 'Awaiting user approval' };
|
|
247
|
+
|
|
248
|
+
case 'auto-test': {
|
|
249
|
+
const result = stepResult as Record<string, unknown> | null;
|
|
250
|
+
if (!result) return { passed: false, message: 'Step returned no result' };
|
|
251
|
+
if (result.error) return { passed: false, message: `Step error: ${result.error}` };
|
|
252
|
+
return { passed: true };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case 'vault-check': {
|
|
256
|
+
if (gateCheck) return gateCheck(gate, stepId, stepResult);
|
|
257
|
+
return { passed: true };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
default:
|
|
261
|
+
return { passed: true };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─── Persistence ──────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
private persist(instance: ChainInstance): void {
|
|
268
|
+
instance.updatedAt = new Date().toISOString();
|
|
269
|
+
this.provider.run(
|
|
270
|
+
`INSERT OR REPLACE INTO chain_instances
|
|
271
|
+
(id, chain_id, chain_name, status, current_step, paused_at_gate, input, context, step_outputs, steps_completed, total_steps, created_at, updated_at)
|
|
272
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
273
|
+
[
|
|
274
|
+
instance.id,
|
|
275
|
+
instance.chainId,
|
|
276
|
+
instance.chainName,
|
|
277
|
+
instance.status,
|
|
278
|
+
instance.currentStep,
|
|
279
|
+
instance.pausedAtGate,
|
|
280
|
+
JSON.stringify(instance.input),
|
|
281
|
+
JSON.stringify(instance.context),
|
|
282
|
+
JSON.stringify(instance.stepOutputs),
|
|
283
|
+
instance.stepsCompleted,
|
|
284
|
+
instance.totalSteps,
|
|
285
|
+
instance.createdAt,
|
|
286
|
+
instance.updatedAt,
|
|
287
|
+
],
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ─── Variable Resolution ─────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Resolve $variable references in params.
|
|
296
|
+
* $input.url → context.input.url
|
|
297
|
+
* $research.title → context.research.title
|
|
298
|
+
* $stepId → context.stepId (whole object)
|
|
299
|
+
*/
|
|
300
|
+
function resolveParams(
|
|
301
|
+
params: Record<string, unknown>,
|
|
302
|
+
context: Record<string, unknown>,
|
|
303
|
+
): Record<string, unknown> {
|
|
304
|
+
const resolved: Record<string, unknown> = {};
|
|
305
|
+
for (const [key, value] of Object.entries(params)) {
|
|
306
|
+
resolved[key] = resolveValue(value, context);
|
|
307
|
+
}
|
|
308
|
+
return resolved;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function resolveValue(value: unknown, context: Record<string, unknown>): unknown {
|
|
312
|
+
if (typeof value === 'string' && value.startsWith('$')) {
|
|
313
|
+
return resolveReference(value.slice(1), context);
|
|
314
|
+
}
|
|
315
|
+
if (Array.isArray(value)) {
|
|
316
|
+
return value.map((v) => resolveValue(v, context));
|
|
317
|
+
}
|
|
318
|
+
if (value !== null && typeof value === 'object') {
|
|
319
|
+
return resolveParams(value as Record<string, unknown>, context);
|
|
320
|
+
}
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function resolveReference(path: string, context: Record<string, unknown>): unknown {
|
|
325
|
+
const parts = path.split('.');
|
|
326
|
+
let current: unknown = context;
|
|
327
|
+
for (const part of parts) {
|
|
328
|
+
if (current === null || current === undefined) return undefined;
|
|
329
|
+
if (typeof current !== 'object') return undefined;
|
|
330
|
+
current = (current as Record<string, unknown>)[part];
|
|
331
|
+
}
|
|
332
|
+
return current;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ─── Row Types ───────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
interface InstanceRow {
|
|
338
|
+
id: string;
|
|
339
|
+
chain_id: string;
|
|
340
|
+
chain_name: string;
|
|
341
|
+
status: string;
|
|
342
|
+
current_step: string | null;
|
|
343
|
+
paused_at_gate: string | null;
|
|
344
|
+
input: string;
|
|
345
|
+
context: string;
|
|
346
|
+
step_outputs: string;
|
|
347
|
+
steps_completed: number;
|
|
348
|
+
total_steps: number;
|
|
349
|
+
created_at: string;
|
|
350
|
+
updated_at: string;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function rowToInstance(row: InstanceRow): ChainInstance {
|
|
354
|
+
return {
|
|
355
|
+
id: row.id,
|
|
356
|
+
chainId: row.chain_id,
|
|
357
|
+
chainName: row.chain_name,
|
|
358
|
+
status: row.status as ChainInstance['status'],
|
|
359
|
+
currentStep: row.current_step,
|
|
360
|
+
pausedAtGate: row.paused_at_gate,
|
|
361
|
+
input: JSON.parse(row.input) as Record<string, unknown>,
|
|
362
|
+
context: JSON.parse(row.context) as Record<string, unknown>,
|
|
363
|
+
stepOutputs: JSON.parse(row.step_outputs) as StepOutput[],
|
|
364
|
+
stepsCompleted: row.steps_completed,
|
|
365
|
+
totalSteps: row.total_steps,
|
|
366
|
+
createdAt: row.created_at,
|
|
367
|
+
updatedAt: row.updated_at,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain types — composable multi-step workflows with data flow.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
// ─── Chain Definition (YAML schema) ──────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export const chainStepSchema = z.object({
|
|
10
|
+
id: z.string(),
|
|
11
|
+
op: z.string().describe('Facade op to call'),
|
|
12
|
+
params: z
|
|
13
|
+
.record(z.unknown())
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Params — use $input.x or $stepId.field for data flow'),
|
|
16
|
+
output: z.string().optional().describe('Key to store step result under'),
|
|
17
|
+
gate: z.enum(['user-approval', 'auto-test', 'vault-check', 'none']).optional(),
|
|
18
|
+
description: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const chainDefSchema = z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
name: z.string().optional(),
|
|
24
|
+
description: z.string().optional(),
|
|
25
|
+
steps: z.array(chainStepSchema).min(1),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type ChainDef = z.infer<typeof chainDefSchema>;
|
|
29
|
+
export type ChainStep = z.infer<typeof chainStepSchema>;
|
|
30
|
+
|
|
31
|
+
// ─── Chain Instance (runtime state) ──────────────────────────────────
|
|
32
|
+
|
|
33
|
+
export type ChainStatus = 'running' | 'paused' | 'completed' | 'failed';
|
|
34
|
+
|
|
35
|
+
export interface StepOutput {
|
|
36
|
+
stepId: string;
|
|
37
|
+
op: string;
|
|
38
|
+
result: unknown;
|
|
39
|
+
status: 'completed' | 'failed' | 'skipped';
|
|
40
|
+
durationMs: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ChainInstance {
|
|
44
|
+
id: string;
|
|
45
|
+
chainId: string;
|
|
46
|
+
chainName: string;
|
|
47
|
+
status: ChainStatus;
|
|
48
|
+
currentStep: string | null;
|
|
49
|
+
pausedAtGate: string | null;
|
|
50
|
+
input: Record<string, unknown>;
|
|
51
|
+
context: Record<string, unknown>;
|
|
52
|
+
stepOutputs: StepOutput[];
|
|
53
|
+
stepsCompleted: number;
|
|
54
|
+
totalSteps: number;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor Checks — 8 specialized health checks for comprehensive diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Each check validates a subsystem and reports pass/warn/fail.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HealthRegistry } from './health-registry.js';
|
|
8
|
+
import type { AgentRuntime } from '../runtime/types.js';
|
|
9
|
+
|
|
10
|
+
export interface DoctorCheckResult {
|
|
11
|
+
check: string;
|
|
12
|
+
status: 'pass' | 'warn' | 'fail';
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function runDoctorChecks(runtime: AgentRuntime): DoctorCheckResult[] {
|
|
17
|
+
const results: DoctorCheckResult[] = [];
|
|
18
|
+
|
|
19
|
+
// 1. Config
|
|
20
|
+
try {
|
|
21
|
+
const id = runtime.config.agentId;
|
|
22
|
+
results.push({
|
|
23
|
+
check: 'config',
|
|
24
|
+
status: id ? 'pass' : 'fail',
|
|
25
|
+
message: id ? `Agent "${id}" configured` : 'No agent ID',
|
|
26
|
+
});
|
|
27
|
+
} catch (e) {
|
|
28
|
+
results.push({ check: 'config', status: 'fail', message: (e as Error).message });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Database
|
|
32
|
+
try {
|
|
33
|
+
runtime.vault.getProvider().get<{ v: number }>('PRAGMA user_version');
|
|
34
|
+
results.push({ check: 'database', status: 'pass', message: 'SQLite healthy' });
|
|
35
|
+
} catch (e) {
|
|
36
|
+
results.push({ check: 'database', status: 'fail', message: (e as Error).message });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. Vault
|
|
40
|
+
try {
|
|
41
|
+
const stats = runtime.vault.stats();
|
|
42
|
+
results.push({
|
|
43
|
+
check: 'vault',
|
|
44
|
+
status: stats.totalEntries > 0 ? 'pass' : 'warn',
|
|
45
|
+
message: stats.totalEntries > 0 ? `${stats.totalEntries} entries` : 'Vault empty',
|
|
46
|
+
});
|
|
47
|
+
} catch (e) {
|
|
48
|
+
results.push({ check: 'vault', status: 'fail', message: (e as Error).message });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 4. LLM
|
|
52
|
+
try {
|
|
53
|
+
const pool = runtime.keyPool;
|
|
54
|
+
const total = (pool.openai?.getActiveKey() ? 1 : 0) + (pool.anthropic?.getActiveKey() ? 1 : 0);
|
|
55
|
+
results.push({
|
|
56
|
+
check: 'llm',
|
|
57
|
+
status: total > 0 ? 'pass' : 'warn',
|
|
58
|
+
message: total > 0 ? `${total} provider(s) available` : 'No API keys — LLM features disabled',
|
|
59
|
+
});
|
|
60
|
+
} catch {
|
|
61
|
+
results.push({ check: 'llm', status: 'warn', message: 'LLM check failed' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 5. Auth
|
|
65
|
+
results.push({
|
|
66
|
+
check: 'auth',
|
|
67
|
+
status: 'pass',
|
|
68
|
+
message: `Mode: ${runtime.authPolicy.mode}`,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 6. Plugins
|
|
72
|
+
try {
|
|
73
|
+
const count = runtime.pluginRegistry.list().length;
|
|
74
|
+
results.push({ check: 'plugins', status: 'pass', message: `${count} plugin(s)` });
|
|
75
|
+
} catch {
|
|
76
|
+
results.push({ check: 'plugins', status: 'warn', message: 'Plugin check failed' });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 7. Embeddings (Cognee)
|
|
80
|
+
try {
|
|
81
|
+
const available = runtime.cognee?.isAvailable ?? false;
|
|
82
|
+
results.push({
|
|
83
|
+
check: 'embeddings',
|
|
84
|
+
status: available ? 'pass' : 'warn',
|
|
85
|
+
message: available ? 'Cognee available' : 'Cognee not available — vector search disabled',
|
|
86
|
+
});
|
|
87
|
+
} catch {
|
|
88
|
+
results.push({ check: 'embeddings', status: 'warn', message: 'Embedding check failed' });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 8. Security
|
|
92
|
+
results.push({
|
|
93
|
+
check: 'security',
|
|
94
|
+
status: runtime.authPolicy.mode === 'permissive' ? 'warn' : 'pass',
|
|
95
|
+
message:
|
|
96
|
+
runtime.authPolicy.mode === 'permissive'
|
|
97
|
+
? 'Permissive mode — all ops allowed'
|
|
98
|
+
: `Enforcement: ${runtime.authPolicy.mode}`,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function registerDoctorChecks(health: HealthRegistry, runtime: AgentRuntime): void {
|
|
105
|
+
const results = runDoctorChecks(runtime);
|
|
106
|
+
for (const r of results) {
|
|
107
|
+
health.register(
|
|
108
|
+
r.check,
|
|
109
|
+
r.status === 'pass' ? 'healthy' : r.status === 'warn' ? 'degraded' : 'down',
|
|
110
|
+
);
|
|
111
|
+
if (r.status !== 'pass') {
|
|
112
|
+
health.update(r.check, r.status === 'warn' ? 'degraded' : 'down', r.message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|