@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
|
@@ -17,6 +17,7 @@ import { collectGitEvidence } from '../planning/evidence-collector.js';
|
|
|
17
17
|
import { matchPlaybooks, type PlaybookMatchResult } from '../playbooks/index.js';
|
|
18
18
|
import { entryToPlaybookDefinition } from '../playbooks/index.js';
|
|
19
19
|
import { closeIssueWithComment } from './github-integration.js';
|
|
20
|
+
import { recordPlanFeedback } from './plan-feedback-helper.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Create 22 extended planning operations for an agent runtime.
|
|
@@ -37,9 +38,9 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
37
38
|
{
|
|
38
39
|
name: 'plan_iterate',
|
|
39
40
|
description:
|
|
40
|
-
'Revise a draft plan — change objective, scope, decisions, or add/remove tasks. Only works on draft plans.',
|
|
41
|
+
'Revise a draft plan — change objective, scope, decisions, alternatives, or add/remove tasks. Only works on draft plans.',
|
|
41
42
|
auth: 'write',
|
|
42
|
-
schema: z.
|
|
43
|
+
schema: z.strictObject({
|
|
43
44
|
planId: z.string().describe('ID of the draft plan to iterate on'),
|
|
44
45
|
objective: z.string().optional().describe('New objective (replaces existing)'),
|
|
45
46
|
scope: z.string().optional().describe('New scope (replaces existing)'),
|
|
@@ -49,21 +50,54 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
49
50
|
.describe(
|
|
50
51
|
'New decisions list (replaces existing) — strings or {decision, rationale} objects',
|
|
51
52
|
),
|
|
53
|
+
alternatives: z
|
|
54
|
+
.array(
|
|
55
|
+
z.object({
|
|
56
|
+
approach: z.string().describe('The alternative approach considered'),
|
|
57
|
+
pros: z.array(z.string()).describe('Advantages of this approach'),
|
|
58
|
+
cons: z.array(z.string()).describe('Disadvantages of this approach'),
|
|
59
|
+
rejected_reason: z.string().describe('Why this alternative was rejected'),
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
.optional()
|
|
63
|
+
.describe('Rejected alternative approaches (replaces existing)'),
|
|
52
64
|
addTasks: z
|
|
53
|
-
.array(
|
|
65
|
+
.array(
|
|
66
|
+
z.object({
|
|
67
|
+
title: z.string(),
|
|
68
|
+
description: z.string(),
|
|
69
|
+
phase: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('Phase this task belongs to (e.g., "wave-1", "discovery")'),
|
|
73
|
+
milestone: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe('Milestone this task contributes to (e.g., "v1.0", "mvp")'),
|
|
77
|
+
parentTaskId: z.string().optional().describe('Parent task ID for sub-task hierarchy'),
|
|
78
|
+
}),
|
|
79
|
+
)
|
|
54
80
|
.optional()
|
|
55
81
|
.describe('Tasks to append'),
|
|
56
82
|
removeTasks: z.array(z.string()).optional().describe('Task IDs to remove'),
|
|
57
83
|
}),
|
|
58
84
|
handler: async (params) => {
|
|
59
85
|
try {
|
|
60
|
-
const plan = planner.iterate(params.planId as string, {
|
|
86
|
+
const { plan, mutated } = planner.iterate(params.planId as string, {
|
|
61
87
|
objective: params.objective as string | undefined,
|
|
62
88
|
scope: params.scope as string | undefined,
|
|
63
89
|
decisions: params.decisions as string[] | undefined,
|
|
90
|
+
alternatives: params.alternatives as
|
|
91
|
+
| Array<{ approach: string; pros: string[]; cons: string[]; rejected_reason: string }>
|
|
92
|
+
| undefined,
|
|
64
93
|
addTasks: params.addTasks as Array<{ title: string; description: string }> | undefined,
|
|
65
94
|
removeTasks: params.removeTasks as string[] | undefined,
|
|
66
95
|
});
|
|
96
|
+
|
|
97
|
+
if (mutated === 0) {
|
|
98
|
+
return { iterated: false, reason: 'no changes detected', plan };
|
|
99
|
+
}
|
|
100
|
+
|
|
67
101
|
return { iterated: true, plan };
|
|
68
102
|
} catch (err) {
|
|
69
103
|
return { error: (err as Error).message };
|
|
@@ -85,6 +119,17 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
85
119
|
title: z.string(),
|
|
86
120
|
description: z.string(),
|
|
87
121
|
dependsOn: z.array(z.string()).optional().describe('Task IDs this task depends on'),
|
|
122
|
+
phase: z
|
|
123
|
+
.string()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe(
|
|
126
|
+
'Phase this task belongs to (e.g., "wave-1", "discovery", "implementation")',
|
|
127
|
+
),
|
|
128
|
+
milestone: z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe('Milestone this task contributes to (e.g., "v1.0", "mvp", "beta")'),
|
|
132
|
+
parentTaskId: z.string().optional().describe('Parent task ID for sub-task hierarchy'),
|
|
88
133
|
}),
|
|
89
134
|
)
|
|
90
135
|
.describe('New task list with optional dependency references (task-1, task-2, etc.)'),
|
|
@@ -93,7 +138,14 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
93
138
|
try {
|
|
94
139
|
const plan = planner.splitTasks(
|
|
95
140
|
params.planId as string,
|
|
96
|
-
params.tasks as Array<{
|
|
141
|
+
params.tasks as Array<{
|
|
142
|
+
title: string;
|
|
143
|
+
description: string;
|
|
144
|
+
dependsOn?: string[];
|
|
145
|
+
phase?: string;
|
|
146
|
+
milestone?: string;
|
|
147
|
+
parentTaskId?: string;
|
|
148
|
+
}>,
|
|
97
149
|
);
|
|
98
150
|
|
|
99
151
|
// Auto-start brain session linked to the plan for learning pipeline
|
|
@@ -139,20 +191,16 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
139
191
|
.describe('Specific drift items — differences between plan and reality'),
|
|
140
192
|
}),
|
|
141
193
|
handler: async (params) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
};
|
|
153
|
-
} catch (err) {
|
|
154
|
-
return { error: (err as Error).message };
|
|
155
|
-
}
|
|
194
|
+
const plan = planner.reconcile(params.planId as string, {
|
|
195
|
+
actualOutcome: params.actualOutcome as string,
|
|
196
|
+
driftItems: params.driftItems as DriftItem[] | undefined,
|
|
197
|
+
});
|
|
198
|
+
return {
|
|
199
|
+
reconciled: true,
|
|
200
|
+
accuracy: plan.reconciliation!.accuracy,
|
|
201
|
+
driftCount: plan.reconciliation!.driftItems.length,
|
|
202
|
+
plan,
|
|
203
|
+
};
|
|
156
204
|
},
|
|
157
205
|
},
|
|
158
206
|
|
|
@@ -228,20 +276,7 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
228
276
|
}
|
|
229
277
|
|
|
230
278
|
// Auto-record positive feedback for vault entries used as recommendations
|
|
231
|
-
|
|
232
|
-
const entryIdRegex = /\[entryId:([^\]]+)\]/;
|
|
233
|
-
for (const d of plan.decisions) {
|
|
234
|
-
const decisionStr = typeof d === 'string' ? d : d.decision;
|
|
235
|
-
const match = entryIdRegex.exec(decisionStr);
|
|
236
|
-
if (match) {
|
|
237
|
-
try {
|
|
238
|
-
brain.recordFeedback(plan.objective, match[1], 'accepted');
|
|
239
|
-
feedbackRecorded++;
|
|
240
|
-
} catch {
|
|
241
|
-
// Graceful degradation — skip if entry not found or already recorded
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
279
|
+
const feedbackRecorded = recordPlanFeedback(plan, brain, brainIntelligence);
|
|
245
280
|
|
|
246
281
|
// Auto-close linked GitHub issue if plan has one
|
|
247
282
|
let issueClosed = false;
|
|
@@ -818,6 +853,34 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
818
853
|
},
|
|
819
854
|
},
|
|
820
855
|
|
|
856
|
+
// ─── Plan Close Stale ───────────────────────────────────────
|
|
857
|
+
{
|
|
858
|
+
name: 'plan_close_stale',
|
|
859
|
+
description:
|
|
860
|
+
'Close stale plans stuck in non-terminal states. Draft/approved plans expire after 30 min. Executing/reconciling plans expire after the configured threshold (default 24h). Returns list of closed plans.',
|
|
861
|
+
auth: 'write',
|
|
862
|
+
schema: z.object({
|
|
863
|
+
olderThanMs: z
|
|
864
|
+
.number()
|
|
865
|
+
.optional()
|
|
866
|
+
.describe(
|
|
867
|
+
'Custom threshold in milliseconds for executing/reconciling plans (default: 24h). Set to 0 to close ALL non-terminal plans immediately.',
|
|
868
|
+
),
|
|
869
|
+
}),
|
|
870
|
+
handler: async (params) => {
|
|
871
|
+
const olderThanMs = params.olderThanMs as number | undefined;
|
|
872
|
+
const result = planner.closeStale(olderThanMs);
|
|
873
|
+
return {
|
|
874
|
+
closed: result.closedPlans.length,
|
|
875
|
+
plans: result.closedPlans,
|
|
876
|
+
message:
|
|
877
|
+
result.closedPlans.length > 0
|
|
878
|
+
? `Closed ${result.closedPlans.length} stale plan(s)`
|
|
879
|
+
: 'No stale plans found',
|
|
880
|
+
};
|
|
881
|
+
},
|
|
882
|
+
},
|
|
883
|
+
|
|
821
884
|
// ─── Purge Plans (#215) ──────────────────────────────────────────
|
|
822
885
|
{
|
|
823
886
|
name: 'plan_purge',
|
|
@@ -81,7 +81,10 @@ vi.mock('../governance/governance.js', () => ({
|
|
|
81
81
|
}));
|
|
82
82
|
|
|
83
83
|
vi.mock('../loop/loop-manager.js', () => ({
|
|
84
|
-
LoopManager:
|
|
84
|
+
LoopManager: vi.fn(function (this: Record<string, unknown>) {
|
|
85
|
+
this.isActive = vi.fn().mockReturnValue(false);
|
|
86
|
+
this.cancelLoop = vi.fn();
|
|
87
|
+
}),
|
|
85
88
|
}));
|
|
86
89
|
|
|
87
90
|
vi.mock('../control/identity-manager.js', () => ({
|
|
@@ -185,7 +188,9 @@ vi.mock('../context/context-engine.js', () => ({
|
|
|
185
188
|
}));
|
|
186
189
|
|
|
187
190
|
vi.mock('../agency/agency-manager.js', () => ({
|
|
188
|
-
AgencyManager:
|
|
191
|
+
AgencyManager: vi.fn(function (this: Record<string, unknown>) {
|
|
192
|
+
this.disable = vi.fn();
|
|
193
|
+
}),
|
|
189
194
|
}));
|
|
190
195
|
|
|
191
196
|
vi.mock('../vault/knowledge-review.js', () => ({
|
|
@@ -220,6 +225,7 @@ vi.mock('../queue/job-queue.js', () => ({
|
|
|
220
225
|
vi.mock('../queue/pipeline-runner.js', () => ({
|
|
221
226
|
PipelineRunner: vi.fn(function (this: Record<string, unknown>) {
|
|
222
227
|
this.registerHandler = vi.fn();
|
|
228
|
+
this.stop = vi.fn();
|
|
223
229
|
}),
|
|
224
230
|
}));
|
|
225
231
|
|
|
@@ -261,6 +267,18 @@ vi.mock('./context-health.js', () => ({
|
|
|
261
267
|
}),
|
|
262
268
|
}));
|
|
263
269
|
|
|
270
|
+
vi.mock('./shutdown-registry.js', () => ({
|
|
271
|
+
ShutdownRegistry: vi.fn(function (this: Record<string, unknown>) {
|
|
272
|
+
this.register = vi.fn();
|
|
273
|
+
this.closeAll = vi.fn().mockResolvedValue(undefined);
|
|
274
|
+
this.closeAllSync = vi.fn();
|
|
275
|
+
this.size = 0;
|
|
276
|
+
this.isClosed = false;
|
|
277
|
+
this.entries = [];
|
|
278
|
+
this.closed = false;
|
|
279
|
+
}),
|
|
280
|
+
}));
|
|
281
|
+
|
|
264
282
|
vi.mock('node:fs', () => ({
|
|
265
283
|
existsSync: vi.fn().mockReturnValue(false),
|
|
266
284
|
mkdirSync: vi.fn(),
|
|
@@ -360,4 +378,34 @@ describe('createAgentRuntime', () => {
|
|
|
360
378
|
it('initializes context health monitor', () => {
|
|
361
379
|
expect(runtime.contextHealth).toBeDefined();
|
|
362
380
|
});
|
|
381
|
+
|
|
382
|
+
it('initializes shutdown registry', () => {
|
|
383
|
+
expect(runtime.shutdownRegistry).toBeDefined();
|
|
384
|
+
expect(runtime.shutdownRegistry.register).toBeDefined();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('registers cleanup callbacks with shutdown registry', () => {
|
|
388
|
+
// vaultManager, pipelineRunner, agencyManager, loopManager
|
|
389
|
+
expect(runtime.shutdownRegistry.register).toHaveBeenCalledWith(
|
|
390
|
+
'vaultManager',
|
|
391
|
+
expect.any(Function),
|
|
392
|
+
);
|
|
393
|
+
expect(runtime.shutdownRegistry.register).toHaveBeenCalledWith(
|
|
394
|
+
'pipelineRunner',
|
|
395
|
+
expect.any(Function),
|
|
396
|
+
);
|
|
397
|
+
expect(runtime.shutdownRegistry.register).toHaveBeenCalledWith(
|
|
398
|
+
'agencyManager',
|
|
399
|
+
expect.any(Function),
|
|
400
|
+
);
|
|
401
|
+
expect(runtime.shutdownRegistry.register).toHaveBeenCalledWith(
|
|
402
|
+
'loopManager',
|
|
403
|
+
expect.any(Function),
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('close() calls shutdownRegistry.closeAllSync()', () => {
|
|
408
|
+
runtime.close();
|
|
409
|
+
expect(runtime.shutdownRegistry.closeAllSync).toHaveBeenCalled();
|
|
410
|
+
});
|
|
363
411
|
});
|
package/src/runtime/runtime.ts
CHANGED
|
@@ -60,6 +60,7 @@ import { loadPersona } from '../persona/loader.js';
|
|
|
60
60
|
import { generatePersonaInstructions } from '../persona/prompt-generator.js';
|
|
61
61
|
import { OperatorProfileStore } from '../operator/operator-profile.js';
|
|
62
62
|
import { ContextHealthMonitor } from './context-health.js';
|
|
63
|
+
import { ShutdownRegistry } from './shutdown-registry.js';
|
|
63
64
|
|
|
64
65
|
/**
|
|
65
66
|
* Create a fully initialized agent runtime.
|
|
@@ -221,6 +222,117 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
221
222
|
learningRadar.setOperatorProfile(operatorProfile);
|
|
222
223
|
brainIntelligence.setOperatorProfile(operatorProfile);
|
|
223
224
|
|
|
225
|
+
// ─── Shutdown Registry ────────────────────────────────────────────
|
|
226
|
+
const shutdownRegistry = new ShutdownRegistry();
|
|
227
|
+
|
|
228
|
+
// Build pipeline runner before the runtime object so we can reference it
|
|
229
|
+
const pipelineRunner = (() => {
|
|
230
|
+
const jq = new JobQueue(vault.getProvider());
|
|
231
|
+
const pr = new PipelineRunner(jq);
|
|
232
|
+
// Register default job handlers for curator pipeline
|
|
233
|
+
pr.registerHandler('tag-normalize', async (job) => {
|
|
234
|
+
const entry = vault.get(job.entryId ?? '');
|
|
235
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
236
|
+
const result = curator.normalizeTag(entry.tags[0] ?? '');
|
|
237
|
+
return result;
|
|
238
|
+
});
|
|
239
|
+
pr.registerHandler('dedup-check', async (job) => {
|
|
240
|
+
const entry = vault.get(job.entryId ?? '');
|
|
241
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
242
|
+
return curator.detectDuplicates(entry.id);
|
|
243
|
+
});
|
|
244
|
+
pr.registerHandler('auto-link', async (job) => {
|
|
245
|
+
if (linkManager) {
|
|
246
|
+
const suggestions = linkManager.suggestLinks(job.entryId ?? '', 3);
|
|
247
|
+
for (const s of suggestions) {
|
|
248
|
+
linkManager.addLink(
|
|
249
|
+
job.entryId ?? '',
|
|
250
|
+
s.entryId,
|
|
251
|
+
s.suggestedType,
|
|
252
|
+
`pipeline: ${s.reason}`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return { linked: suggestions.length };
|
|
256
|
+
}
|
|
257
|
+
return { skipped: true, reason: 'link manager not available' };
|
|
258
|
+
});
|
|
259
|
+
pr.registerHandler('quality-gate', async (job) => {
|
|
260
|
+
const entry = vault.get(job.entryId ?? '');
|
|
261
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
262
|
+
return evaluateQuality(entry, llmClient);
|
|
263
|
+
});
|
|
264
|
+
pr.registerHandler('classify', async (job) => {
|
|
265
|
+
const entry = vault.get(job.entryId ?? '');
|
|
266
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
267
|
+
return classifyEntry(entry, llmClient);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// ─── 9 additional handlers for full Salvador parity (#216) ────
|
|
271
|
+
pr.registerHandler('enrich-frontmatter', async (job) => {
|
|
272
|
+
const entry = vault.get(job.entryId ?? '');
|
|
273
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
274
|
+
return curator.enrichMetadata(entry.id);
|
|
275
|
+
});
|
|
276
|
+
pr.registerHandler('detect-staleness', async (job) => {
|
|
277
|
+
const entry = vault.get(job.entryId ?? '');
|
|
278
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
279
|
+
// Check if entry is older than 90 days (using validFrom or fallback to 0)
|
|
280
|
+
const entryTimestamp = (entry.validFrom ?? 0) * 1000 || Date.now();
|
|
281
|
+
const ageMs = Date.now() - entryTimestamp;
|
|
282
|
+
const staleDays = 90;
|
|
283
|
+
const isStale = ageMs > staleDays * 86400000;
|
|
284
|
+
return { stale: isStale, ageDays: Math.floor(ageMs / 86400000), entryId: entry.id };
|
|
285
|
+
});
|
|
286
|
+
pr.registerHandler('detect-duplicate', async (job) => {
|
|
287
|
+
const entry = vault.get(job.entryId ?? '');
|
|
288
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
289
|
+
return curator.detectDuplicates(entry.id);
|
|
290
|
+
});
|
|
291
|
+
pr.registerHandler('detect-contradiction', async (job) => {
|
|
292
|
+
const entry = vault.get(job.entryId ?? '');
|
|
293
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
294
|
+
const contradictions = curator.detectContradictions(0.4);
|
|
295
|
+
const relevant = contradictions.filter(
|
|
296
|
+
(c) => c.patternId === job.entryId || c.antipatternId === job.entryId,
|
|
297
|
+
);
|
|
298
|
+
return { found: relevant.length, contradictions: relevant };
|
|
299
|
+
});
|
|
300
|
+
pr.registerHandler('consolidate-duplicates', async (_job) => {
|
|
301
|
+
return curator.consolidate({ dryRun: false, staleDaysThreshold: 90 });
|
|
302
|
+
});
|
|
303
|
+
pr.registerHandler('archive-stale', async (_job) => {
|
|
304
|
+
// Run consolidation with stale detection
|
|
305
|
+
const result = curator.consolidate({ dryRun: false, staleDaysThreshold: 90 });
|
|
306
|
+
return { archived: result.staleEntries.length, result };
|
|
307
|
+
});
|
|
308
|
+
pr.registerHandler('verify-searchable', async (job) => {
|
|
309
|
+
const entry = vault.get(job.entryId ?? '');
|
|
310
|
+
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
311
|
+
const searchResults = vault.search(entry.title, { limit: 1 });
|
|
312
|
+
const found = searchResults.some((r) => r.entry.id === entry.id);
|
|
313
|
+
return { searchable: found, entryId: entry.id };
|
|
314
|
+
});
|
|
315
|
+
return pr;
|
|
316
|
+
})();
|
|
317
|
+
|
|
318
|
+
// ─── Register cleanup callbacks (LIFO: first registered = last closed) ──
|
|
319
|
+
// Vault manager closes last (other modules may flush to vault during close)
|
|
320
|
+
shutdownRegistry.register('vaultManager', () => vaultManager.close());
|
|
321
|
+
// Pipeline runner — clear its polling interval
|
|
322
|
+
shutdownRegistry.register('pipelineRunner', () => pipelineRunner.stop());
|
|
323
|
+
// Agency manager — close FSWatchers and debounce timers
|
|
324
|
+
shutdownRegistry.register('agencyManager', () => agencyManager.disable());
|
|
325
|
+
// Loop manager — clear accumulated state
|
|
326
|
+
shutdownRegistry.register('loopManager', () => {
|
|
327
|
+
if (loop.isActive()) {
|
|
328
|
+
try {
|
|
329
|
+
loop.cancelLoop();
|
|
330
|
+
} catch {
|
|
331
|
+
// Loop may already be inactive
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
224
336
|
return {
|
|
225
337
|
config,
|
|
226
338
|
logger,
|
|
@@ -256,94 +368,7 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
256
368
|
knowledgeSynthesizer: new KnowledgeSynthesizer(brain, llmClient),
|
|
257
369
|
chainRunner: new ChainRunner(vault.getProvider()),
|
|
258
370
|
jobQueue: new JobQueue(vault.getProvider()),
|
|
259
|
-
pipelineRunner
|
|
260
|
-
const jq = new JobQueue(vault.getProvider());
|
|
261
|
-
const pr = new PipelineRunner(jq);
|
|
262
|
-
// Register default job handlers for curator pipeline
|
|
263
|
-
pr.registerHandler('tag-normalize', async (job) => {
|
|
264
|
-
const entry = vault.get(job.entryId ?? '');
|
|
265
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
266
|
-
const result = curator.normalizeTag(entry.tags[0] ?? '');
|
|
267
|
-
return result;
|
|
268
|
-
});
|
|
269
|
-
pr.registerHandler('dedup-check', async (job) => {
|
|
270
|
-
const entry = vault.get(job.entryId ?? '');
|
|
271
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
272
|
-
return curator.detectDuplicates(entry.id);
|
|
273
|
-
});
|
|
274
|
-
pr.registerHandler('auto-link', async (job) => {
|
|
275
|
-
if (linkManager) {
|
|
276
|
-
const suggestions = linkManager.suggestLinks(job.entryId ?? '', 3);
|
|
277
|
-
for (const s of suggestions) {
|
|
278
|
-
linkManager.addLink(
|
|
279
|
-
job.entryId ?? '',
|
|
280
|
-
s.entryId,
|
|
281
|
-
s.suggestedType,
|
|
282
|
-
`pipeline: ${s.reason}`,
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
return { linked: suggestions.length };
|
|
286
|
-
}
|
|
287
|
-
return { skipped: true, reason: 'link manager not available' };
|
|
288
|
-
});
|
|
289
|
-
pr.registerHandler('quality-gate', async (job) => {
|
|
290
|
-
const entry = vault.get(job.entryId ?? '');
|
|
291
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
292
|
-
return evaluateQuality(entry, llmClient);
|
|
293
|
-
});
|
|
294
|
-
pr.registerHandler('classify', async (job) => {
|
|
295
|
-
const entry = vault.get(job.entryId ?? '');
|
|
296
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
297
|
-
return classifyEntry(entry, llmClient);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
// ─── 9 additional handlers for full Salvador parity (#216) ────
|
|
301
|
-
pr.registerHandler('enrich-frontmatter', async (job) => {
|
|
302
|
-
const entry = vault.get(job.entryId ?? '');
|
|
303
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
304
|
-
return curator.enrichMetadata(entry.id);
|
|
305
|
-
});
|
|
306
|
-
pr.registerHandler('detect-staleness', async (job) => {
|
|
307
|
-
const entry = vault.get(job.entryId ?? '');
|
|
308
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
309
|
-
// Check if entry is older than 90 days (using validFrom or fallback to 0)
|
|
310
|
-
const entryTimestamp = (entry.validFrom ?? 0) * 1000 || Date.now();
|
|
311
|
-
const ageMs = Date.now() - entryTimestamp;
|
|
312
|
-
const staleDays = 90;
|
|
313
|
-
const isStale = ageMs > staleDays * 86400000;
|
|
314
|
-
return { stale: isStale, ageDays: Math.floor(ageMs / 86400000), entryId: entry.id };
|
|
315
|
-
});
|
|
316
|
-
pr.registerHandler('detect-duplicate', async (job) => {
|
|
317
|
-
const entry = vault.get(job.entryId ?? '');
|
|
318
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
319
|
-
return curator.detectDuplicates(entry.id);
|
|
320
|
-
});
|
|
321
|
-
pr.registerHandler('detect-contradiction', async (job) => {
|
|
322
|
-
const entry = vault.get(job.entryId ?? '');
|
|
323
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
324
|
-
const contradictions = curator.detectContradictions(0.4);
|
|
325
|
-
const relevant = contradictions.filter(
|
|
326
|
-
(c) => c.patternId === job.entryId || c.antipatternId === job.entryId,
|
|
327
|
-
);
|
|
328
|
-
return { found: relevant.length, contradictions: relevant };
|
|
329
|
-
});
|
|
330
|
-
pr.registerHandler('consolidate-duplicates', async (_job) => {
|
|
331
|
-
return curator.consolidate({ dryRun: false, staleDaysThreshold: 90 });
|
|
332
|
-
});
|
|
333
|
-
pr.registerHandler('archive-stale', async (_job) => {
|
|
334
|
-
// Run consolidation with stale detection
|
|
335
|
-
const result = curator.consolidate({ dryRun: false, staleDaysThreshold: 90 });
|
|
336
|
-
return { archived: result.staleEntries.length, result };
|
|
337
|
-
});
|
|
338
|
-
pr.registerHandler('verify-searchable', async (job) => {
|
|
339
|
-
const entry = vault.get(job.entryId ?? '');
|
|
340
|
-
if (!entry) return { skipped: true, reason: 'entry not found' };
|
|
341
|
-
const searchResults = vault.search(entry.title, { limit: 1 });
|
|
342
|
-
const found = searchResults.some((r) => r.entry.id === entry.id);
|
|
343
|
-
return { searchable: found, entryId: entry.id };
|
|
344
|
-
});
|
|
345
|
-
return pr;
|
|
346
|
-
})(),
|
|
371
|
+
pipelineRunner,
|
|
347
372
|
operatorProfile,
|
|
348
373
|
persona: (() => {
|
|
349
374
|
const p = loadPersona(agentId, config.persona ?? undefined);
|
|
@@ -355,9 +380,12 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
355
380
|
return generatePersonaInstructions(p);
|
|
356
381
|
})(),
|
|
357
382
|
contextHealth: new ContextHealthMonitor(),
|
|
383
|
+
shutdownRegistry,
|
|
358
384
|
createdAt: Date.now(),
|
|
359
385
|
close: () => {
|
|
360
|
-
|
|
386
|
+
// Synchronous close — runs all registered callbacks in LIFO order,
|
|
387
|
+
// then closes the vault (registered first, so runs last).
|
|
388
|
+
shutdownRegistry.closeAllSync();
|
|
361
389
|
},
|
|
362
390
|
};
|
|
363
391
|
}
|
|
@@ -78,8 +78,17 @@ export function createSessionBriefingOps(runtime: AgentRuntime): OpDefinition[]
|
|
|
78
78
|
// Session data unavailable — skip
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// 2. Active plans
|
|
81
|
+
// 2. Active plans — auto-close ancient ones (>24h)
|
|
82
82
|
try {
|
|
83
|
+
// Auto-close plans stuck in non-terminal states for more than 24h
|
|
84
|
+
const staleResult = planner.closeStale();
|
|
85
|
+
if (staleResult.closedPlans.length > 0) {
|
|
86
|
+
sections.push({
|
|
87
|
+
label: 'Plans auto-closed',
|
|
88
|
+
content: `${staleResult.closedPlans.length} stale plan(s) closed: ${staleResult.closedPlans.map((p) => `${p.id.slice(0, 25)}… (${p.reason})`).join(', ')}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
83
92
|
const plans = planner.list();
|
|
84
93
|
const active = plans.filter(
|
|
85
94
|
(p) =>
|