memorix 1.2.1 → 1.2.2

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 (199) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +14 -2
  3. package/README.zh-CN.md +14 -2
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15407 -13779
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1321 -529
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8458 -8087
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +16 -0
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +1349 -535
  15. package/dist/sdk.js.map +1 -1
  16. package/dist/types.d.ts +49 -1
  17. package/dist/types.js.map +1 -1
  18. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  20. package/docs/API_REFERENCE.md +24 -4
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/README.md +1 -1
  23. package/docs/dev-log/progress.txt +91 -11
  24. package/package.json +1 -1
  25. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  26. package/src/audit/index.ts +156 -156
  27. package/src/cli/command-guide.ts +192 -0
  28. package/src/cli/commands/audit-list.ts +89 -89
  29. package/src/cli/commands/audit.ts +9 -4
  30. package/src/cli/commands/background.ts +659 -659
  31. package/src/cli/commands/cleanup.ts +5 -1
  32. package/src/cli/commands/codegraph.ts +15 -5
  33. package/src/cli/commands/context.ts +3 -2
  34. package/src/cli/commands/doctor.ts +4 -2
  35. package/src/cli/commands/explain.ts +9 -3
  36. package/src/cli/commands/formation.ts +48 -48
  37. package/src/cli/commands/git-hook-install.ts +111 -111
  38. package/src/cli/commands/handoff.ts +75 -61
  39. package/src/cli/commands/hooks-status.ts +63 -63
  40. package/src/cli/commands/identity.ts +116 -0
  41. package/src/cli/commands/ingest-commit.ts +153 -153
  42. package/src/cli/commands/ingest-image.ts +71 -69
  43. package/src/cli/commands/ingest-log.ts +180 -180
  44. package/src/cli/commands/ingest.ts +44 -44
  45. package/src/cli/commands/integrate-shared.ts +15 -15
  46. package/src/cli/commands/lock.ts +93 -92
  47. package/src/cli/commands/memory.ts +58 -21
  48. package/src/cli/commands/message.ts +123 -118
  49. package/src/cli/commands/operator-shared.ts +98 -3
  50. package/src/cli/commands/poll.ts +74 -64
  51. package/src/cli/commands/purge-all-memory.ts +85 -85
  52. package/src/cli/commands/purge-project-memory.ts +83 -83
  53. package/src/cli/commands/reasoning.ts +135 -121
  54. package/src/cli/commands/retention.ts +9 -4
  55. package/src/cli/commands/serve-http.ts +8 -2
  56. package/src/cli/commands/serve-shared.ts +118 -118
  57. package/src/cli/commands/session.ts +29 -3
  58. package/src/cli/commands/skills.ts +124 -119
  59. package/src/cli/commands/status.ts +4 -3
  60. package/src/cli/commands/task.ts +193 -184
  61. package/src/cli/commands/team.ts +14 -10
  62. package/src/cli/commands/transfer.ts +108 -55
  63. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  64. package/src/cli/identity.ts +89 -0
  65. package/src/cli/index.ts +96 -19
  66. package/src/cli/invocation.ts +115 -0
  67. package/src/cli/tui/ChatView.tsx +234 -234
  68. package/src/cli/tui/CommandBar.tsx +312 -312
  69. package/src/cli/tui/ContextRail.tsx +118 -118
  70. package/src/cli/tui/HeaderBar.tsx +72 -72
  71. package/src/cli/tui/LogoBanner.tsx +51 -51
  72. package/src/cli/tui/Sidebar.tsx +179 -179
  73. package/src/cli/tui/chat-service.ts +41 -18
  74. package/src/cli/tui/data.ts +23 -44
  75. package/src/cli/tui/index.ts +41 -41
  76. package/src/cli/tui/markdown-render.tsx +371 -371
  77. package/src/cli/tui/operator-context.ts +60 -0
  78. package/src/cli/tui/use-mouse.ts +157 -157
  79. package/src/cli/tui/useNavigation.ts +56 -56
  80. package/src/cli/tui/views/MemoryView.tsx +10 -8
  81. package/src/cli/update-checker.ts +211 -211
  82. package/src/cli/version.ts +7 -7
  83. package/src/cli/workbench.ts +1 -1
  84. package/src/codegraph/auto-context.ts +31 -2
  85. package/src/codegraph/context-pack.ts +1 -0
  86. package/src/codegraph/project-context.ts +2 -0
  87. package/src/compact/engine.ts +26 -10
  88. package/src/compact/index-format.ts +25 -2
  89. package/src/compact/token-budget.ts +74 -74
  90. package/src/dashboard/project-classification.ts +64 -64
  91. package/src/dashboard/server.ts +46 -9
  92. package/src/embedding/fastembed-provider.ts +142 -142
  93. package/src/embedding/transformers-provider.ts +111 -111
  94. package/src/git/extractor.ts +209 -209
  95. package/src/git/hooks-path.ts +85 -85
  96. package/src/hooks/admission.ts +117 -0
  97. package/src/hooks/handler.ts +98 -91
  98. package/src/hooks/pattern-detector.ts +173 -173
  99. package/src/hooks/significance-filter.ts +250 -250
  100. package/src/knowledge/context-assembly.ts +97 -0
  101. package/src/knowledge/workset.ts +179 -10
  102. package/src/llm/memory-manager.ts +328 -328
  103. package/src/llm/provider.ts +885 -885
  104. package/src/llm/quality.ts +248 -248
  105. package/src/memory/admission.ts +57 -0
  106. package/src/memory/attribution-guard.ts +249 -249
  107. package/src/memory/consolidation.ts +13 -2
  108. package/src/memory/disclosure-policy.ts +140 -135
  109. package/src/memory/entity-extractor.ts +197 -197
  110. package/src/memory/export-import.ts +11 -3
  111. package/src/memory/formation/evaluate.ts +217 -217
  112. package/src/memory/formation/extract.ts +361 -361
  113. package/src/memory/formation/index.ts +417 -417
  114. package/src/memory/formation/resolve.ts +344 -344
  115. package/src/memory/formation/types.ts +315 -315
  116. package/src/memory/freshness.ts +122 -122
  117. package/src/memory/graph-context.ts +8 -2
  118. package/src/memory/graph.ts +197 -197
  119. package/src/memory/observations.ts +162 -4
  120. package/src/memory/quality-audit.ts +2 -0
  121. package/src/memory/refs.ts +94 -94
  122. package/src/memory/retention.ts +22 -2
  123. package/src/memory/secret-filter.ts +79 -79
  124. package/src/memory/session.ts +5 -2
  125. package/src/memory/visibility.ts +80 -0
  126. package/src/multimodal/image-loader.ts +143 -143
  127. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  128. package/src/orchestrate/adapters/claude.ts +111 -111
  129. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  130. package/src/orchestrate/adapters/codex.ts +41 -41
  131. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  132. package/src/orchestrate/adapters/gemini.ts +42 -42
  133. package/src/orchestrate/adapters/index.ts +73 -73
  134. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  135. package/src/orchestrate/adapters/opencode.ts +47 -47
  136. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  137. package/src/orchestrate/adapters/types.ts +77 -77
  138. package/src/orchestrate/capability-router.ts +284 -284
  139. package/src/orchestrate/context-compact.ts +188 -188
  140. package/src/orchestrate/cost-tracker.ts +219 -219
  141. package/src/orchestrate/error-recovery.ts +191 -191
  142. package/src/orchestrate/evidence.ts +140 -140
  143. package/src/orchestrate/ledger.ts +110 -110
  144. package/src/orchestrate/memorix-bridge.ts +378 -340
  145. package/src/orchestrate/output-budget.ts +80 -80
  146. package/src/orchestrate/permission.ts +152 -152
  147. package/src/orchestrate/pipeline-trace.ts +131 -131
  148. package/src/orchestrate/prompt-builder.ts +155 -155
  149. package/src/orchestrate/ring-buffer.ts +37 -37
  150. package/src/orchestrate/task-graph.ts +389 -389
  151. package/src/orchestrate/worktree.ts +232 -232
  152. package/src/project/aliases.ts +374 -374
  153. package/src/project/detector.ts +268 -268
  154. package/src/rules/adapters/claude-code.ts +99 -99
  155. package/src/rules/adapters/codex.ts +97 -97
  156. package/src/rules/adapters/copilot.ts +124 -124
  157. package/src/rules/adapters/cursor.ts +114 -114
  158. package/src/rules/adapters/kiro.ts +126 -126
  159. package/src/rules/adapters/trae.ts +56 -56
  160. package/src/rules/adapters/windsurf.ts +83 -83
  161. package/src/rules/syncer.ts +235 -235
  162. package/src/runtime/control-plane-maintenance.ts +1 -0
  163. package/src/runtime/isolated-maintenance.ts +1 -0
  164. package/src/runtime/lifecycle.ts +18 -0
  165. package/src/runtime/maintenance-jobs.ts +1 -0
  166. package/src/runtime/maintenance-runner.ts +2 -0
  167. package/src/runtime/project-maintenance.ts +89 -0
  168. package/src/sdk.ts +334 -304
  169. package/src/search/intent-detector.ts +289 -289
  170. package/src/search/query-expansion.ts +52 -52
  171. package/src/server/formation-timeout.ts +27 -27
  172. package/src/server.ts +260 -81
  173. package/src/skills/mini-skills.ts +386 -386
  174. package/src/store/chat-store.ts +119 -119
  175. package/src/store/graph-store.ts +249 -249
  176. package/src/store/mini-skill-store.ts +349 -349
  177. package/src/store/orama-store.ts +61 -6
  178. package/src/store/persistence-json.ts +212 -212
  179. package/src/store/persistence.ts +291 -291
  180. package/src/store/project-affinity.ts +195 -195
  181. package/src/store/sqlite-db.ts +23 -1
  182. package/src/store/sqlite-store.ts +12 -2
  183. package/src/team/event-bus.ts +76 -76
  184. package/src/team/file-locks.ts +173 -173
  185. package/src/team/handoff.ts +168 -161
  186. package/src/team/messages.ts +203 -203
  187. package/src/team/poll.ts +132 -132
  188. package/src/team/tasks.ts +211 -211
  189. package/src/types.ts +51 -0
  190. package/src/wiki/generator.ts +2 -0
  191. package/src/workspace/mcp-adapters/codex.ts +191 -191
  192. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  193. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  194. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  195. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  196. package/src/workspace/mcp-adapters/trae.ts +134 -134
  197. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  198. package/src/workspace/sanitizer.ts +60 -60
  199. package/src/workspace/workflow-sync.ts +131 -131
