@soleri/core 9.3.0 → 9.4.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/brain/intelligence.d.ts +5 -0
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +115 -26
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/learning-radar.d.ts +3 -3
- package/dist/brain/learning-radar.d.ts.map +1 -1
- package/dist/brain/learning-radar.js +8 -4
- package/dist/brain/learning-radar.js.map +1 -1
- package/dist/control/intent-router.d.ts +2 -2
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +35 -1
- package/dist/control/intent-router.js.map +1 -1
- package/dist/control/types.d.ts +10 -2
- package/dist/control/types.d.ts.map +1 -1
- package/dist/curator/curator.d.ts +4 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +23 -1
- package/dist/curator/curator.js.map +1 -1
- package/dist/curator/schema.d.ts +1 -1
- package/dist/curator/schema.d.ts.map +1 -1
- package/dist/curator/schema.js +8 -0
- package/dist/curator/schema.js.map +1 -1
- package/dist/domain-packs/types.d.ts +6 -0
- package/dist/domain-packs/types.d.ts.map +1 -1
- package/dist/domain-packs/types.js +1 -0
- package/dist/domain-packs/types.js.map +1 -1
- package/dist/engine/module-manifest.d.ts +2 -0
- package/dist/engine/module-manifest.d.ts.map +1 -1
- package/dist/engine/module-manifest.js +117 -2
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts +9 -0
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +59 -1
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/facades/types.d.ts +5 -1
- package/dist/facades/types.d.ts.map +1 -1
- package/dist/facades/types.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/operator/operator-context-store.d.ts +54 -0
- package/dist/operator/operator-context-store.d.ts.map +1 -0
- package/dist/operator/operator-context-store.js +434 -0
- package/dist/operator/operator-context-store.js.map +1 -0
- package/dist/operator/operator-context-types.d.ts +101 -0
- package/dist/operator/operator-context-types.d.ts.map +1 -0
- package/dist/operator/operator-context-types.js +27 -0
- package/dist/operator/operator-context-types.js.map +1 -0
- package/dist/packs/index.d.ts +2 -2
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +1 -1
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/lockfile.d.ts +3 -0
- package/dist/packs/lockfile.d.ts.map +1 -1
- package/dist/packs/lockfile.js.map +1 -1
- package/dist/packs/types.d.ts +8 -2
- package/dist/packs/types.d.ts.map +1 -1
- package/dist/packs/types.js +6 -0
- package/dist/packs/types.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts +12 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +52 -19
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner-types.d.ts +6 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/planning/planner.d.ts +21 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +62 -3
- package/dist/planning/planner.js.map +1 -1
- package/dist/planning/task-complexity-assessor.d.ts +42 -0
- package/dist/planning/task-complexity-assessor.d.ts.map +1 -0
- package/dist/planning/task-complexity-assessor.js +132 -0
- package/dist/planning/task-complexity-assessor.js.map +1 -0
- package/dist/plugins/types.d.ts +18 -18
- package/dist/runtime/admin-ops.d.ts +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +118 -3
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +19 -9
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +35 -7
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +4 -2
- 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 +8 -2
- package/dist/runtime/facades/control-facade.js.map +1 -1
- package/dist/runtime/facades/curator-facade.d.ts.map +1 -1
- package/dist/runtime/facades/curator-facade.js +13 -0
- package/dist/runtime/facades/curator-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +10 -12
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +36 -1
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +20 -4
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +109 -31
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/plan-feedback-helper.d.ts +21 -0
- package/dist/runtime/plan-feedback-helper.d.ts.map +1 -0
- package/dist/runtime/plan-feedback-helper.js +52 -0
- package/dist/runtime/plan-feedback-helper.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +73 -34
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +9 -1
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/types.d.ts +3 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +13 -7
- package/dist/skills/sync-skills.js.map +1 -1
- package/package.json +1 -1
- package/src/brain/brain-intelligence.test.ts +30 -0
- package/src/brain/extraction-quality.test.ts +323 -0
- package/src/brain/intelligence.ts +133 -30
- package/src/brain/learning-radar.ts +8 -5
- package/src/brain/second-brain-features.test.ts +1 -1
- package/src/control/intent-router.test.ts +73 -3
- package/src/control/intent-router.ts +38 -1
- package/src/control/types.ts +13 -2
- package/src/curator/curator.test.ts +92 -0
- package/src/curator/curator.ts +29 -1
- package/src/curator/schema.ts +8 -0
- package/src/domain-packs/types.ts +8 -0
- package/src/engine/module-manifest.test.ts +51 -2
- package/src/engine/module-manifest.ts +119 -2
- package/src/engine/register-engine.test.ts +73 -1
- package/src/engine/register-engine.ts +61 -1
- package/src/facades/types.ts +5 -0
- package/src/index.ts +30 -0
- package/src/operator/operator-context-store.test.ts +698 -0
- package/src/operator/operator-context-store.ts +569 -0
- package/src/operator/operator-context-types.ts +139 -0
- package/src/packs/index.ts +3 -1
- package/src/packs/lockfile.ts +3 -0
- package/src/packs/types.ts +9 -0
- package/src/planning/plan-lifecycle.ts +80 -22
- package/src/planning/planner-types.ts +6 -0
- package/src/planning/planner.ts +74 -4
- package/src/planning/task-complexity-assessor.test.ts +302 -0
- package/src/planning/task-complexity-assessor.ts +180 -0
- package/src/runtime/admin-ops.test.ts +159 -3
- package/src/runtime/admin-ops.ts +123 -3
- package/src/runtime/admin-setup-ops.ts +30 -10
- package/src/runtime/capture-ops.test.ts +84 -0
- package/src/runtime/capture-ops.ts +35 -7
- package/src/runtime/facades/admin-facade.test.ts +1 -1
- package/src/runtime/facades/brain-facade.ts +6 -3
- package/src/runtime/facades/control-facade.ts +10 -2
- package/src/runtime/facades/curator-facade.ts +18 -0
- package/src/runtime/facades/memory-facade.test.ts +14 -12
- package/src/runtime/facades/memory-facade.ts +10 -12
- package/src/runtime/facades/orchestrate-facade.ts +33 -1
- package/src/runtime/facades/plan-facade.test.ts +213 -0
- package/src/runtime/facades/plan-facade.ts +23 -4
- package/src/runtime/orchestrate-ops.test.ts +404 -0
- package/src/runtime/orchestrate-ops.ts +129 -37
- package/src/runtime/plan-feedback-helper.test.ts +173 -0
- package/src/runtime/plan-feedback-helper.ts +63 -0
- package/src/runtime/planning-extra-ops.test.ts +43 -1
- package/src/runtime/planning-extra-ops.ts +96 -33
- package/src/runtime/session-briefing.test.ts +1 -0
- package/src/runtime/session-briefing.ts +10 -1
- package/src/runtime/types.ts +3 -0
- package/src/skills/sync-skills.ts +14 -7
- package/src/vault/vault-scaling.test.ts +5 -5
- package/vitest.config.ts +1 -0
|
@@ -12,7 +12,7 @@ import { createChainOps } from '../chain-ops.js';
|
|
|
12
12
|
import { PlanGradeRejectionError } from '../../planning/planner.js';
|
|
13
13
|
|
|
14
14
|
export function createPlanFacadeOps(runtime: AgentRuntime): OpDefinition[] {
|
|
15
|
-
const { planner } = runtime;
|
|
15
|
+
const { planner, vault } = runtime;
|
|
16
16
|
|
|
17
17
|
return [
|
|
18
18
|
// ─── Planning (inline from core-ops.ts) ─────────────────────
|
|
@@ -45,16 +45,35 @@ export function createPlanFacadeOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
45
45
|
.describe('Rejected alternative approaches — plans with 2+ alternatives score higher'),
|
|
46
46
|
}),
|
|
47
47
|
handler: async (params) => {
|
|
48
|
+
const objective = params.objective as string;
|
|
49
|
+
const decisions = ((params.decisions as string[]) ?? []).slice();
|
|
50
|
+
|
|
51
|
+
// Vault enrichment: search for patterns matching the objective
|
|
52
|
+
let vaultEntryIds: string[] = [];
|
|
53
|
+
try {
|
|
54
|
+
const results = vault.search(objective, { limit: 5 });
|
|
55
|
+
if (results.length > 0) {
|
|
56
|
+
vaultEntryIds = results.map((r) => r.entry.id);
|
|
57
|
+
for (const r of results) {
|
|
58
|
+
decisions.push(
|
|
59
|
+
`Vault pattern: ${r.entry.title ?? r.entry.id} (score: ${r.score.toFixed(2)}) [entryId:${r.entry.id}]`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// Vault search failed — proceed without enrichment
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
const plan = planner.create({
|
|
49
|
-
objective
|
|
68
|
+
objective,
|
|
50
69
|
scope: params.scope as string,
|
|
51
|
-
decisions
|
|
70
|
+
decisions,
|
|
52
71
|
tasks: (params.tasks as Array<{ title: string; description: string }>) ?? [],
|
|
53
72
|
alternatives: params.alternatives as
|
|
54
73
|
| Array<{ approach: string; pros: string[]; cons: string[]; rejected_reason: string }>
|
|
55
74
|
| undefined,
|
|
56
75
|
});
|
|
57
|
-
return { created: true, plan };
|
|
76
|
+
return { created: true, plan, vaultEntryIds };
|
|
58
77
|
},
|
|
59
78
|
},
|
|
60
79
|
{
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
4
|
import { createOrchestrateOps } from './orchestrate-ops.js';
|
|
5
|
+
import { assessTaskComplexity } from '../planning/task-complexity-assessor.js';
|
|
3
6
|
import type { AgentRuntime } from './types.js';
|
|
4
7
|
|
|
8
|
+
vi.mock('node:fs', () => ({
|
|
9
|
+
default: {
|
|
10
|
+
mkdirSync: vi.fn(),
|
|
11
|
+
writeFileSync: vi.fn(),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
5
15
|
// ---------------------------------------------------------------------------
|
|
6
16
|
// Mocks for external modules
|
|
7
17
|
// ---------------------------------------------------------------------------
|
|
@@ -241,6 +251,73 @@ describe('createOrchestrateOps', () => {
|
|
|
241
251
|
await op.handler({ planId: 'plan-1', sessionId: 'session-1' });
|
|
242
252
|
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-1');
|
|
243
253
|
});
|
|
254
|
+
|
|
255
|
+
it('works without a preceding plan', async () => {
|
|
256
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
257
|
+
const result = (await op.handler({
|
|
258
|
+
sessionId: 'session-1',
|
|
259
|
+
outcome: 'completed',
|
|
260
|
+
summary: 'Fixed a typo in the README',
|
|
261
|
+
})) as Record<string, unknown>;
|
|
262
|
+
|
|
263
|
+
// Should not call planner.complete
|
|
264
|
+
expect(rt.planner.complete).not.toHaveBeenCalled();
|
|
265
|
+
|
|
266
|
+
// Should return a lightweight completion record
|
|
267
|
+
const plan = result.plan as Record<string, unknown>;
|
|
268
|
+
expect(plan.status).toBe('completed');
|
|
269
|
+
expect(plan.objective).toBe('Fixed a typo in the README');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('captures knowledge even without plan', async () => {
|
|
273
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
274
|
+
await op.handler({
|
|
275
|
+
sessionId: 'session-1',
|
|
276
|
+
summary: 'Refactored utility function',
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Brain session end and knowledge extraction still run
|
|
280
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
281
|
+
expect.objectContaining({ action: 'end', sessionId: 'session-1' }),
|
|
282
|
+
);
|
|
283
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-1');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('skips anti-rationalization gate when no criteria', async () => {
|
|
287
|
+
const { detectRationalizations } = await import('../planning/rationalization-detector.js');
|
|
288
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
289
|
+
|
|
290
|
+
await op.handler({
|
|
291
|
+
sessionId: 'session-1',
|
|
292
|
+
outcome: 'completed',
|
|
293
|
+
summary: 'This was basically done already',
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// detectRationalizations should never be called since there are no criteria
|
|
297
|
+
expect(detectRationalizations).not.toHaveBeenCalled();
|
|
298
|
+
// Should still complete successfully
|
|
299
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalled();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('still runs brain session end without plan', async () => {
|
|
303
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
304
|
+
const result = (await op.handler({
|
|
305
|
+
sessionId: 'session-1',
|
|
306
|
+
outcome: 'completed',
|
|
307
|
+
toolsUsed: ['grep', 'edit'],
|
|
308
|
+
filesModified: [],
|
|
309
|
+
})) as Record<string, unknown>;
|
|
310
|
+
|
|
311
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
312
|
+
expect.objectContaining({
|
|
313
|
+
action: 'end',
|
|
314
|
+
sessionId: 'session-1',
|
|
315
|
+
planOutcome: 'completed',
|
|
316
|
+
toolsUsed: ['grep', 'edit'],
|
|
317
|
+
}),
|
|
318
|
+
);
|
|
319
|
+
expect(result.session).toBeDefined();
|
|
320
|
+
});
|
|
244
321
|
});
|
|
245
322
|
|
|
246
323
|
// ─── orchestrate_status ───────────────────────────────────────
|
|
@@ -299,4 +376,331 @@ describe('createOrchestrateOps', () => {
|
|
|
299
376
|
await expect(op.handler({ planId: 'missing' })).rejects.toThrow('not found');
|
|
300
377
|
});
|
|
301
378
|
});
|
|
379
|
+
|
|
380
|
+
// ─── task auto-assessment routing ────────────────────────────
|
|
381
|
+
//
|
|
382
|
+
// Integration-style tests that verify the full assess → route → complete flow:
|
|
383
|
+
// 1. Use TaskComplexityAssessor to classify the task
|
|
384
|
+
// 2. Route to direct execution (simple) or planning (complex)
|
|
385
|
+
// 3. Complete via orchestrate_complete in both paths
|
|
386
|
+
|
|
387
|
+
describe('task auto-assessment routing', () => {
|
|
388
|
+
it('simple task routes to direct execution + complete', async () => {
|
|
389
|
+
// Step 1: Assess — "fix typo in README" should be simple
|
|
390
|
+
const assessment = assessTaskComplexity({ prompt: 'fix typo in README' });
|
|
391
|
+
expect(assessment.classification).toBe('simple');
|
|
392
|
+
|
|
393
|
+
// Step 2: Skip planning, go straight to complete without a planId
|
|
394
|
+
const completeOp = findOp(ops, 'orchestrate_complete');
|
|
395
|
+
const result = (await completeOp.handler({
|
|
396
|
+
sessionId: 'session-simple',
|
|
397
|
+
outcome: 'completed',
|
|
398
|
+
summary: 'Fixed typo in README',
|
|
399
|
+
})) as Record<string, unknown>;
|
|
400
|
+
|
|
401
|
+
// Should not touch the planner at all
|
|
402
|
+
expect(rt.planner.complete).not.toHaveBeenCalled();
|
|
403
|
+
|
|
404
|
+
// Should still produce a valid completion record
|
|
405
|
+
const plan = result.plan as Record<string, unknown>;
|
|
406
|
+
expect(plan.status).toBe('completed');
|
|
407
|
+
expect(plan.objective).toBe('Fixed typo in README');
|
|
408
|
+
|
|
409
|
+
// Knowledge should still be captured
|
|
410
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-simple');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('complex task routes through planning + complete', async () => {
|
|
414
|
+
// Step 1: Assess — cross-cutting auth task should be complex
|
|
415
|
+
const assessment = assessTaskComplexity({
|
|
416
|
+
prompt: 'add authentication across all API routes',
|
|
417
|
+
filesEstimated: 8,
|
|
418
|
+
});
|
|
419
|
+
expect(assessment.classification).toBe('complex');
|
|
420
|
+
|
|
421
|
+
// Step 2: Create a plan via orchestrate_plan
|
|
422
|
+
const planOp = findOp(ops, 'orchestrate_plan');
|
|
423
|
+
const planResult = (await planOp.handler({
|
|
424
|
+
prompt: 'add authentication across all API routes',
|
|
425
|
+
})) as Record<string, unknown>;
|
|
426
|
+
expect(planResult).toHaveProperty('plan');
|
|
427
|
+
expect(planResult).toHaveProperty('flow');
|
|
428
|
+
|
|
429
|
+
// Step 3: Complete with the planId
|
|
430
|
+
const completeOp = findOp(ops, 'orchestrate_complete');
|
|
431
|
+
const result = (await completeOp.handler({
|
|
432
|
+
planId: 'plan-1',
|
|
433
|
+
sessionId: 'session-complex',
|
|
434
|
+
outcome: 'completed',
|
|
435
|
+
summary: 'Added authentication middleware to all API routes',
|
|
436
|
+
})) as Record<string, unknown>;
|
|
437
|
+
|
|
438
|
+
// Should complete via the planner lifecycle
|
|
439
|
+
expect(rt.planner.complete).toHaveBeenCalledWith('plan-1');
|
|
440
|
+
|
|
441
|
+
// Knowledge should be captured
|
|
442
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
443
|
+
expect.objectContaining({ action: 'end', sessionId: 'session-complex' }),
|
|
444
|
+
);
|
|
445
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-complex');
|
|
446
|
+
|
|
447
|
+
// Plan should be marked completed
|
|
448
|
+
const completedPlan = result.plan as Record<string, unknown>;
|
|
449
|
+
expect(completedPlan.status).toBe('completed');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('orchestrate_complete captures knowledge in both paths', async () => {
|
|
453
|
+
// ── Simple path (no planId) ──
|
|
454
|
+
vi.clearAllMocks();
|
|
455
|
+
rt = mockRuntime();
|
|
456
|
+
ops = createOrchestrateOps(rt);
|
|
457
|
+
|
|
458
|
+
await findOp(ops, 'orchestrate_complete').handler({
|
|
459
|
+
sessionId: 'session-simple',
|
|
460
|
+
outcome: 'completed',
|
|
461
|
+
summary: 'Renamed a variable',
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Brain session end called
|
|
465
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
466
|
+
expect.objectContaining({ action: 'end', sessionId: 'session-simple' }),
|
|
467
|
+
);
|
|
468
|
+
// Knowledge extraction called
|
|
469
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-simple');
|
|
470
|
+
// Planner.complete NOT called (no plan)
|
|
471
|
+
expect(rt.planner.complete).not.toHaveBeenCalled();
|
|
472
|
+
|
|
473
|
+
// ── Complex path (with planId) ──
|
|
474
|
+
vi.clearAllMocks();
|
|
475
|
+
rt = mockRuntime();
|
|
476
|
+
ops = createOrchestrateOps(rt);
|
|
477
|
+
|
|
478
|
+
await findOp(ops, 'orchestrate_complete').handler({
|
|
479
|
+
planId: 'plan-1',
|
|
480
|
+
sessionId: 'session-complex',
|
|
481
|
+
outcome: 'completed',
|
|
482
|
+
summary: 'Implemented full auth layer',
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Brain session end called
|
|
486
|
+
expect(rt.brainIntelligence.lifecycle).toHaveBeenCalledWith(
|
|
487
|
+
expect.objectContaining({ action: 'end', sessionId: 'session-complex' }),
|
|
488
|
+
);
|
|
489
|
+
// Knowledge extraction called
|
|
490
|
+
expect(rt.brainIntelligence.extractKnowledge).toHaveBeenCalledWith('session-complex');
|
|
491
|
+
// Planner.complete IS called (has plan)
|
|
492
|
+
expect(rt.planner.complete).toHaveBeenCalledWith('plan-1');
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('assessment result includes non-empty reasoning for simple tasks', () => {
|
|
496
|
+
const result = assessTaskComplexity({ prompt: 'fix typo in README' });
|
|
497
|
+
expect(result.classification).toBe('simple');
|
|
498
|
+
expect(typeof result.reasoning).toBe('string');
|
|
499
|
+
expect(result.reasoning.length).toBeGreaterThan(0);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('orchestrate_complete compounds operator signals when provided', async () => {
|
|
503
|
+
const compoundSignalsMock = vi.fn();
|
|
504
|
+
(rt as Record<string, unknown>).operatorContextStore = {
|
|
505
|
+
compoundSignals: compoundSignalsMock,
|
|
506
|
+
hasDrifted: vi.fn().mockReturnValue(false),
|
|
507
|
+
renderContextFile: vi.fn(),
|
|
508
|
+
};
|
|
509
|
+
ops = createOrchestrateOps(rt);
|
|
510
|
+
|
|
511
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
512
|
+
await op.handler({
|
|
513
|
+
sessionId: 'session-1',
|
|
514
|
+
outcome: 'completed',
|
|
515
|
+
operatorSignals: {
|
|
516
|
+
expertise: [{ topic: 'typescript', level: 'expert', confidence: 0.9 }],
|
|
517
|
+
corrections: [{ rule: 'use conventional commits', scope: 'global' }],
|
|
518
|
+
interests: [{ tag: 'coffee' }],
|
|
519
|
+
patterns: [{ pattern: 'prefers small PRs', frequency: 'frequent' }],
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
expect(compoundSignalsMock).toHaveBeenCalledWith(
|
|
524
|
+
{
|
|
525
|
+
expertise: [{ topic: 'typescript', level: 'expert', confidence: 0.9 }],
|
|
526
|
+
corrections: [{ rule: 'use conventional commits', scope: 'global' }],
|
|
527
|
+
interests: [{ tag: 'coffee' }],
|
|
528
|
+
patterns: [{ pattern: 'prefers small PRs', frequency: 'frequent' }],
|
|
529
|
+
},
|
|
530
|
+
'session-1',
|
|
531
|
+
);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('orchestrate_complete handles empty operator signals gracefully', async () => {
|
|
535
|
+
const compoundSignalsMock = vi.fn();
|
|
536
|
+
(rt as Record<string, unknown>).operatorContextStore = {
|
|
537
|
+
compoundSignals: compoundSignalsMock,
|
|
538
|
+
hasDrifted: vi.fn().mockReturnValue(false),
|
|
539
|
+
renderContextFile: vi.fn(),
|
|
540
|
+
};
|
|
541
|
+
ops = createOrchestrateOps(rt);
|
|
542
|
+
|
|
543
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
544
|
+
await op.handler({
|
|
545
|
+
sessionId: 'session-1',
|
|
546
|
+
outcome: 'completed',
|
|
547
|
+
operatorSignals: {},
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Empty object with default arrays should be passed through
|
|
551
|
+
expect(compoundSignalsMock).toHaveBeenCalledTimes(1);
|
|
552
|
+
const [passedSignals, passedSessionId] = compoundSignalsMock.mock.calls[0];
|
|
553
|
+
expect(passedSessionId).toBe('session-1');
|
|
554
|
+
// Zod defaults produce empty arrays for each field
|
|
555
|
+
expect(passedSignals).toBeDefined();
|
|
556
|
+
expect(Array.isArray(passedSignals.expertise ?? [])).toBe(true);
|
|
557
|
+
expect(Array.isArray(passedSignals.corrections ?? [])).toBe(true);
|
|
558
|
+
expect(Array.isArray(passedSignals.interests ?? [])).toBe(true);
|
|
559
|
+
expect(Array.isArray(passedSignals.patterns ?? [])).toBe(true);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('orchestrate_complete works when operatorContextStore not available', async () => {
|
|
563
|
+
// Ensure no operatorContextStore on runtime (backward compat)
|
|
564
|
+
delete (rt as Record<string, unknown>).operatorContextStore;
|
|
565
|
+
ops = createOrchestrateOps(rt);
|
|
566
|
+
|
|
567
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
568
|
+
const result = (await op.handler({
|
|
569
|
+
sessionId: 'session-1',
|
|
570
|
+
outcome: 'completed',
|
|
571
|
+
operatorSignals: {
|
|
572
|
+
expertise: [{ topic: 'react', level: 'intermediate' }],
|
|
573
|
+
corrections: [],
|
|
574
|
+
interests: [],
|
|
575
|
+
patterns: [],
|
|
576
|
+
},
|
|
577
|
+
})) as Record<string, unknown>;
|
|
578
|
+
|
|
579
|
+
// Should complete normally without errors
|
|
580
|
+
expect(result).toHaveProperty('plan');
|
|
581
|
+
expect(result).toHaveProperty('session');
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('orchestrate_complete re-renders context file when drift detected', async () => {
|
|
585
|
+
const compoundSignalsMock = vi.fn();
|
|
586
|
+
const hasDriftedMock = vi.fn().mockReturnValue(true);
|
|
587
|
+
const renderContextFileMock = vi
|
|
588
|
+
.fn()
|
|
589
|
+
.mockReturnValue(
|
|
590
|
+
'# Operator Context\n\n**Expertise:** typescript (expert, 1 sessions, confidence 0.90).',
|
|
591
|
+
);
|
|
592
|
+
(rt as Record<string, unknown>).operatorContextStore = {
|
|
593
|
+
compoundSignals: compoundSignalsMock,
|
|
594
|
+
hasDrifted: hasDriftedMock,
|
|
595
|
+
renderContextFile: renderContextFileMock,
|
|
596
|
+
};
|
|
597
|
+
rt.config.agentDir = '/tmp/test-agent';
|
|
598
|
+
ops = createOrchestrateOps(rt);
|
|
599
|
+
|
|
600
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
601
|
+
await op.handler({
|
|
602
|
+
sessionId: 'session-1',
|
|
603
|
+
outcome: 'completed',
|
|
604
|
+
operatorSignals: {
|
|
605
|
+
expertise: [{ topic: 'typescript', level: 'expert', confidence: 0.9 }],
|
|
606
|
+
corrections: [],
|
|
607
|
+
interests: [],
|
|
608
|
+
patterns: [],
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
expect(compoundSignalsMock).toHaveBeenCalled();
|
|
613
|
+
expect(hasDriftedMock).toHaveBeenCalled();
|
|
614
|
+
expect(renderContextFileMock).toHaveBeenCalled();
|
|
615
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join('/tmp/test-agent', 'instructions'), {
|
|
616
|
+
recursive: true,
|
|
617
|
+
});
|
|
618
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
619
|
+
path.join('/tmp/test-agent', 'instructions', 'operator-context.md'),
|
|
620
|
+
'# Operator Context\n\n**Expertise:** typescript (expert, 1 sessions, confidence 0.90).',
|
|
621
|
+
'utf-8',
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('orchestrate_complete skips file write when no agentDir', async () => {
|
|
626
|
+
const compoundSignalsMock = vi.fn();
|
|
627
|
+
const hasDriftedMock = vi.fn().mockReturnValue(true);
|
|
628
|
+
const renderContextFileMock = vi.fn().mockReturnValue('# Operator Context');
|
|
629
|
+
(rt as Record<string, unknown>).operatorContextStore = {
|
|
630
|
+
compoundSignals: compoundSignalsMock,
|
|
631
|
+
hasDrifted: hasDriftedMock,
|
|
632
|
+
renderContextFile: renderContextFileMock,
|
|
633
|
+
};
|
|
634
|
+
// agentDir is NOT set
|
|
635
|
+
delete (rt.config as Record<string, unknown>).agentDir;
|
|
636
|
+
ops = createOrchestrateOps(rt);
|
|
637
|
+
|
|
638
|
+
vi.mocked(fs.mkdirSync).mockClear();
|
|
639
|
+
vi.mocked(fs.writeFileSync).mockClear();
|
|
640
|
+
|
|
641
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
642
|
+
await op.handler({
|
|
643
|
+
sessionId: 'session-1',
|
|
644
|
+
outcome: 'completed',
|
|
645
|
+
operatorSignals: {
|
|
646
|
+
expertise: [{ topic: 'react', level: 'intermediate' }],
|
|
647
|
+
corrections: [],
|
|
648
|
+
interests: [],
|
|
649
|
+
patterns: [],
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
expect(compoundSignalsMock).toHaveBeenCalled();
|
|
654
|
+
expect(hasDriftedMock).toHaveBeenCalled();
|
|
655
|
+
// Should NOT write to disk since agentDir is missing
|
|
656
|
+
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
657
|
+
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('orchestrate_complete skips file write when no drift', async () => {
|
|
661
|
+
const compoundSignalsMock = vi.fn();
|
|
662
|
+
const hasDriftedMock = vi.fn().mockReturnValue(false);
|
|
663
|
+
const renderContextFileMock = vi.fn();
|
|
664
|
+
(rt as Record<string, unknown>).operatorContextStore = {
|
|
665
|
+
compoundSignals: compoundSignalsMock,
|
|
666
|
+
hasDrifted: hasDriftedMock,
|
|
667
|
+
renderContextFile: renderContextFileMock,
|
|
668
|
+
};
|
|
669
|
+
rt.config.agentDir = '/tmp/test-agent';
|
|
670
|
+
ops = createOrchestrateOps(rt);
|
|
671
|
+
|
|
672
|
+
vi.mocked(fs.mkdirSync).mockClear();
|
|
673
|
+
vi.mocked(fs.writeFileSync).mockClear();
|
|
674
|
+
|
|
675
|
+
const op = findOp(ops, 'orchestrate_complete');
|
|
676
|
+
await op.handler({
|
|
677
|
+
sessionId: 'session-1',
|
|
678
|
+
outcome: 'completed',
|
|
679
|
+
operatorSignals: {
|
|
680
|
+
expertise: [],
|
|
681
|
+
corrections: [],
|
|
682
|
+
interests: [],
|
|
683
|
+
patterns: [],
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
expect(compoundSignalsMock).toHaveBeenCalled();
|
|
688
|
+
expect(hasDriftedMock).toHaveBeenCalled();
|
|
689
|
+
// No drift means no file write
|
|
690
|
+
expect(renderContextFileMock).not.toHaveBeenCalled();
|
|
691
|
+
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
692
|
+
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('assessment result includes non-empty reasoning for complex tasks', () => {
|
|
696
|
+
const result = assessTaskComplexity({
|
|
697
|
+
prompt: 'add authentication across all API routes',
|
|
698
|
+
filesEstimated: 8,
|
|
699
|
+
domains: ['auth', 'api', 'middleware'],
|
|
700
|
+
});
|
|
701
|
+
expect(result.classification).toBe('complex');
|
|
702
|
+
expect(typeof result.reasoning).toBe('string');
|
|
703
|
+
expect(result.reasoning.length).toBeGreaterThan(0);
|
|
704
|
+
});
|
|
705
|
+
});
|
|
302
706
|
});
|