@ddtcorex/dsh-maestro-memory 1.0.1
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 +96 -0
- package/LICENSE +21 -0
- package/README.md +448 -0
- package/cordis.patch.yml +13 -0
- package/lib/client.js +756 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +627 -0
- package/lib/index.js.map +1 -0
- package/lib/memory/batch.d.ts +44 -0
- package/lib/memory/batch.d.ts.map +1 -0
- package/lib/memory/batch.js +75 -0
- package/lib/memory/batch.js.map +1 -0
- package/lib/memory/feedback.d.ts +18 -0
- package/lib/memory/feedback.d.ts.map +1 -0
- package/lib/memory/feedback.js +29 -0
- package/lib/memory/feedback.js.map +1 -0
- package/lib/memory/store.d.ts +154 -0
- package/lib/memory/store.d.ts.map +1 -0
- package/lib/memory/store.js +606 -0
- package/lib/memory/store.js.map +1 -0
- package/lib/migration/cli.d.ts +18 -0
- package/lib/migration/cli.d.ts.map +1 -0
- package/lib/migration/cli.js +143 -0
- package/lib/migration/cli.js.map +1 -0
- package/lib/migration/fixture.d.ts +45 -0
- package/lib/migration/fixture.d.ts.map +1 -0
- package/lib/migration/fixture.js +177 -0
- package/lib/migration/fixture.js.map +1 -0
- package/lib/migration/service.d.ts +75 -0
- package/lib/migration/service.d.ts.map +1 -0
- package/lib/migration/service.js +713 -0
- package/lib/migration/service.js.map +1 -0
- package/lib/prompt/snapshot.d.ts +26 -0
- package/lib/prompt/snapshot.d.ts.map +1 -0
- package/lib/prompt/snapshot.js +74 -0
- package/lib/prompt/snapshot.js.map +1 -0
- package/lib/review/queue.d.ts +72 -0
- package/lib/review/queue.d.ts.map +1 -0
- package/lib/review/queue.js +306 -0
- package/lib/review/queue.js.map +1 -0
- package/lib/skills-browser.d.ts +67 -0
- package/lib/skills-browser.d.ts.map +1 -0
- package/lib/skills-browser.js +185 -0
- package/lib/skills-browser.js.map +1 -0
- package/lib/storage/atomic-store.d.ts +211 -0
- package/lib/storage/atomic-store.d.ts.map +1 -0
- package/lib/storage/atomic-store.js +582 -0
- package/lib/storage/atomic-store.js.map +1 -0
- package/lib/storage/layout.d.ts +81 -0
- package/lib/storage/layout.d.ts.map +1 -0
- package/lib/storage/layout.js +162 -0
- package/lib/storage/layout.js.map +1 -0
- package/lib/storage/legacy-format.d.ts +104 -0
- package/lib/storage/legacy-format.d.ts.map +1 -0
- package/lib/storage/legacy-format.js +262 -0
- package/lib/storage/legacy-format.js.map +1 -0
- package/lib/sync/config.d.ts +22 -0
- package/lib/sync/config.d.ts.map +1 -0
- package/lib/sync/config.js +57 -0
- package/lib/sync/config.js.map +1 -0
- package/lib/sync/git.d.ts +97 -0
- package/lib/sync/git.d.ts.map +1 -0
- package/lib/sync/git.js +282 -0
- package/lib/sync/git.js.map +1 -0
- package/lib/sync/index.d.ts +6 -0
- package/lib/sync/index.d.ts.map +1 -0
- package/lib/sync/index.js +6 -0
- package/lib/sync/index.js.map +1 -0
- package/lib/sync/layout.d.ts +15 -0
- package/lib/sync/layout.d.ts.map +1 -0
- package/lib/sync/layout.js +33 -0
- package/lib/sync/layout.js.map +1 -0
- package/lib/sync/merge.d.ts +42 -0
- package/lib/sync/merge.d.ts.map +1 -0
- package/lib/sync/merge.js +159 -0
- package/lib/sync/merge.js.map +1 -0
- package/lib/sync/service.d.ts +70 -0
- package/lib/sync/service.d.ts.map +1 -0
- package/lib/sync/service.js +508 -0
- package/lib/sync/service.js.map +1 -0
- package/lib/todo/store.d.ts +142 -0
- package/lib/todo/store.d.ts.map +1 -0
- package/lib/todo/store.js +452 -0
- package/lib/todo/store.js.map +1 -0
- package/lib/types/client/index.d.ts +7 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/package.json +63 -0
- package/src/client/index.tsx +994 -0
- package/src/host/index.ts +591 -0
- package/src/host/memory/batch.ts +107 -0
- package/src/host/memory/feedback.ts +36 -0
- package/src/host/memory/store.ts +650 -0
- package/src/host/migration/cli.ts +139 -0
- package/src/host/migration/fixture.ts +218 -0
- package/src/host/migration/service.ts +786 -0
- package/src/host/prompt/snapshot.ts +95 -0
- package/src/host/review/queue.ts +325 -0
- package/src/host/skills-browser.ts +191 -0
- package/src/host/storage/atomic-store.ts +605 -0
- package/src/host/storage/layout.ts +191 -0
- package/src/host/storage/legacy-format.ts +308 -0
- package/src/host/sync/config.ts +67 -0
- package/src/host/sync/git.ts +280 -0
- package/src/host/sync/index.ts +5 -0
- package/src/host/sync/layout.ts +39 -0
- package/src/host/sync/merge.ts +188 -0
- package/src/host/sync/service.ts +494 -0
- package/src/host/todo/store.ts +480 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const inject: readonly ["tools", "systemPrompt", "connection"];
|
|
2
|
+
export interface MaestroMemoryConfig {
|
|
3
|
+
memoryDir?: string | null;
|
|
4
|
+
snapshotOrder?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULTS: Required<MaestroMemoryConfig>;
|
|
7
|
+
export type MemoryTarget = 'memory' | 'user' | 'project' | 'key' | 'daily';
|
|
8
|
+
export type MemoryAction = 'add' | 'list' | 'replace' | 'remove' | 'archive' | 'expand';
|
|
9
|
+
export declare function apply(ctx: any, config?: MaestroMemoryConfig): void;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,MAAM,kDAAmD,CAAA;AAEtE,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,QAAQ,EAAE,QAAQ,CAAC,mBAAmB,CAGlD,CAAA;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,CAmiBtE"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-maestro-memory — host entry (M2-PR-B: confirmation queue, gated memory_suggest, RPC decide, Review UI)
|
|
3
|
+
*/
|
|
4
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
5
|
+
import { MaestroMemoryStore } from "./memory/store.js";
|
|
6
|
+
import { applyBatch } from "./memory/batch.js";
|
|
7
|
+
import { buildFeedbackLine } from "./memory/feedback.js";
|
|
8
|
+
import { TodoStore, resolveQuadrant } from "./todo/store.js";
|
|
9
|
+
import { TODO_TARGETS, TODO_STATUSES } from "./storage/legacy-format.js";
|
|
10
|
+
import { SuggestionQueue, enqueueSuggestion, approveSuggestions, rejectSuggestions } from "./review/queue.js";
|
|
11
|
+
import { resolveMemoryRoot, suggestionsPath, globalArchivePath, userArchivePath, projectKeyArchivePath, todoArchivePath } from "./storage/layout.js";
|
|
12
|
+
import { appendEntryAtomicSync } from "./storage/atomic-store.js";
|
|
13
|
+
import * as migration from "./migration/service.js";
|
|
14
|
+
import { SyncService } from "./sync/service.js";
|
|
15
|
+
import { RealGitAdapter } from "./sync/git.js";
|
|
16
|
+
import { listSkillsSync, resolveDefaultMaestroSkillsDir } from "./skills-browser.js";
|
|
17
|
+
import { renderSnapshot } from "./prompt/snapshot.js";
|
|
18
|
+
export const inject = ['tools', 'systemPrompt', 'connection'];
|
|
19
|
+
export const DEFAULTS = {
|
|
20
|
+
memoryDir: null,
|
|
21
|
+
snapshotOrder: 500,
|
|
22
|
+
};
|
|
23
|
+
const CONTENT_OUTPUT = {
|
|
24
|
+
schema: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
properties: { content: { type: 'array', required: true } },
|
|
28
|
+
},
|
|
29
|
+
render: (_args, value) => value.content,
|
|
30
|
+
};
|
|
31
|
+
export function apply(ctx, config = {}) {
|
|
32
|
+
const order = config.snapshotOrder ?? DEFAULTS.snapshotOrder;
|
|
33
|
+
const store = new MaestroMemoryStore(config.memoryDir ?? null);
|
|
34
|
+
const todoStore = new TodoStore(config.memoryDir ?? null);
|
|
35
|
+
const root = resolveMemoryRoot(config.memoryDir ?? null);
|
|
36
|
+
const queue = new SuggestionQueue(suggestionsPath(root));
|
|
37
|
+
const syncService = new SyncService(config.memoryDir ?? null, new RealGitAdapter());
|
|
38
|
+
ctx.effect(() => {
|
|
39
|
+
const dispose = ctx.systemPrompt.context({
|
|
40
|
+
name: 'memory:snapshot',
|
|
41
|
+
order,
|
|
42
|
+
text: (promptCtx) => {
|
|
43
|
+
const cwd = promptCtx?.agent?.session?.header?.cwd ?? null;
|
|
44
|
+
const branch = promptCtx?.agent?.session?.header?.branch ?? undefined;
|
|
45
|
+
const sessionId = promptCtx?.agent?.session?.header?.sessionId
|
|
46
|
+
?? promptCtx?.agent?.session?.id
|
|
47
|
+
?? undefined;
|
|
48
|
+
const sessionName = promptCtx?.agent?.session?.header?.sessionName
|
|
49
|
+
?? promptCtx?.agent?.session?.name
|
|
50
|
+
?? undefined;
|
|
51
|
+
return renderSnapshot(store, { cwd, branch, sessionId, sessionName });
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
return () => {
|
|
55
|
+
if (typeof dispose === 'function')
|
|
56
|
+
dispose();
|
|
57
|
+
};
|
|
58
|
+
}, 'maestro-memory: snapshot');
|
|
59
|
+
ctx.effect(() => {
|
|
60
|
+
const tool = defineTool({
|
|
61
|
+
name: 'memory',
|
|
62
|
+
description: 'Maestro memory (M2: five tracks, query, replace/remove, archive, branch, summary/expand)',
|
|
63
|
+
parameters: {
|
|
64
|
+
action: { type: 'string', required: true, enum: ['add', 'list', 'replace', 'remove', 'archive', 'expand'], description: 'Memory action' },
|
|
65
|
+
target: { type: 'string', description: 'Memory track (daily=YYYY-MM-DD file, project=per-cwd log, key=per-cwd long-term); optional only when entries[] is used' },
|
|
66
|
+
entries: { type: 'array', description: 'Batch add: array of {target,content,cwd?,date?,branches?,summary?} — sequential through store.add with rollback on first failure' },
|
|
67
|
+
content: { type: 'string', description: 'Entry content (add) or new content (replace)' },
|
|
68
|
+
match: { type: 'string', description: 'Unique substring identifying entry (replace/remove/archive)' },
|
|
69
|
+
filter: { type: 'string', description: 'Content substring filter (list)' },
|
|
70
|
+
since: { type: 'string', description: 'Start date YYYY-MM-DD (list)' },
|
|
71
|
+
until: { type: 'string', description: 'End date YYYY-MM-DD (list)' },
|
|
72
|
+
limit: { type: 'integer', description: 'Max entries (list)' },
|
|
73
|
+
recent: { type: 'boolean', description: 'Newest first (list)' },
|
|
74
|
+
branch: { type: 'string', description: 'Branch filter for key (list)' },
|
|
75
|
+
branches: { type: 'string', description: 'Branch scope csv for key add, e.g. main,dev (empty=all)' },
|
|
76
|
+
summary: { type: 'string', description: 'One-line summary for key add (progressive disclosure)' },
|
|
77
|
+
sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'], description: 'Attach [Feedback] line on add when set (single add or entries[])' },
|
|
78
|
+
category: { type: 'string', description: 'Feedback category (requires sentiment)' },
|
|
79
|
+
quote: { type: 'string', description: 'Feedback quote (requires sentiment)' },
|
|
80
|
+
note: { type: 'string', description: 'Feedback note (requires sentiment)' },
|
|
81
|
+
id: { type: 'string', description: 'Entry id for expand (key)' },
|
|
82
|
+
archived: { type: 'boolean', description: 'Query archive files (list)' },
|
|
83
|
+
cwd: { type: 'string', description: 'Working directory for project/key tracks' },
|
|
84
|
+
date: { type: 'string', description: 'Date YYYY-MM-DD for daily track (add/list/replace/remove)' },
|
|
85
|
+
},
|
|
86
|
+
output: CONTENT_OUTPUT,
|
|
87
|
+
execute: async (args, exec) => {
|
|
88
|
+
const target = args.target;
|
|
89
|
+
const action = args.action;
|
|
90
|
+
const cwd = args.cwd ?? exec?.agent?.session?.header?.cwd;
|
|
91
|
+
try {
|
|
92
|
+
switch (action) {
|
|
93
|
+
case 'add': {
|
|
94
|
+
// Batch path: entries[] takes precedence over the single target/content form.
|
|
95
|
+
if (Array.isArray(args.entries)) {
|
|
96
|
+
if (!args.target && !args.content) {
|
|
97
|
+
// Inject the session cwd as per-entry fallback, mirroring the
|
|
98
|
+
// single-add path — otherwise project/key entries without an
|
|
99
|
+
// explicit cwd fail deep inside the store mid-batch.
|
|
100
|
+
const batchEntries = args.entries.map((entry) => ({ ...entry, cwd: entry.cwd ?? cwd }));
|
|
101
|
+
const batchRes = applyBatch(store, batchEntries);
|
|
102
|
+
if (!batchRes.ok) {
|
|
103
|
+
return { content: [{ type: 'text', text: `batch failed at [${batchRes.index}]: ${batchRes.error}` }] };
|
|
104
|
+
}
|
|
105
|
+
return { content: [{ type: 'text', text: `added ${batchRes.ids.length} ${batchRes.ids.length === 1 ? 'entry' : 'entries'} (batch)` }] };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (!target) {
|
|
109
|
+
return { content: [{ type: 'text', text: 'add failed: target is required for single add (or pass entries[])' }] };
|
|
110
|
+
}
|
|
111
|
+
let entryText = args.content ?? '';
|
|
112
|
+
if (args.sentiment !== undefined) {
|
|
113
|
+
entryText = `${entryText.trimEnd()} ${buildFeedbackLine({
|
|
114
|
+
sentiment: args.sentiment,
|
|
115
|
+
category: args.category,
|
|
116
|
+
quote: args.quote,
|
|
117
|
+
note: args.note,
|
|
118
|
+
})}`;
|
|
119
|
+
}
|
|
120
|
+
const res = store.add(target, entryText, cwd, {
|
|
121
|
+
branches: args.branches,
|
|
122
|
+
summary: args.summary,
|
|
123
|
+
// daily add targets a specific day when the caller passes date
|
|
124
|
+
date: args.date,
|
|
125
|
+
});
|
|
126
|
+
if (!res.ok)
|
|
127
|
+
return { content: [{ type: 'text', text: `add failed: ${res.error}` }] };
|
|
128
|
+
return { content: [{ type: 'text', text: res.duplicate ? 'duplicate' : `added to ${target}` }] };
|
|
129
|
+
}
|
|
130
|
+
case 'list': {
|
|
131
|
+
const entries = store.list(target, cwd, {
|
|
132
|
+
filter: args.filter,
|
|
133
|
+
since: args.since,
|
|
134
|
+
until: args.until,
|
|
135
|
+
limit: args.limit,
|
|
136
|
+
recent: args.recent,
|
|
137
|
+
branch: args.branch,
|
|
138
|
+
archived: args.archived,
|
|
139
|
+
date: args.date,
|
|
140
|
+
});
|
|
141
|
+
const text = entries.length ? entries.join('\n---\n') : '(no entries)';
|
|
142
|
+
return { content: [{ type: 'text', text }] };
|
|
143
|
+
}
|
|
144
|
+
case 'replace': {
|
|
145
|
+
const res = store.replace(target, args.match ?? '', args.content ?? '', cwd, { date: args.date });
|
|
146
|
+
if (!res.ok)
|
|
147
|
+
return { content: [{ type: 'text', text: `replace failed: ${res.error}` }] };
|
|
148
|
+
return { content: [{ type: 'text', text: 'replaced' }] };
|
|
149
|
+
}
|
|
150
|
+
case 'remove': {
|
|
151
|
+
const res = store.remove(target, args.match ?? '', cwd, { date: args.date });
|
|
152
|
+
if (!res.ok)
|
|
153
|
+
return { content: [{ type: 'text', text: `remove failed: ${res.error}` }] };
|
|
154
|
+
return { content: [{ type: 'text', text: 'removed' }] };
|
|
155
|
+
}
|
|
156
|
+
case 'archive': {
|
|
157
|
+
const res = store.archive(target, args.match ?? '', cwd);
|
|
158
|
+
if (!res.ok)
|
|
159
|
+
return { content: [{ type: 'text', text: `archive failed: ${res.error}` }] };
|
|
160
|
+
return { content: [{ type: 'text', text: 'archived' }] };
|
|
161
|
+
}
|
|
162
|
+
case 'expand': {
|
|
163
|
+
const res = store.expand(target, args.id ?? '', cwd);
|
|
164
|
+
if (!res.ok)
|
|
165
|
+
return { content: [{ type: 'text', text: `expand failed: ${res.error}` }] };
|
|
166
|
+
return { content: [{ type: 'text', text: res.entry }] };
|
|
167
|
+
}
|
|
168
|
+
default:
|
|
169
|
+
return { content: [{ type: 'text', text: `unknown action ${action}` }] };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
return { content: [{ type: 'text', text: `error: ${e?.message ?? String(e)}` }] };
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const dispose = ctx.tools.register(tool);
|
|
178
|
+
return () => {
|
|
179
|
+
if (typeof dispose === 'function')
|
|
180
|
+
dispose();
|
|
181
|
+
};
|
|
182
|
+
}, 'maestro-memory: tool');
|
|
183
|
+
// Gated memory_suggest tool — model proposals go to queue, never directly to memory
|
|
184
|
+
ctx.effect(() => {
|
|
185
|
+
const tool = defineTool({
|
|
186
|
+
name: 'memory_suggest',
|
|
187
|
+
description: 'Propose memory/todo for confirmation queue (gated, requires user approve). Targets: memory/user/key/todo-*',
|
|
188
|
+
parameters: {
|
|
189
|
+
target: { type: 'string', required: true, enum: ['memory', 'user', 'key', 'todo-life', 'todo-work', 'todo-project', 'todo-daily'] },
|
|
190
|
+
content: { type: 'string', required: true },
|
|
191
|
+
reason: { type: 'string', required: true },
|
|
192
|
+
},
|
|
193
|
+
output: CONTENT_OUTPUT,
|
|
194
|
+
execute: async (args, exec) => {
|
|
195
|
+
const target = String(args.target ?? '').trim();
|
|
196
|
+
const content = String(args.content ?? '').trim();
|
|
197
|
+
const reason = String(args.reason ?? '').trim();
|
|
198
|
+
const valid = ['memory', 'user', 'key', 'todo-life', 'todo-work', 'todo-project', 'todo-daily'];
|
|
199
|
+
if (!valid.includes(target))
|
|
200
|
+
return { content: [{ type: 'text', text: `invalid target ${target}` }] };
|
|
201
|
+
if (!content)
|
|
202
|
+
return { content: [{ type: 'text', text: 'empty content' }] };
|
|
203
|
+
if (!reason)
|
|
204
|
+
return { content: [{ type: 'text', text: 'empty reason' }] };
|
|
205
|
+
const agent = exec?.agent;
|
|
206
|
+
const res = enqueueSuggestion(queue, target, content, reason, agent);
|
|
207
|
+
if (!res.ok)
|
|
208
|
+
return { content: [{ type: 'text', text: res.message ?? 'failed' }] };
|
|
209
|
+
const msg = res.hits ? `queued (deduped hits=${res.hits})` : `queued (${res.queued})`;
|
|
210
|
+
return { content: [{ type: 'text', text: msg }] };
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
const dispose = ctx.tools.register(tool);
|
|
214
|
+
return () => {
|
|
215
|
+
if (typeof dispose === 'function')
|
|
216
|
+
dispose();
|
|
217
|
+
};
|
|
218
|
+
}, 'maestro-memory: suggest-tool');
|
|
219
|
+
// dtodo compatibility tool (four tracks, IDs, status/due/quadrant, smart view, historical lookup)
|
|
220
|
+
ctx.effect(() => {
|
|
221
|
+
const tool = defineTool({
|
|
222
|
+
name: 'dtodo',
|
|
223
|
+
description: 'Todos: life/work/project/daily with IDs, status/due/quadrant, smart view (overdue/today/project/Q1-Q2, limit 8), historical daily lookup',
|
|
224
|
+
parameters: {
|
|
225
|
+
action: { type: 'string', required: true, enum: ['add', 'list', 'done', 'update', 'remove'] },
|
|
226
|
+
target: { type: 'string', enum: [...TODO_TARGETS] },
|
|
227
|
+
content: { type: 'string' },
|
|
228
|
+
id: { type: 'string' },
|
|
229
|
+
due: { type: 'string' },
|
|
230
|
+
quadrant: { type: 'string', enum: ['q1', 'q2', 'q3', 'q4'] },
|
|
231
|
+
important: { type: 'boolean' },
|
|
232
|
+
urgent: { type: 'boolean' },
|
|
233
|
+
cat: { type: 'string' },
|
|
234
|
+
status: { type: 'string', enum: [...TODO_STATUSES] },
|
|
235
|
+
all: { type: 'boolean' },
|
|
236
|
+
past: { type: 'boolean' },
|
|
237
|
+
expired: { type: 'boolean' },
|
|
238
|
+
cwd: { type: 'string' },
|
|
239
|
+
date: { type: 'string' },
|
|
240
|
+
},
|
|
241
|
+
output: CONTENT_OUTPUT,
|
|
242
|
+
execute: async (args, exec) => {
|
|
243
|
+
const action = args.action;
|
|
244
|
+
const cwd = args.cwd ?? exec?.agent?.session?.header?.cwd;
|
|
245
|
+
const dateArg = (v) => (typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) ? v : undefined);
|
|
246
|
+
try {
|
|
247
|
+
if (action === 'add') {
|
|
248
|
+
const target = (args.target ?? (cwd ? 'project' : 'work'));
|
|
249
|
+
if (!TODO_TARGETS.includes(target))
|
|
250
|
+
return { content: [{ type: 'text', text: `invalid target ${target}` }] };
|
|
251
|
+
const due = typeof args.due === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(args.due) ? args.due : undefined;
|
|
252
|
+
const cat = typeof args.cat === 'string' && args.cat.trim() !== '' ? args.cat.trim() : undefined;
|
|
253
|
+
const quadrant = resolveQuadrant({ quadrant: args.quadrant, important: args.important, urgent: args.urgent });
|
|
254
|
+
const res = todoStore.addTodo(target, String(args.content ?? ''), { quadrant, due, cat }, cwd);
|
|
255
|
+
if (!res.ok)
|
|
256
|
+
return { content: [{ type: 'text', text: res.message }] };
|
|
257
|
+
return { content: [{ type: 'text', text: `${res.message} (id: ${res.id})` }] };
|
|
258
|
+
}
|
|
259
|
+
if (action === 'list') {
|
|
260
|
+
const targets = args.target !== undefined ? [args.target] : [...TODO_TARGETS];
|
|
261
|
+
const projectCwd = typeof args.cwd === 'string' && args.cwd.trim() !== '' ? args.cwd.trim() : cwd;
|
|
262
|
+
const result = todoStore.listTodos(targets, {
|
|
263
|
+
status: args.status,
|
|
264
|
+
quadrant: args.quadrant,
|
|
265
|
+
due: args.due,
|
|
266
|
+
cat: args.cat,
|
|
267
|
+
all: args.all === true,
|
|
268
|
+
past: args.past === true,
|
|
269
|
+
expired: args.expired === true,
|
|
270
|
+
date: dateArg(args.date),
|
|
271
|
+
}, projectCwd, dateArg(args.date));
|
|
272
|
+
const text = todoStore.formatList(result, dateArg(args.date));
|
|
273
|
+
return { content: [{ type: 'text', text }] };
|
|
274
|
+
}
|
|
275
|
+
if (action === 'done') {
|
|
276
|
+
const id = String(args.id ?? '').trim();
|
|
277
|
+
if (!id)
|
|
278
|
+
return { content: [{ type: 'text', text: 'id required' }] };
|
|
279
|
+
const res = todoStore.doneTodo(args.target, id, cwd, dateArg(args.date));
|
|
280
|
+
return { content: [{ type: 'text', text: res.message }] };
|
|
281
|
+
}
|
|
282
|
+
if (action === 'remove') {
|
|
283
|
+
const id = String(args.id ?? '').trim();
|
|
284
|
+
if (!id)
|
|
285
|
+
return { content: [{ type: 'text', text: 'id required' }] };
|
|
286
|
+
const res = todoStore.removeTodo(args.target, id, cwd, dateArg(args.date));
|
|
287
|
+
return { content: [{ type: 'text', text: res.message }] };
|
|
288
|
+
}
|
|
289
|
+
if (action === 'update') {
|
|
290
|
+
const id = String(args.id ?? '').trim();
|
|
291
|
+
if (!id)
|
|
292
|
+
return { content: [{ type: 'text', text: 'id required' }] };
|
|
293
|
+
const patch = {};
|
|
294
|
+
if (args.status !== undefined)
|
|
295
|
+
patch.status = args.status;
|
|
296
|
+
if (args.quadrant !== undefined)
|
|
297
|
+
patch.quadrant = /^q[1-4]$/.test(args.quadrant) ? args.quadrant : undefined;
|
|
298
|
+
else if (args.important !== undefined || args.urgent !== undefined) {
|
|
299
|
+
const q = resolveQuadrant({ important: args.important, urgent: args.urgent });
|
|
300
|
+
if (q)
|
|
301
|
+
patch.quadrant = q;
|
|
302
|
+
}
|
|
303
|
+
if (args.due !== undefined)
|
|
304
|
+
patch.due = /^\d{4}-\d{2}-\d{2}$/.test(args.due) ? args.due : null;
|
|
305
|
+
if (args.cat !== undefined)
|
|
306
|
+
patch.cat = args.cat === '' ? null : args.cat;
|
|
307
|
+
if (args.content !== undefined)
|
|
308
|
+
patch.content = String(args.content);
|
|
309
|
+
const res = todoStore.updateTodo(args.target, id, patch, cwd, dateArg(args.date));
|
|
310
|
+
return { content: [{ type: 'text', text: res.message }] };
|
|
311
|
+
}
|
|
312
|
+
return { content: [{ type: 'text', text: `unknown action ${action}` }] };
|
|
313
|
+
}
|
|
314
|
+
catch (e) {
|
|
315
|
+
return { content: [{ type: 'text', text: `error: ${e?.message ?? String(e)}` }] };
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
const dispose = ctx.tools.register(tool);
|
|
320
|
+
return () => {
|
|
321
|
+
if (typeof dispose === 'function')
|
|
322
|
+
dispose();
|
|
323
|
+
};
|
|
324
|
+
}, 'maestro-memory: dtodo-tool');
|
|
325
|
+
// RPC channel for Review queue — explicit user-click decisions
|
|
326
|
+
ctx.effect(() => {
|
|
327
|
+
const channel = '/dsh-maestro-memory';
|
|
328
|
+
const rpcDate = (v) => (typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) ? v : undefined);
|
|
329
|
+
const legacyHandler = async (endpoint, payload) => {
|
|
330
|
+
switch (endpoint) {
|
|
331
|
+
case 'queue.list': {
|
|
332
|
+
const entries = queue.read();
|
|
333
|
+
return { ok: true, entries };
|
|
334
|
+
}
|
|
335
|
+
case 'queue.decide': {
|
|
336
|
+
const action = payload?.action;
|
|
337
|
+
const indices = Array.isArray(payload?.indices) ? payload.indices.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n >= 1) : [];
|
|
338
|
+
if (indices.length === 0)
|
|
339
|
+
return { ok: false, error: 'indices required' };
|
|
340
|
+
if (action === 'approve') {
|
|
341
|
+
const editsRaw = payload?.edits;
|
|
342
|
+
const targetsRaw = payload?.targets;
|
|
343
|
+
const edits = editsRaw ? new Map(Object.entries(editsRaw).map(([k, v]) => [Number(k), String(v)])) : undefined;
|
|
344
|
+
const targets = targetsRaw ? new Map(Object.entries(targetsRaw).map(([k, v]) => [Number(k), String(v)])) : undefined;
|
|
345
|
+
const agent = payload?.cwd ? { session: { header: { cwd: payload.cwd } } } : undefined;
|
|
346
|
+
const res = approveSuggestions(store, todoStore, queue, indices, agent, edits, targets);
|
|
347
|
+
return { ok: true, ...res };
|
|
348
|
+
}
|
|
349
|
+
if (action === 'reject') {
|
|
350
|
+
const res = rejectSuggestions(queue, indices);
|
|
351
|
+
return { ok: true, ...res };
|
|
352
|
+
}
|
|
353
|
+
if (action === 'archive') {
|
|
354
|
+
const res = queue.mutate((entries) => {
|
|
355
|
+
const kept = [];
|
|
356
|
+
const lines = [];
|
|
357
|
+
entries.forEach((entry, idx) => {
|
|
358
|
+
const number = idx + 1;
|
|
359
|
+
if (!indices.includes(number)) {
|
|
360
|
+
kept.push(entry);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
const stamp = new Date().toISOString().slice(0, 10);
|
|
365
|
+
const originTag = entry.target.startsWith('todo-') ? `\n(original track: ${entry.target})` : '';
|
|
366
|
+
const stamped = `[${stamp}] ${entry.content}${originTag}${entry.reason ? `\n(archive reason: ${entry.reason})` : ''}`;
|
|
367
|
+
let archiveFile = null;
|
|
368
|
+
if (entry.target === 'memory')
|
|
369
|
+
archiveFile = globalArchivePath(root);
|
|
370
|
+
else if (entry.target === 'user')
|
|
371
|
+
archiveFile = userArchivePath(root);
|
|
372
|
+
else if (entry.target === 'key' && entry.cwd)
|
|
373
|
+
archiveFile = projectKeyArchivePath(root, entry.cwd);
|
|
374
|
+
else if (entry.target.startsWith('todo-'))
|
|
375
|
+
archiveFile = todoArchivePath(root);
|
|
376
|
+
// No archivable target (e.g. key without cwd): DO NOT silently drop the suggestion.
|
|
377
|
+
if (!archiveFile) {
|
|
378
|
+
lines.push(`✗ #${number} [${entry.target}] cannot archive (missing cwd or unsupported target)`);
|
|
379
|
+
kept.push(entry);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const appended = appendEntryAtomicSync(archiveFile, stamped);
|
|
383
|
+
if (!appended.ok) {
|
|
384
|
+
lines.push(`✗ #${number} [${entry.target}] archive failed: ${appended.error}`);
|
|
385
|
+
kept.push(entry);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
lines.push(`#${number} [${entry.target}] archived`);
|
|
389
|
+
}
|
|
390
|
+
catch (e) {
|
|
391
|
+
lines.push(`✗ #${number} [${entry.target}] ${e?.message ?? String(e)}`);
|
|
392
|
+
kept.push(entry);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
entries.length = 0;
|
|
396
|
+
entries.push(...kept);
|
|
397
|
+
return { lines, remaining: kept.length };
|
|
398
|
+
});
|
|
399
|
+
return { ok: true, ...res };
|
|
400
|
+
}
|
|
401
|
+
return { ok: false, error: `unknown action ${action}` };
|
|
402
|
+
}
|
|
403
|
+
case 'memory.list': {
|
|
404
|
+
const target = payload?.target;
|
|
405
|
+
const cwd = payload?.cwd;
|
|
406
|
+
const entries = store.list(target, cwd, { ...(payload?.opts ?? {}), date: payload?.date });
|
|
407
|
+
return { ok: true, entries };
|
|
408
|
+
}
|
|
409
|
+
case 'memory.mutate': {
|
|
410
|
+
const action = payload?.action;
|
|
411
|
+
const target = payload?.target;
|
|
412
|
+
const cwd = payload?.cwd;
|
|
413
|
+
const content = payload?.content;
|
|
414
|
+
const match = payload?.match;
|
|
415
|
+
const id = payload?.id;
|
|
416
|
+
const opts = payload?.opts ?? {};
|
|
417
|
+
try {
|
|
418
|
+
if (action === 'list') {
|
|
419
|
+
return { ok: true, entries: store.list(target, cwd, { ...opts, date: payload?.date }) };
|
|
420
|
+
}
|
|
421
|
+
if (action === 'add') {
|
|
422
|
+
return store.add(target, content ?? '', cwd, { branches: payload?.branches, summary: payload?.summary, date: payload?.date });
|
|
423
|
+
}
|
|
424
|
+
if (action === 'replace') {
|
|
425
|
+
return store.replace(target, match ?? '', content ?? '', cwd, { date: payload?.date });
|
|
426
|
+
}
|
|
427
|
+
if (action === 'remove') {
|
|
428
|
+
return store.remove(target, match ?? '', cwd, { date: payload?.date });
|
|
429
|
+
}
|
|
430
|
+
if (action === 'archive') {
|
|
431
|
+
return store.archive(target, match ?? '', cwd);
|
|
432
|
+
}
|
|
433
|
+
if (action === 'expand') {
|
|
434
|
+
return store.expand(target, id ?? '', cwd);
|
|
435
|
+
}
|
|
436
|
+
return { ok: false, error: `unknown memory action ${action}` };
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
return { ok: false, error: e?.message ?? String(e) };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
case 'todo.list': {
|
|
443
|
+
const targets = payload?.targets ?? (payload?.target ? [payload.target] : [...TODO_TARGETS]);
|
|
444
|
+
const cwd = payload?.cwd;
|
|
445
|
+
const opts = payload?.opts ?? payload ?? {};
|
|
446
|
+
const date = rpcDate(opts.date);
|
|
447
|
+
const result = todoStore.listTodos(targets, {
|
|
448
|
+
status: opts.status,
|
|
449
|
+
quadrant: opts.quadrant,
|
|
450
|
+
due: opts.due,
|
|
451
|
+
cat: opts.cat,
|
|
452
|
+
all: opts.all === true,
|
|
453
|
+
past: opts.past === true,
|
|
454
|
+
expired: opts.expired === true,
|
|
455
|
+
date,
|
|
456
|
+
}, cwd, date);
|
|
457
|
+
return { ok: true, ...result, text: todoStore.formatList(result, date) };
|
|
458
|
+
}
|
|
459
|
+
case 'todo.mutate': {
|
|
460
|
+
const action = payload?.action;
|
|
461
|
+
const target = payload?.target;
|
|
462
|
+
const cwd = payload?.cwd;
|
|
463
|
+
const date = rpcDate(payload?.date);
|
|
464
|
+
if (action === 'add') {
|
|
465
|
+
const due = typeof payload?.due === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(payload.due) ? payload.due : undefined;
|
|
466
|
+
const quadrant = resolveQuadrant({ quadrant: payload?.quadrant, important: payload?.important, urgent: payload?.urgent });
|
|
467
|
+
const res = todoStore.addTodo(target ?? (cwd ? 'project' : 'work'), String(payload?.content ?? ''), { quadrant, due, cat: payload?.cat }, cwd);
|
|
468
|
+
return { ...res };
|
|
469
|
+
}
|
|
470
|
+
if (action === 'done') {
|
|
471
|
+
const res = todoStore.doneTodo(target, String(payload?.id ?? ''), cwd, date);
|
|
472
|
+
return { ...res };
|
|
473
|
+
}
|
|
474
|
+
if (action === 'remove') {
|
|
475
|
+
const res = todoStore.removeTodo(target, String(payload?.id ?? ''), cwd, date);
|
|
476
|
+
return { ...res };
|
|
477
|
+
}
|
|
478
|
+
if (action === 'update') {
|
|
479
|
+
const patch = {};
|
|
480
|
+
if (payload?.status !== undefined)
|
|
481
|
+
patch.status = payload.status;
|
|
482
|
+
if (payload?.quadrant !== undefined)
|
|
483
|
+
patch.quadrant = payload.quadrant;
|
|
484
|
+
if (payload?.due !== undefined)
|
|
485
|
+
patch.due = payload.due;
|
|
486
|
+
if (payload?.cat !== undefined)
|
|
487
|
+
patch.cat = payload.cat;
|
|
488
|
+
if (payload?.content !== undefined)
|
|
489
|
+
patch.content = payload.content;
|
|
490
|
+
const res = todoStore.updateTodo(target, String(payload?.id ?? ''), patch, cwd, date);
|
|
491
|
+
return { ...res };
|
|
492
|
+
}
|
|
493
|
+
return { ok: false, error: `unknown todo action ${action}` };
|
|
494
|
+
}
|
|
495
|
+
case 'status': {
|
|
496
|
+
return { ok: true, queue: queue.read().length, blocked: migration.isWriteBlocked(root) };
|
|
497
|
+
}
|
|
498
|
+
case 'migration.inspect': {
|
|
499
|
+
const insp = await migration.inspect(root);
|
|
500
|
+
return { ...insp };
|
|
501
|
+
}
|
|
502
|
+
case 'migration.dryRun': {
|
|
503
|
+
const res = await migration.dryRun(root);
|
|
504
|
+
return { ...res };
|
|
505
|
+
}
|
|
506
|
+
case 'migration.run': {
|
|
507
|
+
// RPC run requires explicit apply flag in payload to enforce CLI's --apply semantics
|
|
508
|
+
if (payload?.apply !== true) {
|
|
509
|
+
return { ok: false, error: 'migration requires explicit apply=true (read-only by default)' };
|
|
510
|
+
}
|
|
511
|
+
const res = await migration.run(root);
|
|
512
|
+
return { ...res };
|
|
513
|
+
}
|
|
514
|
+
case 'migration.verify': {
|
|
515
|
+
const res = await migration.verify(root, payload?.runId);
|
|
516
|
+
return { ...res };
|
|
517
|
+
}
|
|
518
|
+
case 'sync.enable': {
|
|
519
|
+
const cwd = String(payload?.cwd ?? '').trim();
|
|
520
|
+
const remoteUrl = String(payload?.remoteUrl ?? payload?.remote ?? '').trim();
|
|
521
|
+
const branch = payload?.branch ? String(payload.branch).trim() : undefined;
|
|
522
|
+
if (!cwd)
|
|
523
|
+
return { ok: false, error: 'cwd required' };
|
|
524
|
+
if (!remoteUrl)
|
|
525
|
+
return { ok: false, error: 'remoteUrl required' };
|
|
526
|
+
const res = syncService.enable(cwd, remoteUrl, branch);
|
|
527
|
+
return { ...res };
|
|
528
|
+
}
|
|
529
|
+
case 'sync.disable': {
|
|
530
|
+
const cwd = String(payload?.cwd ?? '').trim();
|
|
531
|
+
if (!cwd)
|
|
532
|
+
return { ok: false, error: 'cwd required' };
|
|
533
|
+
const res = syncService.disable(cwd);
|
|
534
|
+
return { ...res };
|
|
535
|
+
}
|
|
536
|
+
case 'sync.status': {
|
|
537
|
+
const cwd = String(payload?.cwd ?? '').trim();
|
|
538
|
+
if (!cwd)
|
|
539
|
+
return { ok: false, error: 'cwd required' };
|
|
540
|
+
const reveal = payload?.reveal === true;
|
|
541
|
+
// explicit fetch on status when requested
|
|
542
|
+
if (payload?.fetch === true) {
|
|
543
|
+
const fetchRes = await syncService.fetch(cwd);
|
|
544
|
+
if (!fetchRes.ok)
|
|
545
|
+
return { ok: false, error: fetchRes.error };
|
|
546
|
+
const st = syncService.status(cwd, reveal);
|
|
547
|
+
return { ok: true, ...st, fetched: true, conflicts: fetchRes.conflicts };
|
|
548
|
+
}
|
|
549
|
+
const st = syncService.status(cwd, reveal);
|
|
550
|
+
return { ok: true, ...st };
|
|
551
|
+
}
|
|
552
|
+
case 'sync.fetch': {
|
|
553
|
+
const cwd = String(payload?.cwd ?? '').trim();
|
|
554
|
+
if (!cwd)
|
|
555
|
+
return { ok: false, error: 'cwd required' };
|
|
556
|
+
const res = await syncService.fetch(cwd);
|
|
557
|
+
return { ...res };
|
|
558
|
+
}
|
|
559
|
+
case 'sync.push': {
|
|
560
|
+
const cwd = String(payload?.cwd ?? '').trim();
|
|
561
|
+
if (!cwd)
|
|
562
|
+
return { ok: false, error: 'cwd required' };
|
|
563
|
+
const res = await syncService.push(cwd, payload?.message ? String(payload.message) : undefined);
|
|
564
|
+
return { ...res };
|
|
565
|
+
}
|
|
566
|
+
case 'sync.pull': {
|
|
567
|
+
const cwd = String(payload?.cwd ?? '').trim();
|
|
568
|
+
if (!cwd)
|
|
569
|
+
return { ok: false, error: 'cwd required' };
|
|
570
|
+
const res = await syncService.pull(cwd);
|
|
571
|
+
return { ...res };
|
|
572
|
+
}
|
|
573
|
+
case 'sync.resolve': {
|
|
574
|
+
const cwd = String(payload?.cwd ?? '').trim();
|
|
575
|
+
const id = String(payload?.id ?? '').trim();
|
|
576
|
+
const choice = String(payload?.choice ?? '').trim();
|
|
577
|
+
if (!cwd)
|
|
578
|
+
return { ok: false, error: 'cwd required' };
|
|
579
|
+
if (!id)
|
|
580
|
+
return { ok: false, error: 'id required' };
|
|
581
|
+
const res = syncService.resolve(cwd, id, choice);
|
|
582
|
+
return { ...res };
|
|
583
|
+
}
|
|
584
|
+
case 'sync.listConflicts': {
|
|
585
|
+
const cwd = String(payload?.cwd ?? '').trim();
|
|
586
|
+
if (!cwd)
|
|
587
|
+
return { ok: false, error: 'cwd required' };
|
|
588
|
+
const conflicts = syncService.listConflicts(cwd);
|
|
589
|
+
return { ok: true, conflicts };
|
|
590
|
+
}
|
|
591
|
+
case 'skills.list': {
|
|
592
|
+
// M6 read-first: list metadata/origin only, no mutation, no body content.
|
|
593
|
+
// Constrained to the resolved default maestro-skills checkout — the RPC
|
|
594
|
+
// must NOT honor an arbitrary client-supplied dir/roots (that would let a
|
|
595
|
+
// payload read any directory). The client sends {} and relies on the default.
|
|
596
|
+
try {
|
|
597
|
+
const def = resolveDefaultMaestroSkillsDir();
|
|
598
|
+
if (def) {
|
|
599
|
+
const entries = listSkillsSync(def, 'maestro-skills');
|
|
600
|
+
return { ok: true, entries };
|
|
601
|
+
}
|
|
602
|
+
return { ok: true, entries: [] };
|
|
603
|
+
}
|
|
604
|
+
catch (e) {
|
|
605
|
+
return { ok: false, error: e?.message ?? String(e) };
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
default:
|
|
609
|
+
return { ok: false, error: `unknown endpoint ${endpoint}` };
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
const handler = async (endpoint, payload, _signal) => ({
|
|
613
|
+
ok: true,
|
|
614
|
+
value: await legacyHandler(endpoint, payload),
|
|
615
|
+
});
|
|
616
|
+
// ctx.connection may be undefined in tests; guard
|
|
617
|
+
const conn = ctx.connection ?? (ctx.get && ctx.get('connection'));
|
|
618
|
+
if (!conn?.rpc?.handle)
|
|
619
|
+
return () => { };
|
|
620
|
+
const dispose = conn.rpc.handle(channel, handler, { authority: 'loopback' });
|
|
621
|
+
return () => {
|
|
622
|
+
if (typeof dispose === 'function')
|
|
623
|
+
dispose();
|
|
624
|
+
};
|
|
625
|
+
}, 'maestro-memory: rpc');
|
|
626
|
+
}
|
|
627
|
+
//# sourceMappingURL=index.js.map
|