@@ -1,121 +1,126 @@
1
- import { defineCommand } from 'citty';
2
- import { emitError, emitResult, getCliProjectContext, shortId } from './operator-shared.js';
3
-
4
- export default defineCommand({
5
- meta: {
6
- name: 'message',
7
- description: 'Send, broadcast, and read team messages from the CLI',
8
- },
9
- args: {
10
- from: { type: 'string', description: 'Sender agent ID' },
11
- to: { type: 'string', description: 'Recipient agent ID' },
12
- type: { type: 'string', description: 'Message type (request, response, info, handoff, etc.)' },
13
- content: { type: 'string', description: 'Message content' },
14
- agentId: { type: 'string', description: 'Agent ID for inbox reads' },
15
- markRead: { type: 'boolean', description: 'Mark inbox messages as read after listing them' },
16
- toRole: { type: 'string', description: 'Optional role target for the message' },
17
- handoffStatus: { type: 'string', description: 'Optional handoff status when type=handoff' },
18
- json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
19
- },
20
- run: async ({ args }) => {
21
- const action = (args._ as string[])?.[0] || '';
22
- const asJson = !!args.json;
23
-
24
- try {
25
- const { project, teamStore } = await getCliProjectContext();
26
-
27
- switch (action) {
28
- case 'send': {
29
- if (!args.from || !args.type || !args.content) {
30
- emitError('from, type, and content are required for "memorix message send"', asJson);
1
+ import { defineCommand } from 'citty';
2
+ import { emitError, emitResult, getCliProjectContext, resolveCliActorId, shortId } from './operator-shared.js';
3
+
4
+ export default defineCommand({
5
+ meta: {
6
+ name: 'message',
7
+ description: 'Send, broadcast, and read team messages from the CLI',
8
+ },
9
+ args: {
10
+ from: { type: 'string', description: 'Sender agent ID' },
11
+ to: { type: 'string', description: 'Recipient agent ID' },
12
+ type: { type: 'string', description: 'Message type (request, response, info, handoff, etc.)' },
13
+ content: { type: 'string', description: 'Message content' },
14
+ agentId: { type: 'string', description: 'Agent ID for inbox reads' },
15
+ markRead: { type: 'boolean', description: 'Mark inbox messages as read after listing them' },
16
+ toRole: { type: 'string', description: 'Optional role target for the message' },
17
+ handoffStatus: { type: 'string', description: 'Optional handoff status when type=handoff' },
18
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
19
+ },
20
+ run: async ({ args }) => {
21
+ const action = (args._ as string[])?.[0] || '';
22
+ const asJson = !!args.json;
23
+
24
+ try {
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
+ );
32
+
33
+ switch (action) {
34
+ case 'send': {
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);
37
+ return;
38
+ }
39
+ if (!args.to && !args.toRole) {
40
+ emitError('Either --to or --toRole is required for "memorix message send"', asJson);
41
+ return;
42
+ }
43
+ const message = teamStore.sendMessage({
44
+ projectId: project.id,
45
+ senderAgentId,
46
+ recipientAgentId: (args.to as string | undefined) ?? null,
47
+ type: args.type as string,
48
+ content: args.content as string,
49
+ toRole: (args.toRole as string | undefined) ?? null,
50
+ handoffStatus:
51
+ (args.handoffStatus as string | undefined) ??
52
+ ((args.type as string) === 'handoff' ? 'open' : null),
53
+ });
54
+ if ('error' in message) {
55
+ emitError(message.error, asJson);
56
+ return;
57
+ }
58
+ emitResult(
59
+ { project, message },
60
+ `Message sent: ${shortId(message.id)} to ${args.to ? shortId(args.to as string) : `role ${args.toRole}`}`,
61
+ asJson,
62
+ );
63
+ return;
64
+ }
65
+
66
+ case 'broadcast': {
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);
69
+ return;
70
+ }
71
+ const message = teamStore.sendMessage({
72
+ projectId: project.id,
73
+ senderAgentId,
74
+ recipientAgentId: null,
75
+ type: args.type as string,
76
+ content: args.content as string,
77
+ });
78
+ if ('error' in message) {
79
+ emitError(message.error, asJson);
80
+ return;
81
+ }
82
+ emitResult(
83
+ { project, message },
84
+ `Broadcast sent: ${shortId(message.id)}`,
85
+ asJson,
86
+ );
87
+ return;
88
+ }
89
+
90
+ case 'inbox': {
91
+ if (!inboxAgentId) {
92
+ emitError('an active CLI identity or agentId is required for "memorix message inbox"', asJson);
31
93
  return;
32
94
  }
33
- if (!args.to && !args.toRole) {
34
- emitError('Either --to or --toRole is required for "memorix message send"', asJson);
35
- return;
36
- }
37
- const message = teamStore.sendMessage({
38
- projectId: project.id,
39
- senderAgentId: args.from as string,
40
- recipientAgentId: (args.to as string | undefined) ?? null,
41
- type: args.type as string,
42
- content: args.content as string,
43
- toRole: (args.toRole as string | undefined) ?? null,
44
- handoffStatus:
45
- (args.handoffStatus as string | undefined) ??
46
- ((args.type as string) === 'handoff' ? 'open' : null),
47
- });
48
- if ('error' in message) {
49
- emitError(message.error, asJson);
50
- return;
51
- }
52
- emitResult(
53
- { project, message },
54
- `Message sent: ${shortId(message.id)} to ${args.to ? shortId(args.to as string) : `role ${args.toRole}`}`,
55
- asJson,
56
- );
57
- return;
58
- }
59
-
60
- case 'broadcast': {
61
- if (!args.from || !args.type || !args.content) {
62
- emitError('from, type, and content are required for "memorix message broadcast"', asJson);
63
- return;
64
- }
65
- const message = teamStore.sendMessage({
66
- projectId: project.id,
67
- senderAgentId: args.from as string,
68
- recipientAgentId: null,
69
- type: args.type as string,
70
- content: args.content as string,
71
- });
72
- if ('error' in message) {
73
- emitError(message.error, asJson);
74
- return;
75
- }
76
- emitResult(
77
- { project, message },
78
- `Broadcast sent: ${shortId(message.id)}`,
79
- asJson,
80
- );
81
- return;
82
- }
83
-
84
- case 'inbox': {
85
- const agentId = (args.agentId as string | undefined) || (args.from as string | undefined);
86
- if (!agentId) {
87
- emitError('agentId is required for "memorix message inbox"', asJson);
88
- return;
89
- }
90
- const messages = teamStore.getInbox(project.id, agentId);
91
- const unreadCount = teamStore.getUnreadCount(project.id, agentId);
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, agentId);
94
- }
95
- emitResult(
96
- { project, unreadCount, messages },
97
- messages.length === 0
98
- ? 'Inbox empty.'
99
- : [`Inbox: ${unreadCount} unread / ${messages.length} total`, '', ...messages.map((message) => {
100
- const sender = teamStore.getAgent(message.sender_agent_id);
101
- return `- ${message.read_at ? ' ' : '*'} [${message.type}] from ${sender?.name ?? shortId(message.sender_agent_id)}: ${message.content}`;
102
- })].join('\n'),
103
- asJson,
104
- );
105
- return;
106
- }
107
-
108
- default:
109
- console.log('Memorix Message Commands');
110
- console.log('');
111
- console.log('Usage:');
112
- console.log(' memorix message send --from <agentId> --to <agentId> --type info --content "..."');
113
- console.log(' memorix message send --from <agentId> --toRole reviewer --type handoff --content "..."');
114
- console.log(' memorix message broadcast --from <agentId> --type announcement --content "..."');
115
- console.log(' memorix message inbox --agentId <id> [--markRead]');
116
- }
117
- } catch (error) {
118
- emitError(error instanceof Error ? error.message : String(error), asJson);
119
- }
120
- },
121
- });
98
+ teamStore.markAllRead(project.id, inboxAgentId);
99
+ }
100
+ emitResult(
101
+ { project, unreadCount, messages },
102
+ messages.length === 0
103
+ ? 'Inbox empty.'
104
+ : [`Inbox: ${unreadCount} unread / ${messages.length} total`, '', ...messages.map((message) => {
105
+ const sender = teamStore.getAgent(message.sender_agent_id);
106
+ return `- ${message.read_at ? ' ' : '*'} [${message.type}] from ${sender?.name ?? shortId(message.sender_agent_id)}: ${message.content}`;
107
+ })].join('\n'),
108
+ asJson,
109
+ );
110
+ return;
111
+ }
112
+
113
+ default:
114
+ console.log('Memorix Message Commands');
115
+ console.log('');
116
+ console.log('Usage:');
117
+ console.log(' memorix message send --from <agentId> --to <agentId> --type info --content "..."');
118
+ console.log(' memorix message send --from <agentId> --toRole reviewer --type handoff --content "..."');
119
+ console.log(' memorix message broadcast --from <agentId> --type announcement --content "..."');
120
+ console.log(' memorix message inbox --agentId <id> [--markRead]');
121
+ }
122
+ } catch (error) {
123
+ emitError(error instanceof Error ? error.message : String(error), asJson);
124
+ }
125
+ },
126
+ });
@@ -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
+ }
@@ -1,70 +1,80 @@
1
- import { defineCommand } from 'citty';
2
- import { computePoll, computeWatermark } from '../../team/poll.js';
3
- import { getObservationStore } from '../../store/obs-store.js';
1
+ import { defineCommand } from 'citty';
2
+ import { computePoll, computeWatermark } from '../../team/poll.js';
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';
7
-
8
- export default defineCommand({
9
- meta: {
10
- name: 'poll',
11
- description: 'Get a full project coordination snapshot for an agent',
12
- },
13
- args: {
14
- agentId: { type: 'string', description: 'Agent ID for personalized situational awareness' },
15
- markInboxRead: { type: 'boolean', description: 'Mark all inbox messages as read after returning them' },
16
- json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
17
- },
18
- run: async ({ args }) => {
19
- const asJson = !!args.json;
20
-
21
- try {
22
- const { project, teamStore } = await getCliProjectContext();
23
- const agentId = args.agentId as string | undefined;
24
-
25
- let watermark = computeWatermark(0, 0, 0);
26
- if (agentId) {
27
- const agent = teamStore.getAgent(agentId);
28
- if (!agent) {
6
+ import { filterReadableObservations } from '../../memory/visibility.js';
7
+ import { emitError, emitResult, getCliProjectContext, resolveCliActorId, shortId } from './operator-shared.js';
8
+
9
+ export default defineCommand({
10
+ meta: {
11
+ name: 'poll',
12
+ description: 'Get a full project coordination snapshot for an agent',
13
+ },
14
+ args: {
15
+ agentId: { type: 'string', description: 'Agent ID for personalized situational awareness' },
16
+ markInboxRead: { type: 'boolean', description: 'Mark all inbox messages as read after returning them' },
17
+ json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
18
+ },
19
+ run: async ({ args }) => {
20
+ const asJson = !!args.json;
21
+
22
+ try {
23
+ const { project, teamStore, identity, reader: activeReader } = await getCliProjectContext();
24
+ const agentId = resolveCliActorId(args.agentId, identity);
25
+ let reader = activeReader;
26
+
27
+ let watermark = computeWatermark(0, 0, 0);
28
+ if (agentId) {
29
+ const agent = teamStore.getAgent(agentId);
30
+ if (!agent || agent.project_id !== project.id) {
29
31
  emitError(`Unknown agent "${agentId}"`, asJson);
30
32
  return;
31
33
  }
32
-
33
- const lastSeen = agent.last_seen_obs_generation;
34
- const currentGen = getObservationStore().getGeneration();
35
- const projectObs = await withFreshIndex(() =>
36
- getAllObservations().filter(
37
- (obs) => obs.projectId === project.id && (obs.writeGeneration ?? 0) > lastSeen,
34
+ reader = {
35
+ projectId: project.id,
36
+ agentId,
37
+ isTeamMember: agent.status === 'active',
38
+ };
39
+
40
+ const lastSeen = agent.last_seen_obs_generation;
41
+ const currentGen = getObservationStore().getGeneration();
42
+ const projectObs = await withFreshIndex(() =>
43
+ filterReadableObservations(
44
+ getAllObservations().filter(
45
+ (obs) => obs.projectId === project.id && (obs.writeGeneration ?? 0) > lastSeen,
46
+ ),
47
+ reader,
38
48
  ),
39
- );
40
- watermark = computeWatermark(lastSeen, currentGen, projectObs.length);
41
- teamStore.updateWatermark(agentId, currentGen);
42
- teamStore.heartbeat(agentId);
43
- }
44
-
45
- const poll = computePoll(teamStore, project.id, agentId ?? null, watermark);
46
- if (args.markInboxRead && agentId) {
47
- teamStore.markAllRead(project.id, agentId);
48
- }
49
-
50
- const text = [
51
- poll.agent ? `You: ${shortId(poll.agent.agentId)} (${poll.agent.status})` : 'Project-level overview',
52
- poll.watermark.newObservationCount > 0
53
- ? `${poll.watermark.newObservationCount} new observation(s)`
54
- : 'No unseen observations',
55
- poll.inbox.unreadCount > 0 ? `${poll.inbox.unreadCount} unread message(s)` : 'Inbox clear',
56
- poll.tasks.myInProgress.length > 0
57
- ? `${poll.tasks.myInProgress.length} in-progress task(s)`
58
- : 'No in-progress tasks',
59
- poll.tasks.availableToClaim.length > 0
60
- ? `${poll.tasks.availableToClaim.length} task(s) available to claim`
61
- : 'No claimable tasks',
62
- `Team: ${poll.team.activeAgents.length} active / ${poll.team.totalAgents} total`,
63
- ].join('\n');
64
-
65
- emitResult({ project, poll }, text, asJson);
66
- } catch (error) {
67
- emitError(error instanceof Error ? error.message : String(error), asJson);
68
- }
69
- },
70
- });
49
+ );
50
+ watermark = computeWatermark(lastSeen, currentGen, projectObs.length);
51
+ teamStore.updateWatermark(agentId, currentGen);
52
+ teamStore.heartbeat(agentId);
53
+ }
54
+
55
+ const poll = computePoll(teamStore, project.id, agentId ?? null, watermark);
56
+ if (args.markInboxRead && agentId) {
57
+ teamStore.markAllRead(project.id, agentId);
58
+ }
59
+
60
+ const text = [
61
+ poll.agent ? `You: ${shortId(poll.agent.agentId)} (${poll.agent.status})` : 'Project-level overview',
62
+ poll.watermark.newObservationCount > 0
63
+ ? `${poll.watermark.newObservationCount} new observation(s)`
64
+ : 'No unseen observations',
65
+ poll.inbox.unreadCount > 0 ? `${poll.inbox.unreadCount} unread message(s)` : 'Inbox clear',
66
+ poll.tasks.myInProgress.length > 0
67
+ ? `${poll.tasks.myInProgress.length} in-progress task(s)`
68
+ : 'No in-progress tasks',
69
+ poll.tasks.availableToClaim.length > 0
70
+ ? `${poll.tasks.availableToClaim.length} task(s) available to claim`
71
+ : 'No claimable tasks',
72
+ `Team: ${poll.team.activeAgents.length} active / ${poll.team.totalAgents} total`,
73
+ ].join('\n');
74
+
75
+ emitResult({ project, poll }, text, asJson);
76
+ } catch (error) {
77
+ emitError(error instanceof Error ? error.message : String(error), asJson);
78
+ }
79
+ },
80
+ });