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.
Files changed (90) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +14 -2
  3. package/README.zh-CN.md +14 -2
  4. package/dist/cli/index.js +15424 -13780
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/index.js +1337 -536
  7. package/dist/index.js.map +1 -1
  8. package/dist/maintenance-runner.d.ts +1 -1
  9. package/dist/maintenance-runner.js +8458 -8087
  10. package/dist/maintenance-runner.js.map +1 -1
  11. package/dist/memcode-runtime/CHANGELOG.md +23 -0
  12. package/dist/sdk.d.ts +7 -2
  13. package/dist/sdk.js +1365 -542
  14. package/dist/sdk.js.map +1 -1
  15. package/dist/types.d.ts +49 -1
  16. package/dist/types.js.map +1 -1
  17. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  18. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  19. package/docs/API_REFERENCE.md +24 -4
  20. package/docs/README.md +1 -1
  21. package/docs/dev-log/progress.txt +101 -11
  22. package/package.json +1 -1
  23. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  24. package/src/cli/command-guide.ts +192 -0
  25. package/src/cli/commands/audit.ts +9 -4
  26. package/src/cli/commands/cleanup.ts +5 -1
  27. package/src/cli/commands/codegraph.ts +15 -5
  28. package/src/cli/commands/context.ts +3 -2
  29. package/src/cli/commands/doctor.ts +4 -2
  30. package/src/cli/commands/explain.ts +9 -3
  31. package/src/cli/commands/handoff.ts +21 -7
  32. package/src/cli/commands/identity.ts +116 -0
  33. package/src/cli/commands/ingest-image.ts +5 -3
  34. package/src/cli/commands/lock.ts +11 -10
  35. package/src/cli/commands/memory.ts +58 -21
  36. package/src/cli/commands/message.ts +19 -14
  37. package/src/cli/commands/operator-shared.ts +98 -3
  38. package/src/cli/commands/poll.ts +16 -6
  39. package/src/cli/commands/reasoning.ts +17 -3
  40. package/src/cli/commands/retention.ts +9 -4
  41. package/src/cli/commands/serve-http.ts +8 -2
  42. package/src/cli/commands/session.ts +44 -10
  43. package/src/cli/commands/skills.ts +10 -5
  44. package/src/cli/commands/status.ts +4 -3
  45. package/src/cli/commands/task.ts +26 -17
  46. package/src/cli/commands/team.ts +14 -10
  47. package/src/cli/commands/transfer.ts +63 -10
  48. package/src/cli/identity.ts +89 -0
  49. package/src/cli/index.ts +96 -19
  50. package/src/cli/invocation.ts +115 -0
  51. package/src/cli/tui/chat-service.ts +41 -18
  52. package/src/cli/tui/data.ts +23 -44
  53. package/src/cli/tui/operator-context.ts +60 -0
  54. package/src/cli/tui/session-service.ts +3 -2
  55. package/src/cli/tui/views/MemoryView.tsx +10 -8
  56. package/src/codegraph/auto-context.ts +31 -2
  57. package/src/codegraph/context-pack.ts +1 -0
  58. package/src/codegraph/project-context.ts +2 -0
  59. package/src/compact/engine.ts +26 -10
  60. package/src/compact/index-format.ts +25 -2
  61. package/src/dashboard/server.ts +46 -9
  62. package/src/hooks/admission.ts +117 -0
  63. package/src/hooks/handler.ts +98 -91
  64. package/src/knowledge/context-assembly.ts +97 -0
  65. package/src/knowledge/workset.ts +179 -10
  66. package/src/memory/admission.ts +57 -0
  67. package/src/memory/consolidation.ts +13 -2
  68. package/src/memory/disclosure-policy.ts +6 -1
  69. package/src/memory/export-import.ts +11 -3
  70. package/src/memory/graph-context.ts +8 -2
  71. package/src/memory/observations.ts +162 -4
  72. package/src/memory/quality-audit.ts +2 -0
  73. package/src/memory/retention.ts +22 -2
  74. package/src/memory/session.ts +29 -11
  75. package/src/memory/visibility.ts +80 -0
  76. package/src/orchestrate/memorix-bridge.ts +38 -0
  77. package/src/runtime/control-plane-maintenance.ts +1 -0
  78. package/src/runtime/isolated-maintenance.ts +1 -0
  79. package/src/runtime/lifecycle.ts +18 -0
  80. package/src/runtime/maintenance-jobs.ts +1 -0
  81. package/src/runtime/maintenance-runner.ts +2 -0
  82. package/src/runtime/project-maintenance.ts +89 -0
  83. package/src/sdk.ts +35 -5
  84. package/src/server.ts +267 -83
  85. package/src/store/orama-store.ts +61 -6
  86. package/src/store/sqlite-db.ts +23 -1
  87. package/src/store/sqlite-store.ts +12 -2
  88. package/src/team/handoff.ts +7 -0
  89. package/src/types.ts +51 -0
  90. 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 { ProjectInfo, ObservationType, ObservationStatus } from '../../types.js';
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 detection = detectProjectWithDiagnostics(options?.projectRoot ?? process.cwd());
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
- return { project, dataDir, teamStore };
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
+ }
@@ -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 { emitError, emitResult, getCliProjectContext, shortId } from './operator-shared.js';
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 as string | undefined;
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
- getAllObservations().filter(
37
- (obs) => obs.projectId === project.id && (obs.writeGeneration ?? 0) > lastSeen,
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 { emitError, emitResult, getCliProjectContext, parseCsvList, parsePositiveInt } from './operator-shared.js';
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 = getAllObservations()
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 store.loadByProject(projectId, status ? { status } : undefined);
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
- const result = await startSession(dataDir, project.id, {
39
- sessionId: args.sessionId as string | undefined,
40
- agent: args.agent as string | undefined,
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
- getAllObservations().filter(
74
- (obs) => obs.projectId === project.id && (obs.writeGeneration ?? 0) > lastSeen,
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 = (await getObservationStore().loadAll())
41
- .filter((obs) => obs.projectId === project.id)
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 = (await getObservationStore().loadAll())
86
- .filter((obs) => obs.projectId === project.id)
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 data = await store.loadAll() as Array<{ projectId?: string; status?: string }>;
43
- const projectObs = data.filter(o => o.projectId === project.id);
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 allObs = await store.loadAll() as Array<{ source?: string; type?: string }>;
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) {
@@ -1,5 +1,13 @@
1
1
  import { defineCommand } from 'citty';
2
- import { emitError, emitResult, getCliProjectContext, parseCsvList, parseOptionalJsonObject, shortId } from './operator-shared.js';
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: args.agentId as string | undefined,
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 || !args.agentId) {
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, args.agentId 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 || !args.agentId || !args.result) {
87
- emitError('taskId, agentId, and result are required for "memorix task complete"', asJson);
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, args.agentId as string, args.result 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 || !args.agentId || !args.result) {
105
- emitError('taskId, agentId, and result are required for "memorix task fail"', asJson);
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, args.agentId as string, args.result 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 || !args.agentId) {
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, args.agentId 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 && args.agentId
142
- ? teamStore.listTasksForAgent(project.id, args.agentId as string)
150
+ args.available && agentId
151
+ ? teamStore.listTasksForAgent(project.id, agentId)
143
152
  : teamStore.listTasks(
144
153
  project.id,
145
154
  args.available
@@ -1,6 +1,7 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { AGENT_TYPE_ROLE_MAP } from '../../team/team-store.js';
3
- import { emitError, emitResult, getCliProjectContext, parseCsvList, parsePositiveInt, shortId } from './operator-shared.js';
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
- const agentId = args.agentId as string | undefined;
57
- if (!agentId) {
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(agentId);
62
- const releasedLocks = teamStore.releaseAllLocks(agentId);
63
- const releasedTasks = teamStore.releaseTasksByAgent(agentId);
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 "${agentId}" not found`, asJson);
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
  );