memorix 1.9.4 → 1.9.6
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 +75 -1
- package/README.md +12 -6
- package/README.zh-CN.md +12 -6
- package/dist/cli/index.js +2533 -585
- package/dist/index.d.ts +15 -8
- package/dist/index.js +1201 -284
- package/dist/maintenance-runner.js +387 -216
- package/dist/media-audio-runner.js +300 -129
- package/dist/media-video-runner.js +300 -129
- package/dist/memcode-runtime/CHANGELOG.md +75 -1
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.js +1882 -998
- package/dist/types.d.ts +1 -1
- package/dist/vector-backfill-runner.js +300 -129
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +47 -3
- package/docs/API_REFERENCE.md +39 -1
- package/docs/CONFIGURATION.md +58 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/DOCKER.md +23 -3
- package/docs/INTEGRATIONS.md +1 -1
- package/docs/KNOWN_ISSUES_AND_ROADMAP.md +61 -1
- package/docs/MULTI-DEVICE-SYNC-RESEARCH.md +4 -0
- package/docs/PERFORMANCE.md +46 -2
- package/docs/README.md +2 -1
- package/docs/SETUP.md +56 -1
- package/package.json +3 -2
- package/plugins/antigravity/memorix/plugin.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/hermes/memorix/plugin.yaml +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/capability-map.ts +1 -0
- package/src/cli/command-guide.ts +15 -3
- package/src/cli/commands/agent-integrations.ts +57 -0
- package/src/cli/commands/background.ts +11 -2
- package/src/cli/commands/context.ts +2 -0
- package/src/cli/commands/continuity.ts +155 -0
- package/src/cli/commands/init.ts +5 -2
- package/src/cli/commands/serve-http.ts +435 -84
- package/src/cli/commands/setup.ts +77 -10
- package/src/cli/commands/sync-store.ts +47 -12
- package/src/cli/commands/sync.ts +26 -0
- package/src/cli/index.ts +3 -1
- package/src/cli/tui/ConfigureView.tsx +2 -0
- package/src/codegraph/auto-context.ts +17 -0
- package/src/config/legacy-loader.ts +2 -2
- package/src/config/resolved-config.ts +8 -5
- package/src/config/yaml-loader.ts +20 -10
- package/src/dashboard/server.ts +10 -12
- package/src/embedding/api-provider.ts +4 -2
- package/src/embedding/fastembed-provider.ts +47 -14
- package/src/embedding/transformers-provider.ts +29 -7
- package/src/evaluation/memory-quality-gate.ts +90 -0
- package/src/hooks/official-skills.ts +2 -0
- package/src/integrations/registry.ts +1 -1
- package/src/knowledge/context-assembly.ts +1 -0
- package/src/knowledge/context-receipt.ts +15 -0
- package/src/knowledge/task-continuity.ts +413 -0
- package/src/knowledge/workset.ts +77 -0
- package/src/llm/provider.ts +2 -1
- package/src/memory/graph.ts +10 -8
- package/src/memory/session.ts +1 -1
- package/src/orchestrate/coordinator.ts +60 -0
- package/src/orchestrate/effect-ledger.ts +160 -0
- package/src/orchestrate/pipeline-trace.ts +40 -6
- package/src/runtime/maintenance-jobs.ts +12 -6
- package/src/sdk.ts +24 -0
- package/src/server/mcp-binding-store.ts +14 -3
- package/src/server/modern-mcp-bridge.ts +153 -27
- package/src/server/tool-profile.ts +3 -2
- package/src/server.ts +131 -7
- package/src/store/file-lock.ts +28 -3
- package/src/store/graph-store.ts +178 -46
- package/src/store/persistence.ts +14 -9
- package/src/store/sqlite-db.ts +150 -7
- package/src/store/sqlite-store.ts +32 -27
- package/src/sync/engine.ts +9 -7
- package/src/sync/namespace.ts +23 -0
- package/src/sync/policy.ts +19 -4
- package/src/sync/store-port.ts +43 -13
- package/src/sync/types.ts +2 -0
- package/src/team/team-store.ts +62 -27
- package/src/types.ts +1 -1
- package/src/workspace/mcp-adapters/grok.ts +32 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { sanitizeCredentials } from '../memory/secret-filter.js';
|
|
3
|
+
import { getDatabase } from '../store/sqlite-db.js';
|
|
4
|
+
|
|
5
|
+
export const TASK_CONTINUITY_EVENT_KINDS = [
|
|
6
|
+
'task',
|
|
7
|
+
'requirement',
|
|
8
|
+
'decision',
|
|
9
|
+
'verification',
|
|
10
|
+
'risk',
|
|
11
|
+
'outcome',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export type TaskContinuityEventKind = typeof TASK_CONTINUITY_EVENT_KINDS[number];
|
|
15
|
+
export type TaskContinuityVerificationStatus = 'pending' | 'passed' | 'failed' | 'skipped';
|
|
16
|
+
export type TaskContinuityOutcomeStatus = 'completed' | 'blocked' | 'abandoned';
|
|
17
|
+
export type TaskContinuityStatus = 'open' | TaskContinuityOutcomeStatus;
|
|
18
|
+
export type TaskContinuityOutcomeState = 'validated' | 'validated-with-risks' | 'at-risk' | 'in-progress' | 'unverified';
|
|
19
|
+
|
|
20
|
+
export interface TaskContinuityEvent {
|
|
21
|
+
id: string;
|
|
22
|
+
projectId: string;
|
|
23
|
+
taskId: string;
|
|
24
|
+
task: string;
|
|
25
|
+
kind: TaskContinuityEventKind;
|
|
26
|
+
content: string;
|
|
27
|
+
status?: TaskContinuityVerificationStatus | TaskContinuityOutcomeStatus;
|
|
28
|
+
sourceRef?: string;
|
|
29
|
+
evidenceRefs: string[];
|
|
30
|
+
actor?: string;
|
|
31
|
+
at: string;
|
|
32
|
+
eventKey?: string;
|
|
33
|
+
itemKey?: string;
|
|
34
|
+
supersedesId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TaskContinuityVerification {
|
|
38
|
+
id?: string;
|
|
39
|
+
content: string;
|
|
40
|
+
status: TaskContinuityVerificationStatus;
|
|
41
|
+
sourceRef?: string;
|
|
42
|
+
evidenceRefs: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface TaskContinuityLedger {
|
|
46
|
+
taskId: string;
|
|
47
|
+
projectId: string;
|
|
48
|
+
task: string;
|
|
49
|
+
status: TaskContinuityStatus;
|
|
50
|
+
requirements: string[];
|
|
51
|
+
decisions: string[];
|
|
52
|
+
verification: TaskContinuityVerification[];
|
|
53
|
+
risks: string[];
|
|
54
|
+
outcomes: string[];
|
|
55
|
+
outcome: TaskContinuityOutcomeProjection;
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
events: TaskContinuityEvent[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TaskContinuityOutcomeProjection {
|
|
61
|
+
state: TaskContinuityOutcomeState;
|
|
62
|
+
score: number;
|
|
63
|
+
verification: {
|
|
64
|
+
passed: number;
|
|
65
|
+
failed: number;
|
|
66
|
+
pending: number;
|
|
67
|
+
skipped: number;
|
|
68
|
+
};
|
|
69
|
+
reasons: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const MAX_TEXT_LENGTH = 2_000;
|
|
73
|
+
const MAX_TASK_LENGTH = 1_000;
|
|
74
|
+
const MAX_LIST_ITEMS = 8;
|
|
75
|
+
const MAX_EVIDENCE_REFS = 8;
|
|
76
|
+
const MAX_EVENTS_PER_TASK = 200;
|
|
77
|
+
|
|
78
|
+
function clean(value: string, maxLength = MAX_TEXT_LENGTH): string {
|
|
79
|
+
return sanitizeCredentials(value).replace(/\s+/g, ' ').trim().slice(0, maxLength);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function cleanList(values: string[] | undefined, maxLength = MAX_TEXT_LENGTH): string[] {
|
|
83
|
+
return [...new Set((values ?? []).map(value => clean(value, maxLength)).filter(Boolean))].slice(0, MAX_LIST_ITEMS);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseRefs(value: unknown): string[] {
|
|
87
|
+
if (typeof value !== 'string') return [];
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(value);
|
|
90
|
+
return Array.isArray(parsed)
|
|
91
|
+
? cleanList(parsed.filter(item => typeof item === 'string'), 500).slice(0, MAX_EVIDENCE_REFS)
|
|
92
|
+
: [];
|
|
93
|
+
} catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function evaluateTaskContinuity(ledger: Pick<
|
|
99
|
+
TaskContinuityLedger,
|
|
100
|
+
'status' | 'verification' | 'outcomes' | 'risks'
|
|
101
|
+
>): TaskContinuityOutcomeProjection {
|
|
102
|
+
const verification = {
|
|
103
|
+
passed: ledger.verification.filter(item => item.status === 'passed').length,
|
|
104
|
+
failed: ledger.verification.filter(item => item.status === 'failed').length,
|
|
105
|
+
pending: ledger.verification.filter(item => item.status === 'pending').length,
|
|
106
|
+
skipped: ledger.verification.filter(item => item.status === 'skipped').length,
|
|
107
|
+
};
|
|
108
|
+
const reasons: string[] = [];
|
|
109
|
+
if (verification.failed > 0) reasons.push('verification-failed');
|
|
110
|
+
if (verification.pending > 0) reasons.push('verification-pending');
|
|
111
|
+
if (ledger.risks.length > 0) reasons.push('open-risks');
|
|
112
|
+
if (ledger.status === 'blocked') reasons.push('task-blocked');
|
|
113
|
+
if (ledger.status === 'abandoned') reasons.push('task-abandoned');
|
|
114
|
+
|
|
115
|
+
if (ledger.status === 'blocked' || ledger.status === 'abandoned' || verification.failed > 0) {
|
|
116
|
+
return {
|
|
117
|
+
state: 'at-risk',
|
|
118
|
+
score: verification.passed > verification.failed ? 0.55 : 0.2,
|
|
119
|
+
verification,
|
|
120
|
+
reasons,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (ledger.status === 'completed') {
|
|
124
|
+
if (verification.passed > 0 && verification.pending === 0 && verification.skipped === 0) {
|
|
125
|
+
return {
|
|
126
|
+
state: ledger.risks.length > 0 ? 'validated-with-risks' : 'validated',
|
|
127
|
+
score: ledger.risks.length > 0 ? 0.8 : 1,
|
|
128
|
+
verification,
|
|
129
|
+
reasons: reasons.length > 0 ? reasons : ['verification-passed'],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
state: 'unverified',
|
|
134
|
+
score: 0.4,
|
|
135
|
+
verification,
|
|
136
|
+
reasons: reasons.length > 0 ? reasons : ['no-passing-verification'],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
state: 'in-progress',
|
|
141
|
+
score: verification.passed > 0 && verification.pending === 0 ? 0.75 : 0.5,
|
|
142
|
+
verification,
|
|
143
|
+
reasons: reasons.length > 0 ? reasons : ['task-open'],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function rowToEvent(row: any): TaskContinuityEvent {
|
|
148
|
+
const status = row.item_status ? String(row.item_status) : undefined;
|
|
149
|
+
return {
|
|
150
|
+
id: String(row.id),
|
|
151
|
+
projectId: String(row.project_id),
|
|
152
|
+
taskId: String(row.task_id),
|
|
153
|
+
task: clean(String(row.task), MAX_TASK_LENGTH),
|
|
154
|
+
kind: row.kind as TaskContinuityEventKind,
|
|
155
|
+
content: clean(String(row.content)),
|
|
156
|
+
...(status ? { status: status as TaskContinuityEvent['status'] } : {}),
|
|
157
|
+
...(row.source_ref ? { sourceRef: clean(String(row.source_ref), 500) } : {}),
|
|
158
|
+
evidenceRefs: parseRefs(row.evidence_json),
|
|
159
|
+
...(row.actor ? { actor: clean(String(row.actor), 200) } : {}),
|
|
160
|
+
at: String(row.created_at),
|
|
161
|
+
...(row.event_key ? { eventKey: clean(String(row.event_key), 200) } : {}),
|
|
162
|
+
...(row.item_key ? { itemKey: clean(String(row.item_key), 120) } : {}),
|
|
163
|
+
...(row.supersedes_id ? { supersedesId: clean(String(row.supersedes_id), 120) } : {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export class TaskContinuityStore {
|
|
168
|
+
private db: any = null;
|
|
169
|
+
|
|
170
|
+
async init(dataDir: string): Promise<void> {
|
|
171
|
+
this.db = getDatabase(dataDir);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private requireDb(): any {
|
|
175
|
+
if (!this.db) throw new Error('TaskContinuityStore is not initialized.');
|
|
176
|
+
return this.db;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private insert(input: {
|
|
180
|
+
projectId: string;
|
|
181
|
+
taskId: string;
|
|
182
|
+
task: string;
|
|
183
|
+
kind: TaskContinuityEventKind;
|
|
184
|
+
content: string;
|
|
185
|
+
status?: string;
|
|
186
|
+
sourceRef?: string;
|
|
187
|
+
evidenceRefs?: string[];
|
|
188
|
+
actor?: string;
|
|
189
|
+
at?: string;
|
|
190
|
+
eventKey?: string;
|
|
191
|
+
itemKey?: string;
|
|
192
|
+
supersedesId?: string;
|
|
193
|
+
}): TaskContinuityEvent {
|
|
194
|
+
const db = this.requireDb();
|
|
195
|
+
const projectId = clean(input.projectId, 500);
|
|
196
|
+
const taskId = clean(input.taskId, 120);
|
|
197
|
+
const eventKey = input.eventKey ? clean(input.eventKey, 200) : undefined;
|
|
198
|
+
if (eventKey) {
|
|
199
|
+
const existing = db.prepare(
|
|
200
|
+
'SELECT * FROM task_continuity_events WHERE project_id = ? AND task_id = ? AND event_key = ?',
|
|
201
|
+
).get(projectId, taskId, eventKey);
|
|
202
|
+
if (existing) return rowToEvent(existing);
|
|
203
|
+
}
|
|
204
|
+
const existingCount = db.prepare(
|
|
205
|
+
'SELECT COUNT(*) AS count FROM task_continuity_events WHERE project_id = ? AND task_id = ?',
|
|
206
|
+
).get(projectId, taskId) as { count?: number };
|
|
207
|
+
if (Number(existingCount?.count ?? 0) >= MAX_EVENTS_PER_TASK) {
|
|
208
|
+
throw new Error('Task continuity ledger reached its 200-event limit.');
|
|
209
|
+
}
|
|
210
|
+
const event: TaskContinuityEvent = {
|
|
211
|
+
id: randomUUID(),
|
|
212
|
+
projectId,
|
|
213
|
+
taskId,
|
|
214
|
+
task: clean(input.task, MAX_TASK_LENGTH),
|
|
215
|
+
kind: input.kind,
|
|
216
|
+
content: clean(input.content),
|
|
217
|
+
...(input.status ? { status: input.status as TaskContinuityEvent['status'] } : {}),
|
|
218
|
+
...(input.sourceRef ? { sourceRef: clean(input.sourceRef, 500) } : {}),
|
|
219
|
+
evidenceRefs: cleanList(input.evidenceRefs, 500).slice(0, MAX_EVIDENCE_REFS),
|
|
220
|
+
...(input.actor ? { actor: clean(input.actor, 200) } : {}),
|
|
221
|
+
at: input.at ?? new Date().toISOString(),
|
|
222
|
+
...(eventKey ? { eventKey } : {}),
|
|
223
|
+
...(input.itemKey ? { itemKey: clean(input.itemKey, 120) } : {}),
|
|
224
|
+
...(input.supersedesId ? { supersedesId: clean(input.supersedesId, 120) } : {}),
|
|
225
|
+
};
|
|
226
|
+
db.prepare(`
|
|
227
|
+
INSERT INTO task_continuity_events (
|
|
228
|
+
id, project_id, task_id, task, kind, content, item_status,
|
|
229
|
+
source_ref, evidence_json, actor, created_at, event_key, item_key, supersedes_id
|
|
230
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
231
|
+
`).run(
|
|
232
|
+
event.id,
|
|
233
|
+
event.projectId,
|
|
234
|
+
event.taskId,
|
|
235
|
+
event.task,
|
|
236
|
+
event.kind,
|
|
237
|
+
event.content,
|
|
238
|
+
event.status ?? null,
|
|
239
|
+
event.sourceRef ?? null,
|
|
240
|
+
JSON.stringify(event.evidenceRefs),
|
|
241
|
+
event.actor ?? null,
|
|
242
|
+
event.at,
|
|
243
|
+
event.eventKey ?? null,
|
|
244
|
+
event.itemKey ?? null,
|
|
245
|
+
event.supersedesId ?? null,
|
|
246
|
+
);
|
|
247
|
+
return event;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
start(input: {
|
|
251
|
+
projectId: string;
|
|
252
|
+
task: string;
|
|
253
|
+
taskId?: string;
|
|
254
|
+
requirements?: string[];
|
|
255
|
+
actor?: string;
|
|
256
|
+
idempotencyKey?: string;
|
|
257
|
+
}): { taskId: string; ledger: TaskContinuityLedger } {
|
|
258
|
+
const task = clean(input.task, MAX_TASK_LENGTH);
|
|
259
|
+
if (!task) throw new Error('A non-empty task is required.');
|
|
260
|
+
const idempotencyKey = input.idempotencyKey ? clean(input.idempotencyKey, 200) : undefined;
|
|
261
|
+
const taskId = clean(input.taskId ?? idempotencyKey ?? '', 120) || randomUUID();
|
|
262
|
+
const requirements = cleanList(input.requirements);
|
|
263
|
+
const transaction = this.requireDb().transaction(() => {
|
|
264
|
+
this.insert({
|
|
265
|
+
projectId: input.projectId,
|
|
266
|
+
taskId,
|
|
267
|
+
task,
|
|
268
|
+
kind: 'task',
|
|
269
|
+
content: task,
|
|
270
|
+
status: 'open',
|
|
271
|
+
actor: input.actor,
|
|
272
|
+
...(idempotencyKey ? { eventKey: `start:${idempotencyKey}` } : {}),
|
|
273
|
+
});
|
|
274
|
+
for (const requirement of requirements) {
|
|
275
|
+
this.insert({
|
|
276
|
+
projectId: input.projectId,
|
|
277
|
+
taskId,
|
|
278
|
+
task,
|
|
279
|
+
kind: 'requirement',
|
|
280
|
+
content: requirement,
|
|
281
|
+
actor: input.actor,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
transaction.immediate();
|
|
286
|
+
return { taskId, ledger: this.get(input.projectId, taskId)! };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
record(input: {
|
|
290
|
+
projectId: string;
|
|
291
|
+
taskId: string;
|
|
292
|
+
kind: Exclude<TaskContinuityEventKind, 'task' | 'outcome'>;
|
|
293
|
+
content: string;
|
|
294
|
+
verificationStatus?: TaskContinuityVerificationStatus;
|
|
295
|
+
sourceRef?: string;
|
|
296
|
+
evidenceRefs?: string[];
|
|
297
|
+
actor?: string;
|
|
298
|
+
at?: string;
|
|
299
|
+
verificationId?: string;
|
|
300
|
+
idempotencyKey?: string;
|
|
301
|
+
}): { event: TaskContinuityEvent; ledger: TaskContinuityLedger } {
|
|
302
|
+
const projectId = clean(input.projectId, 500);
|
|
303
|
+
const taskId = clean(input.taskId, 120);
|
|
304
|
+
const existing = this.get(projectId, taskId);
|
|
305
|
+
if (!existing) throw new Error('Task continuity ledger not found: ' + taskId);
|
|
306
|
+
const transaction = this.requireDb().transaction(() => this.insert({
|
|
307
|
+
projectId,
|
|
308
|
+
taskId,
|
|
309
|
+
task: existing.task,
|
|
310
|
+
kind: input.kind,
|
|
311
|
+
content: input.content,
|
|
312
|
+
...(input.kind === 'verification' && input.verificationStatus
|
|
313
|
+
? { status: input.verificationStatus }
|
|
314
|
+
: {}),
|
|
315
|
+
sourceRef: input.sourceRef,
|
|
316
|
+
evidenceRefs: input.evidenceRefs,
|
|
317
|
+
actor: input.actor,
|
|
318
|
+
at: input.at,
|
|
319
|
+
...(input.idempotencyKey ? { eventKey: input.idempotencyKey } : {}),
|
|
320
|
+
...(input.verificationId ? { itemKey: input.verificationId } : {}),
|
|
321
|
+
}));
|
|
322
|
+
const event = transaction.immediate();
|
|
323
|
+
return { event, ledger: this.get(projectId, taskId)! };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
close(input: {
|
|
327
|
+
projectId: string;
|
|
328
|
+
taskId: string;
|
|
329
|
+
status: TaskContinuityOutcomeStatus;
|
|
330
|
+
content: string;
|
|
331
|
+
sourceRef?: string;
|
|
332
|
+
evidenceRefs?: string[];
|
|
333
|
+
actor?: string;
|
|
334
|
+
at?: string;
|
|
335
|
+
idempotencyKey?: string;
|
|
336
|
+
}): { event: TaskContinuityEvent; ledger: TaskContinuityLedger } {
|
|
337
|
+
const projectId = clean(input.projectId, 500);
|
|
338
|
+
const taskId = clean(input.taskId, 120);
|
|
339
|
+
const existing = this.get(projectId, taskId);
|
|
340
|
+
if (!existing) throw new Error('Task continuity ledger not found: ' + taskId);
|
|
341
|
+
const transaction = this.requireDb().transaction(() => this.insert({
|
|
342
|
+
projectId,
|
|
343
|
+
taskId,
|
|
344
|
+
task: existing.task,
|
|
345
|
+
kind: 'outcome',
|
|
346
|
+
content: input.content,
|
|
347
|
+
status: input.status,
|
|
348
|
+
sourceRef: input.sourceRef,
|
|
349
|
+
evidenceRefs: input.evidenceRefs,
|
|
350
|
+
actor: input.actor,
|
|
351
|
+
at: input.at,
|
|
352
|
+
...(input.idempotencyKey ? { eventKey: input.idempotencyKey } : {}),
|
|
353
|
+
}));
|
|
354
|
+
const event = transaction.immediate();
|
|
355
|
+
return { event, ledger: this.get(projectId, taskId)! };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
get(projectId: string, taskId: string): TaskContinuityLedger | undefined {
|
|
359
|
+
const normalizedProjectId = clean(projectId, 500);
|
|
360
|
+
const normalizedTaskId = clean(taskId, 120);
|
|
361
|
+
const rows = this.requireDb().prepare(`
|
|
362
|
+
SELECT * FROM task_continuity_events
|
|
363
|
+
WHERE project_id = ? AND task_id = ?
|
|
364
|
+
ORDER BY created_at ASC, rowid ASC
|
|
365
|
+
`).all(normalizedProjectId, normalizedTaskId);
|
|
366
|
+
if (rows.length === 0) return undefined;
|
|
367
|
+
return aggregate(rows.map(rowToEvent));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
list(projectId: string, limit = 20): TaskContinuityLedger[] {
|
|
371
|
+
const rows = this.requireDb().prepare(`
|
|
372
|
+
SELECT task_id
|
|
373
|
+
FROM task_continuity_events
|
|
374
|
+
WHERE project_id = ?
|
|
375
|
+
GROUP BY task_id
|
|
376
|
+
ORDER BY MAX(created_at) DESC
|
|
377
|
+
LIMIT ?
|
|
378
|
+
`).all(projectId, Math.min(100, Math.max(1, Math.floor(limit))));
|
|
379
|
+
return rows
|
|
380
|
+
.map((row: any) => this.get(projectId, String(row.task_id)))
|
|
381
|
+
.filter((ledger: TaskContinuityLedger | undefined): ledger is TaskContinuityLedger => Boolean(ledger));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function aggregate(events: TaskContinuityEvent[]): TaskContinuityLedger {
|
|
386
|
+
const first = events.find(event => event.kind === 'task') ?? events[0];
|
|
387
|
+
const latestOutcome = [...events].reverse().find(event => event.kind === 'outcome');
|
|
388
|
+
const verificationByKey = new Map<string, TaskContinuityEvent>();
|
|
389
|
+
for (const event of events.filter(event => event.kind === 'verification')) {
|
|
390
|
+
verificationByKey.set(event.itemKey ?? `event:${event.id}`, event);
|
|
391
|
+
}
|
|
392
|
+
const ledger = {
|
|
393
|
+
taskId: first.taskId,
|
|
394
|
+
projectId: first.projectId,
|
|
395
|
+
task: first.task,
|
|
396
|
+
status: (latestOutcome?.status as TaskContinuityStatus | undefined) ?? 'open',
|
|
397
|
+
requirements: events.filter(event => event.kind === 'requirement').map(event => event.content),
|
|
398
|
+
decisions: events.filter(event => event.kind === 'decision').map(event => event.content),
|
|
399
|
+
verification: [...verificationByKey.values()]
|
|
400
|
+
.map(event => ({
|
|
401
|
+
id: event.itemKey ?? event.id,
|
|
402
|
+
content: event.content,
|
|
403
|
+
status: (event.status as TaskContinuityVerificationStatus | undefined) ?? 'pending',
|
|
404
|
+
...(event.sourceRef ? { sourceRef: event.sourceRef } : {}),
|
|
405
|
+
evidenceRefs: event.evidenceRefs,
|
|
406
|
+
})),
|
|
407
|
+
risks: events.filter(event => event.kind === 'risk').map(event => event.content),
|
|
408
|
+
outcomes: events.filter(event => event.kind === 'outcome').map(event => event.content),
|
|
409
|
+
updatedAt: events[events.length - 1].at,
|
|
410
|
+
events,
|
|
411
|
+
};
|
|
412
|
+
return { ...ledger, outcome: evaluateTaskContinuity(ledger) };
|
|
413
|
+
}
|
package/src/knowledge/workset.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type { KnowledgePageRecord, KnowledgeWorkspace } from './workspace-types.
|
|
|
25
25
|
import type { WorkflowSelection } from './workflow-types.js';
|
|
26
26
|
import type { ObservationReader } from '../types.js';
|
|
27
27
|
import type { LongTermMemory, LongTermMemoryEvidence } from '../memory/long-term-types.js';
|
|
28
|
+
import type { TaskContinuityLedger } from './task-continuity.js';
|
|
28
29
|
|
|
29
30
|
export type WorksetCautionKind =
|
|
30
31
|
| 'dirty-worktree'
|
|
@@ -148,6 +149,7 @@ export interface WorksetCodeEvolution {
|
|
|
148
149
|
export interface TaskWorkset {
|
|
149
150
|
version: '1.3';
|
|
150
151
|
task: string;
|
|
152
|
+
continuity?: TaskContinuityLedger;
|
|
151
153
|
lens: string;
|
|
152
154
|
/** Always-on "who you are and what this workspace is doing" block. */
|
|
153
155
|
alwaysOn?: {
|
|
@@ -193,6 +195,7 @@ export interface BuildTaskWorksetInput {
|
|
|
193
195
|
projectId: string;
|
|
194
196
|
dataDir: string;
|
|
195
197
|
task?: string;
|
|
198
|
+
continuity?: TaskContinuityLedger;
|
|
196
199
|
/** Optional target used to select workflows explicitly compatible with this agent. */
|
|
197
200
|
agent?: AgentTarget;
|
|
198
201
|
lens: string;
|
|
@@ -366,6 +369,7 @@ function freshnessForMemory(status: WorksetMemorySource['status']): ContextCandi
|
|
|
366
369
|
}
|
|
367
370
|
|
|
368
371
|
function receiptOmissionKind(raw: string): ContextCandidateKind | undefined {
|
|
372
|
+
if (raw.includes('continuity')) return 'continuity';
|
|
369
373
|
if (raw.includes('continuation')) return 'continuation';
|
|
370
374
|
if (raw.includes('task')) return 'task';
|
|
371
375
|
if (raw.includes('fact')) return 'current-fact';
|
|
@@ -518,6 +522,78 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
518
522
|
});
|
|
519
523
|
appendLine(lines, 'Task lens: ' + input.lens, maxTokens, omitted, 'lens');
|
|
520
524
|
|
|
525
|
+
if (input.continuity) {
|
|
526
|
+
const continuity = input.continuity;
|
|
527
|
+
appendLine(lines, '', maxTokens, omitted, 'continuity-heading');
|
|
528
|
+
appendLine(lines, 'Task continuity', maxTokens, omitted, 'continuity-heading', selected, {
|
|
529
|
+
kind: 'continuity',
|
|
530
|
+
id: 'continuity:' + continuity.taskId,
|
|
531
|
+
reason: 'explicit task continuity ledger',
|
|
532
|
+
trust: 'source-backed',
|
|
533
|
+
});
|
|
534
|
+
appendLine(
|
|
535
|
+
lines,
|
|
536
|
+
'- Status: ' + continuity.status + ' | ' + short(continuity.task, 30),
|
|
537
|
+
maxTokens,
|
|
538
|
+
omitted,
|
|
539
|
+
'continuity-task',
|
|
540
|
+
);
|
|
541
|
+
appendLine(
|
|
542
|
+
lines,
|
|
543
|
+
'- Outcome: ' + continuity.outcome.state,
|
|
544
|
+
maxTokens,
|
|
545
|
+
omitted,
|
|
546
|
+
'continuity-outcome',
|
|
547
|
+
selected,
|
|
548
|
+
{
|
|
549
|
+
kind: 'continuity',
|
|
550
|
+
id: 'continuity:' + continuity.taskId,
|
|
551
|
+
reason: 'deterministic task outcome projection',
|
|
552
|
+
trust: 'derived',
|
|
553
|
+
},
|
|
554
|
+
);
|
|
555
|
+
for (const requirement of continuity.requirements.slice(0, 3)) {
|
|
556
|
+
appendLine(lines, '- Requirement: ' + short(requirement, 24), maxTokens, omitted, 'continuity-requirement', selected, {
|
|
557
|
+
kind: 'continuity',
|
|
558
|
+
id: 'continuity:' + continuity.taskId,
|
|
559
|
+
reason: 'explicit task requirement',
|
|
560
|
+
trust: 'source-backed',
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
for (const decision of continuity.decisions.slice(0, 2)) {
|
|
564
|
+
appendLine(lines, '- Decision: ' + short(decision, 24), maxTokens, omitted, 'continuity-decision', selected, {
|
|
565
|
+
kind: 'continuity',
|
|
566
|
+
id: 'continuity:' + continuity.taskId,
|
|
567
|
+
reason: 'explicit task decision',
|
|
568
|
+
trust: 'source-backed',
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
for (const verification of continuity.verification.slice(-3)) {
|
|
572
|
+
appendLine(
|
|
573
|
+
lines,
|
|
574
|
+
'- Verify [' + verification.status + ']: ' + short(verification.content, 22),
|
|
575
|
+
maxTokens,
|
|
576
|
+
omitted,
|
|
577
|
+
'continuity-verification',
|
|
578
|
+
selected,
|
|
579
|
+
{
|
|
580
|
+
kind: 'continuity',
|
|
581
|
+
id: 'continuity:' + continuity.taskId,
|
|
582
|
+
reason: 'task verification state',
|
|
583
|
+
trust: 'source-backed',
|
|
584
|
+
},
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
for (const risk of continuity.risks.slice(0, 2)) {
|
|
588
|
+
appendLine(lines, '- Risk: ' + short(risk, 22), maxTokens, omitted, 'continuity-risk', selected, {
|
|
589
|
+
kind: 'continuity',
|
|
590
|
+
id: 'continuity:' + continuity.taskId,
|
|
591
|
+
reason: 'open task risk',
|
|
592
|
+
trust: 'source-backed',
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
521
597
|
// Always-on block: the small "who you are and what this workspace is
|
|
522
598
|
// doing" context every brief carries, mirroring the memory-native feel of
|
|
523
599
|
// a per-session MEMORY.md. Lines are pre-truncated by the caller; the
|
|
@@ -1114,6 +1190,7 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
|
|
|
1114
1190
|
version: '1.3' as const,
|
|
1115
1191
|
task,
|
|
1116
1192
|
lens: input.lens,
|
|
1193
|
+
...(input.continuity ? { continuity: input.continuity } : {}),
|
|
1117
1194
|
...(input.alwaysOn
|
|
1118
1195
|
&& (
|
|
1119
1196
|
input.alwaysOn.profile.length > 0
|
package/src/llm/provider.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from '../config.js';
|
|
21
21
|
|
|
22
22
|
export interface LLMConfig {
|
|
23
|
-
provider: 'openai' | 'anthropic' | 'openrouter' | 'custom';
|
|
23
|
+
provider: 'openai' | 'anthropic' | 'openrouter' | 'atlascloud' | 'custom';
|
|
24
24
|
apiKey: string;
|
|
25
25
|
model?: string;
|
|
26
26
|
baseUrl?: string;
|
|
@@ -201,6 +201,7 @@ const PROVIDER_DEFAULTS: Record<string, { baseUrl: string; model: string }> = {
|
|
|
201
201
|
openai: { baseUrl: 'https://api.openai.com/v1', model: 'gpt-4.1-nano' },
|
|
202
202
|
anthropic: { baseUrl: 'https://api.anthropic.com/v1', model: 'claude-3-5-haiku-latest' },
|
|
203
203
|
openrouter: { baseUrl: 'https://openrouter.ai/api/v1', model: 'openai/gpt-4.1-nano' },
|
|
204
|
+
atlascloud: { baseUrl: 'https://api.atlascloud.ai/v1', model: 'deepseek-ai/deepseek-v3.2' },
|
|
204
205
|
custom: { baseUrl: 'http://localhost:11434/v1', model: 'llama3' },
|
|
205
206
|
};
|
|
206
207
|
|
package/src/memory/graph.ts
CHANGED
|
@@ -11,18 +11,21 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { Entity, Relation, KnowledgeGraph } from '../types.js';
|
|
14
|
-
import { initGraphStore,
|
|
14
|
+
import { initGraphStore, type GraphSqliteStore } from '../store/graph-store.js';
|
|
15
15
|
|
|
16
16
|
export class KnowledgeGraphManager {
|
|
17
17
|
private entities: Entity[] = [];
|
|
18
18
|
private relations: Relation[] = [];
|
|
19
19
|
private projectDir: string;
|
|
20
|
+
private projectId: string;
|
|
21
|
+
private store: GraphSqliteStore | null = null;
|
|
20
22
|
private initialized = false;
|
|
21
23
|
/** Index: lowercase entity name → Entity for O(1) lookups */
|
|
22
24
|
private entityIndex = new Map<string, Entity>();
|
|
23
25
|
|
|
24
|
-
constructor(projectDir: string) {
|
|
26
|
+
constructor(projectDir: string, projectId = '__default__') {
|
|
25
27
|
this.projectDir = projectDir;
|
|
28
|
+
this.projectId = projectId;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/** Rebuild the entity name index */
|
|
@@ -36,10 +39,9 @@ export class KnowledgeGraphManager {
|
|
|
36
39
|
/** Load graph from SQLite on first access */
|
|
37
40
|
async init(): Promise<void> {
|
|
38
41
|
if (this.initialized) return;
|
|
39
|
-
await initGraphStore(this.projectDir);
|
|
40
|
-
|
|
41
|
-
this.
|
|
42
|
-
this.relations = store.loadRelations();
|
|
42
|
+
this.store = await initGraphStore(this.projectDir, this.projectId);
|
|
43
|
+
this.entities = this.store.loadEntities();
|
|
44
|
+
this.relations = this.store.loadRelations();
|
|
43
45
|
this.rebuildIndex();
|
|
44
46
|
this.initialized = true;
|
|
45
47
|
}
|
|
@@ -56,8 +58,8 @@ export class KnowledgeGraphManager {
|
|
|
56
58
|
|
|
57
59
|
/** Persist current state to SQLite */
|
|
58
60
|
private async save(): Promise<void> {
|
|
59
|
-
|
|
60
|
-
store.replaceAll(this.entities, this.relations);
|
|
61
|
+
if (!this.store) throw new Error('[memorix] KnowledgeGraphManager not initialized');
|
|
62
|
+
this.store.replaceAll(this.entities, this.relations);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
/** Create new entities (skip duplicates by name) */
|
package/src/memory/session.ts
CHANGED
|
@@ -616,7 +616,7 @@ export async function getSessionContext(
|
|
|
616
616
|
let graphNeighbors: string[] = [];
|
|
617
617
|
if (!reader && activeEntities.length > 0) {
|
|
618
618
|
try {
|
|
619
|
-
const graphMgr = new KnowledgeGraphManager(projectDir);
|
|
619
|
+
const graphMgr = new KnowledgeGraphManager(projectDir, projectId);
|
|
620
620
|
await graphMgr.init();
|
|
621
621
|
const { relations } = await graphMgr.readGraph();
|
|
622
622
|
const activeSet = new Set(activeEntities.map((n) => n.toLowerCase()));
|