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,16 +3,33 @@ import { getProjectDataDir } from '../../store/persistence.js';
|
|
|
3
3
|
import { initObservations, prepareSearchIndex } from '../../memory/observations.js';
|
|
4
4
|
import { initSessionStore } from '../../store/session-store.js';
|
|
5
5
|
import { initTeamStore, type TeamStore } from '../../team/team-store.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ProjectInfo,
|
|
8
|
+
ObservationReader,
|
|
9
|
+
ObservationStatus,
|
|
10
|
+
ObservationType,
|
|
11
|
+
ObservationVisibility,
|
|
12
|
+
} from '../../types.js';
|
|
13
|
+
import { getCliInvocation } from '../invocation.js';
|
|
14
|
+
import { resolveCliIdentity, type CliIdentity } from '../identity.js';
|
|
7
15
|
|
|
8
16
|
export interface CliProjectContext {
|
|
9
17
|
project: ProjectInfo;
|
|
10
18
|
dataDir: string;
|
|
11
19
|
teamStore: TeamStore;
|
|
20
|
+
reader: ObservationReader;
|
|
21
|
+
identity: CliIdentity | null;
|
|
22
|
+
identityWarning?: string;
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
export async function getCliProjectContext(options?: { searchIndex?: boolean; projectRoot?: string }): Promise<CliProjectContext> {
|
|
15
|
-
const
|
|
26
|
+
const invocation = getCliInvocation();
|
|
27
|
+
const detection = detectProjectWithDiagnostics(
|
|
28
|
+
options?.projectRoot
|
|
29
|
+
?? invocation.projectRoot
|
|
30
|
+
?? process.env.MEMORIX_PROJECT_ROOT
|
|
31
|
+
?? process.cwd(),
|
|
32
|
+
);
|
|
16
33
|
if (!detection.project) {
|
|
17
34
|
const detail = detection.failure?.detail ?? 'No git repository found in the current directory.';
|
|
18
35
|
throw new Error(detail);
|
|
@@ -20,6 +37,16 @@ export async function getCliProjectContext(options?: { searchIndex?: boolean; pr
|
|
|
20
37
|
|
|
21
38
|
const project = detection.project;
|
|
22
39
|
const dataDir = await getProjectDataDir(project.id);
|
|
40
|
+
try {
|
|
41
|
+
const { MaintenanceTargetStore } = await import('../../runtime/maintenance-targets.js');
|
|
42
|
+
new MaintenanceTargetStore(dataDir).register({
|
|
43
|
+
projectId: project.id,
|
|
44
|
+
projectRoot: project.rootPath,
|
|
45
|
+
dataDir,
|
|
46
|
+
});
|
|
47
|
+
} catch {
|
|
48
|
+
// CLI reads remain available even if optional background maintenance metadata fails.
|
|
49
|
+
}
|
|
23
50
|
await initObservations(dataDir);
|
|
24
51
|
await initSessionStore(dataDir);
|
|
25
52
|
const teamStore = await initTeamStore(dataDir);
|
|
@@ -28,7 +55,21 @@ export async function getCliProjectContext(options?: { searchIndex?: boolean; pr
|
|
|
28
55
|
await prepareSearchIndex();
|
|
29
56
|
}
|
|
30
57
|
|
|
31
|
-
|
|
58
|
+
const identity = await resolveCliIdentity({
|
|
59
|
+
project,
|
|
60
|
+
dataDir,
|
|
61
|
+
teamStore,
|
|
62
|
+
explicitActorId: invocation.actorId,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
project,
|
|
67
|
+
dataDir,
|
|
68
|
+
teamStore,
|
|
69
|
+
reader: identity.reader,
|
|
70
|
+
identity: identity.identity,
|
|
71
|
+
...(identity.warning ? { identityWarning: identity.warning } : {}),
|
|
72
|
+
};
|
|
32
73
|
}
|
|
33
74
|
|
|
34
75
|
export function emitResult<T>(data: T, text: string, asJson?: boolean): void {
|
|
@@ -108,3 +149,57 @@ export function coerceObservationStatus(input?: string): ObservationStatus {
|
|
|
108
149
|
}
|
|
109
150
|
return normalized;
|
|
110
151
|
}
|
|
152
|
+
|
|
153
|
+
export function coerceObservationVisibility(input?: string): ObservationVisibility {
|
|
154
|
+
const normalized = (input ?? 'project').trim().toLowerCase();
|
|
155
|
+
if (normalized === 'personal' || normalized === 'project' || normalized === 'team') {
|
|
156
|
+
return normalized;
|
|
157
|
+
}
|
|
158
|
+
throw new Error('visibility must be personal, project, or team');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Project scope is deliberately the default. Private and team-scoped records
|
|
163
|
+
* require an identity selected by the operator, so a plain shell does not
|
|
164
|
+
* accidentally gain access to another agent's work.
|
|
165
|
+
*/
|
|
166
|
+
export function resolveCliWriteScope(
|
|
167
|
+
context: Pick<CliProjectContext, 'identity' | 'reader'>,
|
|
168
|
+
visibilityInput?: string,
|
|
169
|
+
): {
|
|
170
|
+
visibility: ObservationVisibility;
|
|
171
|
+
createdByAgentId?: string;
|
|
172
|
+
visibilityReader: ObservationReader;
|
|
173
|
+
} {
|
|
174
|
+
const visibility = coerceObservationVisibility(visibilityInput);
|
|
175
|
+
if (visibility !== 'project' && !context.identity) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`visibility=${visibility} requires an active CLI identity. Run "memorix identity join --agent-type <agent>" or "memorix identity use --agent-id <id>" first.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (visibility === 'team' && context.reader.isTeamMember !== true) {
|
|
181
|
+
throw new Error('team visibility requires an active coordination identity for this project.');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
visibility,
|
|
186
|
+
...(context.identity ? { createdByAgentId: context.identity.agentId } : {}),
|
|
187
|
+
visibilityReader: context.reader,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Coordination commands can use the current CLI identity implicitly, while
|
|
193
|
+
* retaining explicit IDs for scripts and backwards-compatible invocations.
|
|
194
|
+
*/
|
|
195
|
+
export function resolveCliActorId(
|
|
196
|
+
explicitValue: unknown,
|
|
197
|
+
identity: CliIdentity | null,
|
|
198
|
+
field = 'agentId',
|
|
199
|
+
): string | undefined {
|
|
200
|
+
const explicit = typeof explicitValue === 'string' ? explicitValue.trim() : '';
|
|
201
|
+
if (identity && explicit && explicit !== identity.agentId) {
|
|
202
|
+
throw new Error(`${field} does not match the active CLI identity. Use "memorix identity clear" before acting as a different agent.`);
|
|
203
|
+
}
|
|
204
|
+
return explicit || identity?.agentId;
|
|
205
|
+
}
|
package/src/cli/commands/poll.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { computePoll, computeWatermark } from '../../team/poll.js';
|
|
|
3
3
|
import { getObservationStore } from '../../store/obs-store.js';
|
|
4
4
|
import { getAllObservations } from '../../memory/observations.js';
|
|
5
5
|
import { withFreshIndex } from '../../memory/freshness.js';
|
|
6
|
-
import {
|
|
6
|
+
import { filterReadableObservations } from '../../memory/visibility.js';
|
|
7
|
+
import { emitError, emitResult, getCliProjectContext, resolveCliActorId, shortId } from './operator-shared.js';
|
|
7
8
|
|
|
8
9
|
export default defineCommand({
|
|
9
10
|
meta: {
|
|
@@ -19,22 +20,31 @@ export default defineCommand({
|
|
|
19
20
|
const asJson = !!args.json;
|
|
20
21
|
|
|
21
22
|
try {
|
|
22
|
-
const { project, teamStore } = await getCliProjectContext();
|
|
23
|
-
const agentId = args.agentId
|
|
23
|
+
const { project, teamStore, identity, reader: activeReader } = await getCliProjectContext();
|
|
24
|
+
const agentId = resolveCliActorId(args.agentId, identity);
|
|
25
|
+
let reader = activeReader;
|
|
24
26
|
|
|
25
27
|
let watermark = computeWatermark(0, 0, 0);
|
|
26
28
|
if (agentId) {
|
|
27
29
|
const agent = teamStore.getAgent(agentId);
|
|
28
|
-
if (!agent) {
|
|
30
|
+
if (!agent || agent.project_id !== project.id) {
|
|
29
31
|
emitError(`Unknown agent "${agentId}"`, asJson);
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
34
|
+
reader = {
|
|
35
|
+
projectId: project.id,
|
|
36
|
+
agentId,
|
|
37
|
+
isTeamMember: agent.status === 'active',
|
|
38
|
+
};
|
|
32
39
|
|
|
33
40
|
const lastSeen = agent.last_seen_obs_generation;
|
|
34
41
|
const currentGen = getObservationStore().getGeneration();
|
|
35
42
|
const projectObs = await withFreshIndex(() =>
|
|
36
|
-
|
|
37
|
-
(
|
|
43
|
+
filterReadableObservations(
|
|
44
|
+
getAllObservations().filter(
|
|
45
|
+
(obs) => obs.projectId === project.id && (obs.writeGeneration ?? 0) > lastSeen,
|
|
46
|
+
),
|
|
47
|
+
reader,
|
|
38
48
|
),
|
|
39
49
|
);
|
|
40
50
|
watermark = computeWatermark(lastSeen, currentGen, projectObs.length);
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { compactSearch } from '../../compact/engine.js';
|
|
3
3
|
import { storeObservation } from '../../memory/observations.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
emitError,
|
|
6
|
+
emitResult,
|
|
7
|
+
getCliProjectContext,
|
|
8
|
+
parseCsvList,
|
|
9
|
+
parsePositiveInt,
|
|
10
|
+
resolveCliWriteScope,
|
|
11
|
+
} from './operator-shared.js';
|
|
5
12
|
|
|
6
13
|
export default defineCommand({
|
|
7
14
|
meta: {
|
|
@@ -20,6 +27,7 @@ export default defineCommand({
|
|
|
20
27
|
files: { type: 'string', description: 'Comma-separated file list' },
|
|
21
28
|
relatedCommits: { type: 'string', description: 'Comma-separated related commit hashes' },
|
|
22
29
|
relatedEntities: { type: 'string', description: 'Comma-separated related entity names' },
|
|
30
|
+
visibility: { type: 'string', description: 'Reasoning visibility: project (default), personal, or team' },
|
|
23
31
|
query: { type: 'string', description: 'Search query for reasoning traces' },
|
|
24
32
|
limit: { type: 'string', description: 'Search result limit' },
|
|
25
33
|
scope: { type: 'string', description: 'project or global scope for search' },
|
|
@@ -30,7 +38,7 @@ export default defineCommand({
|
|
|
30
38
|
const asJson = !!args.json;
|
|
31
39
|
|
|
32
40
|
try {
|
|
33
|
-
const { project } = await getCliProjectContext({ searchIndex: action === 'search' });
|
|
41
|
+
const { project, reader, identity } = await getCliProjectContext({ searchIndex: action === 'search' });
|
|
34
42
|
|
|
35
43
|
switch (action) {
|
|
36
44
|
case 'store': {
|
|
@@ -63,6 +71,10 @@ export default defineCommand({
|
|
|
63
71
|
if (risks.length > 0) {
|
|
64
72
|
narrativeParts.push(`Risks: ${risks.join('; ')}`);
|
|
65
73
|
}
|
|
74
|
+
const writeScope = resolveCliWriteScope(
|
|
75
|
+
{ reader, identity },
|
|
76
|
+
args.visibility as string | undefined,
|
|
77
|
+
);
|
|
66
78
|
|
|
67
79
|
const result = await storeObservation({
|
|
68
80
|
entityName,
|
|
@@ -84,6 +96,7 @@ export default defineCommand({
|
|
|
84
96
|
relatedEntities,
|
|
85
97
|
projectId: project.id,
|
|
86
98
|
source: 'manual',
|
|
99
|
+
...writeScope,
|
|
87
100
|
});
|
|
88
101
|
|
|
89
102
|
emitResult(
|
|
@@ -108,6 +121,7 @@ export default defineCommand({
|
|
|
108
121
|
type: 'reasoning',
|
|
109
122
|
projectId: scope === 'global' ? undefined : project.id,
|
|
110
123
|
status: 'active',
|
|
124
|
+
reader: scope === 'global' ? {} : reader,
|
|
111
125
|
});
|
|
112
126
|
emitResult(
|
|
113
127
|
{ project, scope, entries: result.entries },
|
|
@@ -121,7 +135,7 @@ export default defineCommand({
|
|
|
121
135
|
console.log('Memorix Reasoning Commands');
|
|
122
136
|
console.log('');
|
|
123
137
|
console.log('Usage:');
|
|
124
|
-
console.log(' memorix reasoning store --entity auth --decision "Use SQLite" --rationale "..."');
|
|
138
|
+
console.log(' memorix reasoning store --entity auth --decision "Use SQLite" --rationale "..." [--visibility project|personal|team]');
|
|
125
139
|
console.log(' memorix reasoning search --query "why sqlite" [--scope project|global --limit 10]');
|
|
126
140
|
}
|
|
127
141
|
} catch (error) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { getAllObservations } from '../../memory/observations.js';
|
|
3
3
|
import { archiveExpired, explainRetention, getArchiveCandidates, getRetentionSummary, getRetentionZone, rankByRelevance } from '../../memory/retention.js';
|
|
4
|
+
import { filterReadableObservations } from '../../memory/visibility.js';
|
|
4
5
|
import type { MemorixDocument } from '../../types.js';
|
|
5
6
|
import { emitError, emitResult, getCliProjectContext } from './operator-shared.js';
|
|
6
7
|
|
|
@@ -24,6 +25,8 @@ function toDocument(obs: Awaited<ReturnType<typeof getAllObservations>>[number])
|
|
|
24
25
|
source: obs.source ?? 'agent',
|
|
25
26
|
sourceDetail: obs.sourceDetail ?? '',
|
|
26
27
|
valueCategory: obs.valueCategory ?? '',
|
|
28
|
+
admissionState: obs.admissionState ?? '',
|
|
29
|
+
admissionReason: obs.admissionReason ?? '',
|
|
27
30
|
};
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -40,9 +43,11 @@ export default defineCommand({
|
|
|
40
43
|
const asJson = !!args.json;
|
|
41
44
|
|
|
42
45
|
try {
|
|
43
|
-
const { project, dataDir } = await getCliProjectContext();
|
|
44
|
-
const activeDocs =
|
|
45
|
-
.filter((obs) => obs.projectId === project.id && (obs.status ?? 'active') === 'active')
|
|
46
|
+
const { project, dataDir, reader } = await getCliProjectContext();
|
|
47
|
+
const activeDocs = filterReadableObservations(
|
|
48
|
+
getAllObservations().filter((obs) => obs.projectId === project.id && (obs.status ?? 'active') === 'active'),
|
|
49
|
+
reader,
|
|
50
|
+
)
|
|
46
51
|
.map(toDocument);
|
|
47
52
|
|
|
48
53
|
switch (action) {
|
|
@@ -65,7 +70,7 @@ export default defineCommand({
|
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
case 'archive': {
|
|
68
|
-
const result = await archiveExpired(dataDir, undefined, undefined, project.id);
|
|
73
|
+
const result = await archiveExpired(dataDir, undefined, undefined, project.id, reader);
|
|
69
74
|
emitResult(
|
|
70
75
|
{ project, result },
|
|
71
76
|
result.archived === 0
|
|
@@ -24,6 +24,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
|
24
24
|
import type { ObservationStore } from '../../store/obs-store.js';
|
|
25
25
|
import { resolveToolProfile } from '../../server/tool-profile.js';
|
|
26
26
|
import { scopeKnowledgeGraphToProject } from '../../memory/graph-scope.js';
|
|
27
|
+
import { canManageObservation, filterReadableObservations } from '../../memory/visibility.js';
|
|
27
28
|
|
|
28
29
|
export const DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1000;
|
|
29
30
|
|
|
@@ -558,7 +559,7 @@ export default defineCommand({
|
|
|
558
559
|
|
|
559
560
|
async function loadDashboardObservations(dataDir: string) {
|
|
560
561
|
const store = await getDashboardObservationStore(dataDir);
|
|
561
|
-
return store.loadAll();
|
|
562
|
+
return filterReadableObservations(await store.loadAll(), {});
|
|
562
563
|
}
|
|
563
564
|
|
|
564
565
|
async function loadDashboardProjectObservations(
|
|
@@ -567,7 +568,10 @@ export default defineCommand({
|
|
|
567
568
|
status?: string,
|
|
568
569
|
) {
|
|
569
570
|
const store = await getDashboardObservationStore(dataDir);
|
|
570
|
-
return
|
|
571
|
+
return filterReadableObservations(
|
|
572
|
+
await store.loadByProject(projectId, status ? { status } : undefined),
|
|
573
|
+
{ projectId },
|
|
574
|
+
);
|
|
571
575
|
}
|
|
572
576
|
|
|
573
577
|
// Resolve static directory (dist/dashboard/static)
|
|
@@ -1321,6 +1325,8 @@ export default defineCommand({
|
|
|
1321
1325
|
sendJson({ error: 'Observation not found' }, 404);
|
|
1322
1326
|
} else if ((matchObs as any).projectId !== delProjectId) {
|
|
1323
1327
|
sendJson({ error: `Observation #${obsId} belongs to project "${(matchObs as any).projectId}", not "${delProjectId}"` }, 403);
|
|
1328
|
+
} else if (!canManageObservation(matchObs, { projectId: delProjectId })) {
|
|
1329
|
+
sendJson({ error: `Observation #${obsId} is not manageable from the unbound dashboard.` }, 403);
|
|
1324
1330
|
} else {
|
|
1325
1331
|
await store.remove(obsId);
|
|
1326
1332
|
// Sync: clean up graph entity references
|
|
@@ -5,6 +5,8 @@ import { computeWatermark } from '../../team/poll.js';
|
|
|
5
5
|
import { withFreshIndex } from '../../memory/freshness.js';
|
|
6
6
|
import { getAllObservations } from '../../memory/observations.js';
|
|
7
7
|
import { getObservationStore } from '../../store/obs-store.js';
|
|
8
|
+
import { filterReadableObservations } from '../../memory/visibility.js';
|
|
9
|
+
import { saveCliIdentity } from '../identity.js';
|
|
8
10
|
import { emitError, emitResult, getCliProjectContext, parsePositiveInt } from './operator-shared.js';
|
|
9
11
|
|
|
10
12
|
export default defineCommand({
|
|
@@ -18,6 +20,7 @@ export default defineCommand({
|
|
|
18
20
|
instanceId: { type: 'string', description: 'Stable instance identity across restarts' },
|
|
19
21
|
projectRoot: { type: 'string', description: 'Absolute project root used to bind the session context' },
|
|
20
22
|
joinTeam: { type: 'boolean', description: 'Explicitly join orchestration coordination state for this session' },
|
|
23
|
+
use: { type: 'boolean', description: 'Make the joined coordination identity active for later CLI commands' },
|
|
21
24
|
role: { type: 'string', description: 'Explicit role override used only when --joinTeam is set' },
|
|
22
25
|
sessionId: { type: 'string', description: 'Custom session ID (optional)' },
|
|
23
26
|
summary: { type: 'string', description: 'Structured session summary for session end' },
|
|
@@ -29,17 +32,20 @@ export default defineCommand({
|
|
|
29
32
|
const asJson = !!args.json;
|
|
30
33
|
|
|
31
34
|
try {
|
|
32
|
-
const { project, dataDir, teamStore } = await getCliProjectContext({
|
|
35
|
+
const { project, dataDir, teamStore, reader } = await getCliProjectContext({
|
|
33
36
|
projectRoot: args.projectRoot as string | undefined,
|
|
34
37
|
});
|
|
35
38
|
|
|
36
39
|
switch (action) {
|
|
37
40
|
case 'start': {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
41
|
+
if (args.use && !args.joinTeam) {
|
|
42
|
+
emitError('use requires --joinTeam when starting a session.', asJson);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (args.use && !args.agent && !args.agentType) {
|
|
46
|
+
emitError('use requires --agent or --agentType so Memorix can activate a coordination identity.', asJson);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
43
49
|
const shouldJoinTeam = !!args.joinTeam;
|
|
44
50
|
let agentRecord: ReturnType<typeof teamStore.registerAgent> | null = null;
|
|
45
51
|
let teamJoinNotice: string | null = null;
|
|
@@ -61,6 +67,29 @@ export default defineCommand({
|
|
|
61
67
|
teamJoinNotice = 'Coordination join skipped: provide --agent or --agentType to create a coordination identity.';
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
const contextReader = agentRecord
|
|
71
|
+
? {
|
|
72
|
+
projectId: project.id,
|
|
73
|
+
agentId: agentRecord.agent_id,
|
|
74
|
+
isTeamMember: agentRecord.status === 'active',
|
|
75
|
+
}
|
|
76
|
+
: reader;
|
|
77
|
+
const result = await startSession(dataDir, project.id, {
|
|
78
|
+
sessionId: args.sessionId as string | undefined,
|
|
79
|
+
agent: args.agent as string | undefined,
|
|
80
|
+
reader: contextReader,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let identityActivated = false;
|
|
84
|
+
if (args.use && agentRecord) {
|
|
85
|
+
await saveCliIdentity(dataDir, {
|
|
86
|
+
agentId: agentRecord.agent_id,
|
|
87
|
+
projectId: project.id,
|
|
88
|
+
activatedAt: new Date().toISOString(),
|
|
89
|
+
});
|
|
90
|
+
identityActivated = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
64
93
|
let watermark = computeWatermark(0, 0, 0);
|
|
65
94
|
let rescuedAgentIds: string[] = [];
|
|
66
95
|
let availableTasks = 0;
|
|
@@ -70,8 +99,11 @@ export default defineCommand({
|
|
|
70
99
|
const store = getObservationStore();
|
|
71
100
|
const currentGen = store.getGeneration();
|
|
72
101
|
const projectObs = await withFreshIndex(() =>
|
|
73
|
-
|
|
74
|
-
(
|
|
102
|
+
filterReadableObservations(
|
|
103
|
+
getAllObservations().filter(
|
|
104
|
+
(obs) => obs.projectId === project.id && (obs.writeGeneration ?? 0) > lastSeen,
|
|
105
|
+
),
|
|
106
|
+
{ projectId: project.id, agentId: agentRecord.agent_id, isTeamMember: agentRecord.status === 'active' },
|
|
75
107
|
),
|
|
76
108
|
);
|
|
77
109
|
watermark = computeWatermark(lastSeen, currentGen, projectObs.length);
|
|
@@ -97,6 +129,7 @@ export default defineCommand({
|
|
|
97
129
|
joined: !!agentRecord,
|
|
98
130
|
notice: teamJoinNotice,
|
|
99
131
|
},
|
|
132
|
+
identityActivated,
|
|
100
133
|
watermark,
|
|
101
134
|
rescue: {
|
|
102
135
|
staleAgents: rescuedAgentIds,
|
|
@@ -113,6 +146,7 @@ export default defineCommand({
|
|
|
113
146
|
agentRecord
|
|
114
147
|
? `Agent: ${agentRecord.name} [${agentRecord.agent_type}] as ${agentRecord.role} (${agentRecord.agent_id})`
|
|
115
148
|
: '',
|
|
149
|
+
identityActivated ? 'CLI identity activated for follow-up memory and coordination commands.' : '',
|
|
116
150
|
!agentRecord ? 'Coordination identity: not joined (memory/session context only)' : '',
|
|
117
151
|
teamJoinNotice ?? '',
|
|
118
152
|
agentRecord && watermark.newObservationCount > 0
|
|
@@ -152,7 +186,7 @@ export default defineCommand({
|
|
|
152
186
|
case 'context': {
|
|
153
187
|
const limit = parsePositiveInt(args.limit as string | undefined, 3);
|
|
154
188
|
const [context, sessions] = await Promise.all([
|
|
155
|
-
getSessionContext(dataDir, project.id, limit),
|
|
189
|
+
getSessionContext(dataDir, project.id, limit, reader),
|
|
156
190
|
listSessions(dataDir, project.id),
|
|
157
191
|
]);
|
|
158
192
|
const active = sessions.filter((session) => session.status === 'active').length;
|
|
@@ -173,7 +207,7 @@ export default defineCommand({
|
|
|
173
207
|
console.log('Memorix Session Commands');
|
|
174
208
|
console.log('');
|
|
175
209
|
console.log('Usage:');
|
|
176
|
-
console.log(' memorix session start [--agent codex --agentType codex --instanceId abc] [--projectRoot <path>] [--joinTeam]');
|
|
210
|
+
console.log(' memorix session start [--agent codex --agentType codex --instanceId abc] [--projectRoot <path>] [--joinTeam --use]');
|
|
177
211
|
console.log(' memorix session end --sessionId <id> [--summary "..."]');
|
|
178
212
|
console.log(' memorix session context [--limit 3]');
|
|
179
213
|
console.log('');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import type { AgentTarget } from '../../types.js';
|
|
3
3
|
import { getObservationStore } from '../../store/obs-store.js';
|
|
4
|
+
import { filterReadableObservations, resolveObservationVisibility } from '../../memory/visibility.js';
|
|
4
5
|
import { emitError, emitResult, getCliProjectContext } from './operator-shared.js';
|
|
5
6
|
|
|
6
7
|
export default defineCommand({
|
|
@@ -19,7 +20,7 @@ export default defineCommand({
|
|
|
19
20
|
const asJson = !!args.json;
|
|
20
21
|
|
|
21
22
|
try {
|
|
22
|
-
const { project } = await getCliProjectContext();
|
|
23
|
+
const { project, reader } = await getCliProjectContext();
|
|
23
24
|
const { SkillsEngine } = await import('../../skills/engine.js');
|
|
24
25
|
const engine = new SkillsEngine(project.rootPath);
|
|
25
26
|
|
|
@@ -37,8 +38,10 @@ export default defineCommand({
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
case 'generate': {
|
|
40
|
-
const observations = (
|
|
41
|
-
|
|
41
|
+
const observations = filterReadableObservations(
|
|
42
|
+
await getObservationStore().loadAll(),
|
|
43
|
+
reader,
|
|
44
|
+
).filter((observation) => resolveObservationVisibility(observation) === 'project')
|
|
42
45
|
.map((obs) => ({
|
|
43
46
|
id: obs.id,
|
|
44
47
|
entityName: obs.entityName,
|
|
@@ -82,8 +85,10 @@ export default defineCommand({
|
|
|
82
85
|
return;
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
const observations = (
|
|
86
|
-
|
|
88
|
+
const observations = filterReadableObservations(
|
|
89
|
+
await getObservationStore().loadAll(),
|
|
90
|
+
reader,
|
|
91
|
+
).filter((observation) => resolveObservationVisibility(observation) === 'project')
|
|
87
92
|
.map((obs) => ({
|
|
88
93
|
id: obs.id,
|
|
89
94
|
entityName: obs.entityName,
|
|
@@ -39,8 +39,8 @@ export default defineCommand({
|
|
|
39
39
|
const { initObservationStore, getObservationStore } = await import('../../store/obs-store.js');
|
|
40
40
|
await initObservationStore(dataDir);
|
|
41
41
|
const store = getObservationStore();
|
|
42
|
-
const
|
|
43
|
-
const projectObs =
|
|
42
|
+
const { filterReadableObservations } = await import('../../memory/visibility.js');
|
|
43
|
+
const projectObs = filterReadableObservations(await store.loadAll(), { projectId: project.id });
|
|
44
44
|
obsCount = projectObs.length;
|
|
45
45
|
activeCount = projectObs.filter(o => (o.status ?? 'active') === 'active').length;
|
|
46
46
|
} catch { /* ignore */ }
|
|
@@ -165,7 +165,8 @@ export default defineCommand({
|
|
|
165
165
|
const { initObservationStore, getObservationStore } = await import('../../store/obs-store.js');
|
|
166
166
|
await initObservationStore(dataDir);
|
|
167
167
|
const store = getObservationStore();
|
|
168
|
-
const
|
|
168
|
+
const { filterReadableObservations } = await import('../../memory/visibility.js');
|
|
169
|
+
const allObs = filterReadableObservations(await store.loadAll(), { projectId: project.id });
|
|
169
170
|
const gitCount = allObs.filter(o => o.source === 'git').length;
|
|
170
171
|
const reasoningCount = allObs.filter(o => o.type === 'reasoning').length;
|
|
171
172
|
if (gitCount > 0 || reasoningCount > 0) {
|
package/src/cli/commands/task.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
emitError,
|
|
4
|
+
emitResult,
|
|
5
|
+
getCliProjectContext,
|
|
6
|
+
parseCsvList,
|
|
7
|
+
parseOptionalJsonObject,
|
|
8
|
+
resolveCliActorId,
|
|
9
|
+
shortId,
|
|
10
|
+
} from './operator-shared.js';
|
|
3
11
|
|
|
4
12
|
export default defineCommand({
|
|
5
13
|
meta: {
|
|
@@ -24,7 +32,8 @@ export default defineCommand({
|
|
|
24
32
|
const asJson = !!args.json;
|
|
25
33
|
|
|
26
34
|
try {
|
|
27
|
-
const { project, teamStore } = await getCliProjectContext();
|
|
35
|
+
const { project, teamStore, identity } = await getCliProjectContext();
|
|
36
|
+
const agentId = resolveCliActorId(args.agentId, identity);
|
|
28
37
|
|
|
29
38
|
switch (action) {
|
|
30
39
|
case 'create': {
|
|
@@ -51,7 +60,7 @@ export default defineCommand({
|
|
|
51
60
|
description: args.description as string,
|
|
52
61
|
deps: parseCsvList(args.deps as string | undefined),
|
|
53
62
|
metadata,
|
|
54
|
-
createdBy:
|
|
63
|
+
createdBy: agentId,
|
|
55
64
|
requiredRole: args.requiredRole as string | undefined,
|
|
56
65
|
preferredRole: args.preferredRole as string | undefined,
|
|
57
66
|
});
|
|
@@ -65,11 +74,11 @@ export default defineCommand({
|
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
case 'claim': {
|
|
68
|
-
if (!args.taskId || !
|
|
69
|
-
emitError('taskId and agentId are required for "memorix task claim"', asJson);
|
|
77
|
+
if (!args.taskId || !agentId) {
|
|
78
|
+
emitError('taskId and an active CLI identity or agentId are required for "memorix task claim"', asJson);
|
|
70
79
|
return;
|
|
71
80
|
}
|
|
72
|
-
const result = teamStore.claimTask(args.taskId as string,
|
|
81
|
+
const result = teamStore.claimTask(args.taskId as string, agentId);
|
|
73
82
|
if (!result.success) {
|
|
74
83
|
emitError(result.reason ?? 'Unable to claim task', asJson);
|
|
75
84
|
return;
|
|
@@ -83,11 +92,11 @@ export default defineCommand({
|
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
case 'complete': {
|
|
86
|
-
if (!args.taskId || !
|
|
87
|
-
emitError('taskId,
|
|
95
|
+
if (!args.taskId || !agentId || !args.result) {
|
|
96
|
+
emitError('taskId, result, and an active CLI identity or agentId are required for "memorix task complete"', asJson);
|
|
88
97
|
return;
|
|
89
98
|
}
|
|
90
|
-
const result = teamStore.completeTask(args.taskId as string,
|
|
99
|
+
const result = teamStore.completeTask(args.taskId as string, agentId, args.result as string);
|
|
91
100
|
if (!result.success) {
|
|
92
101
|
emitError(result.reason ?? 'Unable to complete task', asJson);
|
|
93
102
|
return;
|
|
@@ -101,11 +110,11 @@ export default defineCommand({
|
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
case 'fail': {
|
|
104
|
-
if (!args.taskId || !
|
|
105
|
-
emitError('taskId,
|
|
113
|
+
if (!args.taskId || !agentId || !args.result) {
|
|
114
|
+
emitError('taskId, result, and an active CLI identity or agentId are required for "memorix task fail"', asJson);
|
|
106
115
|
return;
|
|
107
116
|
}
|
|
108
|
-
const result = teamStore.failTask(args.taskId as string,
|
|
117
|
+
const result = teamStore.failTask(args.taskId as string, agentId, args.result as string);
|
|
109
118
|
if (!result.success) {
|
|
110
119
|
emitError(result.reason ?? 'Unable to fail task', asJson);
|
|
111
120
|
return;
|
|
@@ -119,11 +128,11 @@ export default defineCommand({
|
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
case 'release': {
|
|
122
|
-
if (!args.taskId || !
|
|
123
|
-
emitError('taskId and agentId are required for "memorix task release"', asJson);
|
|
131
|
+
if (!args.taskId || !agentId) {
|
|
132
|
+
emitError('taskId and an active CLI identity or agentId are required for "memorix task release"', asJson);
|
|
124
133
|
return;
|
|
125
134
|
}
|
|
126
|
-
const result = teamStore.releaseTask(args.taskId as string,
|
|
135
|
+
const result = teamStore.releaseTask(args.taskId as string, agentId);
|
|
127
136
|
if (!result.success) {
|
|
128
137
|
emitError(result.reason ?? 'Unable to release task', asJson);
|
|
129
138
|
return;
|
|
@@ -138,8 +147,8 @@ export default defineCommand({
|
|
|
138
147
|
|
|
139
148
|
case 'list': {
|
|
140
149
|
const tasks =
|
|
141
|
-
args.available &&
|
|
142
|
-
? teamStore.listTasksForAgent(project.id,
|
|
150
|
+
args.available && agentId
|
|
151
|
+
? teamStore.listTasksForAgent(project.id, agentId)
|
|
143
152
|
: teamStore.listTasks(
|
|
144
153
|
project.id,
|
|
145
154
|
args.available
|
package/src/cli/commands/team.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { AGENT_TYPE_ROLE_MAP } from '../../team/team-store.js';
|
|
3
|
-
import {
|
|
3
|
+
import { clearCliIdentity } from '../identity.js';
|
|
4
|
+
import { emitError, emitResult, getCliProjectContext, parseCsvList, parsePositiveInt, resolveCliActorId, shortId } from './operator-shared.js';
|
|
4
5
|
|
|
5
6
|
export default defineCommand({
|
|
6
7
|
meta: {
|
|
@@ -27,7 +28,8 @@ export default defineCommand({
|
|
|
27
28
|
const asJson = !!args.json;
|
|
28
29
|
|
|
29
30
|
try {
|
|
30
|
-
const { project, teamStore } = await getCliProjectContext();
|
|
31
|
+
const { project, dataDir, teamStore, identity } = await getCliProjectContext();
|
|
32
|
+
const selectedAgentId = resolveCliActorId(args.agentId, identity);
|
|
31
33
|
|
|
32
34
|
switch (action) {
|
|
33
35
|
case 'join': {
|
|
@@ -53,20 +55,22 @@ export default defineCommand({
|
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
case 'leave': {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
emitError('agentId is required for "memorix team leave"', asJson);
|
|
58
|
+
if (!selectedAgentId) {
|
|
59
|
+
emitError('an active CLI identity or agentId is required for "memorix team leave"', asJson);
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
|
-
const left = teamStore.leaveAgent(
|
|
62
|
-
const releasedLocks = teamStore.releaseAllLocks(
|
|
63
|
-
const releasedTasks = teamStore.releaseTasksByAgent(
|
|
62
|
+
const left = teamStore.leaveAgent(selectedAgentId);
|
|
63
|
+
const releasedLocks = teamStore.releaseAllLocks(selectedAgentId);
|
|
64
|
+
const releasedTasks = teamStore.releaseTasksByAgent(selectedAgentId);
|
|
64
65
|
if (!left) {
|
|
65
|
-
emitError(`Agent "${
|
|
66
|
+
emitError(`Agent "${selectedAgentId}" not found`, asJson);
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
69
|
+
if (identity?.agentId === selectedAgentId) {
|
|
70
|
+
await clearCliIdentity(dataDir);
|
|
71
|
+
}
|
|
68
72
|
emitResult(
|
|
69
|
-
{ project, agentId, releasedLocks, releasedTasks },
|
|
73
|
+
{ project, agentId: selectedAgentId, releasedLocks, releasedTasks },
|
|
70
74
|
`Left team: released ${releasedLocks} lock(s), ${releasedTasks} task(s)`,
|
|
71
75
|
asJson,
|
|
72
76
|
);
|