@soleri/core 9.10.0 → 9.12.1

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 (248) hide show
  1. package/dist/adapters/types.d.ts +2 -0
  2. package/dist/adapters/types.d.ts.map +1 -1
  3. package/dist/brain/brain.d.ts +5 -1
  4. package/dist/brain/brain.d.ts.map +1 -1
  5. package/dist/brain/brain.js +97 -10
  6. package/dist/brain/brain.js.map +1 -1
  7. package/dist/brain/intelligence.d.ts.map +1 -1
  8. package/dist/brain/intelligence.js +4 -0
  9. package/dist/brain/intelligence.js.map +1 -1
  10. package/dist/brain/types.d.ts +1 -1
  11. package/dist/brain/types.d.ts.map +1 -1
  12. package/dist/dream/cron-manager.d.ts +10 -0
  13. package/dist/dream/cron-manager.d.ts.map +1 -0
  14. package/dist/dream/cron-manager.js +122 -0
  15. package/dist/dream/cron-manager.js.map +1 -0
  16. package/dist/dream/dream-engine.d.ts +34 -0
  17. package/dist/dream/dream-engine.d.ts.map +1 -0
  18. package/dist/dream/dream-engine.js +88 -0
  19. package/dist/dream/dream-engine.js.map +1 -0
  20. package/dist/dream/dream-ops.d.ts +8 -0
  21. package/dist/dream/dream-ops.d.ts.map +1 -0
  22. package/dist/dream/dream-ops.js +49 -0
  23. package/dist/dream/dream-ops.js.map +1 -0
  24. package/dist/dream/index.d.ts +7 -0
  25. package/dist/dream/index.d.ts.map +1 -0
  26. package/dist/dream/index.js +5 -0
  27. package/dist/dream/index.js.map +1 -0
  28. package/dist/dream/schema.d.ts +3 -0
  29. package/dist/dream/schema.d.ts.map +1 -0
  30. package/dist/dream/schema.js +16 -0
  31. package/dist/dream/schema.js.map +1 -0
  32. package/dist/embeddings/index.d.ts +5 -0
  33. package/dist/embeddings/index.d.ts.map +1 -0
  34. package/dist/embeddings/index.js +3 -0
  35. package/dist/embeddings/index.js.map +1 -0
  36. package/dist/embeddings/openai-provider.d.ts +31 -0
  37. package/dist/embeddings/openai-provider.d.ts.map +1 -0
  38. package/dist/embeddings/openai-provider.js +120 -0
  39. package/dist/embeddings/openai-provider.js.map +1 -0
  40. package/dist/embeddings/pipeline.d.ts +36 -0
  41. package/dist/embeddings/pipeline.d.ts.map +1 -0
  42. package/dist/embeddings/pipeline.js +78 -0
  43. package/dist/embeddings/pipeline.js.map +1 -0
  44. package/dist/embeddings/types.d.ts +62 -0
  45. package/dist/embeddings/types.d.ts.map +1 -0
  46. package/dist/embeddings/types.js +3 -0
  47. package/dist/embeddings/types.js.map +1 -0
  48. package/dist/engine/bin/soleri-engine.js +4 -1
  49. package/dist/engine/bin/soleri-engine.js.map +1 -1
  50. package/dist/engine/module-manifest.d.ts.map +1 -1
  51. package/dist/engine/module-manifest.js +20 -0
  52. package/dist/engine/module-manifest.js.map +1 -1
  53. package/dist/engine/register-engine.d.ts.map +1 -1
  54. package/dist/engine/register-engine.js +12 -0
  55. package/dist/engine/register-engine.js.map +1 -1
  56. package/dist/flows/chain-types.d.ts +8 -8
  57. package/dist/flows/dispatch-registry.d.ts +15 -1
  58. package/dist/flows/dispatch-registry.d.ts.map +1 -1
  59. package/dist/flows/dispatch-registry.js +28 -1
  60. package/dist/flows/dispatch-registry.js.map +1 -1
  61. package/dist/flows/executor.d.ts +20 -2
  62. package/dist/flows/executor.d.ts.map +1 -1
  63. package/dist/flows/executor.js +79 -1
  64. package/dist/flows/executor.js.map +1 -1
  65. package/dist/flows/index.d.ts +2 -1
  66. package/dist/flows/index.d.ts.map +1 -1
  67. package/dist/flows/index.js.map +1 -1
  68. package/dist/flows/types.d.ts +43 -21
  69. package/dist/flows/types.d.ts.map +1 -1
  70. package/dist/index.d.ts +5 -0
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +3 -0
  73. package/dist/index.js.map +1 -1
  74. package/dist/planning/plan-lifecycle.d.ts.map +1 -1
  75. package/dist/planning/plan-lifecycle.js +4 -2
  76. package/dist/planning/plan-lifecycle.js.map +1 -1
  77. package/dist/planning/planner-types.d.ts +1 -1
  78. package/dist/planning/planner-types.d.ts.map +1 -1
  79. package/dist/plugins/types.d.ts +31 -31
  80. package/dist/runtime/admin-ops.d.ts.map +1 -1
  81. package/dist/runtime/admin-ops.js +15 -0
  82. package/dist/runtime/admin-ops.js.map +1 -1
  83. package/dist/runtime/admin-setup-ops.js +2 -2
  84. package/dist/runtime/admin-setup-ops.js.map +1 -1
  85. package/dist/runtime/embedding-ops.d.ts +12 -0
  86. package/dist/runtime/embedding-ops.d.ts.map +1 -0
  87. package/dist/runtime/embedding-ops.js +96 -0
  88. package/dist/runtime/embedding-ops.js.map +1 -0
  89. package/dist/runtime/facades/embedding-facade.d.ts +7 -0
  90. package/dist/runtime/facades/embedding-facade.d.ts.map +1 -0
  91. package/dist/runtime/facades/embedding-facade.js +8 -0
  92. package/dist/runtime/facades/embedding-facade.js.map +1 -0
  93. package/dist/runtime/facades/index.d.ts.map +1 -1
  94. package/dist/runtime/facades/index.js +12 -0
  95. package/dist/runtime/facades/index.js.map +1 -1
  96. package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
  97. package/dist/runtime/facades/orchestrate-facade.js +120 -0
  98. package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
  99. package/dist/runtime/feature-flags.d.ts.map +1 -1
  100. package/dist/runtime/feature-flags.js +4 -0
  101. package/dist/runtime/feature-flags.js.map +1 -1
  102. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  103. package/dist/runtime/orchestrate-ops.js +146 -12
  104. package/dist/runtime/orchestrate-ops.js.map +1 -1
  105. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  106. package/dist/runtime/planning-extra-ops.js +51 -0
  107. package/dist/runtime/planning-extra-ops.js.map +1 -1
  108. package/dist/runtime/preflight.d.ts +32 -0
  109. package/dist/runtime/preflight.d.ts.map +1 -0
  110. package/dist/runtime/preflight.js +29 -0
  111. package/dist/runtime/preflight.js.map +1 -0
  112. package/dist/runtime/quality-signals.d.ts +6 -1
  113. package/dist/runtime/quality-signals.d.ts.map +1 -1
  114. package/dist/runtime/quality-signals.js +41 -5
  115. package/dist/runtime/quality-signals.js.map +1 -1
  116. package/dist/runtime/runtime.d.ts.map +1 -1
  117. package/dist/runtime/runtime.js +33 -2
  118. package/dist/runtime/runtime.js.map +1 -1
  119. package/dist/runtime/types.d.ts +27 -0
  120. package/dist/runtime/types.d.ts.map +1 -1
  121. package/dist/skills/step-tracker.d.ts +39 -0
  122. package/dist/skills/step-tracker.d.ts.map +1 -0
  123. package/dist/skills/step-tracker.js +105 -0
  124. package/dist/skills/step-tracker.js.map +1 -0
  125. package/dist/skills/sync-skills.d.ts +3 -2
  126. package/dist/skills/sync-skills.d.ts.map +1 -1
  127. package/dist/skills/sync-skills.js +42 -8
  128. package/dist/skills/sync-skills.js.map +1 -1
  129. package/dist/subagent/dispatcher.d.ts +4 -3
  130. package/dist/subagent/dispatcher.d.ts.map +1 -1
  131. package/dist/subagent/dispatcher.js +57 -35
  132. package/dist/subagent/dispatcher.js.map +1 -1
  133. package/dist/subagent/index.d.ts +1 -0
  134. package/dist/subagent/index.d.ts.map +1 -1
  135. package/dist/subagent/index.js.map +1 -1
  136. package/dist/subagent/orphan-reaper.d.ts +51 -4
  137. package/dist/subagent/orphan-reaper.d.ts.map +1 -1
  138. package/dist/subagent/orphan-reaper.js +103 -3
  139. package/dist/subagent/orphan-reaper.js.map +1 -1
  140. package/dist/subagent/types.d.ts +7 -0
  141. package/dist/subagent/types.d.ts.map +1 -1
  142. package/dist/subagent/workspace-resolver.d.ts +2 -0
  143. package/dist/subagent/workspace-resolver.d.ts.map +1 -1
  144. package/dist/subagent/workspace-resolver.js +3 -1
  145. package/dist/subagent/workspace-resolver.js.map +1 -1
  146. package/dist/vault/vault-entries.d.ts +18 -0
  147. package/dist/vault/vault-entries.d.ts.map +1 -1
  148. package/dist/vault/vault-entries.js +73 -0
  149. package/dist/vault/vault-entries.js.map +1 -1
  150. package/dist/vault/vault-manager.d.ts.map +1 -1
  151. package/dist/vault/vault-manager.js +1 -0
  152. package/dist/vault/vault-manager.js.map +1 -1
  153. package/dist/vault/vault-schema.d.ts.map +1 -1
  154. package/dist/vault/vault-schema.js +14 -0
  155. package/dist/vault/vault-schema.js.map +1 -1
  156. package/dist/vault/vault.d.ts +1 -0
  157. package/dist/vault/vault.d.ts.map +1 -1
  158. package/dist/vault/vault.js.map +1 -1
  159. package/package.json +3 -5
  160. package/src/__tests__/cron-manager.test.ts +132 -0
  161. package/src/__tests__/deviation-detection.test.ts +234 -0
  162. package/src/__tests__/embeddings.test.ts +536 -0
  163. package/src/__tests__/preflight.test.ts +97 -0
  164. package/src/__tests__/step-persistence.test.ts +324 -0
  165. package/src/__tests__/step-tracker.test.ts +260 -0
  166. package/src/__tests__/subagent/dispatcher.test.ts +122 -4
  167. package/src/__tests__/subagent/orphan-reaper.test.ts +148 -12
  168. package/src/__tests__/subagent/process-lifecycle.test.ts +422 -0
  169. package/src/__tests__/subagent/workspace-resolver.test.ts +6 -1
  170. package/src/adapters/types.ts +2 -0
  171. package/src/brain/brain.ts +117 -9
  172. package/src/brain/intelligence.ts +4 -0
  173. package/src/brain/types.ts +6 -1
  174. package/src/dream/cron-manager.ts +137 -0
  175. package/src/dream/dream-engine.ts +119 -0
  176. package/src/dream/dream-ops.ts +56 -0
  177. package/src/dream/dream.test.ts +182 -0
  178. package/src/dream/index.ts +6 -0
  179. package/src/dream/schema.ts +17 -0
  180. package/src/embeddings/openai-provider.ts +158 -0
  181. package/src/embeddings/pipeline.ts +126 -0
  182. package/src/embeddings/types.ts +67 -0
  183. package/src/engine/bin/soleri-engine.ts +4 -1
  184. package/src/engine/module-manifest.test.ts +4 -4
  185. package/src/engine/module-manifest.ts +20 -0
  186. package/src/engine/register-engine.ts +12 -0
  187. package/src/flows/dispatch-registry.ts +44 -1
  188. package/src/flows/executor.ts +93 -2
  189. package/src/flows/index.ts +2 -0
  190. package/src/flows/types.ts +39 -1
  191. package/src/index.ts +11 -0
  192. package/src/planning/goal-ancestry.test.ts +3 -5
  193. package/src/planning/plan-lifecycle.ts +5 -2
  194. package/src/planning/planner-types.ts +1 -1
  195. package/src/planning/planner.test.ts +73 -3
  196. package/src/runtime/admin-ops.test.ts +2 -2
  197. package/src/runtime/admin-ops.ts +17 -0
  198. package/src/runtime/admin-setup-ops.ts +2 -2
  199. package/src/runtime/embedding-ops.ts +116 -0
  200. package/src/runtime/facades/admin-facade.test.ts +31 -0
  201. package/src/runtime/facades/embedding-facade.ts +11 -0
  202. package/src/runtime/facades/index.ts +12 -0
  203. package/src/runtime/facades/orchestrate-facade.test.ts +16 -0
  204. package/src/runtime/facades/orchestrate-facade.ts +146 -0
  205. package/src/runtime/feature-flags.ts +4 -0
  206. package/src/runtime/orchestrate-ops.test.ts +182 -2
  207. package/src/runtime/orchestrate-ops.ts +170 -13
  208. package/src/runtime/planning-extra-ops.ts +77 -0
  209. package/src/runtime/preflight.ts +53 -0
  210. package/src/runtime/quality-signals.test.ts +182 -8
  211. package/src/runtime/quality-signals.ts +44 -5
  212. package/src/runtime/runtime.ts +41 -2
  213. package/src/runtime/types.ts +20 -0
  214. package/src/skills/__tests__/sync-skills.test.ts +132 -0
  215. package/src/skills/step-tracker.ts +162 -0
  216. package/src/skills/sync-skills.ts +54 -9
  217. package/src/subagent/dispatcher.ts +62 -39
  218. package/src/subagent/index.ts +1 -0
  219. package/src/subagent/orphan-reaper.test.ts +135 -0
  220. package/src/subagent/orphan-reaper.ts +130 -7
  221. package/src/subagent/types.ts +10 -0
  222. package/src/subagent/workspace-resolver.ts +3 -1
  223. package/src/vault/vault-entries.ts +112 -0
  224. package/src/vault/vault-manager.ts +1 -0
  225. package/src/vault/vault-scaling.test.ts +3 -2
  226. package/src/vault/vault-schema.ts +15 -0
  227. package/src/vault/vault.ts +1 -0
  228. package/vitest.config.ts +2 -1
  229. package/dist/brain/strength-scorer.d.ts +0 -31
  230. package/dist/brain/strength-scorer.d.ts.map +0 -1
  231. package/dist/brain/strength-scorer.js +0 -264
  232. package/dist/brain/strength-scorer.js.map +0 -1
  233. package/dist/engine/index.d.ts +0 -21
  234. package/dist/engine/index.d.ts.map +0 -1
  235. package/dist/engine/index.js +0 -18
  236. package/dist/engine/index.js.map +0 -1
  237. package/dist/hooks/index.d.ts +0 -2
  238. package/dist/hooks/index.d.ts.map +0 -1
  239. package/dist/hooks/index.js +0 -2
  240. package/dist/hooks/index.js.map +0 -1
  241. package/dist/persona/index.d.ts +0 -5
  242. package/dist/persona/index.d.ts.map +0 -1
  243. package/dist/persona/index.js +0 -4
  244. package/dist/persona/index.js.map +0 -1
  245. package/dist/vault/vault-interfaces.d.ts +0 -153
  246. package/dist/vault/vault-interfaces.d.ts.map +0 -1
  247. package/dist/vault/vault-interfaces.js +0 -2
  248. package/dist/vault/vault-interfaces.js.map +0 -1
