@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.
Files changed (210) hide show
  1. package/dist/agency/agency-manager.d.ts +27 -1
  2. package/dist/agency/agency-manager.d.ts.map +1 -1
  3. package/dist/agency/agency-manager.js +180 -9
  4. package/dist/agency/agency-manager.js.map +1 -1
  5. package/dist/agency/default-rules.d.ts +7 -0
  6. package/dist/agency/default-rules.d.ts.map +1 -0
  7. package/dist/agency/default-rules.js +79 -0
  8. package/dist/agency/default-rules.js.map +1 -0
  9. package/dist/agency/types.d.ts +48 -0
  10. package/dist/agency/types.d.ts.map +1 -1
  11. package/dist/brain/brain.d.ts +17 -2
  12. package/dist/brain/brain.d.ts.map +1 -1
  13. package/dist/brain/brain.js +118 -8
  14. package/dist/brain/brain.js.map +1 -1
  15. package/dist/brain/knowledge-synthesizer.d.ts +37 -0
  16. package/dist/brain/knowledge-synthesizer.d.ts.map +1 -0
  17. package/dist/brain/knowledge-synthesizer.js +161 -0
  18. package/dist/brain/knowledge-synthesizer.js.map +1 -0
  19. package/dist/brain/learning-radar.d.ts +96 -0
  20. package/dist/brain/learning-radar.d.ts.map +1 -0
  21. package/dist/brain/learning-radar.js +202 -0
  22. package/dist/brain/learning-radar.js.map +1 -0
  23. package/dist/brain/types.d.ts +15 -0
  24. package/dist/brain/types.d.ts.map +1 -1
  25. package/dist/context/context-engine.d.ts.map +1 -1
  26. package/dist/context/context-engine.js +82 -17
  27. package/dist/context/context-engine.js.map +1 -1
  28. package/dist/context/types.d.ts +5 -0
  29. package/dist/context/types.d.ts.map +1 -1
  30. package/dist/control/intent-router.d.ts +12 -1
  31. package/dist/control/intent-router.d.ts.map +1 -1
  32. package/dist/control/intent-router.js +68 -0
  33. package/dist/control/intent-router.js.map +1 -1
  34. package/dist/control/types.d.ts +17 -0
  35. package/dist/control/types.d.ts.map +1 -1
  36. package/dist/curator/classifier.d.ts +18 -0
  37. package/dist/curator/classifier.d.ts.map +1 -0
  38. package/dist/curator/classifier.js +61 -0
  39. package/dist/curator/classifier.js.map +1 -0
  40. package/dist/curator/quality-gate.d.ts +29 -0
  41. package/dist/curator/quality-gate.d.ts.map +1 -0
  42. package/dist/curator/quality-gate.js +88 -0
  43. package/dist/curator/quality-gate.js.map +1 -0
  44. package/dist/engine/bin/soleri-engine.js +1 -0
  45. package/dist/engine/bin/soleri-engine.js.map +1 -1
  46. package/dist/events/event-bus.d.ts +30 -0
  47. package/dist/events/event-bus.d.ts.map +1 -0
  48. package/dist/events/event-bus.js +51 -0
  49. package/dist/events/event-bus.js.map +1 -0
  50. package/dist/flows/chain-runner.d.ts +46 -0
  51. package/dist/flows/chain-runner.d.ts.map +1 -0
  52. package/dist/flows/chain-runner.js +271 -0
  53. package/dist/flows/chain-runner.js.map +1 -0
  54. package/dist/flows/chain-types.d.ts +103 -0
  55. package/dist/flows/chain-types.d.ts.map +1 -0
  56. package/dist/flows/chain-types.js +23 -0
  57. package/dist/flows/chain-types.js.map +1 -0
  58. package/dist/health/doctor-checks.d.ts +15 -0
  59. package/dist/health/doctor-checks.d.ts.map +1 -0
  60. package/dist/health/doctor-checks.js +98 -0
  61. package/dist/health/doctor-checks.js.map +1 -0
  62. package/dist/intake/text-ingester.d.ts +52 -0
  63. package/dist/intake/text-ingester.d.ts.map +1 -0
  64. package/dist/intake/text-ingester.js +181 -0
  65. package/dist/intake/text-ingester.js.map +1 -0
  66. package/dist/llm/llm-client.d.ts.map +1 -1
  67. package/dist/llm/llm-client.js +37 -1
  68. package/dist/llm/llm-client.js.map +1 -1
  69. package/dist/llm/oauth-discovery.d.ts +26 -0
  70. package/dist/llm/oauth-discovery.d.ts.map +1 -0
  71. package/dist/llm/oauth-discovery.js +149 -0
  72. package/dist/llm/oauth-discovery.js.map +1 -0
  73. package/dist/planning/evidence-collector.d.ts +41 -0
  74. package/dist/planning/evidence-collector.d.ts.map +1 -0
  75. package/dist/planning/evidence-collector.js +194 -0
  76. package/dist/planning/evidence-collector.js.map +1 -0
  77. package/dist/planning/planner.d.ts +4 -0
  78. package/dist/planning/planner.d.ts.map +1 -1
  79. package/dist/planning/planner.js +11 -0
  80. package/dist/planning/planner.js.map +1 -1
  81. package/dist/queue/job-queue.d.ts +92 -0
  82. package/dist/queue/job-queue.d.ts.map +1 -0
  83. package/dist/queue/job-queue.js +180 -0
  84. package/dist/queue/job-queue.js.map +1 -0
  85. package/dist/queue/pipeline-runner.d.ts +62 -0
  86. package/dist/queue/pipeline-runner.d.ts.map +1 -0
  87. package/dist/queue/pipeline-runner.js +126 -0
  88. package/dist/queue/pipeline-runner.js.map +1 -0
  89. package/dist/runtime/admin-setup-ops.d.ts +20 -0
  90. package/dist/runtime/admin-setup-ops.d.ts.map +1 -0
  91. package/dist/runtime/admin-setup-ops.js +583 -0
  92. package/dist/runtime/admin-setup-ops.js.map +1 -0
  93. package/dist/runtime/chain-ops.d.ts +9 -0
  94. package/dist/runtime/chain-ops.d.ts.map +1 -0
  95. package/dist/runtime/chain-ops.js +107 -0
  96. package/dist/runtime/chain-ops.js.map +1 -0
  97. package/dist/runtime/claude-md-helpers.d.ts +65 -0
  98. package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
  99. package/dist/runtime/claude-md-helpers.js +173 -0
  100. package/dist/runtime/claude-md-helpers.js.map +1 -0
  101. package/dist/runtime/curator-extra-ops.d.ts +3 -2
  102. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  103. package/dist/runtime/curator-extra-ops.js +81 -3
  104. package/dist/runtime/curator-extra-ops.js.map +1 -1
  105. package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
  106. package/dist/runtime/facades/admin-facade.js +4 -0
  107. package/dist/runtime/facades/admin-facade.js.map +1 -1
  108. package/dist/runtime/facades/agency-facade.d.ts.map +1 -1
  109. package/dist/runtime/facades/agency-facade.js +64 -0
  110. package/dist/runtime/facades/agency-facade.js.map +1 -1
  111. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  112. package/dist/runtime/facades/brain-facade.js +122 -1
  113. package/dist/runtime/facades/brain-facade.js.map +1 -1
  114. package/dist/runtime/facades/control-facade.d.ts.map +1 -1
  115. package/dist/runtime/facades/control-facade.js +42 -0
  116. package/dist/runtime/facades/control-facade.js.map +1 -1
  117. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  118. package/dist/runtime/facades/memory-facade.js +20 -2
  119. package/dist/runtime/facades/memory-facade.js.map +1 -1
  120. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  121. package/dist/runtime/facades/plan-facade.js +2 -0
  122. package/dist/runtime/facades/plan-facade.js.map +1 -1
  123. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  124. package/dist/runtime/facades/vault-facade.js +25 -5
  125. package/dist/runtime/facades/vault-facade.js.map +1 -1
  126. package/dist/runtime/intake-ops.d.ts +7 -5
  127. package/dist/runtime/intake-ops.d.ts.map +1 -1
  128. package/dist/runtime/intake-ops.js +98 -5
  129. package/dist/runtime/intake-ops.js.map +1 -1
  130. package/dist/runtime/memory-extra-ops.d.ts +6 -3
  131. package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
  132. package/dist/runtime/memory-extra-ops.js +292 -4
  133. package/dist/runtime/memory-extra-ops.js.map +1 -1
  134. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  135. package/dist/runtime/planning-extra-ops.js +85 -0
  136. package/dist/runtime/planning-extra-ops.js.map +1 -1
  137. package/dist/runtime/playbook-ops.js +1 -1
  138. package/dist/runtime/playbook-ops.js.map +1 -1
  139. package/dist/runtime/runtime.d.ts.map +1 -1
  140. package/dist/runtime/runtime.js +143 -2
  141. package/dist/runtime/runtime.js.map +1 -1
  142. package/dist/runtime/session-briefing.d.ts +23 -0
  143. package/dist/runtime/session-briefing.d.ts.map +1 -0
  144. package/dist/runtime/session-briefing.js +140 -0
  145. package/dist/runtime/session-briefing.js.map +1 -0
  146. package/dist/runtime/types.d.ts +23 -0
  147. package/dist/runtime/types.d.ts.map +1 -1
  148. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  149. package/dist/runtime/vault-linking-ops.js +1 -3
  150. package/dist/runtime/vault-linking-ops.js.map +1 -1
  151. package/dist/vault/vault.d.ts +25 -0
  152. package/dist/vault/vault.d.ts.map +1 -1
  153. package/dist/vault/vault.js +67 -3
  154. package/dist/vault/vault.js.map +1 -1
  155. package/package.json +1 -1
  156. package/src/__tests__/admin-setup-ops.test.ts +355 -0
  157. package/src/__tests__/async-infrastructure.test.ts +307 -0
  158. package/src/__tests__/cognee-client-gaps.test.ts +6 -2
  159. package/src/__tests__/cognee-hybrid-search.test.ts +49 -35
  160. package/src/__tests__/cognee-sync-manager-deep.test.ts +89 -65
  161. package/src/__tests__/curator-extra-ops.test.ts +6 -2
  162. package/src/__tests__/curator-pipeline-e2e.test.ts +358 -0
  163. package/src/__tests__/memory-extra-ops.test.ts +2 -2
  164. package/src/__tests__/planning-extra-ops.test.ts +2 -2
  165. package/src/__tests__/second-brain-features.test.ts +583 -0
  166. package/src/agency/agency-manager.ts +217 -9
  167. package/src/agency/default-rules.ts +83 -0
  168. package/src/agency/types.ts +61 -0
  169. package/src/brain/brain.ts +110 -8
  170. package/src/brain/knowledge-synthesizer.ts +218 -0
  171. package/src/brain/learning-radar.ts +340 -0
  172. package/src/brain/types.ts +16 -0
  173. package/src/context/context-engine.ts +114 -15
  174. package/src/context/types.ts +5 -0
  175. package/src/control/intent-router.ts +107 -0
  176. package/src/control/types.ts +10 -0
  177. package/src/curator/classifier.ts +88 -0
  178. package/src/curator/quality-gate.ts +129 -0
  179. package/src/engine/bin/soleri-engine.ts +1 -0
  180. package/src/events/event-bus.ts +58 -0
  181. package/src/flows/chain-runner.ts +369 -0
  182. package/src/flows/chain-types.ts +57 -0
  183. package/src/health/doctor-checks.ts +115 -0
  184. package/src/intake/text-ingester.ts +234 -0
  185. package/src/llm/llm-client.ts +38 -1
  186. package/src/llm/oauth-discovery.ts +169 -0
  187. package/src/planning/evidence-collector.ts +247 -0
  188. package/src/planning/planner.ts +11 -0
  189. package/src/queue/job-queue.ts +281 -0
  190. package/src/queue/pipeline-runner.ts +149 -0
  191. package/src/runtime/admin-setup-ops.ts +664 -0
  192. package/src/runtime/chain-ops.ts +121 -0
  193. package/src/runtime/claude-md-helpers.ts +236 -0
  194. package/src/runtime/curator-extra-ops.ts +86 -3
  195. package/src/runtime/facades/admin-facade.ts +4 -0
  196. package/src/runtime/facades/agency-facade.ts +68 -0
  197. package/src/runtime/facades/brain-facade.ts +142 -1
  198. package/src/runtime/facades/control-facade.ts +45 -0
  199. package/src/runtime/facades/memory-facade.ts +20 -2
  200. package/src/runtime/facades/plan-facade.ts +2 -0
  201. package/src/runtime/facades/vault-facade.ts +28 -5
  202. package/src/runtime/intake-ops.ts +107 -5
  203. package/src/runtime/memory-extra-ops.ts +312 -4
  204. package/src/runtime/planning-extra-ops.ts +94 -0
  205. package/src/runtime/playbook-ops.ts +1 -1
  206. package/src/runtime/runtime.ts +138 -2
  207. package/src/runtime/session-briefing.ts +161 -0
  208. package/src/runtime/types.ts +23 -0
  209. package/src/runtime/vault-linking-ops.ts +1 -3
  210. 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
+ }