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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { storeObservation } from '../../memory/observations.js';
|
|
3
3
|
import { createHandoffArtifact } from '../../team/handoff.js';
|
|
4
|
-
import { emitError, emitResult, getCliProjectContext, parseCsvList, shortId } from './operator-shared.js';
|
|
4
|
+
import { emitError, emitResult, getCliProjectContext, parseCsvList, resolveCliActorId, shortId } from './operator-shared.js';
|
|
5
5
|
|
|
6
6
|
export default defineCommand({
|
|
7
7
|
meta: {
|
|
@@ -23,19 +23,33 @@ export default defineCommand({
|
|
|
23
23
|
const asJson = !!args.json;
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
const { project, teamStore } = await getCliProjectContext();
|
|
26
|
+
const { project, teamStore, identity } = await getCliProjectContext();
|
|
27
|
+
const fromAgentId = resolveCliActorId(args.fromAgentId, identity, 'fromAgentId');
|
|
27
28
|
|
|
28
29
|
switch (action) {
|
|
29
30
|
case 'send': {
|
|
30
|
-
if (!
|
|
31
|
-
emitError('
|
|
31
|
+
if (!fromAgentId || !args.summary || !args.context) {
|
|
32
|
+
emitError('summary, context, and an active CLI identity or fromAgentId are required for "memorix handoff send"', asJson);
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
35
|
+
const sender = teamStore.getAgent(fromAgentId);
|
|
36
|
+
if (!sender || sender.project_id !== project.id || sender.status !== 'active') {
|
|
37
|
+
emitError('The handoff sender must be an active coordination member of this project.', asJson);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const toAgentId = (args.toAgentId as string | undefined)?.trim();
|
|
41
|
+
if (toAgentId) {
|
|
42
|
+
const recipient = teamStore.getAgent(toAgentId);
|
|
43
|
+
if (!recipient || recipient.project_id !== project.id || recipient.status !== 'active') {
|
|
44
|
+
emitError('The handoff recipient must be an active coordination member of this project.', asJson);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
34
48
|
const result = await createHandoffArtifact(
|
|
35
49
|
{
|
|
36
50
|
projectId: project.id,
|
|
37
|
-
fromAgentId
|
|
38
|
-
toAgentId
|
|
51
|
+
fromAgentId,
|
|
52
|
+
toAgentId,
|
|
39
53
|
taskId: args.taskId as string | undefined,
|
|
40
54
|
summary: args.summary as string,
|
|
41
55
|
context: args.context as string,
|
|
@@ -57,7 +71,7 @@ export default defineCommand({
|
|
|
57
71
|
console.log('Memorix Handoff Commands');
|
|
58
72
|
console.log('');
|
|
59
73
|
console.log('Usage:');
|
|
60
|
-
console.log(' memorix handoff send --
|
|
74
|
+
console.log(' memorix handoff send --summary "..." --context "..." [--fromAgentId <id>] [--toAgentId <id>] [--taskId <id>]');
|
|
61
75
|
}
|
|
62
76
|
} catch (error) {
|
|
63
77
|
emitError(error instanceof Error ? error.message : String(error), asJson);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { AGENT_TYPE_ROLE_MAP } from '../../team/team-store.js';
|
|
3
|
+
import { clearCliIdentity, loadCliIdentity, saveCliIdentity } from '../identity.js';
|
|
4
|
+
import { emitError, emitResult, getCliProjectContext, parseCsvList } from './operator-shared.js';
|
|
5
|
+
|
|
6
|
+
function requiredText(value: unknown, label: string): string {
|
|
7
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
8
|
+
if (!text) throw new Error(`${label} is required`);
|
|
9
|
+
return text;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default defineCommand({
|
|
13
|
+
meta: {
|
|
14
|
+
name: 'identity',
|
|
15
|
+
description: 'Select the explicit CLI actor used for private or team-scoped operations',
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
agentId: { type: 'string', description: 'Existing active coordination agent ID' },
|
|
19
|
+
agentType: { type: 'string', description: 'Agent type when joining (for example codex)' },
|
|
20
|
+
instanceId: { type: 'string', description: 'Stable instance identity when joining' },
|
|
21
|
+
name: { type: 'string', description: 'Optional display name when joining' },
|
|
22
|
+
role: { type: 'string', description: 'Optional coordination role when joining' },
|
|
23
|
+
capabilities: { type: 'string', description: 'Comma-separated capability list when joining' },
|
|
24
|
+
json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
|
|
25
|
+
},
|
|
26
|
+
run: async ({ args }) => {
|
|
27
|
+
const action = ((args._ as string[])?.[0] || 'status').toLowerCase();
|
|
28
|
+
const asJson = !!args.json;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const { project, dataDir, teamStore, identity, reader, identityWarning } = await getCliProjectContext();
|
|
32
|
+
|
|
33
|
+
switch (action) {
|
|
34
|
+
case 'status': {
|
|
35
|
+
const saved = await loadCliIdentity(dataDir, project.id);
|
|
36
|
+
const agent = saved ? teamStore.getAgent(saved.agentId) : undefined;
|
|
37
|
+
emitResult(
|
|
38
|
+
{
|
|
39
|
+
project,
|
|
40
|
+
identity,
|
|
41
|
+
savedIdentity: saved,
|
|
42
|
+
agent: agent ?? null,
|
|
43
|
+
reader,
|
|
44
|
+
...(identityWarning ? { warning: identityWarning } : {}),
|
|
45
|
+
},
|
|
46
|
+
identity
|
|
47
|
+
? `CLI identity: ${agent?.name ?? identity.agentId} (${identity.agentId})`
|
|
48
|
+
: identityWarning ?? 'CLI identity: project-scoped only. Use "memorix identity join" or "memorix identity use --agent-id <id>" for private/team operations.',
|
|
49
|
+
asJson,
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case 'join': {
|
|
55
|
+
const agentType = requiredText(args.agentType, 'agentType');
|
|
56
|
+
const agent = teamStore.registerAgent({
|
|
57
|
+
projectId: project.id,
|
|
58
|
+
agentType,
|
|
59
|
+
instanceId: args.instanceId as string | undefined,
|
|
60
|
+
name: args.name as string | undefined,
|
|
61
|
+
role: (args.role as string | undefined) || AGENT_TYPE_ROLE_MAP[agentType] || 'engineer',
|
|
62
|
+
capabilities: parseCsvList(args.capabilities as string | undefined),
|
|
63
|
+
});
|
|
64
|
+
const nextIdentity = {
|
|
65
|
+
agentId: agent.agent_id,
|
|
66
|
+
projectId: project.id,
|
|
67
|
+
activatedAt: new Date().toISOString(),
|
|
68
|
+
};
|
|
69
|
+
await saveCliIdentity(dataDir, nextIdentity);
|
|
70
|
+
emitResult(
|
|
71
|
+
{ project, identity: nextIdentity, agent },
|
|
72
|
+
`CLI identity activated: ${agent.name} (${agent.agent_id})`,
|
|
73
|
+
asJson,
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case 'use': {
|
|
79
|
+
const agentId = requiredText(args.agentId, 'agentId');
|
|
80
|
+
const agent = teamStore.getAgent(agentId);
|
|
81
|
+
if (!agent || agent.project_id !== project.id || agent.status !== 'active') {
|
|
82
|
+
emitError('agentId must identify an active coordination member of this project.', asJson);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const nextIdentity = {
|
|
86
|
+
agentId: agent.agent_id,
|
|
87
|
+
projectId: project.id,
|
|
88
|
+
activatedAt: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
await saveCliIdentity(dataDir, nextIdentity);
|
|
91
|
+
emitResult(
|
|
92
|
+
{ project, identity: nextIdentity, agent },
|
|
93
|
+
`CLI identity activated: ${agent.name} (${agent.agent_id})`,
|
|
94
|
+
asJson,
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case 'clear': {
|
|
100
|
+
await clearCliIdentity(dataDir);
|
|
101
|
+
emitResult(
|
|
102
|
+
{ project, cleared: true },
|
|
103
|
+
'CLI identity cleared. Subsequent commands use project-scoped access only.',
|
|
104
|
+
asJson,
|
|
105
|
+
);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
default:
|
|
110
|
+
emitError(`unknown identity action "${action}". Use "status", "join", "use", or "clear".`, asJson);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
emitError(error instanceof Error ? error.message : String(error), asJson);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
@@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { analyzeImage } from '../../multimodal/image-loader.js';
|
|
5
5
|
import { storeObservation } from '../../memory/observations.js';
|
|
6
|
-
import { emitError, emitResult, getCliProjectContext } from './operator-shared.js';
|
|
6
|
+
import { emitError, emitResult, getCliProjectContext, resolveCliWriteScope } from './operator-shared.js';
|
|
7
7
|
|
|
8
8
|
function inferMimeType(filePath: string): string {
|
|
9
9
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -29,6 +29,7 @@ export default defineCommand({
|
|
|
29
29
|
path: { type: 'string', description: 'Path to the image file' },
|
|
30
30
|
prompt: { type: 'string', description: 'Custom analysis prompt' },
|
|
31
31
|
mimeType: { type: 'string', description: 'Explicit MIME type override' },
|
|
32
|
+
visibility: { type: 'string', description: 'Memory visibility: project (default), personal, or team' },
|
|
32
33
|
json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
|
|
33
34
|
},
|
|
34
35
|
run: async ({ args }) => {
|
|
@@ -39,8 +40,8 @@ export default defineCommand({
|
|
|
39
40
|
emitError('path is required for "memorix ingest image"', asJson);
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
|
-
const { project } = await getCliProjectContext();
|
|
43
|
-
const resolvedPath = path.resolve(
|
|
43
|
+
const { project, reader, identity } = await getCliProjectContext();
|
|
44
|
+
const resolvedPath = path.resolve(project.rootPath, imagePath);
|
|
44
45
|
const base64 = readFileSync(resolvedPath).toString('base64');
|
|
45
46
|
const filename = path.basename(resolvedPath);
|
|
46
47
|
const analysis = await analyzeImage({
|
|
@@ -59,6 +60,7 @@ export default defineCommand({
|
|
|
59
60
|
facts: analysis.entities,
|
|
60
61
|
projectId: project.id,
|
|
61
62
|
source: 'manual',
|
|
63
|
+
...resolveCliWriteScope({ reader, identity }, args.visibility as string | undefined),
|
|
62
64
|
});
|
|
63
65
|
|
|
64
66
|
emitResult(
|
package/src/cli/commands/lock.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
-
import { emitError, emitResult, getCliProjectContext, shortId } from './operator-shared.js';
|
|
2
|
+
import { emitError, emitResult, getCliProjectContext, resolveCliActorId, shortId } from './operator-shared.js';
|
|
3
3
|
|
|
4
4
|
export default defineCommand({
|
|
5
5
|
meta: {
|
|
@@ -16,21 +16,22 @@ export default defineCommand({
|
|
|
16
16
|
const asJson = !!args.json;
|
|
17
17
|
|
|
18
18
|
try {
|
|
19
|
-
const { project, teamStore } = await getCliProjectContext();
|
|
19
|
+
const { project, teamStore, identity } = await getCliProjectContext();
|
|
20
|
+
const agentId = resolveCliActorId(args.agentId, identity);
|
|
20
21
|
|
|
21
22
|
switch (action) {
|
|
22
23
|
case 'lock': {
|
|
23
|
-
if (!args.file || !
|
|
24
|
-
emitError('file and agentId are required for "memorix lock lock"', asJson);
|
|
24
|
+
if (!args.file || !agentId) {
|
|
25
|
+
emitError('file and an active CLI identity or agentId are required for "memorix lock lock"', asJson);
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
|
-
const result = teamStore.acquireLock(project.id, args.file as string,
|
|
28
|
+
const result = teamStore.acquireLock(project.id, args.file as string, agentId);
|
|
28
29
|
if (!result.success) {
|
|
29
30
|
emitError(`File is already locked by ${shortId(result.lockedBy)}`, asJson);
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
emitResult(
|
|
33
|
-
{ project, file: args.file, lockedBy:
|
|
34
|
+
{ project, file: args.file, lockedBy: agentId },
|
|
34
35
|
`Locked: ${args.file as string}`,
|
|
35
36
|
asJson,
|
|
36
37
|
);
|
|
@@ -38,11 +39,11 @@ export default defineCommand({
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
case 'unlock': {
|
|
41
|
-
if (!args.file || !
|
|
42
|
-
emitError('file and agentId are required for "memorix lock unlock"', asJson);
|
|
42
|
+
if (!args.file || !agentId) {
|
|
43
|
+
emitError('file and an active CLI identity or agentId are required for "memorix lock unlock"', asJson);
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
|
-
const released = teamStore.releaseLock(project.id, args.file as string,
|
|
46
|
+
const released = teamStore.releaseLock(project.id, args.file as string, agentId);
|
|
46
47
|
if (!released) {
|
|
47
48
|
emitError('Cannot unlock: not the owner or the file is not locked', asJson);
|
|
48
49
|
return;
|
|
@@ -68,7 +69,7 @@ export default defineCommand({
|
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const locks = teamStore.listLocks(project.id,
|
|
72
|
+
const locks = teamStore.listLocks(project.id, agentId);
|
|
72
73
|
emitResult(
|
|
73
74
|
{ project, locks },
|
|
74
75
|
locks.length === 0
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { compactDetail, compactSearch, compactTimeline } from '../../compact/engine.js';
|
|
3
3
|
import { withFreshIndex } from '../../memory/freshness.js';
|
|
4
|
-
import { getAllObservations, getProjectObservations, resolveObservations, storeObservation, suggestTopicKey } from '../../memory/observations.js';
|
|
4
|
+
import { getAllObservations, getObservation, getProjectObservations, resolveObservations, storeObservation, suggestTopicKey } from '../../memory/observations.js';
|
|
5
5
|
import { buildGraphContextPacket, formatGraphContextPrompt } from '../../memory/graph-context.js';
|
|
6
|
-
import {
|
|
6
|
+
import { canManageObservation, filterReadableObservations, resolveObservationVisibility } from '../../memory/visibility.js';
|
|
7
|
+
import {
|
|
8
|
+
coerceObservationStatus,
|
|
9
|
+
coerceObservationType,
|
|
10
|
+
emitError,
|
|
11
|
+
emitResult,
|
|
12
|
+
getCliProjectContext,
|
|
13
|
+
parseCsvList,
|
|
14
|
+
parsePositiveInt,
|
|
15
|
+
resolveCliWriteScope,
|
|
16
|
+
} from './operator-shared.js';
|
|
7
17
|
|
|
8
18
|
export default defineCommand({
|
|
9
19
|
meta: {
|
|
@@ -19,6 +29,7 @@ export default defineCommand({
|
|
|
19
29
|
facts: { type: 'string', description: 'Comma-separated facts' },
|
|
20
30
|
files: { type: 'string', description: 'Comma-separated file list' },
|
|
21
31
|
concepts: { type: 'string', description: 'Comma-separated concept list' },
|
|
32
|
+
visibility: { type: 'string', description: 'Memory visibility: project (default), personal, or team' },
|
|
22
33
|
ids: { type: 'string', description: 'Comma-separated observation IDs' },
|
|
23
34
|
id: { type: 'string', description: 'Single observation ID' },
|
|
24
35
|
status: { type: 'string', description: 'Resolved or archived' },
|
|
@@ -32,6 +43,7 @@ export default defineCommand({
|
|
|
32
43
|
after: { type: 'string', description: 'Timeline depth after anchor' },
|
|
33
44
|
threshold: { type: 'string', description: 'Similarity threshold for consolidate' },
|
|
34
45
|
dryRun: { type: 'boolean', description: 'Preview changes without mutating data' },
|
|
46
|
+
'dry-run': { type: 'boolean', description: 'Kebab-case alias for --dryRun' },
|
|
35
47
|
trigger: { type: 'string', description: 'Custom trigger text for promoted mini-skills' },
|
|
36
48
|
instruction: { type: 'string', description: 'Custom instruction for promoted mini-skills' },
|
|
37
49
|
tags: { type: 'string', description: 'Comma-separated extra tags for promoted mini-skills' },
|
|
@@ -44,7 +56,7 @@ export default defineCommand({
|
|
|
44
56
|
const asJson = !!args.json;
|
|
45
57
|
|
|
46
58
|
try {
|
|
47
|
-
const { project } = await getCliProjectContext({ searchIndex: true });
|
|
59
|
+
const { project, dataDir, reader, identity } = await getCliProjectContext({ searchIndex: true });
|
|
48
60
|
|
|
49
61
|
switch (action) {
|
|
50
62
|
case 'search': {
|
|
@@ -54,7 +66,7 @@ export default defineCommand({
|
|
|
54
66
|
return;
|
|
55
67
|
}
|
|
56
68
|
const limit = parsePositiveInt(args.limit as string | undefined, 10);
|
|
57
|
-
const result = await compactSearch({ query, limit, projectId: project.id });
|
|
69
|
+
const result = await compactSearch({ query, limit, projectId: project.id, reader });
|
|
58
70
|
emitResult({ project, entries: result.entries }, result.formatted, asJson);
|
|
59
71
|
return;
|
|
60
72
|
}
|
|
@@ -67,7 +79,7 @@ export default defineCommand({
|
|
|
67
79
|
emitError('query is required for "memorix memory graph-context"', asJson);
|
|
68
80
|
return;
|
|
69
81
|
}
|
|
70
|
-
const observations = getAllObservations();
|
|
82
|
+
const observations = filterReadableObservations(getAllObservations(), reader);
|
|
71
83
|
const packet = buildGraphContextPacket(observations, {
|
|
72
84
|
projectId: project.id,
|
|
73
85
|
query,
|
|
@@ -92,7 +104,7 @@ export default defineCommand({
|
|
|
92
104
|
|
|
93
105
|
case 'recent': {
|
|
94
106
|
const limit = parsePositiveInt(args.limit as string | undefined, 10);
|
|
95
|
-
const observations = getProjectObservations(project.id)
|
|
107
|
+
const observations = filterReadableObservations(getProjectObservations(project.id), reader)
|
|
96
108
|
.filter((obs) => (obs.status ?? 'active') === 'active')
|
|
97
109
|
.slice(-limit)
|
|
98
110
|
.reverse();
|
|
@@ -118,6 +130,10 @@ export default defineCommand({
|
|
|
118
130
|
(args.topicKey as string | undefined)?.trim() ||
|
|
119
131
|
suggestTopicKey(type, title) ||
|
|
120
132
|
undefined;
|
|
133
|
+
const writeScope = resolveCliWriteScope(
|
|
134
|
+
{ reader, identity },
|
|
135
|
+
args.visibility as string | undefined,
|
|
136
|
+
);
|
|
121
137
|
const result = await storeObservation({
|
|
122
138
|
entityName: (args.entity as string | undefined)?.trim() || 'general',
|
|
123
139
|
type,
|
|
@@ -129,6 +145,7 @@ export default defineCommand({
|
|
|
129
145
|
projectId: project.id,
|
|
130
146
|
topicKey,
|
|
131
147
|
source: 'manual',
|
|
148
|
+
...writeScope,
|
|
132
149
|
});
|
|
133
150
|
emitResult(
|
|
134
151
|
{ project, observation: result.observation, upserted: result.upserted },
|
|
@@ -166,7 +183,7 @@ export default defineCommand({
|
|
|
166
183
|
? `obs:${numericId}@${project.id}`
|
|
167
184
|
: ref;
|
|
168
185
|
});
|
|
169
|
-
const result = await compactDetail(scopedRefs);
|
|
186
|
+
const result = await compactDetail(scopedRefs, { reader });
|
|
170
187
|
emitResult({ project, documents: result.documents }, result.formatted, asJson);
|
|
171
188
|
return;
|
|
172
189
|
}
|
|
@@ -182,6 +199,7 @@ export default defineCommand({
|
|
|
182
199
|
project.id,
|
|
183
200
|
parsePositiveInt(args.before as string | undefined, 3),
|
|
184
201
|
parsePositiveInt(args.after as string | undefined, 3),
|
|
202
|
+
reader,
|
|
185
203
|
);
|
|
186
204
|
emitResult({ project, timeline: result.timeline }, result.formatted, asJson);
|
|
187
205
|
return;
|
|
@@ -196,7 +214,15 @@ export default defineCommand({
|
|
|
196
214
|
return;
|
|
197
215
|
}
|
|
198
216
|
const status = coerceObservationStatus(args.status as string | undefined);
|
|
199
|
-
const
|
|
217
|
+
const authorizedIds = ids.filter((id) => {
|
|
218
|
+
const observation = getObservation(id, project.id);
|
|
219
|
+
return observation ? canManageObservation(observation, reader) : false;
|
|
220
|
+
});
|
|
221
|
+
if (authorizedIds.length === 0) {
|
|
222
|
+
emitError('No requested observations are manageable with the active CLI scope.', asJson);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const result = await resolveObservations(authorizedIds, status);
|
|
200
226
|
emitResult(
|
|
201
227
|
{ project, result, status },
|
|
202
228
|
`Resolved ${result.resolved.length} observation(s) to ${status}${result.notFound.length > 0 ? `; not found: ${result.notFound.join(', ')}` : ''}`,
|
|
@@ -207,7 +233,7 @@ export default defineCommand({
|
|
|
207
233
|
|
|
208
234
|
case 'deduplicate': {
|
|
209
235
|
const query = (args.query as string | undefined)?.trim();
|
|
210
|
-
const dryRun = !!args.dryRun;
|
|
236
|
+
const dryRun = !!args.dryRun || !!args['dry-run'];
|
|
211
237
|
const { isLLMEnabled } = await import('../../llm/provider.js');
|
|
212
238
|
if (!isLLMEnabled()) {
|
|
213
239
|
emitResult(
|
|
@@ -220,7 +246,10 @@ export default defineCommand({
|
|
|
220
246
|
|
|
221
247
|
const { deduplicateMemory } = await import('../../llm/memory-manager.js');
|
|
222
248
|
const allObs = await withFreshIndex(() =>
|
|
223
|
-
|
|
249
|
+
filterReadableObservations(
|
|
250
|
+
getAllObservations().filter((obs) => (obs.status ?? 'active') === 'active' && obs.projectId === project.id),
|
|
251
|
+
reader,
|
|
252
|
+
),
|
|
224
253
|
);
|
|
225
254
|
|
|
226
255
|
if (allObs.length < 2) {
|
|
@@ -230,7 +259,7 @@ export default defineCommand({
|
|
|
230
259
|
|
|
231
260
|
let candidates = allObs;
|
|
232
261
|
if (query) {
|
|
233
|
-
const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: 'active' });
|
|
262
|
+
const searchResult = await compactSearch({ query, limit: 20, projectId: project.id, status: 'active', reader });
|
|
234
263
|
const ids = new Set(searchResult.entries.map((entry) => entry.id));
|
|
235
264
|
candidates = allObs.filter((obs) => ids.has(obs.id));
|
|
236
265
|
} else {
|
|
@@ -277,7 +306,13 @@ export default defineCommand({
|
|
|
277
306
|
return;
|
|
278
307
|
}
|
|
279
308
|
|
|
280
|
-
const result = await resolveObservations(
|
|
309
|
+
const result = await resolveObservations(
|
|
310
|
+
[...new Set(toResolve)].filter((id) => {
|
|
311
|
+
const observation = getObservation(id, project.id);
|
|
312
|
+
return observation ? canManageObservation(observation, reader) : false;
|
|
313
|
+
}),
|
|
314
|
+
'resolved',
|
|
315
|
+
);
|
|
281
316
|
emitResult(
|
|
282
317
|
{ project, actions, resolved: result.resolved, notFound: result.notFound, dryRun: false },
|
|
283
318
|
`Resolved ${result.resolved.length} duplicate observation(s).`,
|
|
@@ -292,7 +327,7 @@ export default defineCommand({
|
|
|
292
327
|
const { findConsolidationCandidates, executeConsolidation } = await import('../../memory/consolidation.js');
|
|
293
328
|
|
|
294
329
|
if (consolidationAction === 'preview') {
|
|
295
|
-
const clusters = await findConsolidationCandidates(
|
|
330
|
+
const clusters = await findConsolidationCandidates(project.rootPath, project.id, { threshold });
|
|
296
331
|
emitResult(
|
|
297
332
|
{ project, clusters, action: consolidationAction },
|
|
298
333
|
clusters.length === 0
|
|
@@ -306,7 +341,7 @@ export default defineCommand({
|
|
|
306
341
|
}
|
|
307
342
|
|
|
308
343
|
if (consolidationAction === 'execute') {
|
|
309
|
-
const result = await executeConsolidation(
|
|
344
|
+
const result = await executeConsolidation(project.rootPath, project.id, { threshold });
|
|
310
345
|
emitResult(
|
|
311
346
|
{ project, action: consolidationAction, ...result },
|
|
312
347
|
result.clustersFound === 0
|
|
@@ -325,11 +360,10 @@ export default defineCommand({
|
|
|
325
360
|
const promoteAction = (args.action as string | undefined) || 'promote';
|
|
326
361
|
const { promoteToMiniSkill, loadAllMiniSkills, deleteMiniSkill } = await import('../../skills/mini-skills.js');
|
|
327
362
|
const { initMiniSkillStore } = await import('../../store/mini-skill-store.js');
|
|
328
|
-
const { dataDir } = await getCliProjectContext();
|
|
329
363
|
await initMiniSkillStore(dataDir);
|
|
330
364
|
|
|
331
365
|
if (promoteAction === 'list') {
|
|
332
|
-
const skills = await loadAllMiniSkills(
|
|
366
|
+
const skills = await loadAllMiniSkills(project.rootPath);
|
|
333
367
|
emitResult(
|
|
334
368
|
{ project, skills, action: promoteAction },
|
|
335
369
|
skills.length === 0 ? 'No mini-skills found.' : skills.map((skill) => `- #${skill.id} ${skill.title}`).join('\n'),
|
|
@@ -344,7 +378,7 @@ export default defineCommand({
|
|
|
344
378
|
emitError('skillId is required for "memorix memory promote --action delete"', asJson);
|
|
345
379
|
return;
|
|
346
380
|
}
|
|
347
|
-
const deleted = await deleteMiniSkill(
|
|
381
|
+
const deleted = await deleteMiniSkill(project.rootPath, skillId);
|
|
348
382
|
if (!deleted) {
|
|
349
383
|
emitError(`Mini-skill #${skillId} not found`, asJson);
|
|
350
384
|
return;
|
|
@@ -361,12 +395,15 @@ export default defineCommand({
|
|
|
361
395
|
return;
|
|
362
396
|
}
|
|
363
397
|
const observations = await withFreshIndex(() => getAllObservations());
|
|
364
|
-
const matched =
|
|
398
|
+
const matched = filterReadableObservations(
|
|
399
|
+
observations.filter((obs) => obs.projectId === project.id && ids.includes(obs.id)),
|
|
400
|
+
reader,
|
|
401
|
+
).filter((observation) => resolveObservationVisibility(observation) === 'project');
|
|
365
402
|
if (matched.length === 0) {
|
|
366
|
-
emitError(`No observations found for IDs: ${ids.join(', ')}
|
|
403
|
+
emitError(`No project-visible observations found for IDs: ${ids.join(', ')}. Private and team records cannot be promoted into shared skills.`, asJson);
|
|
367
404
|
return;
|
|
368
405
|
}
|
|
369
|
-
const skill = await promoteToMiniSkill(
|
|
406
|
+
const skill = await promoteToMiniSkill(project.rootPath, project.id, matched, {
|
|
370
407
|
trigger: args.trigger as string | undefined,
|
|
371
408
|
instruction: args.instruction as string | undefined,
|
|
372
409
|
tags: parseCsvList(args.tags as string | undefined),
|
|
@@ -385,7 +422,7 @@ export default defineCommand({
|
|
|
385
422
|
console.log('Usage:');
|
|
386
423
|
console.log(' memorix memory search --query "timeout bug" [--limit 10]');
|
|
387
424
|
console.log(' memorix memory recent [--limit 10]');
|
|
388
|
-
console.log(' memorix memory store --text "..." [--title "..."] [--type discovery]');
|
|
425
|
+
console.log(' memorix memory store --text "..." [--title "..."] [--type discovery] [--visibility project|personal|team]');
|
|
389
426
|
console.log(' memorix memory suggest-topic-key --type decision --title "..."');
|
|
390
427
|
console.log(' memorix memory detail --id 42');
|
|
391
428
|
console.log(' memorix memory detail obs:42@org/project');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
-
import { emitError, emitResult, getCliProjectContext, shortId } from './operator-shared.js';
|
|
2
|
+
import { emitError, emitResult, getCliProjectContext, resolveCliActorId, shortId } from './operator-shared.js';
|
|
3
3
|
|
|
4
4
|
export default defineCommand({
|
|
5
5
|
meta: {
|
|
@@ -22,12 +22,18 @@ export default defineCommand({
|
|
|
22
22
|
const asJson = !!args.json;
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
|
-
const { project, teamStore } = await getCliProjectContext();
|
|
25
|
+
const { project, teamStore, identity } = await getCliProjectContext();
|
|
26
|
+
const senderAgentId = resolveCliActorId(args.from, identity, 'from');
|
|
27
|
+
const inboxAgentId = resolveCliActorId(
|
|
28
|
+
(args.agentId as string | undefined) ?? (args.from as string | undefined),
|
|
29
|
+
identity,
|
|
30
|
+
'agentId',
|
|
31
|
+
);
|
|
26
32
|
|
|
27
33
|
switch (action) {
|
|
28
34
|
case 'send': {
|
|
29
|
-
if (!
|
|
30
|
-
emitError('
|
|
35
|
+
if (!senderAgentId || !args.type || !args.content) {
|
|
36
|
+
emitError('type, content, and an active CLI identity or from agentId are required for "memorix message send"', asJson);
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
33
39
|
if (!args.to && !args.toRole) {
|
|
@@ -36,7 +42,7 @@ export default defineCommand({
|
|
|
36
42
|
}
|
|
37
43
|
const message = teamStore.sendMessage({
|
|
38
44
|
projectId: project.id,
|
|
39
|
-
senderAgentId
|
|
45
|
+
senderAgentId,
|
|
40
46
|
recipientAgentId: (args.to as string | undefined) ?? null,
|
|
41
47
|
type: args.type as string,
|
|
42
48
|
content: args.content as string,
|
|
@@ -58,13 +64,13 @@ export default defineCommand({
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
case 'broadcast': {
|
|
61
|
-
if (!
|
|
62
|
-
emitError('
|
|
67
|
+
if (!senderAgentId || !args.type || !args.content) {
|
|
68
|
+
emitError('type, content, and an active CLI identity or from agentId are required for "memorix message broadcast"', asJson);
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
const message = teamStore.sendMessage({
|
|
66
72
|
projectId: project.id,
|
|
67
|
-
senderAgentId
|
|
73
|
+
senderAgentId,
|
|
68
74
|
recipientAgentId: null,
|
|
69
75
|
type: args.type as string,
|
|
70
76
|
content: args.content as string,
|
|
@@ -82,15 +88,14 @@ export default defineCommand({
|
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
case 'inbox': {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
emitError('agentId is required for "memorix message inbox"', asJson);
|
|
91
|
+
if (!inboxAgentId) {
|
|
92
|
+
emitError('an active CLI identity or agentId is required for "memorix message inbox"', asJson);
|
|
88
93
|
return;
|
|
89
94
|
}
|
|
90
|
-
const messages = teamStore.getInbox(project.id,
|
|
91
|
-
const unreadCount = teamStore.getUnreadCount(project.id,
|
|
95
|
+
const messages = teamStore.getInbox(project.id, inboxAgentId);
|
|
96
|
+
const unreadCount = teamStore.getUnreadCount(project.id, inboxAgentId);
|
|
92
97
|
if (args.markRead) {
|
|
93
|
-
teamStore.markAllRead(project.id,
|
|
98
|
+
teamStore.markAllRead(project.id, inboxAgentId);
|
|
94
99
|
}
|
|
95
100
|
emitResult(
|
|
96
101
|
{ project, unreadCount, messages },
|