@@ -245,6 +245,137 @@ describe('createOrchestrateOps', () => {
245
245
  expect(rt.contextHealth.track).toHaveBeenCalled();
246
246
  expect(rt.contextHealth.check).toHaveBeenCalled();
247
247
  });
248
+
249
+ // ─── Post-dispatch cleanup (subagent path) ─────────────────
250
+ describe('post-dispatch cleanup', () => {
251
+ function addSubagentDispatcher(
252
+ runtime: AgentRuntime,
253
+ opts: {
254
+ dispatchResult?: Record<string, unknown>;
255
+ dispatchError?: Error;
256
+ reapResult?: string[];
257
+ reapThrows?: boolean;
258
+ } = {},
259
+ ) {
260
+ const dispatchMock = opts.dispatchError
261
+ ? vi.fn().mockRejectedValue(opts.dispatchError)
262
+ : vi.fn().mockResolvedValue(
263
+ opts.dispatchResult ?? {
264
+ status: 'completed',
265
+ totalTasks: 1,
266
+ completed: 1,
267
+ failed: 0,
268
+ durationMs: 100,
269
+ totalUsage: {},
270
+ },
271
+ );
272
+ const reapMock = opts.reapThrows
273
+ ? vi.fn().mockImplementation(() => {
274
+ throw new Error('reap failed');
275
+ })
276
+ : vi.fn().mockReturnValue({
277
+ reaped: opts.reapResult ?? [],
278
+ alive: [],
279
+ });
280
+ const cleanupMock = vi.fn();
281
+
282
+ (runtime as Record<string, unknown>).subagentDispatcher = {
283
+ dispatch: dispatchMock,
284
+ reapOrphans: reapMock,
285
+ cleanup: cleanupMock,
286
+ };
287
+
288
+ return { dispatchMock, reapMock, cleanupMock };
289
+ }
290
+
291
+ it('calls reapOrphans after successful subagent dispatch', async () => {
292
+ const { reapMock } = addSubagentDispatcher(rt);
293
+ ops = createOrchestrateOps(rt);
294
+
295
+ const op = findOp(ops, 'orchestrate_execute');
296
+ await op.handler({ planId: 'legacy-plan', subagent: true });
297
+
298
+ expect(reapMock).toHaveBeenCalledTimes(1);
299
+ });
300
+
301
+ it('calls reapOrphans even when dispatch fails', async () => {
302
+ const { reapMock } = addSubagentDispatcher(rt, {
303
+ dispatchError: new Error('dispatch boom'),
304
+ });
305
+ ops = createOrchestrateOps(rt);
306
+
307
+ const op = findOp(ops, 'orchestrate_execute');
308
+ await expect(op.handler({ planId: 'legacy-plan', subagent: true })).rejects.toThrow(
309
+ 'dispatch boom',
310
+ );
311
+
312
+ expect(reapMock).toHaveBeenCalledTimes(1);
313
+ });
314
+
315
+ it('includes reapedOrphans in result when orphans found', async () => {
316
+ addSubagentDispatcher(rt, {
317
+ reapResult: ['t1'],
318
+ });
319
+ ops = createOrchestrateOps(rt);
320
+
321
+ const op = findOp(ops, 'orchestrate_execute');
322
+ const result = (await op.handler({
323
+ planId: 'legacy-plan',
324
+ subagent: true,
325
+ })) as Record<string, unknown>;
326
+
327
+ expect(result).toHaveProperty('reapedOrphans');
328
+ const reaped = result.reapedOrphans as Array<{ taskId: string }>;
329
+ expect(reaped).toHaveLength(1);
330
+ expect(reaped[0].taskId).toBe('t1');
331
+ });
332
+
333
+ it('omits reapedOrphans from result when none found', async () => {
334
+ addSubagentDispatcher(rt, { reapResult: [] });
335
+ ops = createOrchestrateOps(rt);
336
+
337
+ const op = findOp(ops, 'orchestrate_execute');
338
+ const result = (await op.handler({
339
+ planId: 'legacy-plan',
340
+ subagent: true,
341
+ })) as Record<string, unknown>;
342
+
343
+ expect(result).not.toHaveProperty('reapedOrphans');
344
+ });
345
+
346
+ it('does not throw when reapOrphans fails', async () => {
347
+ addSubagentDispatcher(rt, { reapThrows: true });
348
+ ops = createOrchestrateOps(rt);
349
+
350
+ const op = findOp(ops, 'orchestrate_execute');
351
+ // Should complete successfully despite reap failure
352
+ const result = (await op.handler({
353
+ planId: 'legacy-plan',
354
+ subagent: true,
355
+ })) as Record<string, unknown>;
356
+
357
+ expect(result).toHaveProperty('subagent');
358
+ expect(result).not.toHaveProperty('reapedOrphans');
359
+ });
360
+
361
+ it('logs reaped orphans to stderr', async () => {
362
+ const stderrSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
363
+ addSubagentDispatcher(rt, {
364
+ reapResult: ['t1', 't2'],
365
+ });
366
+ ops = createOrchestrateOps(rt);
367
+
368
+ const op = findOp(ops, 'orchestrate_execute');
369
+ await op.handler({ planId: 'legacy-plan', subagent: true });
370
+
371
+ expect(stderrSpy).toHaveBeenCalledWith(
372
+ expect.stringContaining('Reaped 2 orphaned subagent(s)'),
373
+ );
374
+ expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('t1'));
375
+ expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('t2'));
376
+ stderrSpy.mockRestore();
377
+ });
378
+ });
248
379
  });
