@soleri/core 9.3.1 → 9.5.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.js +3 -3
- 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/hooks/candidate-scorer.d.ts +28 -0
- package/dist/hooks/candidate-scorer.d.ts.map +1 -0
- package/dist/hooks/candidate-scorer.js +20 -0
- package/dist/hooks/candidate-scorer.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -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 +54 -16
- 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.map +1 -1
- package/dist/planning/task-complexity-assessor.js.map +1 -1
- 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 +100 -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 +71 -4
- 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/brain.ts +120 -46
- package/src/brain/extraction-quality.test.ts +323 -0
- package/src/brain/intelligence.ts +175 -64
- package/src/brain/learning-radar.ts +8 -5
- package/src/brain/second-brain-features.test.ts +1 -1
- package/src/chat/agent-loop.ts +1 -1
- package/src/chat/notifications.ts +4 -0
- package/src/control/intent-router.test.ts +73 -3
- package/src/control/intent-router.ts +48 -9
- package/src/control/types.ts +13 -2
- package/src/curator/curator.test.ts +92 -0
- package/src/curator/curator.ts +162 -18
- package/src/curator/schema.ts +8 -0
- package/src/domain-packs/types.ts +8 -0
- package/src/engine/module-manifest.test.ts +8 -2
- package/src/engine/module-manifest.ts +3 -3
- 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/hooks/candidate-scorer.test.ts +76 -0
- package/src/hooks/candidate-scorer.ts +39 -0
- package/src/hooks/index.ts +6 -0
- package/src/index.ts +24 -0
- package/src/llm/llm-client.ts +1 -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/persistence/sqlite-provider.ts +1 -0
- package/src/planning/github-projection.ts +48 -44
- package/src/planning/plan-lifecycle.ts +93 -22
- package/src/planning/planner-types.ts +6 -0
- package/src/planning/planner.ts +74 -4
- package/src/planning/task-complexity-assessor.test.ts +6 -2
- package/src/planning/task-complexity-assessor.ts +1 -4
- package/src/queue/pipeline-runner.ts +4 -0
- package/src/runtime/admin-ops.test.ts +139 -6
- package/src/runtime/admin-ops.ts +104 -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/curator-extra-ops.test.ts +7 -0
- package/src/runtime/curator-extra-ops.ts +10 -1
- 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.test.ts +7 -0
- 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 +197 -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 +202 -2
- package/src/runtime/orchestrate-ops.ts +88 -7
- 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/runtime.test.ts +50 -2
- package/src/runtime/runtime.ts +117 -89
- package/src/runtime/session-briefing.test.ts +1 -0
- package/src/runtime/session-briefing.ts +10 -1
- package/src/runtime/shutdown-registry.test.ts +151 -0
- package/src/runtime/shutdown-registry.ts +85 -0
- package/src/runtime/types.ts +7 -1
- package/src/skills/sync-skills.ts +14 -7
- package/src/transport/http-server.ts +50 -3
- package/src/transport/ws-server.ts +8 -0
- package/src/vault/linking.test.ts +12 -0
- package/src/vault/linking.ts +90 -44
- package/src/vault/vault-maintenance.ts +11 -18
- package/src/vault/vault-memories.ts +21 -13
- package/src/vault/vault-schema.ts +21 -0
- package/src/vault/vault.ts +8 -3
- package/vitest.config.ts +1 -0
|
@@ -30,6 +30,13 @@ function mockRuntime(): AgentRuntime {
|
|
|
30
30
|
start: vi.fn(),
|
|
31
31
|
stop: vi.fn(),
|
|
32
32
|
},
|
|
33
|
+
shutdownRegistry: {
|
|
34
|
+
register: vi.fn(),
|
|
35
|
+
closeAll: vi.fn(),
|
|
36
|
+
closeAllSync: vi.fn(),
|
|
37
|
+
size: 0,
|
|
38
|
+
isClosed: false,
|
|
39
|
+
},
|
|
33
40
|
} as unknown as AgentRuntime;
|
|
34
41
|
}
|
|
35
42
|
|
|
@@ -36,6 +36,24 @@ export function createCuratorFacadeOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
36
36
|
);
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
name: 'curator_dismiss_duplicate',
|
|
41
|
+
description:
|
|
42
|
+
'Dismiss a flagged duplicate pair — marks two entries as reviewed and intentionally distinct. They will no longer appear in curator_detect_duplicates results.',
|
|
43
|
+
auth: 'write',
|
|
44
|
+
schema: z.object({
|
|
45
|
+
entryIdA: z.string().describe('First entry ID'),
|
|
46
|
+
entryIdB: z.string().describe('Second entry ID'),
|
|
47
|
+
reason: z.string().optional().describe('Why these are not duplicates'),
|
|
48
|
+
}),
|
|
49
|
+
handler: async (params) => {
|
|
50
|
+
return curator.dismissDuplicate(
|
|
51
|
+
params.entryIdA as string,
|
|
52
|
+
params.entryIdB as string,
|
|
53
|
+
params.reason as string | undefined,
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
},
|
|
39
57
|
{
|
|
40
58
|
name: 'curator_contradictions',
|
|
41
59
|
description: 'List or detect contradictions between patterns and anti-patterns.',
|
|
@@ -67,7 +67,8 @@ describe('memory-facade', () => {
|
|
|
67
67
|
it('memory_search returns empty for no matches', async () => {
|
|
68
68
|
const result = await executeOp(ops, 'memory_search', { query: 'nonexistent' });
|
|
69
69
|
expect(result.success).toBe(true);
|
|
70
|
-
|
|
70
|
+
const data = result.data as unknown[];
|
|
71
|
+
expect(data.length).toBe(0);
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
it('memory_search finds captured memories', async () => {
|
|
@@ -79,7 +80,8 @@ describe('memory-facade', () => {
|
|
|
79
80
|
});
|
|
80
81
|
const result = await executeOp(ops, 'memory_search', { query: 'token migration' });
|
|
81
82
|
expect(result.success).toBe(true);
|
|
82
|
-
|
|
83
|
+
const data = result.data as unknown[];
|
|
84
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
83
85
|
});
|
|
84
86
|
|
|
85
87
|
it('memory_search returns summaries by default', async () => {
|
|
@@ -91,9 +93,9 @@ describe('memory-facade', () => {
|
|
|
91
93
|
});
|
|
92
94
|
const result = await executeOp(ops, 'memory_search', { query: 'short summary' });
|
|
93
95
|
expect(result.success).toBe(true);
|
|
94
|
-
const data = result.data as
|
|
95
|
-
expect(data
|
|
96
|
-
expect(data
|
|
96
|
+
const data = result.data as Array<{ summary: string }>;
|
|
97
|
+
expect(data[0]).toHaveProperty('summary');
|
|
98
|
+
expect(data[0]).not.toHaveProperty('context');
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
it('memory_search returns full objects with verbose:true', async () => {
|
|
@@ -105,8 +107,8 @@ describe('memory-facade', () => {
|
|
|
105
107
|
});
|
|
106
108
|
const result = await executeOp(ops, 'memory_search', { query: 'verbose test', verbose: true });
|
|
107
109
|
expect(result.success).toBe(true);
|
|
108
|
-
const data = result.data as
|
|
109
|
-
expect(data
|
|
110
|
+
const data = result.data as Array<Record<string, unknown>>;
|
|
111
|
+
expect(data[0]).toHaveProperty('context');
|
|
110
112
|
});
|
|
111
113
|
|
|
112
114
|
// ─── memory_list ───────────────────────────────────────────────
|
|
@@ -120,9 +122,9 @@ describe('memory-facade', () => {
|
|
|
120
122
|
});
|
|
121
123
|
const result = await executeOp(ops, 'memory_list', {});
|
|
122
124
|
expect(result.success).toBe(true);
|
|
123
|
-
const data = result.data as {
|
|
124
|
-
expect(data.total).toBeGreaterThanOrEqual(1);
|
|
125
|
-
expect(data.
|
|
125
|
+
const data = result.data as { memories: unknown[]; stats: { total: number } };
|
|
126
|
+
expect(data.stats.total).toBeGreaterThanOrEqual(1);
|
|
127
|
+
expect(data.memories.length).toBeGreaterThanOrEqual(1);
|
|
126
128
|
});
|
|
127
129
|
|
|
128
130
|
it('memory_list filters by type', async () => {
|
|
@@ -140,8 +142,8 @@ describe('memory-facade', () => {
|
|
|
140
142
|
});
|
|
141
143
|
const result = await executeOp(ops, 'memory_list', { type: 'lesson' });
|
|
142
144
|
expect(result.success).toBe(true);
|
|
143
|
-
const data = result.data as {
|
|
144
|
-
expect(data.
|
|
145
|
+
const data = result.data as { memories: unknown[]; stats: Record<string, unknown> };
|
|
146
|
+
expect(data.memories.length).toBe(1);
|
|
145
147
|
});
|
|
146
148
|
|
|
147
149
|
it('memory_list verbose returns full objects', async () => {
|
|
@@ -45,17 +45,15 @@ export function createMemoryFacadeOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
45
45
|
limit: (params.limit as number) ?? 10,
|
|
46
46
|
});
|
|
47
47
|
if (params.verbose) {
|
|
48
|
-
return
|
|
48
|
+
return memories;
|
|
49
49
|
}
|
|
50
|
-
return {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
total: memories.length,
|
|
58
|
-
};
|
|
50
|
+
return memories.map((m) => ({
|
|
51
|
+
id: m.id,
|
|
52
|
+
type: m.type,
|
|
53
|
+
summary: truncateSummary(m.summary || m.context),
|
|
54
|
+
score: null,
|
|
55
|
+
project: m.projectPath,
|
|
56
|
+
}));
|
|
59
57
|
},
|
|
60
58
|
},
|
|
61
59
|
{
|
|
@@ -117,13 +115,13 @@ export function createMemoryFacadeOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
117
115
|
return { memories, stats };
|
|
118
116
|
}
|
|
119
117
|
return {
|
|
120
|
-
|
|
118
|
+
memories: memories.map((m) => ({
|
|
121
119
|
id: m.id,
|
|
122
120
|
summary: truncateSummary(m.summary || m.context),
|
|
123
121
|
project: m.projectPath,
|
|
124
122
|
createdAt: m.createdAt,
|
|
125
123
|
})),
|
|
126
|
-
|
|
124
|
+
stats,
|
|
127
125
|
};
|
|
128
126
|
},
|
|
129
127
|
},
|
|
@@ -197,6 +195,193 @@ export function createMemoryFacadeOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
197
195
|
},
|
|
198
196
|
},
|
|
199
197
|
|
|
198
|
+
// ─── Handoff ────────────────────────────────────────────────
|
|
199
|
+
{
|
|
200
|
+
name: 'handoff_generate',
|
|
201
|
+
description:
|
|
202
|
+
'Generate a structured handoff document for context transitions. ' +
|
|
203
|
+
'Pulls from active plan (if any) and recent session memories to produce ' +
|
|
204
|
+
'a markdown document that can bootstrap a new context window. ' +
|
|
205
|
+
'Ephemeral — NOT persisted to vault.',
|
|
206
|
+
auth: 'read',
|
|
207
|
+
schema: z.object({
|
|
208
|
+
projectPath: z
|
|
209
|
+
.string()
|
|
210
|
+
.optional()
|
|
211
|
+
.default('.')
|
|
212
|
+
.describe('Project path for filtering memories'),
|
|
213
|
+
sessionLimit: z
|
|
214
|
+
.number()
|
|
215
|
+
.optional()
|
|
216
|
+
.default(3)
|
|
217
|
+
.describe('Number of recent session memories to include'),
|
|
218
|
+
}),
|
|
219
|
+
handler: async (params) => {
|
|
220
|
+
const { planner } = runtime;
|
|
221
|
+
const projectPath = params.projectPath as string;
|
|
222
|
+
const sessionLimit = (params.sessionLimit as number) ?? 3;
|
|
223
|
+
|
|
224
|
+
const sections: string[] = [];
|
|
225
|
+
const now = new Date().toISOString();
|
|
226
|
+
|
|
227
|
+
sections.push('# Handoff Document');
|
|
228
|
+
sections.push('');
|
|
229
|
+
sections.push(`Generated: ${now}`);
|
|
230
|
+
sections.push('');
|
|
231
|
+
|
|
232
|
+
// ─── Active Plan Context ───────────────────────────
|
|
233
|
+
const activePlans = planner.getActive();
|
|
234
|
+
if (activePlans.length > 0) {
|
|
235
|
+
const plan = activePlans[0]; // Most relevant active plan
|
|
236
|
+
sections.push('## Active Plan');
|
|
237
|
+
sections.push('');
|
|
238
|
+
sections.push(`| Field | Value |`);
|
|
239
|
+
sections.push(`|-------|-------|`);
|
|
240
|
+
sections.push(`| **Plan ID** | ${plan.id} |`);
|
|
241
|
+
sections.push(`| **Objective** | ${plan.objective} |`);
|
|
242
|
+
sections.push(`| **Status** | ${plan.status} |`);
|
|
243
|
+
sections.push(`| **Scope** | ${plan.scope} |`);
|
|
244
|
+
sections.push('');
|
|
245
|
+
|
|
246
|
+
// Decisions
|
|
247
|
+
if (plan.decisions.length > 0) {
|
|
248
|
+
sections.push('### Decisions');
|
|
249
|
+
sections.push('');
|
|
250
|
+
for (const d of plan.decisions) {
|
|
251
|
+
if (typeof d === 'string') {
|
|
252
|
+
sections.push(`- ${d}`);
|
|
253
|
+
} else {
|
|
254
|
+
sections.push(`- **${d.decision}** — ${d.rationale}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
sections.push('');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Task status summary
|
|
261
|
+
if (plan.tasks.length > 0) {
|
|
262
|
+
sections.push('### Tasks');
|
|
263
|
+
sections.push('');
|
|
264
|
+
sections.push('| # | Task | Status |');
|
|
265
|
+
sections.push('|---|------|--------|');
|
|
266
|
+
for (let i = 0; i < plan.tasks.length; i++) {
|
|
267
|
+
const t = plan.tasks[i];
|
|
268
|
+
sections.push(`| ${i + 1} | ${t.title} | ${t.status} |`);
|
|
269
|
+
}
|
|
270
|
+
sections.push('');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Approach
|
|
274
|
+
if (plan.approach) {
|
|
275
|
+
sections.push('### Approach');
|
|
276
|
+
sections.push('');
|
|
277
|
+
sections.push(plan.approach);
|
|
278
|
+
sections.push('');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Additional active plans (just IDs)
|
|
282
|
+
if (activePlans.length > 1) {
|
|
283
|
+
sections.push('### Other Active Plans');
|
|
284
|
+
sections.push('');
|
|
285
|
+
for (let i = 1; i < activePlans.length; i++) {
|
|
286
|
+
const p = activePlans[i];
|
|
287
|
+
sections.push(`- **${p.id}**: ${p.objective} (${p.status})`);
|
|
288
|
+
}
|
|
289
|
+
sections.push('');
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
sections.push('## Active Plan');
|
|
293
|
+
sections.push('');
|
|
294
|
+
sections.push('No active plans.');
|
|
295
|
+
sections.push('');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Recent Session Context ────────────────────────
|
|
299
|
+
const recentSessions = vault.listMemories({
|
|
300
|
+
type: 'session',
|
|
301
|
+
projectPath,
|
|
302
|
+
limit: sessionLimit,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (recentSessions.length > 0) {
|
|
306
|
+
sections.push('## Recent Sessions');
|
|
307
|
+
sections.push('');
|
|
308
|
+
for (const session of recentSessions) {
|
|
309
|
+
sections.push(`### ${session.createdAt}`);
|
|
310
|
+
sections.push('');
|
|
311
|
+
if (session.summary) {
|
|
312
|
+
sections.push(session.summary);
|
|
313
|
+
sections.push('');
|
|
314
|
+
}
|
|
315
|
+
if (session.nextSteps && session.nextSteps.length > 0) {
|
|
316
|
+
sections.push('**Next steps:**');
|
|
317
|
+
for (const step of session.nextSteps) {
|
|
318
|
+
sections.push(`- ${step}`);
|
|
319
|
+
}
|
|
320
|
+
sections.push('');
|
|
321
|
+
}
|
|
322
|
+
if (session.decisions && session.decisions.length > 0) {
|
|
323
|
+
sections.push('**Decisions:**');
|
|
324
|
+
for (const d of session.decisions) {
|
|
325
|
+
sections.push(`- ${d}`);
|
|
326
|
+
}
|
|
327
|
+
sections.push('');
|
|
328
|
+
}
|
|
329
|
+
if (session.filesModified && session.filesModified.length > 0) {
|
|
330
|
+
sections.push(`**Files modified:** ${session.filesModified.join(', ')}`);
|
|
331
|
+
sections.push('');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
sections.push('## Recent Sessions');
|
|
336
|
+
sections.push('');
|
|
337
|
+
sections.push('No recent session memories found.');
|
|
338
|
+
sections.push('');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ─── Resumption Hints ──────────────────────────────
|
|
342
|
+
sections.push('## Resumption');
|
|
343
|
+
sections.push('');
|
|
344
|
+
sections.push('Use this document to restore context after a context window transition.');
|
|
345
|
+
sections.push('');
|
|
346
|
+
if (activePlans.length > 0) {
|
|
347
|
+
const plan = activePlans[0];
|
|
348
|
+
const pendingTasks = plan.tasks.filter(
|
|
349
|
+
(t) => t.status === 'pending' || t.status === 'in_progress',
|
|
350
|
+
);
|
|
351
|
+
if (pendingTasks.length > 0) {
|
|
352
|
+
sections.push('**Immediate next actions:**');
|
|
353
|
+
for (const t of pendingTasks.slice(0, 5)) {
|
|
354
|
+
sections.push(
|
|
355
|
+
`- ${t.status === 'in_progress' ? '[IN PROGRESS]' : '[PENDING]'} ${t.title}`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
sections.push('');
|
|
359
|
+
}
|
|
360
|
+
if (plan.status === 'executing') {
|
|
361
|
+
sections.push(
|
|
362
|
+
'> Plan is in `executing` state. Continue with pending tasks or call `op:plan_reconcile` if complete.',
|
|
363
|
+
);
|
|
364
|
+
} else if (plan.status === 'reconciling') {
|
|
365
|
+
sections.push(
|
|
366
|
+
'> Plan is in `reconciling` state. Call `op:plan_complete_lifecycle` to finalize.',
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const markdown = sections.join('\n');
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
handoff: markdown,
|
|
375
|
+
meta: {
|
|
376
|
+
activePlanCount: activePlans.length,
|
|
377
|
+
activePlanId: activePlans.length > 0 ? activePlans[0].id : null,
|
|
378
|
+
recentSessionCount: recentSessions.length,
|
|
379
|
+
generatedAt: now,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
|
|
200
385
|
// ─── Satellite ops ───────────────────────────────────────────
|
|
201
386
|
...createMemoryExtraOps(runtime),
|
|
202
387
|
...createMemoryCrossProjectOps(runtime),
|
|
@@ -25,7 +25,9 @@ export function createOrchestrateFacadeOps(runtime: AgentRuntime): OpDefinition[
|
|
|
25
25
|
name: z.string().optional().describe('Project display name (derived from path if omitted)'),
|
|
26
26
|
}),
|
|
27
27
|
handler: async (params) => {
|
|
28
|
-
const { resolve } = await import('node:path');
|
|
28
|
+
const { resolve, join } = await import('node:path');
|
|
29
|
+
const { homedir } = await import('node:os');
|
|
30
|
+
const { existsSync, readdirSync, statSync } = await import('node:fs');
|
|
29
31
|
const projectPath = resolve((params.projectPath as string) ?? '.');
|
|
30
32
|
const project = vault.registerProject(projectPath, params.name as string | undefined);
|
|
31
33
|
// Also track in project registry for cross-project features
|
|
@@ -40,6 +42,35 @@ export function createOrchestrateFacadeOps(runtime: AgentRuntime): OpDefinition[
|
|
|
40
42
|
const proposalStats = governance.getProposalStats(projectPath);
|
|
41
43
|
const quotaStatus = governance.getQuotaStatus(projectPath);
|
|
42
44
|
|
|
45
|
+
// Check for stale staging backups (lightweight — stat only, no tree walk)
|
|
46
|
+
let stagingWarning: { count: number; message: string } | undefined;
|
|
47
|
+
try {
|
|
48
|
+
const stagingRoot = join(homedir(), '.soleri', 'staging');
|
|
49
|
+
if (existsSync(stagingRoot)) {
|
|
50
|
+
const maxAgeMs = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
51
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
52
|
+
const dirs = readdirSync(stagingRoot, { withFileTypes: true });
|
|
53
|
+
let staleCount = 0;
|
|
54
|
+
for (const dir of dirs) {
|
|
55
|
+
if (!dir.isDirectory()) continue;
|
|
56
|
+
try {
|
|
57
|
+
const st = statSync(join(stagingRoot, dir.name));
|
|
58
|
+
if (st.mtimeMs < cutoff) staleCount++;
|
|
59
|
+
} catch {
|
|
60
|
+
// skip unreadable entries
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (staleCount > 0) {
|
|
64
|
+
stagingWarning = {
|
|
65
|
+
count: staleCount,
|
|
66
|
+
message: `${staleCount} staging backup(s) older than 7 days. Run: soleri staging cleanup --yes`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// Non-critical — don't fail session start over staging check
|
|
72
|
+
}
|
|
73
|
+
|
|
43
74
|
return {
|
|
44
75
|
project,
|
|
45
76
|
is_new: isNew,
|
|
@@ -56,6 +87,7 @@ export function createOrchestrateFacadeOps(runtime: AgentRuntime): OpDefinition[
|
|
|
56
87
|
isQuotaWarning: quotaStatus.isWarning,
|
|
57
88
|
expiredThisSession: expired,
|
|
58
89
|
},
|
|
90
|
+
...(stagingWarning ? { stagingWarning } : {}),
|
|
59
91
|
};
|
|
60
92
|
},
|
|
61
93
|
},
|
|
@@ -239,6 +239,66 @@ describe('plan-facade', () => {
|
|
|
239
239
|
expect(data.iterated).toBe(true);
|
|
240
240
|
});
|
|
241
241
|
|
|
242
|
+
it('plan_iterate with decisions persists them', async () => {
|
|
243
|
+
const createResult = await executeOp(ops, 'create_plan', {
|
|
244
|
+
objective: 'Decisions test',
|
|
245
|
+
scope: 'test',
|
|
246
|
+
});
|
|
247
|
+
const planId = ((createResult.data as Record<string, unknown>).plan as Record<string, unknown>)
|
|
248
|
+
.id as string;
|
|
249
|
+
|
|
250
|
+
const result = await executeOp(ops, 'plan_iterate', {
|
|
251
|
+
planId,
|
|
252
|
+
decisions: [{ decision: 'Use FTS5', rationale: 'Performance' }],
|
|
253
|
+
});
|
|
254
|
+
expect(result.success).toBe(true);
|
|
255
|
+
const data = result.data as { iterated: boolean; plan: Record<string, unknown> };
|
|
256
|
+
expect(data.iterated).toBe(true);
|
|
257
|
+
const decisions = data.plan.decisions as Array<Record<string, string>>;
|
|
258
|
+
expect(decisions).toHaveLength(1);
|
|
259
|
+
expect(decisions[0].decision).toBe('Use FTS5');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('plan_iterate with alternatives persists them', async () => {
|
|
263
|
+
const createResult = await executeOp(ops, 'create_plan', {
|
|
264
|
+
objective: 'Alternatives test',
|
|
265
|
+
scope: 'test',
|
|
266
|
+
});
|
|
267
|
+
const planId = ((createResult.data as Record<string, unknown>).plan as Record<string, unknown>)
|
|
268
|
+
.id as string;
|
|
269
|
+
|
|
270
|
+
const result = await executeOp(ops, 'plan_iterate', {
|
|
271
|
+
planId,
|
|
272
|
+
alternatives: [
|
|
273
|
+
{ approach: 'Alt A', pros: ['fast'], cons: ['fragile'], rejected_reason: 'Too risky' },
|
|
274
|
+
{ approach: 'Alt B', pros: ['safe'], cons: ['slow'], rejected_reason: 'Too slow' },
|
|
275
|
+
],
|
|
276
|
+
});
|
|
277
|
+
expect(result.success).toBe(true);
|
|
278
|
+
const data = result.data as { iterated: boolean; plan: Record<string, unknown> };
|
|
279
|
+
expect(data.iterated).toBe(true);
|
|
280
|
+
const alternatives = data.plan.alternatives as Array<Record<string, unknown>>;
|
|
281
|
+
expect(alternatives).toHaveLength(2);
|
|
282
|
+
expect(alternatives[0].approach).toBe('Alt A');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('plan_iterate with no effective changes returns iterated: false', async () => {
|
|
286
|
+
const createResult = await executeOp(ops, 'create_plan', {
|
|
287
|
+
objective: 'No-op test',
|
|
288
|
+
scope: 'test',
|
|
289
|
+
});
|
|
290
|
+
const planId = ((createResult.data as Record<string, unknown>).plan as Record<string, unknown>)
|
|
291
|
+
.id as string;
|
|
292
|
+
|
|
293
|
+
const result = await executeOp(ops, 'plan_iterate', {
|
|
294
|
+
planId,
|
|
295
|
+
});
|
|
296
|
+
expect(result.success).toBe(true);
|
|
297
|
+
const data = result.data as { iterated: boolean; reason?: string };
|
|
298
|
+
expect(data.iterated).toBe(false);
|
|
299
|
+
expect(data.reason).toBe('no changes detected');
|
|
300
|
+
});
|
|
301
|
+
|
|
242
302
|
// ─── plan_stats ────────────────────────────────────────────────
|
|
243
303
|
|
|
244
304
|
it('plan_stats returns statistics', async () => {
|
|
@@ -280,4 +340,157 @@ describe('plan-facade', () => {
|
|
|
280
340
|
expect(result.success).toBe(true);
|
|
281
341
|
expect((result.data as Record<string, unknown>).error).toContain('not found');
|
|
282
342
|
});
|
|
343
|
+
|
|
344
|
+
// ─── create_plan vault enrichment ─────────────────────────────
|
|
345
|
+
|
|
346
|
+
it('create_plan enriches decisions with vault patterns when matches exist', async () => {
|
|
347
|
+
// Seed vault with a relevant entry
|
|
348
|
+
vault.add({
|
|
349
|
+
title: 'SQLite FTS5 search pattern',
|
|
350
|
+
description: 'Use FTS5 with porter tokenizer for all text search',
|
|
351
|
+
content: 'Use FTS5 with porter tokenizer for all text search',
|
|
352
|
+
type: 'pattern',
|
|
353
|
+
domain: 'architecture',
|
|
354
|
+
severity: 'suggestion',
|
|
355
|
+
tags: ['sqlite', 'search'],
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const result = await executeOp(ops, 'create_plan', {
|
|
359
|
+
objective: 'Implement text search with SQLite FTS5',
|
|
360
|
+
scope: 'packages/core/src/vault',
|
|
361
|
+
});
|
|
362
|
+
expect(result.success).toBe(true);
|
|
363
|
+
const data = result.data as {
|
|
364
|
+
created: boolean;
|
|
365
|
+
plan: Record<string, unknown>;
|
|
366
|
+
vaultEntryIds: string[];
|
|
367
|
+
};
|
|
368
|
+
expect(data.created).toBe(true);
|
|
369
|
+
expect(data.vaultEntryIds.length).toBeGreaterThan(0);
|
|
370
|
+
// Decisions should contain vault pattern references with entryId markers
|
|
371
|
+
const decisions = data.plan.decisions as string[];
|
|
372
|
+
const vaultDecisions = decisions.filter((d) => d.startsWith('Vault pattern:'));
|
|
373
|
+
expect(vaultDecisions.length).toBeGreaterThan(0);
|
|
374
|
+
// Each vault decision should have an [entryId:...] marker for brain feedback
|
|
375
|
+
for (const vd of vaultDecisions) {
|
|
376
|
+
expect(vd).toMatch(/\[entryId:[^\]]+\]/);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('create_plan works without vault matches (empty vault)', async () => {
|
|
381
|
+
// Fresh vault, no entries
|
|
382
|
+
const result = await executeOp(ops, 'create_plan', {
|
|
383
|
+
objective: 'Something completely unrelated xyz123',
|
|
384
|
+
scope: 'test',
|
|
385
|
+
});
|
|
386
|
+
expect(result.success).toBe(true);
|
|
387
|
+
const data = result.data as {
|
|
388
|
+
created: boolean;
|
|
389
|
+
vaultEntryIds: string[];
|
|
390
|
+
};
|
|
391
|
+
expect(data.created).toBe(true);
|
|
392
|
+
expect(data.vaultEntryIds).toEqual([]);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('create_plan preserves user decisions alongside vault enrichment', async () => {
|
|
396
|
+
vault.add({
|
|
397
|
+
title: 'Testing pattern',
|
|
398
|
+
description: 'Always write tests before implementation',
|
|
399
|
+
content: 'Always write tests before implementation',
|
|
400
|
+
type: 'pattern',
|
|
401
|
+
domain: 'testing',
|
|
402
|
+
severity: 'suggestion',
|
|
403
|
+
tags: ['tdd'],
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const result = await executeOp(ops, 'create_plan', {
|
|
407
|
+
objective: 'Add testing patterns to the project',
|
|
408
|
+
scope: 'packages/core',
|
|
409
|
+
decisions: ['Use vitest as test runner'],
|
|
410
|
+
});
|
|
411
|
+
expect(result.success).toBe(true);
|
|
412
|
+
const data = result.data as { plan: Record<string, unknown>; vaultEntryIds: string[] };
|
|
413
|
+
const decisions = data.plan.decisions as string[];
|
|
414
|
+
// User decision preserved
|
|
415
|
+
expect(decisions).toContain('Use vitest as test runner');
|
|
416
|
+
// Vault enrichment added
|
|
417
|
+
if (data.vaultEntryIds.length > 0) {
|
|
418
|
+
const vaultDecisions = decisions.filter((d) => d.startsWith('Vault pattern:'));
|
|
419
|
+
expect(vaultDecisions.length).toBeGreaterThan(0);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// ─── plan_close_stale ─────────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
it('plan_close_stale op is registered', () => {
|
|
426
|
+
expect([...ops.keys()]).toContain('plan_close_stale');
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('plan_close_stale returns no plans when none are stale', async () => {
|
|
430
|
+
const result = await executeOp(ops, 'plan_close_stale', {});
|
|
431
|
+
expect(result.success).toBe(true);
|
|
432
|
+
const data = result.data as { closed: number; plans: unknown[] };
|
|
433
|
+
expect(data.closed).toBe(0);
|
|
434
|
+
expect(data.plans).toHaveLength(0);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('plan_close_stale with olderThanMs: 0 closes all non-terminal plans', async () => {
|
|
438
|
+
// Create a draft plan
|
|
439
|
+
await executeOp(ops, 'create_plan', { objective: 'Stale test', scope: 'test' });
|
|
440
|
+
|
|
441
|
+
// Close immediately (olderThanMs: 0 means close everything)
|
|
442
|
+
const result = await executeOp(ops, 'plan_close_stale', { olderThanMs: 0 });
|
|
443
|
+
expect(result.success).toBe(true);
|
|
444
|
+
const data = result.data as { closed: number; plans: Array<{ id: string; reason: string }> };
|
|
445
|
+
expect(data.closed).toBeGreaterThanOrEqual(1);
|
|
446
|
+
expect(data.plans[0].reason).toContain('ttl-expired');
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// ─── Planner.closeStale() ─────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
it('closeStale closes draft plans older than TTL', () => {
|
|
452
|
+
const runtime = makeRuntime(vault);
|
|
453
|
+
const planner = runtime.planner;
|
|
454
|
+
|
|
455
|
+
// Create a plan — it's immediately a draft
|
|
456
|
+
planner.create({ objective: 'Old draft', scope: 'test' });
|
|
457
|
+
|
|
458
|
+
// Close with olderThanMs: 0 to force-close regardless of age
|
|
459
|
+
const result = planner.closeStale(0);
|
|
460
|
+
expect(result.closedPlans.length).toBeGreaterThanOrEqual(1);
|
|
461
|
+
expect(result.closedPlans[0].previousStatus).toBe('draft');
|
|
462
|
+
expect(result.closedPlans[0].reason).toContain('ttl-expired');
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('closeStale does not close completed plans', () => {
|
|
466
|
+
const runtime = makeRuntime(vault);
|
|
467
|
+
const planner = runtime.planner;
|
|
468
|
+
|
|
469
|
+
// Create and complete a plan
|
|
470
|
+
const plan = planner.create({
|
|
471
|
+
objective: 'Completed plan',
|
|
472
|
+
scope: 'test',
|
|
473
|
+
decisions: ['d1', 'd2'],
|
|
474
|
+
tasks: [{ title: 'T1', description: 'd1' }],
|
|
475
|
+
});
|
|
476
|
+
planner.approve(plan.id);
|
|
477
|
+
planner.startExecution(plan.id);
|
|
478
|
+
planner.complete(plan.id);
|
|
479
|
+
|
|
480
|
+
// closeStale should not touch it
|
|
481
|
+
const result = planner.closeStale(0);
|
|
482
|
+
expect(result.closedPlans.filter((p) => p.id === plan.id)).toHaveLength(0);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('closeStale respects default TTL — does not close fresh drafts', () => {
|
|
486
|
+
const runtime = makeRuntime(vault);
|
|
487
|
+
const planner = runtime.planner;
|
|
488
|
+
|
|
489
|
+
// Create a fresh plan
|
|
490
|
+
planner.create({ objective: 'Fresh draft', scope: 'test' });
|
|
491
|
+
|
|
492
|
+
// Close with default TTL (30 min) — fresh plan should NOT be closed
|
|
493
|
+
const result = planner.closeStale();
|
|
494
|
+
expect(result.closedPlans).toHaveLength(0);
|
|
495
|
+
});
|
|
283
496
|
});
|
|
@@ -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
|
{
|