memorix 1.2.1 → 1.2.3
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 +23 -0
- package/README.md +14 -2
- package/README.zh-CN.md +14 -2
- package/dist/cli/index.js +15424 -13780
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1337 -536
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8458 -8087
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +23 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +1365 -542
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +49 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
- package/docs/API_REFERENCE.md +24 -4
- package/docs/README.md +1 -1
- package/docs/dev-log/progress.txt +101 -11
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/command-guide.ts +192 -0
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/cleanup.ts +5 -1
- package/src/cli/commands/codegraph.ts +15 -5
- package/src/cli/commands/context.ts +3 -2
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/explain.ts +9 -3
- package/src/cli/commands/handoff.ts +21 -7
- package/src/cli/commands/identity.ts +116 -0
- package/src/cli/commands/ingest-image.ts +5 -3
- package/src/cli/commands/lock.ts +11 -10
- package/src/cli/commands/memory.ts +58 -21
- package/src/cli/commands/message.ts +19 -14
- package/src/cli/commands/operator-shared.ts +98 -3
- package/src/cli/commands/poll.ts +16 -6
- package/src/cli/commands/reasoning.ts +17 -3
- package/src/cli/commands/retention.ts +9 -4
- package/src/cli/commands/serve-http.ts +8 -2
- package/src/cli/commands/session.ts +44 -10
- package/src/cli/commands/skills.ts +10 -5
- package/src/cli/commands/status.ts +4 -3
- package/src/cli/commands/task.ts +26 -17
- package/src/cli/commands/team.ts +14 -10
- package/src/cli/commands/transfer.ts +63 -10
- package/src/cli/identity.ts +89 -0
- package/src/cli/index.ts +96 -19
- package/src/cli/invocation.ts +115 -0
- package/src/cli/tui/chat-service.ts +41 -18
- package/src/cli/tui/data.ts +23 -44
- package/src/cli/tui/operator-context.ts +60 -0
- package/src/cli/tui/session-service.ts +3 -2
- package/src/cli/tui/views/MemoryView.tsx +10 -8
- package/src/codegraph/auto-context.ts +31 -2
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/project-context.ts +2 -0
- package/src/compact/engine.ts +26 -10
- package/src/compact/index-format.ts +25 -2
- package/src/dashboard/server.ts +46 -9
- package/src/hooks/admission.ts +117 -0
- package/src/hooks/handler.ts +98 -91
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/workset.ts +179 -10
- package/src/memory/admission.ts +57 -0
- package/src/memory/consolidation.ts +13 -2
- package/src/memory/disclosure-policy.ts +6 -1
- package/src/memory/export-import.ts +11 -3
- package/src/memory/graph-context.ts +8 -2
- package/src/memory/observations.ts +162 -4
- package/src/memory/quality-audit.ts +2 -0
- package/src/memory/retention.ts +22 -2
- package/src/memory/session.ts +29 -11
- package/src/memory/visibility.ts +80 -0
- package/src/orchestrate/memorix-bridge.ts +38 -0
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +18 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/maintenance-runner.ts +2 -0
- package/src/runtime/project-maintenance.ts +89 -0
- package/src/sdk.ts +35 -5
- package/src/server.ts +267 -83
- package/src/store/orama-store.ts +61 -6
- package/src/store/sqlite-db.ts +23 -1
- package/src/store/sqlite-store.ts +12 -2
- package/src/team/handoff.ts +7 -0
- package/src/types.ts +51 -0
- package/src/wiki/generator.ts +2 -0
|
@@ -3,11 +3,11 @@ import { loadDotenv } from '../../config/dotenv-loader.js';
|
|
|
3
3
|
import { initLLM, isLLMEnabled, getLLMConfig, callLLMWithTools } from '../../llm/provider.js';
|
|
4
4
|
import type { ChatMessage, ToolDefinition, ToolCall } from '../../llm/provider.js';
|
|
5
5
|
import { initObservations, prepareSearchIndex, storeObservation, resolveObservations, getObservation, getAllObservations } from '../../memory/observations.js';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import { canManageObservation, filterReadableObservations } from '../../memory/visibility.js';
|
|
7
|
+
import type { ObservationReader, ObservationType } from '../../types.js';
|
|
8
8
|
import { getLastSearchMode } from '../../store/orama-store.js';
|
|
9
|
-
import { getProjectDataDir } from '../../store/persistence.js';
|
|
10
9
|
import type { MemorixDocument } from '../../types.js';
|
|
10
|
+
import { getTuiOperatorContext } from './operator-context.js';
|
|
11
11
|
|
|
12
12
|
export interface ChatHistoryTurn {
|
|
13
13
|
role: 'user' | 'assistant';
|
|
@@ -208,19 +208,29 @@ async function prepareProjectSearch(projectId: string, dataDir: string): Promise
|
|
|
208
208
|
|
|
209
209
|
interface ToolExecutionContext {
|
|
210
210
|
projectId: string;
|
|
211
|
+
reader: ObservationReader;
|
|
212
|
+
writerAgentId?: string;
|
|
211
213
|
collectedSources: ChatSource[];
|
|
212
214
|
}
|
|
213
215
|
|
|
216
|
+
async function resolveTuiContext() {
|
|
217
|
+
try {
|
|
218
|
+
return await getTuiOperatorContext();
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
214
224
|
function executeSearchMemories(args: { query: string; limit?: number }, ctx: ToolExecutionContext): Promise<string> {
|
|
215
225
|
const limit = Math.min(args.limit ?? SEARCH_LIMIT, 10);
|
|
216
|
-
return compactSearch({ query: args.query, limit, projectId: ctx.projectId, status: 'active' })
|
|
226
|
+
return compactSearch({ query: args.query, limit, projectId: ctx.projectId, status: 'active', reader: ctx.reader })
|
|
217
227
|
.then((result) => {
|
|
218
228
|
const entries = result.entries.slice(0, DETAIL_LIMIT);
|
|
219
229
|
if (entries.length === 0) return 'No memories found for that query.';
|
|
220
230
|
|
|
221
231
|
// Collect sources for citation tracking
|
|
222
232
|
const refs = entries.map((e) => ({ id: e.id, projectId: ctx.projectId }));
|
|
223
|
-
return compactDetail(refs).then((detail) => {
|
|
233
|
+
return compactDetail(refs, { reader: ctx.reader }).then((detail) => {
|
|
224
234
|
for (let i = 0; i < detail.documents.length; i++) {
|
|
225
235
|
const doc = detail.documents[i];
|
|
226
236
|
const score = entries[i]?.score ?? 0;
|
|
@@ -235,7 +245,7 @@ function executeSearchMemories(args: { query: string; limit?: number }, ctx: Too
|
|
|
235
245
|
}
|
|
236
246
|
|
|
237
247
|
function executeGetMemoryDetail(args: { id: number }, ctx: ToolExecutionContext): Promise<string> {
|
|
238
|
-
return compactDetail([{ id: args.id, projectId: ctx.projectId }])
|
|
248
|
+
return compactDetail([{ id: args.id, projectId: ctx.projectId }], { reader: ctx.reader })
|
|
239
249
|
.then((detail) => {
|
|
240
250
|
if (detail.documents.length === 0) return `Observation ${args.id} not found.`;
|
|
241
251
|
const doc = detail.documents[0];
|
|
@@ -295,6 +305,8 @@ function executeStoreMemory(
|
|
|
295
305
|
facts: args.facts,
|
|
296
306
|
projectId: ctx.projectId,
|
|
297
307
|
source: 'agent',
|
|
308
|
+
visibilityReader: ctx.reader,
|
|
309
|
+
...(ctx.writerAgentId ? { createdByAgentId: ctx.writerAgentId } : {}),
|
|
298
310
|
}).then((result) => {
|
|
299
311
|
const obs = result.observation;
|
|
300
312
|
ctx.collectedSources.push({
|
|
@@ -314,7 +326,7 @@ function executeUpdateMemory(
|
|
|
314
326
|
ctx: ToolExecutionContext,
|
|
315
327
|
): string {
|
|
316
328
|
const obs = getObservation(args.id, ctx.projectId);
|
|
317
|
-
if (!obs) return `Observation ${args.id} not found.`;
|
|
329
|
+
if (!obs || !canManageObservation(obs, ctx.reader)) return `Observation ${args.id} not found.`;
|
|
318
330
|
|
|
319
331
|
const shouldAppend = args.append !== false; // default true
|
|
320
332
|
const newNarrative = shouldAppend
|
|
@@ -334,6 +346,7 @@ function executeUpdateMemory(
|
|
|
334
346
|
projectId: ctx.projectId,
|
|
335
347
|
topicKey: obs.topicKey,
|
|
336
348
|
source: 'agent',
|
|
349
|
+
visibilityReader: ctx.reader,
|
|
337
350
|
}).catch(() => {/* non-blocking */});
|
|
338
351
|
|
|
339
352
|
return `Updated observation [obs:${args.id}] — ${shouldAppend ? 'appended to' : 'replaced'} narrative${args.facts?.length ? ` and added ${args.facts.length} facts` : ''}. Changes will be persisted asynchronously.`;
|
|
@@ -344,7 +357,7 @@ function executeDeleteMemory(
|
|
|
344
357
|
ctx: ToolExecutionContext,
|
|
345
358
|
): Promise<string> {
|
|
346
359
|
const obs = getObservation(args.id, ctx.projectId);
|
|
347
|
-
if (!obs) return Promise.resolve(`Observation ${args.id} not found.`);
|
|
360
|
+
if (!obs || !canManageObservation(obs, ctx.reader)) return Promise.resolve(`Observation ${args.id} not found.`);
|
|
348
361
|
|
|
349
362
|
return resolveObservations([args.id], 'resolved')
|
|
350
363
|
.then((result) => {
|
|
@@ -361,8 +374,10 @@ function executeListRecentMemories(
|
|
|
361
374
|
ctx: ToolExecutionContext,
|
|
362
375
|
): string {
|
|
363
376
|
const limit = Math.min(args.limit ?? 10, 20);
|
|
364
|
-
let allObs =
|
|
365
|
-
.filter(o => o.projectId === ctx.projectId && o.status !== 'archived' && o.status !== 'resolved')
|
|
377
|
+
let allObs = filterReadableObservations(
|
|
378
|
+
getAllObservations().filter(o => o.projectId === ctx.projectId && o.status !== 'archived' && o.status !== 'resolved'),
|
|
379
|
+
ctx.reader,
|
|
380
|
+
);
|
|
366
381
|
|
|
367
382
|
if (args.type) {
|
|
368
383
|
allObs = allObs.filter(o => o.type === args.type);
|
|
@@ -399,10 +414,10 @@ export async function askMemoryQuestion(
|
|
|
399
414
|
};
|
|
400
415
|
}
|
|
401
416
|
|
|
402
|
-
const
|
|
417
|
+
const operatorContext = await resolveTuiContext();
|
|
403
418
|
|
|
404
419
|
// No project + no LLM → dead end
|
|
405
|
-
if (!
|
|
420
|
+
if (!operatorContext) {
|
|
406
421
|
loadDotenv(process.cwd());
|
|
407
422
|
initLLM({ scope: 'agent' });
|
|
408
423
|
if (!isLLMEnabled()) {
|
|
@@ -438,10 +453,11 @@ export async function askMemoryQuestion(
|
|
|
438
453
|
};
|
|
439
454
|
}
|
|
440
455
|
|
|
456
|
+
const { project, dataDir, reader, identity } = operatorContext;
|
|
457
|
+
|
|
441
458
|
loadDotenv(project.rootPath);
|
|
442
459
|
initLLM({ scope: 'agent' });
|
|
443
460
|
|
|
444
|
-
const dataDir = await getProjectDataDir(project.id);
|
|
445
461
|
await prepareProjectSearch(project.id, dataDir);
|
|
446
462
|
|
|
447
463
|
const searchMode = normalizeSearchMode(getLastSearchMode(project.id) || 'fulltext');
|
|
@@ -453,11 +469,12 @@ export async function askMemoryQuestion(
|
|
|
453
469
|
limit: SEARCH_LIMIT,
|
|
454
470
|
projectId: project.id,
|
|
455
471
|
status: 'active',
|
|
472
|
+
reader,
|
|
456
473
|
});
|
|
457
474
|
const topEntries = searchResult.entries.slice(0, DETAIL_LIMIT);
|
|
458
475
|
const detailRefs = topEntries.map((entry) => ({ id: entry.id, projectId: project.id }));
|
|
459
476
|
const detailResult = detailRefs.length > 0
|
|
460
|
-
? await compactDetail(detailRefs)
|
|
477
|
+
? await compactDetail(detailRefs, { reader })
|
|
461
478
|
: { documents: [], formatted: '', totalTokens: 0 };
|
|
462
479
|
const sources = detailResult.documents.map((doc, index) => toSource(doc, topEntries[index]?.score ?? 0));
|
|
463
480
|
|
|
@@ -488,6 +505,8 @@ export async function askMemoryQuestion(
|
|
|
488
505
|
|
|
489
506
|
const ctx: ToolExecutionContext = {
|
|
490
507
|
projectId: project.id,
|
|
508
|
+
reader,
|
|
509
|
+
...(identity ? { writerAgentId: identity.agentId } : {}),
|
|
491
510
|
collectedSources: [],
|
|
492
511
|
};
|
|
493
512
|
|
|
@@ -584,10 +603,10 @@ export async function askMemoryQuestionStream(
|
|
|
584
603
|
};
|
|
585
604
|
}
|
|
586
605
|
|
|
587
|
-
const
|
|
606
|
+
const operatorContext = await resolveTuiContext();
|
|
588
607
|
|
|
589
608
|
// No project + no LLM → dead end
|
|
590
|
-
if (!
|
|
609
|
+
if (!operatorContext) {
|
|
591
610
|
loadDotenv(process.cwd());
|
|
592
611
|
initLLM({ scope: 'agent' });
|
|
593
612
|
if (!isLLMEnabled()) {
|
|
@@ -626,10 +645,11 @@ export async function askMemoryQuestionStream(
|
|
|
626
645
|
};
|
|
627
646
|
}
|
|
628
647
|
|
|
648
|
+
const { project, dataDir, reader, identity } = operatorContext;
|
|
649
|
+
|
|
629
650
|
loadDotenv(project.rootPath);
|
|
630
651
|
initLLM({ scope: 'agent' });
|
|
631
652
|
|
|
632
|
-
const dataDir = await getProjectDataDir(project.id);
|
|
633
653
|
await prepareProjectSearch(project.id, dataDir);
|
|
634
654
|
|
|
635
655
|
const searchMode = normalizeSearchMode(getLastSearchMode(project.id) || 'fulltext');
|
|
@@ -641,11 +661,12 @@ export async function askMemoryQuestionStream(
|
|
|
641
661
|
limit: SEARCH_LIMIT,
|
|
642
662
|
projectId: project.id,
|
|
643
663
|
status: 'active',
|
|
664
|
+
reader,
|
|
644
665
|
});
|
|
645
666
|
const topEntries = searchResult.entries.slice(0, DETAIL_LIMIT);
|
|
646
667
|
const detailRefs = topEntries.map((entry) => ({ id: entry.id, projectId: project.id }));
|
|
647
668
|
const detailResult = detailRefs.length > 0
|
|
648
|
-
? await compactDetail(detailRefs)
|
|
669
|
+
? await compactDetail(detailRefs, { reader })
|
|
649
670
|
: { documents: [], formatted: '', totalTokens: 0 };
|
|
650
671
|
const sources = detailResult.documents.map((doc, index) => toSource(doc, topEntries[index]?.score ?? 0));
|
|
651
672
|
|
|
@@ -672,6 +693,8 @@ export async function askMemoryQuestionStream(
|
|
|
672
693
|
|
|
673
694
|
const ctx: ToolExecutionContext = {
|
|
674
695
|
projectId: project.id,
|
|
696
|
+
reader,
|
|
697
|
+
...(identity ? { writerAgentId: identity.agentId } : {}),
|
|
675
698
|
collectedSources: [],
|
|
676
699
|
};
|
|
677
700
|
|
package/src/cli/tui/data.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as fs from 'node:fs';
|
|
9
|
+
import { getTuiOperatorContext } from './operator-context.js';
|
|
9
10
|
|
|
10
11
|
export interface ProjectInfo {
|
|
11
12
|
name: string;
|
|
@@ -98,9 +99,7 @@ function truncate(text: string, max = 60): string {
|
|
|
98
99
|
|
|
99
100
|
export async function getProjectInfo(): Promise<ProjectInfo | null> {
|
|
100
101
|
try {
|
|
101
|
-
const {
|
|
102
|
-
const proj = detectProject(process.cwd());
|
|
103
|
-
if (!proj) return null;
|
|
102
|
+
const { project: proj } = await getTuiOperatorContext();
|
|
104
103
|
return {
|
|
105
104
|
name: proj.name,
|
|
106
105
|
id: proj.id,
|
|
@@ -127,12 +126,8 @@ export async function getHealthInfo(projectId?: string): Promise<HealthInfo> {
|
|
|
127
126
|
};
|
|
128
127
|
|
|
129
128
|
try {
|
|
130
|
-
const { detectProject } = await import('../../project/detector.js');
|
|
131
|
-
const { getProjectDataDir } = await import('../../store/persistence.js');
|
|
132
129
|
const { initObservationStore: initStore, getObservationStore: getStore } = await import('../../store/obs-store.js');
|
|
133
|
-
|
|
134
|
-
const proj = detectProject(process.cwd());
|
|
135
|
-
if (!proj) return defaults;
|
|
130
|
+
const { project: proj, dataDir, reader } = await getTuiOperatorContext();
|
|
136
131
|
|
|
137
132
|
// Load .env BEFORE any process.env reads or provider initialization (#46)
|
|
138
133
|
try {
|
|
@@ -141,11 +136,11 @@ export async function getHealthInfo(projectId?: string): Promise<HealthInfo> {
|
|
|
141
136
|
} catch { /* best-effort */ }
|
|
142
137
|
|
|
143
138
|
const effectiveProjectId = projectId || proj.id;
|
|
144
|
-
|
|
139
|
+
if (effectiveProjectId !== proj.id) return defaults;
|
|
145
140
|
await initStore(dataDir);
|
|
146
141
|
const allObs = (await getStore().loadAll()) as any[];
|
|
147
|
-
|
|
148
|
-
const obs = allObs
|
|
142
|
+
const { filterReadableObservations } = await import('../../memory/visibility.js');
|
|
143
|
+
const obs = filterReadableObservations(allObs, reader);
|
|
149
144
|
const active = obs.filter((o: any) => (o.status ?? 'active') === 'active');
|
|
150
145
|
|
|
151
146
|
defaults.totalMemories = obs.length;
|
|
@@ -219,19 +214,15 @@ export async function getHealthInfo(projectId?: string): Promise<HealthInfo> {
|
|
|
219
214
|
|
|
220
215
|
export async function getRecentMemories(limit = 8, projectId?: string): Promise<MemoryItem[]> {
|
|
221
216
|
try {
|
|
222
|
-
const { detectProject } = await import('../../project/detector.js');
|
|
223
|
-
const { getProjectDataDir } = await import('../../store/persistence.js');
|
|
224
217
|
const { initObservationStore: initStore, getObservationStore: getStore } = await import('../../store/obs-store.js');
|
|
225
|
-
|
|
226
|
-
const proj = detectProject(process.cwd());
|
|
227
|
-
if (!proj) return [];
|
|
218
|
+
const { project: proj, dataDir, reader } = await getTuiOperatorContext();
|
|
228
219
|
|
|
229
220
|
const effectiveProjectId = projectId || proj.id;
|
|
230
|
-
|
|
221
|
+
if (effectiveProjectId !== proj.id) return [];
|
|
231
222
|
await initStore(dataDir);
|
|
232
223
|
const allObs = (await getStore().loadAll()) as any[];
|
|
233
|
-
|
|
234
|
-
const projectObs = allObs
|
|
224
|
+
const { filterReadableObservations } = await import('../../memory/visibility.js');
|
|
225
|
+
const projectObs = filterReadableObservations(allObs, reader);
|
|
235
226
|
const active = projectObs.filter((o: any) => (o.status ?? 'active') === 'active');
|
|
236
227
|
const filtered = active.filter((o: any) => !/^(Ran:|Command:|Executed:)\s/i.test(o.title || ''));
|
|
237
228
|
|
|
@@ -251,18 +242,13 @@ export async function getRecentMemories(limit = 8, projectId?: string): Promise<
|
|
|
251
242
|
export async function searchMemories(query: string, limit = 10): Promise<SearchResult[]> {
|
|
252
243
|
try {
|
|
253
244
|
const { searchObservations } = await import('../../store/orama-store.js');
|
|
254
|
-
const { getProjectDataDir } = await import('../../store/persistence.js');
|
|
255
|
-
const { detectProject } = await import('../../project/detector.js');
|
|
256
245
|
const { initObservations, prepareSearchIndex } = await import('../../memory/observations.js');
|
|
246
|
+
const { project: proj, dataDir, reader } = await getTuiOperatorContext();
|
|
257
247
|
|
|
258
|
-
const proj = detectProject(process.cwd());
|
|
259
|
-
if (!proj) return [];
|
|
260
|
-
|
|
261
|
-
const dataDir = await getProjectDataDir(proj.id);
|
|
262
248
|
await initObservations(dataDir);
|
|
263
249
|
await prepareSearchIndex();
|
|
264
250
|
|
|
265
|
-
const results = await searchObservations({ query, limit, projectId: proj.id });
|
|
251
|
+
const results = await searchObservations({ query, limit, projectId: proj.id, reader });
|
|
266
252
|
|
|
267
253
|
const typeIcons: Record<string, string> = {
|
|
268
254
|
gotcha: '!',
|
|
@@ -290,15 +276,10 @@ export async function searchMemories(query: string, limit = 10): Promise<SearchR
|
|
|
290
276
|
|
|
291
277
|
export async function storeQuickMemory(text: string): Promise<{ id: number; title: string } | null> {
|
|
292
278
|
try {
|
|
293
|
-
const { detectProject } = await import('../../project/detector.js');
|
|
294
|
-
const { getProjectDataDir } = await import('../../store/persistence.js');
|
|
295
279
|
const { initObservations, storeObservation } = await import('../../memory/observations.js');
|
|
296
280
|
const { initObservationStore } = await import('../../store/obs-store.js');
|
|
281
|
+
const { project: proj, dataDir, reader, identity } = await getTuiOperatorContext();
|
|
297
282
|
|
|
298
|
-
const proj = detectProject(process.cwd());
|
|
299
|
-
if (!proj) return null;
|
|
300
|
-
|
|
301
|
-
const dataDir = await getProjectDataDir(proj.id);
|
|
302
283
|
await initObservationStore(dataDir);
|
|
303
284
|
await initObservations(dataDir);
|
|
304
285
|
|
|
@@ -310,6 +291,8 @@ export async function storeQuickMemory(text: string): Promise<{ id: number; titl
|
|
|
310
291
|
facts: [],
|
|
311
292
|
projectId: proj.id,
|
|
312
293
|
sourceDetail: 'explicit',
|
|
294
|
+
visibilityReader: reader,
|
|
295
|
+
...(identity ? { createdByAgentId: identity.agentId } : {}),
|
|
313
296
|
});
|
|
314
297
|
|
|
315
298
|
return { id: result.observation.id, title: text.slice(0, 100) };
|
|
@@ -484,23 +467,21 @@ export async function getKnowledgeGraph(
|
|
|
484
467
|
projectId?: string,
|
|
485
468
|
): Promise<import('../../wiki/types.js').ProjectKnowledgeGraph | null> {
|
|
486
469
|
try {
|
|
487
|
-
const { detectProject } = await import('../../project/detector.js');
|
|
488
|
-
const { getProjectDataDir } = await import('../../store/persistence.js');
|
|
489
470
|
const { initObservationStore } = await import('../../store/obs-store.js');
|
|
490
471
|
const { initObservations, getAllObservations } = await import('../../memory/observations.js');
|
|
472
|
+
const { filterReadableObservations } = await import('../../memory/visibility.js');
|
|
491
473
|
const { initMiniSkillStore, getMiniSkillStore } = await import('../../store/mini-skill-store.js');
|
|
492
474
|
const { generateKnowledgeGraph } = await import('../../wiki/knowledge-graph.js');
|
|
493
475
|
|
|
494
|
-
const proj =
|
|
495
|
-
if (!proj) return null;
|
|
476
|
+
const { project: proj, dataDir, reader } = await getTuiOperatorContext();
|
|
496
477
|
|
|
497
478
|
const effectiveProjectId = projectId || proj.id;
|
|
498
|
-
|
|
479
|
+
if (effectiveProjectId !== proj.id) return null;
|
|
499
480
|
await initObservationStore(dataDir);
|
|
500
481
|
await initObservations(dataDir);
|
|
501
482
|
await initMiniSkillStore(dataDir);
|
|
502
483
|
|
|
503
|
-
const observations = getAllObservations();
|
|
484
|
+
const observations = filterReadableObservations(getAllObservations(), reader);
|
|
504
485
|
const skills = await getMiniSkillStore().loadByProject(effectiveProjectId);
|
|
505
486
|
|
|
506
487
|
return generateKnowledgeGraph({
|
|
@@ -515,23 +496,21 @@ export async function getKnowledgeGraph(
|
|
|
515
496
|
|
|
516
497
|
export async function getKnowledgeBase(projectId?: string): Promise<import('../../wiki/types.js').ProjectKnowledgeOverview | null> {
|
|
517
498
|
try {
|
|
518
|
-
const { detectProject } = await import('../../project/detector.js');
|
|
519
|
-
const { getProjectDataDir } = await import('../../store/persistence.js');
|
|
520
499
|
const { initObservationStore } = await import('../../store/obs-store.js');
|
|
521
500
|
const { initObservations, getAllObservations } = await import('../../memory/observations.js');
|
|
501
|
+
const { filterReadableObservations } = await import('../../memory/visibility.js');
|
|
522
502
|
const { initMiniSkillStore, getMiniSkillStore } = await import('../../store/mini-skill-store.js');
|
|
523
503
|
const { generateKnowledgeBase } = await import('../../wiki/generator.js');
|
|
524
504
|
|
|
525
|
-
const proj =
|
|
526
|
-
if (!proj) return null;
|
|
505
|
+
const { project: proj, dataDir, reader } = await getTuiOperatorContext();
|
|
527
506
|
|
|
528
507
|
const effectiveProjectId = projectId || proj.id;
|
|
529
|
-
|
|
508
|
+
if (effectiveProjectId !== proj.id) return null;
|
|
530
509
|
await initObservationStore(dataDir);
|
|
531
510
|
await initObservations(dataDir);
|
|
532
511
|
await initMiniSkillStore(dataDir);
|
|
533
512
|
|
|
534
|
-
const observations = getAllObservations();
|
|
513
|
+
const observations = filterReadableObservations(getAllObservations(), reader);
|
|
535
514
|
const skills = await getMiniSkillStore().loadByProject(effectiveProjectId);
|
|
536
515
|
|
|
537
516
|
return generateKnowledgeBase({
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { initTeamStore } from '../../team/team-store.js';
|
|
2
|
+
import { loadCliIdentity, resolveCliIdentity } from '../identity.js';
|
|
3
|
+
import { getCliInvocation } from '../invocation.js';
|
|
4
|
+
import type { CliProjectContext } from '../commands/operator-shared.js';
|
|
5
|
+
|
|
6
|
+
export type TuiOperatorContext = Omit<CliProjectContext, 'teamStore'>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The Ink workbench is another CLI entry point, not an alternate security
|
|
10
|
+
* model. It uses the same identity resolver, but avoids opening coordination
|
|
11
|
+
* state until an actor was explicitly selected.
|
|
12
|
+
*/
|
|
13
|
+
export async function getTuiOperatorContext(): Promise<TuiOperatorContext> {
|
|
14
|
+
const invocation = getCliInvocation();
|
|
15
|
+
const { detectProject } = await import('../../project/detector.js');
|
|
16
|
+
const project = detectProject(
|
|
17
|
+
invocation.projectRoot
|
|
18
|
+
?? process.env.MEMORIX_PROJECT_ROOT
|
|
19
|
+
?? process.cwd(),
|
|
20
|
+
);
|
|
21
|
+
if (!project) throw new Error('No git repository found in the current directory.');
|
|
22
|
+
|
|
23
|
+
const { getProjectDataDir } = await import('../../store/persistence.js');
|
|
24
|
+
const dataDir = await getProjectDataDir(project.id);
|
|
25
|
+
try {
|
|
26
|
+
const { MaintenanceTargetStore } = await import('../../runtime/maintenance-targets.js');
|
|
27
|
+
new MaintenanceTargetStore(dataDir).register({
|
|
28
|
+
projectId: project.id,
|
|
29
|
+
projectRoot: project.rootPath,
|
|
30
|
+
dataDir,
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
// Workbench reads remain available when optional maintenance metadata fails.
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const savedIdentity = await loadCliIdentity(dataDir, project.id);
|
|
37
|
+
if (!invocation.actorId && !savedIdentity) {
|
|
38
|
+
return {
|
|
39
|
+
project,
|
|
40
|
+
dataDir,
|
|
41
|
+
reader: { projectId: project.id },
|
|
42
|
+
identity: null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const teamStore = await initTeamStore(dataDir);
|
|
47
|
+
const resolved = await resolveCliIdentity({
|
|
48
|
+
project,
|
|
49
|
+
dataDir,
|
|
50
|
+
teamStore,
|
|
51
|
+
explicitActorId: invocation.actorId,
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
project,
|
|
55
|
+
dataDir,
|
|
56
|
+
reader: resolved.reader,
|
|
57
|
+
identity: resolved.identity,
|
|
58
|
+
...(resolved.warning ? { identityWarning: resolved.warning } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -53,12 +53,13 @@ export async function getSessionState(projectId?: string): Promise<SessionState>
|
|
|
53
53
|
|
|
54
54
|
export async function bindSession(): Promise<SessionState> {
|
|
55
55
|
try {
|
|
56
|
-
const
|
|
57
|
-
|
|
56
|
+
const { getTuiOperatorContext } = await import('./operator-context.js');
|
|
57
|
+
const { project: proj, reader } = await getTuiOperatorContext();
|
|
58
58
|
|
|
59
59
|
const { startSession } = await import('../../memory/session.js');
|
|
60
60
|
const { session, previousContext } = await startSession(proj.rootPath, proj.id, {
|
|
61
61
|
agent: 'memorix-tui',
|
|
62
|
+
reader,
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
return {
|
|
@@ -65,21 +65,23 @@ export function MemoryView({ projectId, focusRefId, selectedIdx, onNavigateKnowl
|
|
|
65
65
|
(async () => {
|
|
66
66
|
try {
|
|
67
67
|
setLoading(true);
|
|
68
|
-
const {
|
|
69
|
-
const {
|
|
68
|
+
const { initObservations, getAllObservations } = await import('../../../memory/observations.js');
|
|
69
|
+
const { filterReadableObservations } = await import('../../../memory/visibility.js');
|
|
70
70
|
const { initObservationStore } = await import('../../../store/obs-store.js');
|
|
71
|
-
const {
|
|
71
|
+
const { getTuiOperatorContext } = await import('../operator-context.js');
|
|
72
72
|
|
|
73
|
-
const proj =
|
|
74
|
-
if (!proj) return;
|
|
73
|
+
const { project: proj, dataDir, reader } = await getTuiOperatorContext();
|
|
75
74
|
|
|
76
|
-
const
|
|
75
|
+
const effectiveProjectId = projectId || proj.id;
|
|
76
|
+
if (effectiveProjectId !== proj.id) return;
|
|
77
77
|
await initObservationStore(dataDir);
|
|
78
78
|
await initObservations(dataDir);
|
|
79
79
|
|
|
80
80
|
const obsId = parseInt(idMatch[1], 10);
|
|
81
|
-
const
|
|
82
|
-
|
|
81
|
+
const obs = filterReadableObservations(
|
|
82
|
+
getAllObservations().filter((observation) => observation.projectId === effectiveProjectId),
|
|
83
|
+
reader,
|
|
84
|
+
).find((observation) => observation.id === obsId);
|
|
83
85
|
if (obs) {
|
|
84
86
|
setDetailItem({
|
|
85
87
|
id: obs.id,
|
|
@@ -2,6 +2,7 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getResolvedConfig } from '../config/resolved-config.js';
|
|
4
4
|
import { buildTaskWorkset, type TaskWorkset, type WorksetCaution } from '../knowledge/workset.js';
|
|
5
|
+
import type { ContextDeliveryTarget } from '../knowledge/context-assembly.js';
|
|
5
6
|
import type { ProjectInfo } from '../types.js';
|
|
6
7
|
import { backfillMissingObservationCodeRefs, type CodeRefBackfillResult } from './binder.js';
|
|
7
8
|
import { collectCurrentProjectFacts, formatGitFact, type CurrentProjectFacts } from './current-facts.js';
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
type ProjectContextOverview,
|
|
20
21
|
} from './project-context.js';
|
|
21
22
|
import { CodeGraphStore } from './store.js';
|
|
23
|
+
import { isEligibleForAutomaticDelivery } from '../memory/admission.js';
|
|
22
24
|
import {
|
|
23
25
|
lensPathCandidates,
|
|
24
26
|
lensVerificationHints,
|
|
@@ -71,6 +73,14 @@ function activeProjectObservations(
|
|
|
71
73
|
return observations.filter(obs => obs.projectId === projectId && (obs.status ?? 'active') === 'active');
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
function deliveryEligibleProjectObservations(
|
|
77
|
+
observations: ProjectContextObservation[],
|
|
78
|
+
projectId: string,
|
|
79
|
+
): ProjectContextObservation[] {
|
|
80
|
+
return activeProjectObservations(observations, projectId)
|
|
81
|
+
.filter((observation) => isEligibleForAutomaticDelivery(observation));
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
function decideRefresh(input: {
|
|
75
85
|
mode: AutoContextRefreshMode;
|
|
76
86
|
status: ReturnType<CodeGraphStore['status']>;
|
|
@@ -118,6 +128,8 @@ export async function buildAutoProjectContext(input: {
|
|
|
118
128
|
* request. MCP and hook callers use this to keep their response path fast.
|
|
119
129
|
*/
|
|
120
130
|
enqueueRefresh?: () => void | Promise<void>;
|
|
131
|
+
/** The caller surface is recorded in the Workset receipt, not its prompt. */
|
|
132
|
+
deliveryTarget?: ContextDeliveryTarget;
|
|
121
133
|
}): Promise<AutoProjectContext> {
|
|
122
134
|
const refreshMode = input.refresh ?? 'auto';
|
|
123
135
|
const now = input.now ?? new Date();
|
|
@@ -173,13 +185,27 @@ export async function buildAutoProjectContext(input: {
|
|
|
173
185
|
activeProjectObservations(input.observations, input.project.id) as any,
|
|
174
186
|
);
|
|
175
187
|
try {
|
|
176
|
-
const {
|
|
188
|
+
const { MaintenanceTargetStore } = await import('../runtime/maintenance-targets.js');
|
|
189
|
+
new MaintenanceTargetStore(input.dataDir).register({
|
|
190
|
+
projectId: input.project.id,
|
|
191
|
+
projectRoot: input.project.rootPath,
|
|
192
|
+
dataDir: input.dataDir,
|
|
193
|
+
});
|
|
194
|
+
const {
|
|
195
|
+
enqueueClaimRequalification,
|
|
196
|
+
enqueueObservationQualification,
|
|
197
|
+
} = await import('../runtime/lifecycle.js');
|
|
177
198
|
enqueueClaimRequalification({
|
|
178
199
|
dataDir: input.dataDir,
|
|
179
200
|
projectId: input.project.id,
|
|
180
201
|
source: 'foreground-refresh',
|
|
181
202
|
snapshotId: store.latestSnapshot(input.project.id)?.id,
|
|
182
203
|
});
|
|
204
|
+
enqueueObservationQualification({
|
|
205
|
+
dataDir: input.dataDir,
|
|
206
|
+
projectId: input.project.id,
|
|
207
|
+
source: 'foreground-refresh',
|
|
208
|
+
});
|
|
183
209
|
} catch {
|
|
184
210
|
// The completed scan remains useful even if its later maintenance cannot queue.
|
|
185
211
|
}
|
|
@@ -198,7 +224,9 @@ export async function buildAutoProjectContext(input: {
|
|
|
198
224
|
const explain = buildProjectContextExplain({
|
|
199
225
|
project: input.project,
|
|
200
226
|
store,
|
|
201
|
-
|
|
227
|
+
// Binding sees all active evidence, but automatic delivery sees only
|
|
228
|
+
// evidence that has passed the control-plane admission gate.
|
|
229
|
+
observations: deliveryEligibleProjectObservations(input.observations, input.project.id),
|
|
202
230
|
exclude,
|
|
203
231
|
});
|
|
204
232
|
const overview = explain.overview;
|
|
@@ -300,6 +328,7 @@ export async function buildAutoProjectContext(input: {
|
|
|
300
328
|
stale: overview.freshness.stale,
|
|
301
329
|
},
|
|
302
330
|
runtimeCautions,
|
|
331
|
+
...(input.deliveryTarget ? { deliveryTarget: input.deliveryTarget } : {}),
|
|
303
332
|
});
|
|
304
333
|
|
|
305
334
|
return {
|
|
@@ -316,6 +316,7 @@ export async function attachTaskWorkset(input: {
|
|
|
316
316
|
stale: input.pack.warnings.filter(warning => warning.status === 'stale').length,
|
|
317
317
|
},
|
|
318
318
|
runtimeCautions: input.runtimeCautions,
|
|
319
|
+
deliveryTarget: 'context-pack',
|
|
319
320
|
});
|
|
320
321
|
return { ...input.pack, workset };
|
|
321
322
|
}
|
|
@@ -16,6 +16,8 @@ export interface ProjectContextObservation {
|
|
|
16
16
|
title: string;
|
|
17
17
|
type: string;
|
|
18
18
|
status?: string;
|
|
19
|
+
/** Automatic capture must be qualified before it can source a project brief. */
|
|
20
|
+
admissionState?: 'ephemeral' | 'candidate' | 'qualified';
|
|
19
21
|
createdAt?: string;
|
|
20
22
|
updatedAt?: string;
|
|
21
23
|
/** Explicit file paths recorded with a memory, used only before Code Memory exists. */
|