249
380
 
250
381
  // ─── orchestrate_complete ─────────────────────────────────────
@@ -363,10 +494,10 @@ describe('createOrchestrateOps', () => {
363
494
  outcome: 'completed',
364
495
  })) as Record<string, unknown>;
365
496
 
366
- // Should complete successfully without evidenceReport
497
+ // Should complete successfully with evidenceReport: null
367
498
  expect(result).toHaveProperty('plan');
368
499
  expect(result).toHaveProperty('session');
369
- expect(result).not.toHaveProperty('evidenceReport');
500
+ expect(result.evidenceReport).toBeNull();
370
501
  });
371
502
 
372
503
  it('adds warning when evidence accuracy is below 50%', async () => {
@@ -395,6 +526,55 @@ describe('createOrchestrateOps', () => {
395
526
  const warnings = result.warnings as string[];
396
527
  expect(warnings.some((w) => w.includes('Low evidence accuracy (30%)'))).toBe(true);
397
528
  });
529
+
530
+ it('runs evidence collection for abandoned plans too', async () => {
531
+ const { collectGitEvidence } = await import('../planning/evidence-collector.js');
532
+ vi.mocked(collectGitEvidence).mockReturnValueOnce({
533
+ planId: 'plan-1',
534
+ planObjective: 'test',
535
+ accuracy: 60,
536
+ evidenceSources: ['git'],
537
+ taskEvidence: [
538
+ {
539
+ taskId: 't1',
540
+ taskTitle: 'Task 1',
541
+ plannedStatus: 'pending',
542
+ matchedFiles: [],
543
+ verdict: 'MISSING',
544
+ },
545
+ ],
546
+ unplannedChanges: [],
547
+ missingWork: [],
548
+ verificationGaps: [],
549
+ summary: '0/1 tasks verified by git evidence',
550
+ });
551
+
552
+ const op = findOp(ops, 'orchestrate_complete');
553
+ const result = (await op.handler({
554
+ planId: 'plan-1',
555
+ sessionId: 'session-1',
556
+ outcome: 'abandoned',
557
+ projectPath: '.',
558
+ })) as Record<string, unknown>;
559
+
560
+ expect(collectGitEvidence).toHaveBeenCalled();
561
+ expect(result).toHaveProperty('evidenceReport');
562
+ const report = result.evidenceReport as Record<string, unknown>;
563
+ expect(report.accuracy).toBe(60);
564
+ expect(Array.isArray(report.taskEvidence)).toBe(true);
565
+ });
566
+
567
+ it('returns evidenceReport as null when no plan is provided', async () => {
568
+ const op = findOp(ops, 'orchestrate_complete');
569
+ const result = (await op.handler({
570
+ sessionId: 'session-1',
571
+ outcome: 'completed',
572
+ summary: 'Direct task without a plan',
573
+ })) as Record<string, unknown>;
574
+
575
+ expect(result).toHaveProperty('evidenceReport');
576
+ expect(result.evidenceReport).toBeNull();
577
+ });
398
578
  });
