@soleri/core 9.3.1 → 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.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/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 +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.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/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 +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/index.ts +22 -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 +6 -2
- package/src/planning/task-complexity-assessor.ts +1 -4
- 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/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 +202 -2
- package/src/runtime/orchestrate-ops.ts +85 -4
- 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/vitest.config.ts +1 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { recordPlanFeedback } from './plan-feedback-helper.js';
|
|
3
|
+
|
|
4
|
+
// ─── Mock Factories ───────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
function makeBrain() {
|
|
7
|
+
return {
|
|
8
|
+
recordFeedback: vi.fn(),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function makeBrainIntelligence() {
|
|
13
|
+
return {
|
|
14
|
+
maybeAutoBuildOnFeedback: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makePlan(decisions: (string | { decision: string })[] = []) {
|
|
19
|
+
return {
|
|
20
|
+
objective: 'Test objective',
|
|
21
|
+
decisions,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Tests ────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
describe('recordPlanFeedback', () => {
|
|
28
|
+
it('should extract entryIds from decision strings and record feedback', () => {
|
|
29
|
+
const brain = makeBrain();
|
|
30
|
+
const intelligence = makeBrainIntelligence();
|
|
31
|
+
const plan = makePlan([
|
|
32
|
+
'Brain pattern: TDD (strength: 52.5) [entryId:method-tdd-123]',
|
|
33
|
+
'Brain pattern: Vault hooks (strength: 87.5) [entryId:arch-vault-456]',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const count = recordPlanFeedback(plan, brain as unknown, intelligence as unknown);
|
|
37
|
+
|
|
38
|
+
expect(count).toBe(2);
|
|
39
|
+
expect(brain.recordFeedback).toHaveBeenCalledTimes(2);
|
|
40
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
41
|
+
'Test objective',
|
|
42
|
+
'method-tdd-123',
|
|
43
|
+
'accepted',
|
|
44
|
+
);
|
|
45
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
46
|
+
'Test objective',
|
|
47
|
+
'arch-vault-456',
|
|
48
|
+
'accepted',
|
|
49
|
+
);
|
|
50
|
+
expect(intelligence.maybeAutoBuildOnFeedback).toHaveBeenCalledOnce();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should handle decision objects with .decision property', () => {
|
|
54
|
+
const brain = makeBrain();
|
|
55
|
+
const plan = makePlan([{ decision: 'Use vault pattern [entryId:obj-entry-1]' }]);
|
|
56
|
+
|
|
57
|
+
const count = recordPlanFeedback(plan, brain as unknown);
|
|
58
|
+
|
|
59
|
+
expect(count).toBe(1);
|
|
60
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith('Test objective', 'obj-entry-1', 'accepted');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should skip decisions without entryId markers', () => {
|
|
64
|
+
const brain = makeBrain();
|
|
65
|
+
const plan = makePlan([
|
|
66
|
+
'Brain pattern: TDD (strength: 52.5)',
|
|
67
|
+
'Some decision without an entry ID',
|
|
68
|
+
'Brain pattern: Vault hooks (strength: 87.5) [entryId:arch-vault-456]',
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
const count = recordPlanFeedback(plan, brain as unknown);
|
|
72
|
+
|
|
73
|
+
expect(count).toBe(1);
|
|
74
|
+
expect(brain.recordFeedback).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
76
|
+
'Test objective',
|
|
77
|
+
'arch-vault-456',
|
|
78
|
+
'accepted',
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should skip malformed entryId markers gracefully', () => {
|
|
83
|
+
const brain = makeBrain();
|
|
84
|
+
const plan = makePlan(['Brain pattern: X [entryId:]', 'Brain pattern: Y [entryId:valid-id]']);
|
|
85
|
+
|
|
86
|
+
const count = recordPlanFeedback(plan, brain as unknown);
|
|
87
|
+
|
|
88
|
+
// [entryId:] won't match because the regex requires at least one char after :
|
|
89
|
+
// Actually the regex [^\]]+ requires 1+ chars, so empty entryId won't match
|
|
90
|
+
expect(count).toBe(1);
|
|
91
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith('Test objective', 'valid-id', 'accepted');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should not double-record duplicate entryIds', () => {
|
|
95
|
+
const brain = makeBrain();
|
|
96
|
+
const plan = makePlan([
|
|
97
|
+
'Decision 1 [entryId:same-entry]',
|
|
98
|
+
'Decision 2 [entryId:same-entry]',
|
|
99
|
+
'Decision 3 [entryId:different-entry]',
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const count = recordPlanFeedback(plan, brain as unknown);
|
|
103
|
+
|
|
104
|
+
expect(count).toBe(2);
|
|
105
|
+
expect(brain.recordFeedback).toHaveBeenCalledTimes(2);
|
|
106
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith('Test objective', 'same-entry', 'accepted');
|
|
107
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith(
|
|
108
|
+
'Test objective',
|
|
109
|
+
'different-entry',
|
|
110
|
+
'accepted',
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should gracefully handle recordFeedback throwing', () => {
|
|
115
|
+
const brain = makeBrain();
|
|
116
|
+
brain.recordFeedback.mockImplementationOnce(() => {
|
|
117
|
+
throw new Error('Entry not found');
|
|
118
|
+
});
|
|
119
|
+
const plan = makePlan([
|
|
120
|
+
'Decision 1 [entryId:missing-entry]',
|
|
121
|
+
'Decision 2 [entryId:valid-entry]',
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
const count = recordPlanFeedback(plan, brain as unknown);
|
|
125
|
+
|
|
126
|
+
// First one throws, second succeeds
|
|
127
|
+
expect(count).toBe(1);
|
|
128
|
+
expect(brain.recordFeedback).toHaveBeenCalledTimes(2);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should return 0 and not call maybeAutoBuild when no entryIds found', () => {
|
|
132
|
+
const brain = makeBrain();
|
|
133
|
+
const intelligence = makeBrainIntelligence();
|
|
134
|
+
const plan = makePlan(['Decision without markers', 'Another plain decision']);
|
|
135
|
+
|
|
136
|
+
const count = recordPlanFeedback(plan, brain as unknown, intelligence as unknown);
|
|
137
|
+
|
|
138
|
+
expect(count).toBe(0);
|
|
139
|
+
expect(brain.recordFeedback).not.toHaveBeenCalled();
|
|
140
|
+
expect(intelligence.maybeAutoBuildOnFeedback).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should handle empty decisions array', () => {
|
|
144
|
+
const brain = makeBrain();
|
|
145
|
+
const plan = makePlan([]);
|
|
146
|
+
|
|
147
|
+
const count = recordPlanFeedback(plan, brain as unknown);
|
|
148
|
+
|
|
149
|
+
expect(count).toBe(0);
|
|
150
|
+
expect(brain.recordFeedback).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should work without brainIntelligence (optional param)', () => {
|
|
154
|
+
const brain = makeBrain();
|
|
155
|
+
const plan = makePlan(['Decision [entryId:entry-1]']);
|
|
156
|
+
|
|
157
|
+
const count = recordPlanFeedback(plan, brain as unknown);
|
|
158
|
+
|
|
159
|
+
expect(count).toBe(1);
|
|
160
|
+
// No error thrown despite missing brainIntelligence
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should extract multiple entryIds from a single decision string', () => {
|
|
164
|
+
const brain = makeBrain();
|
|
165
|
+
const plan = makePlan(['Combined: [entryId:first-entry] and also [entryId:second-entry]']);
|
|
166
|
+
|
|
167
|
+
const count = recordPlanFeedback(plan, brain as unknown);
|
|
168
|
+
|
|
169
|
+
expect(count).toBe(2);
|
|
170
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith('Test objective', 'first-entry', 'accepted');
|
|
171
|
+
expect(brain.recordFeedback).toHaveBeenCalledWith('Test objective', 'second-entry', 'accepted');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for recording brain feedback from plan decisions and context.
|
|
3
|
+
*
|
|
4
|
+
* Used by both plan_complete_lifecycle (planning-extra-ops.ts) and
|
|
5
|
+
* orchestrate_complete (orchestrate-ops.ts) to close the brain learning loop.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Brain } from '../brain/brain.js';
|
|
9
|
+
import type { BrainIntelligence } from '../brain/intelligence.js';
|
|
10
|
+
|
|
11
|
+
/** Regex to extract vault entry IDs embedded in decision/context strings. */
|
|
12
|
+
const ENTRY_ID_REGEX = /\[entryId:([^\]]+)\]/g;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract entry IDs from an array of decision or context strings,
|
|
16
|
+
* record positive feedback for each, and optionally trigger auto-rebuild.
|
|
17
|
+
*
|
|
18
|
+
* @returns Number of feedback entries recorded.
|
|
19
|
+
*/
|
|
20
|
+
export function recordPlanFeedback(
|
|
21
|
+
plan: { objective: string; decisions: (string | { decision: string })[] },
|
|
22
|
+
brain: Brain,
|
|
23
|
+
brainIntelligence?: BrainIntelligence,
|
|
24
|
+
): number {
|
|
25
|
+
let feedbackRecorded = 0;
|
|
26
|
+
const seen = new Set<string>();
|
|
27
|
+
|
|
28
|
+
// Collect all strings to scan: decisions + any context strings
|
|
29
|
+
const strings: string[] = [];
|
|
30
|
+
|
|
31
|
+
for (const d of plan.decisions) {
|
|
32
|
+
const str = typeof d === 'string' ? d : d.decision;
|
|
33
|
+
strings.push(str);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const str of strings) {
|
|
37
|
+
// Use matchAll to find all entryId markers in each string
|
|
38
|
+
for (const match of str.matchAll(ENTRY_ID_REGEX)) {
|
|
39
|
+
const entryId = match[1];
|
|
40
|
+
// Skip duplicates within the same plan
|
|
41
|
+
if (seen.has(entryId)) continue;
|
|
42
|
+
seen.add(entryId);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
brain.recordFeedback(plan.objective, entryId, 'accepted');
|
|
46
|
+
feedbackRecorded++;
|
|
47
|
+
} catch {
|
|
48
|
+
// Graceful degradation — skip if entry not found or already recorded
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Trigger auto-rebuild check after recording feedback
|
|
54
|
+
if (feedbackRecorded > 0 && brainIntelligence) {
|
|
55
|
+
try {
|
|
56
|
+
brainIntelligence.maybeAutoBuildOnFeedback();
|
|
57
|
+
} catch {
|
|
58
|
+
// Auto-rebuild is best-effort
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return feedbackRecorded;
|
|
63
|
+
}
|
|
@@ -69,7 +69,7 @@ function createMockRuntime(): AgentRuntime {
|
|
|
69
69
|
|
|
70
70
|
return {
|
|
71
71
|
planner: {
|
|
72
|
-
iterate: vi.fn(() => plan),
|
|
72
|
+
iterate: vi.fn(() => ({ plan, mutated: 1 })),
|
|
73
73
|
splitTasks: vi.fn(() => ({
|
|
74
74
|
...plan,
|
|
75
75
|
tasks: [plan.tasks[0], { id: 'task-2', title: 'Task 2' }],
|
|
@@ -159,6 +159,48 @@ describe('createPlanningExtraOps', () => {
|
|
|
159
159
|
);
|
|
160
160
|
});
|
|
161
161
|
|
|
162
|
+
it('returns iterated: false when no changes detected', async () => {
|
|
163
|
+
vi.mocked(runtime.planner.iterate).mockReturnValue({
|
|
164
|
+
plan: makePlan() as unknown,
|
|
165
|
+
mutated: 0,
|
|
166
|
+
} as unknown);
|
|
167
|
+
const result = (await findOp(ops, 'plan_iterate').handler({
|
|
168
|
+
planId: 'plan-1',
|
|
169
|
+
})) as Record<string, unknown>;
|
|
170
|
+
expect(result.iterated).toBe(false);
|
|
171
|
+
expect(result.reason).toBe('no changes detected');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('passes alternatives to planner.iterate', async () => {
|
|
175
|
+
const result = (await findOp(ops, 'plan_iterate').handler({
|
|
176
|
+
planId: 'plan-1',
|
|
177
|
+
alternatives: [
|
|
178
|
+
{ approach: 'Alt A', pros: ['fast'], cons: ['fragile'], rejected_reason: 'Too risky' },
|
|
179
|
+
],
|
|
180
|
+
})) as Record<string, unknown>;
|
|
181
|
+
expect(result.iterated).toBe(true);
|
|
182
|
+
expect(runtime.planner.iterate).toHaveBeenCalledWith(
|
|
183
|
+
'plan-1',
|
|
184
|
+
expect.objectContaining({
|
|
185
|
+
alternatives: [expect.objectContaining({ approach: 'Alt A' })],
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('passes decisions to planner.iterate', async () => {
|
|
191
|
+
const result = (await findOp(ops, 'plan_iterate').handler({
|
|
192
|
+
planId: 'plan-1',
|
|
193
|
+
decisions: [{ decision: 'Use FTS5', rationale: 'Performance' }],
|
|
194
|
+
})) as Record<string, unknown>;
|
|
195
|
+
expect(result.iterated).toBe(true);
|
|
196
|
+
expect(runtime.planner.iterate).toHaveBeenCalledWith(
|
|
197
|
+
'plan-1',
|
|
198
|
+
expect.objectContaining({
|
|
199
|
+
decisions: [{ decision: 'Use FTS5', rationale: 'Performance' }],
|
|
200
|
+
}),
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
162
204
|
it('returns error on failure', async () => {
|
|
163
205
|
vi.mocked(runtime.planner.iterate).mockImplementation(() => {
|
|
164
206
|
throw new Error('Not a draft');
|
|
@@ -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',
|
|
@@ -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) =>
|
package/src/runtime/types.ts
CHANGED
|
@@ -34,6 +34,7 @@ import type { ChainRunner } from '../flows/chain-runner.js';
|
|
|
34
34
|
import type { JobQueue } from '../queue/job-queue.js';
|
|
35
35
|
import type { PipelineRunner } from '../queue/pipeline-runner.js';
|
|
36
36
|
import type { OperatorProfileStore } from '../operator/operator-profile.js';
|
|
37
|
+
import type { OperatorContextStore } from '../operator/operator-context-store.js';
|
|
37
38
|
import type { ContextHealthMonitor } from './context-health.js';
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -121,6 +122,8 @@ export interface AgentRuntime {
|
|
|
121
122
|
pipelineRunner: PipelineRunner;
|
|
122
123
|
/** Operator profile — personality learning, signals, adaptation. */
|
|
123
124
|
operatorProfile: OperatorProfileStore;
|
|
125
|
+
/** Operator context store — compounding signals into stable operator profile. */
|
|
126
|
+
operatorContextStore?: OperatorContextStore;
|
|
124
127
|
/** Agent persona — defines character, voice, and cultural texture. */
|
|
125
128
|
persona: import('../persona/types.js').PersonaConfig;
|
|
126
129
|
/** Generated persona system instructions for LLM context. */
|
|
@@ -41,15 +41,19 @@ export function discoverSkills(skillsDirs: string[]): SkillEntry[] {
|
|
|
41
41
|
return skills;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** Inject agent branding after YAML frontmatter */
|
|
45
|
-
function brandSkillContent(content: string, agentName: string): string {
|
|
44
|
+
/** Inject agent branding after YAML frontmatter and rewrite skill name */
|
|
45
|
+
function brandSkillContent(content: string, agentName: string, prefixedName?: string): string {
|
|
46
46
|
// Find end of frontmatter (second ---)
|
|
47
47
|
const fmEnd = content.indexOf('---', content.indexOf('---') + 3);
|
|
48
48
|
if (fmEnd === -1) return content;
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
50
|
+
let before = content.slice(0, fmEnd + 3);
|
|
51
|
+
const after = content.slice(fmEnd + 3);
|
|
52
|
+
|
|
53
|
+
// Rewrite name: field in frontmatter to include agent prefix
|
|
54
|
+
if (prefixedName) {
|
|
55
|
+
before = before.replace(/^(name:\s*).+$/m, `$1${prefixedName}`);
|
|
56
|
+
}
|
|
53
57
|
|
|
54
58
|
const brandLine = `\n\n> **${agentName}** skill\n`;
|
|
55
59
|
return before + brandLine + after;
|
|
@@ -71,10 +75,13 @@ export function syncSkillsToClaudeCode(skillsDirs: string[], agentName?: string)
|
|
|
71
75
|
mkdirSync(commandsDir, { recursive: true });
|
|
72
76
|
|
|
73
77
|
for (const skill of skills) {
|
|
74
|
-
const
|
|
78
|
+
const prefix = agentName ? `${agentName.toLowerCase().replace(/\s+/g, '-')}-` : '';
|
|
79
|
+
const targetPath = join(commandsDir, `${prefix}${skill.name}.md`);
|
|
75
80
|
try {
|
|
76
81
|
const sourceContent = readFileSync(skill.sourcePath, 'utf-8');
|
|
77
|
-
const branded = agentName
|
|
82
|
+
const branded = agentName
|
|
83
|
+
? brandSkillContent(sourceContent, agentName, `${prefix}${skill.name}`)
|
|
84
|
+
: sourceContent;
|
|
78
85
|
|
|
79
86
|
if (!existsSync(targetPath)) {
|
|
80
87
|
writeFileSync(targetPath, branded);
|
package/vitest.config.ts
CHANGED