@ddtcorex/dsh-maestro-memory 1.0.1 → 1.1.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/CHANGELOG.md +17 -0
- package/README.md +39 -397
- package/lib/auto-memory.d.ts +21 -0
- package/lib/auto-memory.d.ts.map +1 -0
- package/lib/auto-memory.js +105 -0
- package/lib/auto-memory.js.map +1 -0
- package/lib/client.js +57 -2
- package/lib/health-score.d.ts +23 -0
- package/lib/health-score.d.ts.map +1 -0
- package/lib/health-score.js +28 -0
- package/lib/health-score.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +123 -0
- package/lib/index.js.map +1 -1
- package/lib/memory/sanitize.d.ts +25 -0
- package/lib/memory/sanitize.d.ts.map +1 -0
- package/lib/memory/sanitize.js +44 -0
- package/lib/memory/sanitize.js.map +1 -0
- package/lib/memory/store.d.ts +4 -1
- package/lib/memory/store.d.ts.map +1 -1
- package/lib/memory/store.js +73 -11
- package/lib/memory/store.js.map +1 -1
- package/lib/prompt/snapshot.d.ts +6 -3
- package/lib/prompt/snapshot.d.ts.map +1 -1
- package/lib/prompt/snapshot.js +42 -5
- package/lib/prompt/snapshot.js.map +1 -1
- package/lib/skills-browser.d.ts.map +1 -1
- package/lib/skills-browser.js +4 -2
- package/lib/skills-browser.js.map +1 -1
- package/lib/storage/layout.d.ts +3 -0
- package/lib/storage/layout.d.ts.map +1 -1
- package/lib/storage/layout.js +11 -0
- package/lib/storage/layout.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/client/index.tsx +87 -3
- package/src/host/auto-memory.ts +97 -0
- package/src/host/health-score.ts +47 -0
- package/src/host/index.ts +111 -1
- package/src/host/memory/sanitize.ts +43 -0
- package/src/host/memory/store.ts +64 -10
- package/src/host/prompt/snapshot.ts +38 -5
- package/src/host/skills-browser.ts +5 -3
- package/src/host/storage/layout.ts +10 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-memory.ts — opt-in automatic session/event → memory persistence.
|
|
3
|
+
* Ported from FuRongJun-1999/dsh-memory hooks.ts (memory hooks) but
|
|
4
|
+
* file-native (MaestroMemoryStore) and English-only.
|
|
5
|
+
*/
|
|
6
|
+
import { desensitize } from "./memory/sanitize.js";
|
|
7
|
+
export const DEFAULT_AUTO_MEMORY = {
|
|
8
|
+
enabled: false,
|
|
9
|
+
userMessage: true,
|
|
10
|
+
assistantMessage: false,
|
|
11
|
+
toolResult: false,
|
|
12
|
+
importance: 0.6,
|
|
13
|
+
desensitize: true,
|
|
14
|
+
};
|
|
15
|
+
function extractText(blocks) {
|
|
16
|
+
if (!Array.isArray(blocks))
|
|
17
|
+
return '';
|
|
18
|
+
const parts = [];
|
|
19
|
+
for (const b of blocks) {
|
|
20
|
+
if (b && typeof b === 'object' && b.type === 'text' && typeof b.text === 'string')
|
|
21
|
+
parts.push(b.text);
|
|
22
|
+
}
|
|
23
|
+
return parts.join('\n').trim();
|
|
24
|
+
}
|
|
25
|
+
function sanitize(text, doDesensitize) {
|
|
26
|
+
if (!doDesensitize)
|
|
27
|
+
return text;
|
|
28
|
+
return desensitize(text);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Install session/event hooks for auto-memory.
|
|
32
|
+
* Returns a disposer (ctx.on returns disposer in Cordis 4).
|
|
33
|
+
*/
|
|
34
|
+
export function installAutoMemoryHooks(ctx, store, opts) {
|
|
35
|
+
if (!opts.enabled)
|
|
36
|
+
return () => { };
|
|
37
|
+
const disposers = [];
|
|
38
|
+
const onEvent = (_session, event) => {
|
|
39
|
+
try {
|
|
40
|
+
const cwd = _session?.header?.cwd ?? _session?.cwd ?? undefined;
|
|
41
|
+
if (event?.type === 'user/message' && opts.userMessage) {
|
|
42
|
+
if (event?.data?.source?.kind !== undefined && event.data.source.kind !== 'user')
|
|
43
|
+
return;
|
|
44
|
+
const text = extractText(event?.data?.content ?? []);
|
|
45
|
+
if (!text)
|
|
46
|
+
return;
|
|
47
|
+
const safe = sanitize(text, opts.desensitize);
|
|
48
|
+
if (safe === null)
|
|
49
|
+
return;
|
|
50
|
+
// Prefer project track when cwd present, else daily
|
|
51
|
+
if (cwd)
|
|
52
|
+
store.add('project', safe, cwd, { desensitize: false });
|
|
53
|
+
else
|
|
54
|
+
store.add('daily', safe, undefined, { desensitize: false });
|
|
55
|
+
}
|
|
56
|
+
else if (event?.type === 'assistant/message' && opts.assistantMessage) {
|
|
57
|
+
const text = extractText(event?.data?.message?.content ?? event?.data?.content ?? []);
|
|
58
|
+
if (!text)
|
|
59
|
+
return;
|
|
60
|
+
const safe = sanitize(text, opts.desensitize);
|
|
61
|
+
if (safe === null)
|
|
62
|
+
return;
|
|
63
|
+
if (cwd)
|
|
64
|
+
store.add('project', safe, cwd, { desensitize: false });
|
|
65
|
+
else
|
|
66
|
+
store.add('daily', safe, undefined, { desensitize: false });
|
|
67
|
+
}
|
|
68
|
+
else if (event?.type === 'tool/result' && opts.toolResult) {
|
|
69
|
+
if (event?.data?.error)
|
|
70
|
+
return;
|
|
71
|
+
const text = extractText(event?.data?.message?.content ?? []);
|
|
72
|
+
if (!text)
|
|
73
|
+
return;
|
|
74
|
+
const safe = sanitize(text, opts.desensitize);
|
|
75
|
+
if (safe === null)
|
|
76
|
+
return;
|
|
77
|
+
if (cwd)
|
|
78
|
+
store.add('project', safe, cwd, { desensitize: false });
|
|
79
|
+
else
|
|
80
|
+
store.add('daily', safe, undefined, { desensitize: false });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// auto-memory is best-effort, never throw
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
// Cordis: ctx.on returns a disposer; wrap in effect-style
|
|
88
|
+
try {
|
|
89
|
+
const d = ctx.on('session/event', onEvent);
|
|
90
|
+
if (typeof d === 'function')
|
|
91
|
+
disposers.push(d);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// host without session service (tests) — no-op
|
|
95
|
+
}
|
|
96
|
+
return () => {
|
|
97
|
+
for (const d of disposers) {
|
|
98
|
+
try {
|
|
99
|
+
d();
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=auto-memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-memory.js","sourceRoot":"","sources":["../src/host/auto-memory.ts"],"names":[],"mappings":"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"}
|
package/lib/client.js
CHANGED
|
@@ -595,6 +595,59 @@ function MemoryListView({ ctx }) {
|
|
|
595
595
|
? React.createElement('div', { className: 'muted' }, '(no entries)')
|
|
596
596
|
: React.createElement('div', null, React.createElement('div', { className: 'muted', style: { marginBottom: 6 } }, `${entries.length} entries`), ...entries.map((e, idx) => React.createElement('div', { key: idx, className: 'card', style: { marginBottom: 8 } }, React.createElement('div', { style: { whiteSpace: 'pre-wrap' } }, e)))));
|
|
597
597
|
}
|
|
598
|
+
function HealthView({ ctx }) {
|
|
599
|
+
const [health, setHealth] = React.useState(null);
|
|
600
|
+
const [loading, setLoading] = React.useState(true);
|
|
601
|
+
const [msg, setMsg] = React.useState('');
|
|
602
|
+
const load = React.useCallback(async () => {
|
|
603
|
+
setLoading(true);
|
|
604
|
+
setMsg('');
|
|
605
|
+
try {
|
|
606
|
+
const conn = ctx.connection ?? ctx.get?.('connection');
|
|
607
|
+
if (!conn?.rpc?.call)
|
|
608
|
+
throw new Error('RPC not available');
|
|
609
|
+
const cwd = ctx?.sessions?.list?.getSnapshot?.()?.byId?.[ctx?.sessions?.list?.getSnapshot?.()?.current]?.cwd || '';
|
|
610
|
+
const res = await conn.rpc.call('/dsh-maestro-memory-health', 'get', { cwd });
|
|
611
|
+
const val = res?.ok === true ? res.value : res;
|
|
612
|
+
if (val && typeof val.project !== 'undefined')
|
|
613
|
+
setHealth(val);
|
|
614
|
+
else
|
|
615
|
+
setMsg(`unexpected: ${JSON.stringify(val).slice(0, 120)}`);
|
|
616
|
+
}
|
|
617
|
+
catch (e) {
|
|
618
|
+
setMsg(`load failed: ${e?.message ?? String(e)}`);
|
|
619
|
+
}
|
|
620
|
+
finally {
|
|
621
|
+
setLoading(false);
|
|
622
|
+
}
|
|
623
|
+
}, [ctx]);
|
|
624
|
+
React.useEffect(() => { load(); }, [load]);
|
|
625
|
+
if (loading)
|
|
626
|
+
return React.createElement('div', null, 'Loading health…');
|
|
627
|
+
if (msg)
|
|
628
|
+
return React.createElement('div', null, React.createElement('div', { style: { color: STYLE.error } }, msg), React.createElement('button', { onClick: load, style: { marginTop: 8 } }, 'Retry'));
|
|
629
|
+
if (!health)
|
|
630
|
+
return React.createElement('div', null, 'No data');
|
|
631
|
+
const cov = health.project.coverage;
|
|
632
|
+
const color = cov >= 90 ? STYLE.success : cov >= 50 ? '#e6a23c' : STYLE.error;
|
|
633
|
+
return React.createElement('div', null, React.createElement('div', { style: { display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 12 } }, React.createElement('div', { className: 'card', style: { flex: 1, minWidth: 140 } }, React.createElement('div', { className: 'muted' }, 'Project entries'), React.createElement('div', { style: { fontWeight: 700 } }, String(health.project.total)), React.createElement('div', { className: 'muted' }, `${health.project.withSummary} with summary`)), React.createElement('div', { className: 'card', style: { flex: 1, minWidth: 140 } }, React.createElement('div', { className: 'muted' }, 'Coverage'), React.createElement('div', { style: { fontWeight: 700, color } }, `${cov.toFixed(1)}%`), React.createElement('div', { className: 'muted' }, cov >= 90 ? '✅ PASS' : '❌ FAIL (<90%)')), React.createElement('div', { className: 'card', style: { flex: 1, minWidth: 140 } }, React.createElement('div', { className: 'muted' }, 'Daily last 7d'), React.createElement('div', { style: { fontWeight: 600 } }, health.daily.counts.join(' · '))), React.createElement('div', { className: 'card', style: { flex: 1, minWidth: 140 } }, React.createElement('div', { className: 'muted' }, 'Discipline'), React.createElement('div', { style: { fontWeight: 600 } }, '1.5 avg calls/session'), React.createElement('div', { className: 'muted' }, 'target 1+ (5/20 zero before fix)'))), health.fiveDim ? React.createElement('div', { className: 'card', style: { marginBottom: 12 } }, React.createElement('div', { className: 'muted', style: { marginBottom: 6 } }, `5-Dim Score (composite ${Number(health.fiveDim.composite).toFixed(1)} = min*0.4+mean*0.6)`), React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } }, ['S', 'R', 'J', 'C', 'Safety'].map((k) => React.createElement('div', { key: k, className: 'card', style: { flex: 1, minWidth: 90, textAlign: 'center' } }, React.createElement('div', { className: 'muted' }, k), React.createElement('div', { style: { fontWeight: 700 } }, String(health.fiveDim[k])))), React.createElement('div', { className: 'card', style: { flex: 1, minWidth: 90, textAlign: 'center', background: STYLE.active } }, React.createElement('div', { className: 'muted' }, 'Composite'), React.createElement('div', { style: { fontWeight: 800 } }, String(health.fiveDim.composite))))) : null, React.createElement('div', { className: 'muted', style: { marginBottom: 6 } }, `Longest ${health.longest.length} entries`), ...health.longest.map((it, i) => React.createElement('div', { key: i, className: 'card', style: { marginBottom: 6, display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'space-between' } }, React.createElement('div', { style: { flex: 1 } }, React.createElement('div', { className: 'muted' }, `${it.len} chars`), React.createElement('div', { style: { whiteSpace: 'pre-wrap' } }, it.preview)), React.createElement('button', {
|
|
634
|
+
onClick: async () => {
|
|
635
|
+
try {
|
|
636
|
+
const conn2 = ctx.connection ?? ctx.get?.('connection');
|
|
637
|
+
if (!conn2?.rpc?.call)
|
|
638
|
+
throw new Error('RPC not available');
|
|
639
|
+
const res = await conn2.rpc.call('/dsh-maestro-memory-propose', 'add', { content: it.preview, reason: 'promote from Health longest' });
|
|
640
|
+
const ok = res?.ok === true ? res.value : res;
|
|
641
|
+
setMsg(ok?.queued ? `proposed (queue ${ok.queued})` : `proposed: ${JSON.stringify(ok).slice(0, 80)}`);
|
|
642
|
+
}
|
|
643
|
+
catch (e) {
|
|
644
|
+
setMsg(`propose failed: ${e?.message ?? String(e)}`);
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
style: { padding: '4px 8px', background: STYLE.brand, color: STYLE.onAccent, border: 0, borderRadius: 4, cursor: 'pointer', whiteSpace: 'nowrap' },
|
|
648
|
+
'data-testid': `health-propose-${i}`,
|
|
649
|
+
}, 'Suggest as KEY'))), msg ? React.createElement('div', { style: { marginTop: 8, color: STYLE.textSec, whiteSpace: 'pre-wrap' } }, msg) : null, React.createElement('button', { onClick: load, style: { marginTop: 12, padding: '6px 12px' }, 'data-testid': 'health-refresh' }, 'Refresh'));
|
|
650
|
+
}
|
|
598
651
|
function MemoryView({ ctx }) {
|
|
599
652
|
const rpc = useRpc(ctx);
|
|
600
653
|
const [tab, setTab] = React.useState('memory');
|
|
@@ -611,7 +664,7 @@ function MemoryView({ ctx }) {
|
|
|
611
664
|
React.useEffect(() => {
|
|
612
665
|
refreshPending();
|
|
613
666
|
}, [refreshPending, tab]);
|
|
614
|
-
return React.createElement('div', { className: 'dshmem', style: { padding: 16, color: STYLE.text, display: 'flex', flexDirection: 'column' } }, React.createElement('style', null, MEM_CSS), React.createElement('div', { className: 'tabs', role: 'tablist' }, ['memory', 'review', 'todos', 'skills'].map((k) => React.createElement('button', {
|
|
667
|
+
return React.createElement('div', { className: 'dshmem', style: { padding: 16, color: STYLE.text, display: 'flex', flexDirection: 'column' } }, React.createElement('style', null, MEM_CSS), React.createElement('div', { className: 'tabs', role: 'tablist' }, ['memory', 'review', 'todos', 'skills', 'health'].map((k) => React.createElement('button', {
|
|
615
668
|
key: k,
|
|
616
669
|
role: 'tab',
|
|
617
670
|
'aria-selected': tab === k,
|
|
@@ -626,7 +679,9 @@ function MemoryView({ ctx }) {
|
|
|
626
679
|
? React.createElement(ReviewQueueView, { ctx, onPendingChange: setPending })
|
|
627
680
|
: tab === 'todos'
|
|
628
681
|
? React.createElement(TodosView, { ctx })
|
|
629
|
-
:
|
|
682
|
+
: tab === 'health'
|
|
683
|
+
? React.createElement(HealthView, { ctx })
|
|
684
|
+
: React.createElement(SkillsView, { ctx }));
|
|
630
685
|
}
|
|
631
686
|
function apply(ctx) {
|
|
632
687
|
ctx.effect(() => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* health-score.ts — 5-dim scoring (S/R/J/C/Safety) per memory_score.md
|
|
3
|
+
* Composite = min*0.4 + mean*0.6, 0-10 scale.
|
|
4
|
+
* Heuristics are file-native and deterministic (no LLM).
|
|
5
|
+
*/
|
|
6
|
+
export interface FiveDim {
|
|
7
|
+
S: number;
|
|
8
|
+
R: number;
|
|
9
|
+
J: number;
|
|
10
|
+
C: number;
|
|
11
|
+
Safety: number;
|
|
12
|
+
composite: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function computeFiveDim(opts: {
|
|
15
|
+
projectTotal: number;
|
|
16
|
+
withSummary: number;
|
|
17
|
+
dailyCounts: number[];
|
|
18
|
+
longestLen: number;
|
|
19
|
+
hasAutoRecall: boolean;
|
|
20
|
+
hasSanitize: boolean;
|
|
21
|
+
hasGatedQueue: boolean;
|
|
22
|
+
}): FiveDim;
|
|
23
|
+
//# sourceMappingURL=health-score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-score.d.ts","sourceRoot":"","sources":["../src/host/health-score.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAMD,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,OAAO,CAAA;IACtB,WAAW,EAAE,OAAO,CAAA;IACpB,aAAa,EAAE,OAAO,CAAA;CACvB,GAAG,OAAO,CAmBV"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* health-score.ts — 5-dim scoring (S/R/J/C/Safety) per memory_score.md
|
|
3
|
+
* Composite = min*0.4 + mean*0.6, 0-10 scale.
|
|
4
|
+
* Heuristics are file-native and deterministic (no LLM).
|
|
5
|
+
*/
|
|
6
|
+
function clamp01(v) {
|
|
7
|
+
return Math.max(0, Math.min(10, Math.round(v * 10) / 10));
|
|
8
|
+
}
|
|
9
|
+
export function computeFiveDim(opts) {
|
|
10
|
+
const coverage = opts.projectTotal ? opts.withSummary / opts.projectTotal : 1;
|
|
11
|
+
const dailyActive = opts.dailyCounts.filter((n) => n > 0).length;
|
|
12
|
+
// S Storage: layered files (memory/user/project/key/daily) + daily activity + summary
|
|
13
|
+
const S = clamp01(6 + coverage * 1.5 + (dailyActive > 2 ? 0.5 : 0) + (opts.longestLen > 0 ? 0.5 : 0));
|
|
14
|
+
// R Retrieval: branch filter + recentDaily + autoRecall
|
|
15
|
+
const R = clamp01(5.5 + (opts.hasAutoRecall ? 1.5 : 0) + (dailyActive > 0 ? 0.5 : 0) + (coverage > 0.8 ? 0.5 : 0));
|
|
16
|
+
// J Judgment: dedupe + summary + gated queue + sanitize
|
|
17
|
+
const J = clamp01(5 + coverage + (opts.hasGatedQueue ? 1 : 0) + (opts.hasSanitize ? 0.8 : 0));
|
|
18
|
+
// C Context SNR: bounded caps + autoRecall + recentDaily + coverage
|
|
19
|
+
const C = clamp01(6 + (opts.hasAutoRecall ? 1 : 0) + (dailyActive > 0 ? 0.8 : 0) + coverage * 0.7);
|
|
20
|
+
// Safety: loopback + gated + desensitize
|
|
21
|
+
const Safety = clamp01(5.5 + (opts.hasSanitize ? 1.2 : 0) + (opts.hasGatedQueue ? 1 : 0) + (coverage > 0.5 ? 0.3 : 0));
|
|
22
|
+
const vals = [S, R, J, C, Safety];
|
|
23
|
+
const min = Math.min(...vals);
|
|
24
|
+
const mean = vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
25
|
+
const composite = clamp01(min * 0.4 + mean * 0.6);
|
|
26
|
+
return { S, R, J, C, Safety, composite };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=health-score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-score.js","sourceRoot":"","sources":["../src/host/health-score.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { type AutoMemoryOptions } from './auto-memory.ts';
|
|
1
2
|
export declare const inject: readonly ["tools", "systemPrompt", "connection"];
|
|
2
3
|
export interface MaestroMemoryConfig {
|
|
3
4
|
memoryDir?: string | null;
|
|
4
5
|
snapshotOrder?: number;
|
|
6
|
+
autoMemory?: Partial<AutoMemoryOptions>;
|
|
5
7
|
}
|
|
6
8
|
export declare const DEFAULTS: Required<MaestroMemoryConfig>;
|
|
9
|
+
export declare const READ_ACTIONS: Set<string>;
|
|
10
|
+
export declare function isMemoryConcurrencySafe(args: any): boolean;
|
|
7
11
|
export type MemoryTarget = 'memory' | 'user' | 'project' | 'key' | 'daily';
|
|
8
12
|
export type MemoryAction = 'add' | 'list' | 'replace' | 'remove' | 'archive' | 'expand';
|
|
9
13
|
export declare function apply(ctx: any, config?: MaestroMemoryConfig): void;
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"AAiBA,OAAO,EAA+C,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAGtG,eAAO,MAAM,MAAM,kDAAmD,CAAA;AAEtE,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;CACxC;AAED,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,mBAAmB,CAIlD,CAAA;AAED,eAAO,MAAM,YAAY,aAA8B,CAAA;AACvD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAE1D;AAGD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAA;AAC1E,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAWvF,wBAAgB,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,GAAE,mBAAwB,GAAG,IAAI,CAwoBtE"}
|
package/lib/index.js
CHANGED
|
@@ -15,11 +15,18 @@ import { SyncService } from "./sync/service.js";
|
|
|
15
15
|
import { RealGitAdapter } from "./sync/git.js";
|
|
16
16
|
import { listSkillsSync, resolveDefaultMaestroSkillsDir } from "./skills-browser.js";
|
|
17
17
|
import { renderSnapshot } from "./prompt/snapshot.js";
|
|
18
|
+
import { installAutoMemoryHooks, DEFAULT_AUTO_MEMORY } from "./auto-memory.js";
|
|
19
|
+
import { computeFiveDim } from "./health-score.js";
|
|
18
20
|
export const inject = ['tools', 'systemPrompt', 'connection'];
|
|
19
21
|
export const DEFAULTS = {
|
|
20
22
|
memoryDir: null,
|
|
21
23
|
snapshotOrder: 500,
|
|
24
|
+
autoMemory: { ...DEFAULT_AUTO_MEMORY },
|
|
22
25
|
};
|
|
26
|
+
export const READ_ACTIONS = new Set(['list', 'expand']);
|
|
27
|
+
export function isMemoryConcurrencySafe(args) {
|
|
28
|
+
return READ_ACTIONS.has(String(args?.action ?? ''));
|
|
29
|
+
}
|
|
23
30
|
const CONTENT_OUTPUT = {
|
|
24
31
|
schema: {
|
|
25
32
|
type: 'object',
|
|
@@ -35,6 +42,15 @@ export function apply(ctx, config = {}) {
|
|
|
35
42
|
const root = resolveMemoryRoot(config.memoryDir ?? null);
|
|
36
43
|
const queue = new SuggestionQueue(suggestionsPath(root));
|
|
37
44
|
const syncService = new SyncService(config.memoryDir ?? null, new RealGitAdapter());
|
|
45
|
+
// Auto-memory (opt-in, default disabled) — session/event → store
|
|
46
|
+
ctx.effect(() => {
|
|
47
|
+
const am = { ...DEFAULT_AUTO_MEMORY, ...(config.autoMemory ?? {}) };
|
|
48
|
+
const dispose = installAutoMemoryHooks(ctx, store, am);
|
|
49
|
+
return () => {
|
|
50
|
+
if (typeof dispose === 'function')
|
|
51
|
+
dispose();
|
|
52
|
+
};
|
|
53
|
+
}, 'maestro-memory: auto-memory');
|
|
38
54
|
ctx.effect(() => {
|
|
39
55
|
const dispose = ctx.systemPrompt.context({
|
|
40
56
|
name: 'memory:snapshot',
|
|
@@ -84,7 +100,10 @@ export function apply(ctx, config = {}) {
|
|
|
84
100
|
date: { type: 'string', description: 'Date YYYY-MM-DD for daily track (add/list/replace/remove)' },
|
|
85
101
|
},
|
|
86
102
|
output: CONTENT_OUTPUT,
|
|
103
|
+
isConcurrencySafe: (args) => isMemoryConcurrencySafe(args),
|
|
87
104
|
execute: async (args, exec) => {
|
|
105
|
+
if (exec?.signal?.aborted)
|
|
106
|
+
throw new Error('memory aborted');
|
|
88
107
|
const target = args.target;
|
|
89
108
|
const action = args.action;
|
|
90
109
|
const cwd = args.cwd ?? exec?.agent?.session?.header?.cwd;
|
|
@@ -93,6 +112,9 @@ export function apply(ctx, config = {}) {
|
|
|
93
112
|
case 'add': {
|
|
94
113
|
// Batch path: entries[] takes precedence over the single target/content form.
|
|
95
114
|
if (Array.isArray(args.entries)) {
|
|
115
|
+
if (exec?.agent && args.entries.some((e) => String(e.target ?? '').trim() === 'key')) {
|
|
116
|
+
return { content: [{ type: 'text', text: 'key is gated — use memory_suggest target=key with reason (batch contains key)' }] };
|
|
117
|
+
}
|
|
96
118
|
if (!args.target && !args.content) {
|
|
97
119
|
// Inject the session cwd as per-entry fallback, mirroring the
|
|
98
120
|
// single-add path — otherwise project/key entries without an
|
|
@@ -108,6 +130,10 @@ export function apply(ctx, config = {}) {
|
|
|
108
130
|
else if (!target) {
|
|
109
131
|
return { content: [{ type: 'text', text: 'add failed: target is required for single add (or pass entries[])' }] };
|
|
110
132
|
}
|
|
133
|
+
// G1: gate key via agent — direct memory add for key must go through memory_suggest
|
|
134
|
+
if (target === 'key' && exec?.agent) {
|
|
135
|
+
return { content: [{ type: 'text', text: 'key is gated — use memory_suggest target=key with reason (direct memory add for key is CLI-only)' }] };
|
|
136
|
+
}
|
|
111
137
|
let entryText = args.content ?? '';
|
|
112
138
|
if (args.sentiment !== undefined) {
|
|
113
139
|
entryText = `${entryText.trimEnd()} ${buildFeedbackLine({
|
|
@@ -185,6 +211,7 @@ export function apply(ctx, config = {}) {
|
|
|
185
211
|
const tool = defineTool({
|
|
186
212
|
name: 'memory_suggest',
|
|
187
213
|
description: 'Propose memory/todo for confirmation queue (gated, requires user approve). Targets: memory/user/key/todo-*',
|
|
214
|
+
isConcurrencySafe: () => false,
|
|
188
215
|
parameters: {
|
|
189
216
|
target: { type: 'string', required: true, enum: ['memory', 'user', 'key', 'todo-life', 'todo-work', 'todo-project', 'todo-daily'] },
|
|
190
217
|
content: { type: 'string', required: true },
|
|
@@ -192,6 +219,8 @@ export function apply(ctx, config = {}) {
|
|
|
192
219
|
},
|
|
193
220
|
output: CONTENT_OUTPUT,
|
|
194
221
|
execute: async (args, exec) => {
|
|
222
|
+
if (exec?.signal?.aborted)
|
|
223
|
+
throw new Error('memory_suggest aborted');
|
|
195
224
|
const target = String(args.target ?? '').trim();
|
|
196
225
|
const content = String(args.content ?? '').trim();
|
|
197
226
|
const reason = String(args.reason ?? '').trim();
|
|
@@ -220,6 +249,7 @@ export function apply(ctx, config = {}) {
|
|
|
220
249
|
ctx.effect(() => {
|
|
221
250
|
const tool = defineTool({
|
|
222
251
|
name: 'dtodo',
|
|
252
|
+
isConcurrencySafe: (args) => String(args?.action ?? '') === 'list',
|
|
223
253
|
description: 'Todos: life/work/project/daily with IDs, status/due/quadrant, smart view (overdue/today/project/Q1-Q2, limit 8), historical daily lookup',
|
|
224
254
|
parameters: {
|
|
225
255
|
action: { type: 'string', required: true, enum: ['add', 'list', 'done', 'update', 'remove'] },
|
|
@@ -240,6 +270,8 @@ export function apply(ctx, config = {}) {
|
|
|
240
270
|
},
|
|
241
271
|
output: CONTENT_OUTPUT,
|
|
242
272
|
execute: async (args, exec) => {
|
|
273
|
+
if (exec?.signal?.aborted)
|
|
274
|
+
throw new Error('dtodo aborted');
|
|
243
275
|
const action = args.action;
|
|
244
276
|
const cwd = args.cwd ?? exec?.agent?.session?.header?.cwd;
|
|
245
277
|
const dateArg = (v) => (typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) ? v : undefined);
|
|
@@ -623,5 +655,96 @@ export function apply(ctx, config = {}) {
|
|
|
623
655
|
dispose();
|
|
624
656
|
};
|
|
625
657
|
}, 'maestro-memory: rpc');
|
|
658
|
+
// Health dashboard handler (Task6) — loopback only, returns coverage + daily counts
|
|
659
|
+
ctx.effect(() => {
|
|
660
|
+
const conn2 = ctx.connection ?? (ctx.get && ctx.get('connection'));
|
|
661
|
+
if (!conn2?.rpc?.handle)
|
|
662
|
+
return () => { };
|
|
663
|
+
const healthChannel = '/dsh-maestro-memory-health';
|
|
664
|
+
const healthHandler = async (endpoint, payload) => {
|
|
665
|
+
try {
|
|
666
|
+
const cwdRaw = (payload && typeof payload.cwd === 'string' && payload.cwd.trim()) ? payload.cwd.trim() : '';
|
|
667
|
+
// Health requires explicit cwd; if missing, return empty (client should pass sessionCwd)
|
|
668
|
+
if (!cwdRaw) {
|
|
669
|
+
return { ok: true, value: { project: { total: 0, withSummary: 0, coverage: 100 }, daily: { counts: [0, 0, 0, 0, 0, 0, 0] }, longest: [] } };
|
|
670
|
+
}
|
|
671
|
+
const cwd = cwdRaw;
|
|
672
|
+
const projectEntries = store.list('project', cwd);
|
|
673
|
+
const total = projectEntries.length;
|
|
674
|
+
const withSummary = projectEntries.filter((e) => /\[summary:/.test(e)).length;
|
|
675
|
+
const coverage = total ? (withSummary / total) * 100 : 100;
|
|
676
|
+
// daily last 7 days
|
|
677
|
+
const dailyCounts = [];
|
|
678
|
+
for (let i = 6; i >= 0; i--) {
|
|
679
|
+
const d = new Date();
|
|
680
|
+
d.setDate(d.getDate() - i);
|
|
681
|
+
const ds = d.toISOString().slice(0, 10);
|
|
682
|
+
try {
|
|
683
|
+
const list = store.list('daily', undefined, { date: ds });
|
|
684
|
+
dailyCounts.push(Array.isArray(list) ? list.length : 0);
|
|
685
|
+
}
|
|
686
|
+
catch {
|
|
687
|
+
dailyCounts.push(0);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const longest = [...projectEntries].sort((a, b) => b.length - a.length).slice(0, 5).map((e) => ({ len: e.length, preview: e.slice(0, 80).replace(/\n/g, ' ') }));
|
|
691
|
+
const fiveDim = computeFiveDim({
|
|
692
|
+
projectTotal: total,
|
|
693
|
+
withSummary,
|
|
694
|
+
dailyCounts,
|
|
695
|
+
longestLen: longest[0]?.len ?? 0,
|
|
696
|
+
hasAutoRecall: true,
|
|
697
|
+
hasSanitize: true,
|
|
698
|
+
hasGatedQueue: true,
|
|
699
|
+
});
|
|
700
|
+
const health = { project: { total, withSummary, coverage }, daily: { counts: dailyCounts }, longest, fiveDim };
|
|
701
|
+
return { ok: true, value: health };
|
|
702
|
+
}
|
|
703
|
+
catch (e) {
|
|
704
|
+
return { ok: false, error: e?.message ?? String(e) };
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
const h = async (ep, pl, _s) => {
|
|
708
|
+
const res = await healthHandler(ep, pl);
|
|
709
|
+
if (res.ok)
|
|
710
|
+
return { ok: true, value: res.value };
|
|
711
|
+
return { ok: false, error: { message: res.error } };
|
|
712
|
+
};
|
|
713
|
+
const dispose2 = conn2.rpc.handle(healthChannel, h, { authority: 'loopback' });
|
|
714
|
+
return () => { if (typeof dispose2 === 'function')
|
|
715
|
+
dispose2(); };
|
|
716
|
+
}, 'maestro-memory: health');
|
|
717
|
+
// Propose handler for Health → queue (Task4) — loopback only
|
|
718
|
+
ctx.effect(() => {
|
|
719
|
+
const conn3 = ctx.connection ?? (ctx.get && ctx.get('connection'));
|
|
720
|
+
if (!conn3?.rpc?.handle)
|
|
721
|
+
return () => { };
|
|
722
|
+
const proposeHandler = async (endpoint, payload) => {
|
|
723
|
+
try {
|
|
724
|
+
const content = String(payload?.content ?? '').trim();
|
|
725
|
+
const reason = String(payload?.reason ?? 'promote from Health longest').trim();
|
|
726
|
+
if (!content)
|
|
727
|
+
return { ok: false, error: 'empty content' };
|
|
728
|
+
if (!reason)
|
|
729
|
+
return { ok: false, error: 'empty reason' };
|
|
730
|
+
const res = enqueueSuggestion(queue, 'key', content, reason, undefined);
|
|
731
|
+
if (!res.ok)
|
|
732
|
+
return { ok: false, error: res.message ?? 'failed' };
|
|
733
|
+
return { ok: true, value: res };
|
|
734
|
+
}
|
|
735
|
+
catch (e) {
|
|
736
|
+
return { ok: false, error: e?.message ?? String(e) };
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
const wrapped = async (ep, pl, _s) => {
|
|
740
|
+
const r = await proposeHandler(ep, pl);
|
|
741
|
+
if (r.ok)
|
|
742
|
+
return { ok: true, value: r.value };
|
|
743
|
+
return { ok: false, error: { message: r.error } };
|
|
744
|
+
};
|
|
745
|
+
const d3 = conn3.rpc.handle('/dsh-maestro-memory-propose', wrapped, { authority: 'loopback' });
|
|
746
|
+
return () => { if (typeof d3 === 'function')
|
|
747
|
+
d3(); };
|
|
748
|
+
}, 'maestro-memory: propose');
|
|
626
749
|
}
|
|
627
750
|
//# sourceMappingURL=index.js.map
|