399
579
 
400
580
  // ─── orchestrate_status ───────────────────────────────────────
@@ -15,10 +15,10 @@ import { z } from 'zod';
15
15
  import type { OpDefinition, FacadeConfig } from '../facades/types.js';
16
16
  import type { AgentRuntime } from './types.js';
17
17
  import { buildPlan } from '../flows/plan-builder.js';
18
- import { FlowExecutor } from '../flows/executor.js';
18
+ import { FlowExecutor, getPlanRunDir, loadManifest, saveManifest } from '../flows/executor.js';
19
19
  import { createDispatcher } from '../flows/dispatch-registry.js';
20
20
  import { runEpilogue } from '../flows/epilogue.js';
21
- import type { OrchestrationPlan, ExecutionResult } from '../flows/types.js';
21
+ import type { OrchestrationPlan, ExecutionResult, PlanRunManifest } from '../flows/types.js';
22
22
  import type { ContextHealthStatus } from './context-health.js';
23
23
  import type { OperatorSignals } from '../operator/operator-context-types.js';
24
24
  import { loadAgentWorkflows, getWorkflowForIntent } from '../workflows/workflow-loader.js';
@@ -43,7 +43,11 @@ import type { ImpactReport } from '../planning/impact-analyzer.js';
43
43
  import { collectGitEvidence } from '../planning/evidence-collector.js';
44
44
  import type { EvidenceReport } from '../planning/evidence-collector.js';
45
45
  import { recordPlanFeedback } from './plan-feedback-helper.js';
46
- import { analyzeQualitySignals, captureQualitySignals } from './quality-signals.js';
46
+ import {
47
+ analyzeQualitySignals,
48
+ captureQualitySignals,
49
+ buildFixTrailSummary,
50
+ } from './quality-signals.js';
47
51
 
48
52
  // ---------------------------------------------------------------------------
49
53
  // Intent detection — keyword-based mapping from prompt to intent
@@ -125,6 +129,13 @@ export function applyWorkflowOverride(plan: OrchestrationPlan, override: Workflo
125
129
  plan.estimatedTools = plan.steps.reduce((acc, s) => acc + s.tools.length, 0);
126
130
  }
127
131
 
132
+ // Set allowedTools from the merged tool set
133
+ for (const step of plan.steps) {
134
+ if (step.tools.length > 0) {
135
+ step.allowedTools = [...new Set(step.tools)];
136
+ }
137
+ }
138
+
128
139
  // Add workflow info to warnings for visibility
129
140
  plan.warnings.push(
130
141
  `Workflow override "${override.name}" applied (${override.gates.length} gate(s), ${override.tools.length} tool(s)).`,
@@ -152,9 +163,14 @@ const planStore = new Map<string, PlanEntry>();
152
163
  * If facades are provided, uses the full dispatch registry.
153
164
  * Otherwise, falls back to a simple runtime-based dispatcher.
154
165
  */
155
- function buildDispatch(agentId: string, runtime: AgentRuntime, facades?: FacadeConfig[]) {
166
+ function buildDispatch(
167
+ agentId: string,
168
+ runtime: AgentRuntime,
169
+ facades?: FacadeConfig[],
170
+ activePlan?: import('../flows/dispatch-registry.js').ActivePlanRef,
171
+ ) {
156
172
  if (facades && facades.length > 0) {
157
- return createDispatcher(agentId, facades);
173
+ return createDispatcher(agentId, facades, activePlan);
158
174
  }
159
175
 
160
176
  // Fallback: runtime-based dispatch for known tool patterns
@@ -530,10 +546,27 @@ export function createOrchestrateOps(
530
546
  })) ??
531
547
  [];
532
548
 
533
- const aggregated = await runtime.subagentDispatcher.dispatch(tasks, {
534
- parallel: parallelMode ?? true,
535
- maxConcurrent: maxConcurrentParam ?? 3,
536
- });
549
+ let aggregated;
550
+ let reapedOrphans: { taskId: string; pid?: number }[] = [];
551
+ try {
552
+ aggregated = await runtime.subagentDispatcher.dispatch(tasks, {
553
+ parallel: parallelMode ?? true,
554
+ maxConcurrent: maxConcurrentParam ?? 3,
555
+ });
556
+ } finally {
557
+ // Post-dispatch cleanup: reap orphaned subagent processes
558
+ try {
559
+ const reapResult = runtime.subagentDispatcher.reapOrphans();
560
+ if (reapResult.reaped.length > 0) {
561
+ reapedOrphans = reapResult.reaped.map((taskId) => ({ taskId }));
562
+ console.error(
563
+ `[soleri] Reaped ${reapResult.reaped.length} orphaned subagent(s): ${reapResult.reaped.join(', ')}`,
564
+ );
565
+ }
566
+ } catch {
567
+ // Orphan reaping is best-effort — never blocks dispatch result
568
+ }
569
+ }
537
570
 
538
571
  // Track in brain session
539
572
  const existingSession = brainIntelligence.getSessionByPlanId(planId);
@@ -565,6 +598,7 @@ export function createOrchestrateOps(
565
598
  durationMs: aggregated.durationMs,
566
599
  totalUsage: aggregated.totalUsage,
567
600
  },
601
+ ...(reapedOrphans.length > 0 ? { reapedOrphans } : {}),
568
602
  ...(healthWarning ? { contextHealth: healthWarning } : {}),
569
603
  };
570
604
  }
@@ -622,8 +656,17 @@ export function createOrchestrateOps(
622
656
 
623
657
  if (entry) {
624
658
  // Flow-engine execution path
625
- const dispatch = buildDispatch(agentId, runtime, facades);
626
- const executor = new FlowExecutor(dispatch);
659
+ const activePlanRef = {
660
+ steps: entry.plan.steps.map((s) => ({
661
+ id: s.id,
662
+ allowedTools: s.allowedTools,
663
+ status: s.status,
664
+ })),
665
+ deviations: entry.plan.deviations,
666
+ };
667
+ const dispatch = buildDispatch(agentId, runtime, facades, activePlanRef);
668
+ const projectPath = (params.projectPath as string) ?? '.';
669
+ const executor = new FlowExecutor(dispatch, projectPath);
627
670
  const executionResult = await executor.execute(entry.plan);
628
671
 
629
672
  // Store result
@@ -832,7 +875,7 @@ export function createOrchestrateOps(
832
875
 
833
876
  // Evidence-based reconciliation: cross-reference plan tasks against git diff
834
877
  let evidenceReport: EvidenceReport | null = null;
835
- if (planObj && outcome === 'completed') {
878
+ if (planObj) {
836
879
  try {
837
880
  evidenceReport = collectGitEvidence(
838
881
  planObj,
@@ -840,6 +883,9 @@ export function createOrchestrateOps(
840
883
  'main',
841
884
  );
842
885
  if (evidenceReport.accuracy < 50) {
886
+ console.error(
887
+ `[soleri] Evidence accuracy ${evidenceReport.accuracy}% — significant drift detected between plan and git state`,
888
+ );
843
889
  warnings.push(
844
890
  `Low evidence accuracy (${evidenceReport.accuracy}%) — plan tasks may not match git changes.`,
845
891
  );
@@ -873,6 +919,7 @@ export function createOrchestrateOps(
873
919
  }
874
920
 
875
921
  // End brain session — runs regardless of plan existence
922
+ const fixTrail = evidenceReport ? buildFixTrailSummary(evidenceReport) : undefined;
876
923
  const session = brainIntelligence.lifecycle({
877
924
  action: 'end',
878
925
  sessionId,
@@ -880,6 +927,7 @@ export function createOrchestrateOps(
880
927
  planOutcome: outcome,
881
928
  toolsUsed,
882
929
  filesModified,
930
+ ...(fixTrail ? { context: `Fix trail: ${fixTrail}` } : {}),
883
931
  });
884
932
 
885
933
  // Record brain feedback for vault entries referenced in plan decisions
@@ -974,7 +1022,7 @@ export function createOrchestrateOps(
974
1022
  extraction,
975
1023
  epilogue: epilogueResult,
976
1024
  ...(impactReport ? { impactAnalysis: impactReport } : {}),
977
- ...(evidenceReport ? { evidenceReport } : {}),
1025
+ evidenceReport,
978
1026
  ...(warnings.length > 0 ? { warnings } : {}),
979
1027
  };
980
1028
  },
@@ -1317,5 +1365,114 @@ export function createOrchestrateOps(
1317
1365
  };
1318
1366
  },
1319
1367
  },
1368
+
1369
+ // ─── orchestrate_rerun_step ──────────────────────────────────────
1370
+ {
1371
+ name: 'orchestrate_rerun_step',
1372
+ description:
1373
+ 'Re-execute a single plan step without full restart. Marks the target step as invalidated then rerun, ' +
1374
+ 'marks downstream steps as stale (or rerun if within cascadeTo range). ' +
1375
+ 'Reads and writes the plan-run manifest on disk.',
1376
+ auth: 'write',
1377
+ schema: z.object({
1378
+ planId: z.string().describe('Plan ID'),
1379
+ stepNumber: z.number().describe('0-based step index to re-run'),
1380
+ reason: z.string().describe('Why the step is being re-run'),
1381
+ projectPath: z
1382
+ .string()
1383
+ .optional()
1384
+ .default('.')
1385
+ .describe('Project root (for manifest location)'),
1386
+ cascadeTo: z
1387
+ .number()
1388
+ .optional()
1389
+ .describe(
1390
+ 'If set, also mark steps up to this index (exclusive) as rerun instead of just stale',
1391
+ ),
1392
+ }),
1393
+ handler: async (params) => {
1394
+ const planId = params.planId as string;
1395
+ const stepNumber = params.stepNumber as number;
1396
+ const reason = params.reason as string;
1397
+ const projectPath = params.projectPath as string;
1398
+ const cascadeTo = params.cascadeTo as number | undefined;
1399
+
1400
+ const runDir = getPlanRunDir(projectPath, planId);
1401
+ let manifest: PlanRunManifest;
1402
+ try {
1403
+ manifest = loadManifest(runDir, planId);
1404
+ } catch (err) {
1405
+ return { error: `Failed to load manifest: ${(err as Error).message}` };
1406
+ }
1407
+
1408
+ const stepKeys = Object.keys(manifest.steps);
1409
+ if (stepKeys.length === 0 && stepNumber > 0) {
1410
+ return {
1411
+ error:
1412
+ 'No step data in manifest — the plan may not have been executed with persistence enabled.',
1413
+ };
1414
+ }
1415
+
1416
+ // Find the step key at the target index by checking all steps
1417
+ // Steps are keyed by stepId — we match by position in the plan
1418
+ const allStepIds = Object.keys(manifest.steps);
1419
+ const targetStepId = allStepIds[stepNumber];
1420
+
1421
+ if (!targetStepId && !manifest.steps[String(stepNumber)]) {
1422
+ return {
1423
+ error: `Step ${stepNumber} not found in manifest. Available steps: ${allStepIds.join(', ') || '(none)'}`,
1424
+ };
1425
+ }
1426
+
1427
+ const now = new Date().toISOString();
1428
+ const affected: { stepId: string; status: string }[] = [];
1429
+
1430
+ // Mark all steps by their position
1431
+ const sortedStepIds = Object.keys(manifest.steps);
1432
+ for (let i = 0; i < sortedStepIds.length; i++) {
1433
+ const sid = sortedStepIds[i];
1434
+ const state = manifest.steps[sid];
1435
+
1436
+ if (i === stepNumber) {
1437
+ // Target step: invalidated → rerun
1438
+ state.status = 'rerun';
1439
+ state.rerunCount += 1;
1440
+ state.rerunReason = reason;
1441
+ state.timestamp = now;
1442
+ affected.push({ stepId: sid, status: 'rerun' });
1443
+ } else if (i > stepNumber) {
1444
+ // Downstream step
1445
+ if (cascadeTo !== undefined && i < cascadeTo) {
1446
+ state.status = 'rerun';
1447
+ state.rerunCount += 1;
1448
+ state.rerunReason = `Cascade from step ${stepNumber}: ${reason}`;
1449
+ state.timestamp = now;
1450
+ affected.push({ stepId: sid, status: 'rerun' });
1451
+ } else {
1452
+ state.status = 'stale';
1453
+ state.timestamp = now;
1454
+ affected.push({ stepId: sid, status: 'stale' });
1455
+ }
1456
+ }
1457
+ }
1458
+
1459
+ manifest.lastRun = now;
1460
+
1461
+ try {
1462
+ saveManifest(runDir, manifest);
1463
+ } catch (err) {
1464
+ return { error: `Failed to save manifest: ${(err as Error).message}` };
1465
+ }
1466
+
1467
+ return {
1468
+ planId,
1469
+ stepNumber,
1470
+ reason,
1471
+ cascadeTo: cascadeTo ?? null,
1472
+ affected,
1473
+ manifestPath: runDir,
1474
+ };
1475
+ },
1476
+ },
1320
1477
  ];
1321
1478
  }
@@ -9,10 +9,13 @@
9
9
  * deliverable submission, and deliverable verification.
10
10
  */
11
11
 
12
+ import fs from 'node:fs';
12
13
  import { z } from 'zod';
13
14
  import type { OpDefinition } from '../facades/types.js';
14
15
  import type { AgentRuntime } from './types.js';
15
16
  import type { DriftItem, TaskEvidence } from '../planning/planner.js';
17
+ import type { PlanRunManifest } from '../flows/types.js';
18
+ import { getPlanRunDir } from '../flows/executor.js';
16
19
  import { collectGitEvidence } from '../planning/evidence-collector.js';
17
20
  import { matchPlaybooks, type PlaybookMatchResult } from '../playbooks/index.js';
18
21
  import { entryToPlaybookDefinition } from '../playbooks/index.js';
@@ -178,6 +181,11 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
178
181
  schema: z.object({
179
182
  planId: z.string().describe('Plan ID to reconcile'),
180
183
  actualOutcome: z.string().describe('Description of what actually happened'),
184
+ projectPath: z
185
+ .string()
186
+ .optional()
187
+ .default('.')
188
+ .describe('Project root (for reading rerun manifest)'),
181
189
  driftItems: z
182
190
  .array(
183
191
  z.object({
@@ -195,10 +203,79 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
195
203
  actualOutcome: params.actualOutcome as string,
196
204
  driftItems: params.driftItems as DriftItem[] | undefined,
197
205
  });
206
+
207
+ // Add tool deviations to drift report
208
+ const deviations =
209
+ (
210
+ plan as unknown as {
211
+ deviations?: Array<{
212
+ stepId: string;
213
+ expectedTools: string[];
214
+ actualTool: string;
215
+ timestamp: string;
216
+ }>;
217
+ }
218
+ ).deviations ?? [];
219
+ const toolDeviations =
220
+ deviations.length > 0
221
+ ? {
222
+ count: deviations.length,
223
+ byStep: Object.entries(
224
+ deviations.reduce(
225
+ (acc, d) => {
226
+ (acc[d.stepId] = acc[d.stepId] || []).push(d);
227
+ return acc;
228
+ },
229
+ {} as Record<string, typeof deviations>,
230
+ ),
231
+ ).map(([stepId, devs]) => ({
232
+ stepId,
233
+ deviationCount: devs.length,
234
+ unexpectedTools: [...new Set(devs.map((d) => d.actualTool))],
235
+ })),
236
+ }
237
+ : undefined;
238
+
239
+ // Attach rerun report if a plan-run manifest exists
240
+ let rerunReport: {
241
+ totalReruns: number;
242
+ staleSteps: number;
243
+ steps: { stepId: string; status: string; rerunCount: number; rerunReason?: string }[];
244
+ } | null = null;
245
+
246
+ try {
247
+ const projectPath = params.projectPath as string;
248
+ const runDir = getPlanRunDir(projectPath, params.planId as string);
249
+ const manifestPath = `${runDir}/manifest.json`;
250
+ if (fs.existsSync(manifestPath)) {
251
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as PlanRunManifest;
252
+ const entries = Object.entries(manifest.steps);
253
+ const rerunSteps = entries.filter(
254
+ ([, s]) => s.rerunCount > 0 || s.status === 'rerun' || s.status === 'stale',
255
+ );
256
+ if (rerunSteps.length > 0) {
257
+ rerunReport = {
258
+ totalReruns: rerunSteps.reduce((sum, [, s]) => sum + s.rerunCount, 0),
259
+ staleSteps: entries.filter(([, s]) => s.status === 'stale').length,
260
+ steps: rerunSteps.map(([id, s]) => ({
261
+ stepId: id,
262
+ status: s.status,
263
+ rerunCount: s.rerunCount,
264
+ rerunReason: s.rerunReason,
265
+ })),
266
+ };
267
+ }
268
+ }
269
+ } catch {
270
+ // Manifest reading is best-effort
271
+ }
272
+
198
273
  return {
199
274
  reconciled: true,
200
275
  accuracy: plan.reconciliation!.accuracy,
201
276
  driftCount: plan.reconciliation!.driftItems.length,
277
+ toolDeviations,
278
+ rerunReport,
202
279
  plan,
203
280
  };
204
281
  },
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Builds the pre-flight manifest for session_start responses.
3
+ * Extracted as a pure function for testability.
4
+ */
5
+
6
+ import type { PreflightManifest } from './types.js';
7
+
8
+ export interface PreflightInput {
9
+ /** Facade definitions: array of { name, ops: Array<{ name, description }> } */
10
+ facades: Array<{
11
+ name: string;
12
+ ops: Array<{ name: string; description: string }>;
13
+ }>;
14
+ /** Skill names installed for this agent */
15
+ skills: string[];
16
+ /** Plans currently in executing state */
17
+ executingPlans: Array<{ id: string; objective: string; status: string }>;
18
+ /** Whether the vault is connected */
19
+ vaultConnected?: boolean;
20
+ /** Vault stats */
21
+ vaultStats: {
22
+ totalEntries: number;
23
+ byDomain: Record<string, number>;
24
+ };
25
+ }
26
+
27
+ export function buildPreflightManifest(input: PreflightInput): PreflightManifest {
28
+ const tools: PreflightManifest['tools'] = [];
29
+ for (const facade of input.facades) {
30
+ for (const op of facade.ops) {
31
+ tools.push({ facade: facade.name, op: op.name, description: op.description });
32
+ }
33
+ }
34
+
35
+ const activePlans = input.executingPlans.map((p) => ({
36
+ planId: p.id,
37
+ title: p.objective,
38
+ status: p.status,
39
+ }));
40
+
41
+ const domains = Object.keys(input.vaultStats.byDomain);
42
+
43
+ return {
44
+ tools,
45
+ skills: input.skills,
46
+ activePlans,
47
+ vaultSummary: {
48
+ entryCount: input.vaultStats.totalEntries,
49
+ connected: input.vaultConnected ?? true,
50
+ domains,
51
+ },
52
+ };
53
+ }