memorix 1.1.7 → 1.1.8

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 (174) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/CLAUDE.md +6 -1
  3. package/README.md +21 -0
  4. package/README.zh-CN.md +21 -0
  5. package/TEAM.md +86 -86
  6. package/dist/cli/index.js +730 -150
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/dashboard/static/index.html +201 -201
  9. package/dist/dashboard/static/style.css +3584 -3584
  10. package/dist/index.js +15 -8
  11. package/dist/index.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +13 -0
  13. package/dist/memcode-runtime/package.json +4 -4
  14. package/dist/sdk.js +15 -8
  15. package/dist/sdk.js.map +1 -1
  16. package/docs/AGENT_OPERATOR_PLAYBOOK.md +18 -0
  17. package/docs/DESIGN_DECISIONS.md +357 -357
  18. package/docs/SETUP.md +10 -0
  19. package/docs/dev-log/progress.txt +35 -26
  20. package/package.json +1 -1
  21. package/src/audit/index.ts +156 -156
  22. package/src/cli/commands/agent-integrations.ts +623 -0
  23. package/src/cli/commands/audit-list.ts +89 -89
  24. package/src/cli/commands/background.ts +659 -659
  25. package/src/cli/commands/cleanup.ts +255 -255
  26. package/src/cli/commands/doctor.ts +23 -0
  27. package/src/cli/commands/formation.ts +48 -48
  28. package/src/cli/commands/git-hook-install.ts +111 -111
  29. package/src/cli/commands/handoff.ts +66 -66
  30. package/src/cli/commands/hooks-status.ts +63 -63
  31. package/src/cli/commands/ingest-commit.ts +153 -153
  32. package/src/cli/commands/ingest-image.ts +73 -73
  33. package/src/cli/commands/ingest-log.ts +180 -180
  34. package/src/cli/commands/ingest.ts +44 -44
  35. package/src/cli/commands/integrate-shared.ts +15 -15
  36. package/src/cli/commands/lock.ts +96 -96
  37. package/src/cli/commands/message.ts +121 -121
  38. package/src/cli/commands/poll.ts +70 -70
  39. package/src/cli/commands/purge-all-memory.ts +85 -85
  40. package/src/cli/commands/purge-project-memory.ts +83 -83
  41. package/src/cli/commands/reasoning.ts +132 -132
  42. package/src/cli/commands/repair.ts +60 -0
  43. package/src/cli/commands/retention.ts +108 -108
  44. package/src/cli/commands/serve-shared.ts +118 -118
  45. package/src/cli/commands/setup.ts +3 -3
  46. package/src/cli/commands/skills.ts +123 -123
  47. package/src/cli/commands/task.ts +192 -192
  48. package/src/cli/commands/transfer.ts +73 -73
  49. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  50. package/src/cli/index.ts +3 -1
  51. package/src/cli/tui/ChatView.tsx +234 -234
  52. package/src/cli/tui/CommandBar.tsx +312 -312
  53. package/src/cli/tui/ContextRail.tsx +118 -118
  54. package/src/cli/tui/HeaderBar.tsx +72 -72
  55. package/src/cli/tui/LogoBanner.tsx +51 -51
  56. package/src/cli/tui/Panels.tsx +632 -632
  57. package/src/cli/tui/Sidebar.tsx +179 -179
  58. package/src/cli/tui/chat-service.ts +742 -742
  59. package/src/cli/tui/data.ts +547 -547
  60. package/src/cli/tui/index.ts +41 -41
  61. package/src/cli/tui/markdown-render.tsx +371 -371
  62. package/src/cli/tui/theme.ts +178 -178
  63. package/src/cli/tui/use-mouse.ts +157 -157
  64. package/src/cli/tui/useNavigation.ts +56 -56
  65. package/src/cli/update-checker.ts +211 -211
  66. package/src/cli/version.ts +7 -7
  67. package/src/cli/workbench.ts +1 -1
  68. package/src/codegraph/context-pack.ts +5 -1
  69. package/src/codegraph/lite-provider.ts +5 -1
  70. package/src/codegraph/project-context.ts +5 -1
  71. package/src/compact/token-budget.ts +74 -74
  72. package/src/config/behavior.ts +59 -59
  73. package/src/dashboard/project-classification.ts +64 -64
  74. package/src/dashboard/static/index.html +201 -201
  75. package/src/dashboard/static/style.css +3584 -3584
  76. package/src/embedding/fastembed-provider.ts +142 -142
  77. package/src/embedding/transformers-provider.ts +111 -111
  78. package/src/git/extractor.ts +209 -209
  79. package/src/git/hooks-path.ts +85 -85
  80. package/src/git/noise-filter.ts +210 -210
  81. package/src/hooks/installers/index.ts +4 -4
  82. package/src/hooks/official-skills.ts +1 -1
  83. package/src/hooks/pattern-detector.ts +173 -173
  84. package/src/hooks/rules/memorix-agent-rules.md +2 -2
  85. package/src/hooks/significance-filter.ts +250 -250
  86. package/src/llm/memory-manager.ts +328 -328
  87. package/src/llm/provider.ts +885 -885
  88. package/src/llm/quality.ts +248 -248
  89. package/src/memory/attribution-guard.ts +249 -249
  90. package/src/memory/auto-relations.ts +107 -107
  91. package/src/memory/consolidation.ts +302 -302
  92. package/src/memory/disclosure-policy.ts +141 -141
  93. package/src/memory/entity-extractor.ts +197 -197
  94. package/src/memory/formation/evaluate.ts +217 -217
  95. package/src/memory/formation/extract.ts +361 -361
  96. package/src/memory/formation/index.ts +417 -417
  97. package/src/memory/formation/resolve.ts +344 -344
  98. package/src/memory/formation/types.ts +315 -315
  99. package/src/memory/freshness.ts +122 -122
  100. package/src/memory/graph.ts +197 -197
  101. package/src/memory/refs.ts +94 -94
  102. package/src/memory/retention.ts +433 -433
  103. package/src/memory/secret-filter.ts +79 -79
  104. package/src/memory/session.ts +523 -523
  105. package/src/multimodal/image-loader.ts +143 -143
  106. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  107. package/src/orchestrate/adapters/claude.ts +111 -111
  108. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  109. package/src/orchestrate/adapters/codex.ts +41 -41
  110. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  111. package/src/orchestrate/adapters/gemini.ts +42 -42
  112. package/src/orchestrate/adapters/index.ts +73 -73
  113. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  114. package/src/orchestrate/adapters/opencode.ts +47 -47
  115. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  116. package/src/orchestrate/adapters/types.ts +77 -77
  117. package/src/orchestrate/capability-router.ts +284 -284
  118. package/src/orchestrate/context-compact.ts +188 -188
  119. package/src/orchestrate/cost-tracker.ts +219 -219
  120. package/src/orchestrate/error-recovery.ts +191 -191
  121. package/src/orchestrate/evidence.ts +140 -140
  122. package/src/orchestrate/ledger.ts +110 -110
  123. package/src/orchestrate/memorix-bridge.ts +380 -380
  124. package/src/orchestrate/output-budget.ts +80 -80
  125. package/src/orchestrate/permission.ts +152 -152
  126. package/src/orchestrate/pipeline-trace.ts +131 -131
  127. package/src/orchestrate/prompt-builder.ts +155 -155
  128. package/src/orchestrate/ring-buffer.ts +37 -37
  129. package/src/orchestrate/task-graph.ts +389 -389
  130. package/src/orchestrate/verify-gate.ts +219 -219
  131. package/src/orchestrate/worktree.ts +232 -232
  132. package/src/project/aliases.ts +374 -374
  133. package/src/project/detector.ts +268 -268
  134. package/src/rules/adapters/claude-code.ts +99 -99
  135. package/src/rules/adapters/codex.ts +97 -97
  136. package/src/rules/adapters/copilot.ts +124 -124
  137. package/src/rules/adapters/cursor.ts +114 -114
  138. package/src/rules/adapters/kiro.ts +126 -126
  139. package/src/rules/adapters/trae.ts +56 -56
  140. package/src/rules/adapters/windsurf.ts +83 -83
  141. package/src/rules/syncer.ts +235 -235
  142. package/src/sdk.ts +327 -327
  143. package/src/search/intent-detector.ts +289 -289
  144. package/src/search/query-expansion.ts +52 -52
  145. package/src/server/formation-timeout.ts +27 -27
  146. package/src/skills/mini-skills.ts +386 -386
  147. package/src/store/chat-store.ts +119 -119
  148. package/src/store/file-lock.ts +100 -100
  149. package/src/store/graph-store.ts +249 -249
  150. package/src/store/mini-skill-store.ts +349 -349
  151. package/src/store/obs-store.ts +255 -255
  152. package/src/store/persistence-json.ts +212 -212
  153. package/src/store/persistence.ts +291 -291
  154. package/src/store/project-affinity.ts +195 -195
  155. package/src/store/session-store.ts +259 -259
  156. package/src/store/sqlite-store.ts +339 -339
  157. package/src/team/event-bus.ts +76 -76
  158. package/src/team/file-locks.ts +173 -173
  159. package/src/team/handoff.ts +167 -167
  160. package/src/team/messages.ts +203 -203
  161. package/src/team/poll.ts +132 -132
  162. package/src/team/tasks.ts +211 -211
  163. package/src/wiki/generator.ts +237 -237
  164. package/src/wiki/knowledge-graph.ts +334 -334
  165. package/src/wiki/types.ts +85 -85
  166. package/src/workspace/mcp-adapters/codex.ts +191 -191
  167. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  168. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  169. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  170. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  171. package/src/workspace/mcp-adapters/trae.ts +134 -134
  172. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  173. package/src/workspace/sanitizer.ts +60 -60
  174. package/src/workspace/workflow-sync.ts +131 -131
@@ -1,203 +1,203 @@
1
- /**
2
- * Message Bus — Agent-to-agent communication
3
- *
4
- * In-memory message queue for team coordination.
5
- * Messages are temporary (not persisted as observations).
6
- * Supports direct send, broadcast, inbox retrieval, and read tracking.
7
- */
8
-
9
- import { randomUUID } from 'node:crypto';
10
- import type { AgentRegistry } from './registry.js';
11
-
12
- // ─── Types ───────────────────────────────────────────────────────────
13
-
14
- export type MessageType = 'request' | 'response' | 'info' | 'announcement' | 'contract' | 'error';
15
-
16
- export interface MessageSendInput {
17
- from: string;
18
- to: string;
19
- type: MessageType;
20
- content: string;
21
- }
22
-
23
- export interface MessageBroadcastInput {
24
- from: string;
25
- type: MessageType;
26
- content: string;
27
- }
28
-
29
- export interface Message {
30
- id: string;
31
- from: string;
32
- to: string;
33
- type: MessageType;
34
- content: string;
35
- timestamp: Date;
36
- read: boolean;
37
- }
38
-
39
- // ─── Message Bus ─────────────────────────────────────────────────────
40
-
41
- /** Max messages per inbox. Oldest read messages are evicted first. */
42
- const MAX_INBOX_SIZE = 200;
43
- /** Max message content length in bytes */
44
- const MAX_CONTENT_LENGTH = 10_000;
45
-
46
- export class MessageBus {
47
- private inboxes = new Map<string, Message[]>();
48
- private registry: AgentRegistry;
49
-
50
- constructor(registry: AgentRegistry) {
51
- this.registry = registry;
52
- }
53
-
54
- /**
55
- * Send a message to a specific agent.
56
- * Throws if receiver is unknown.
57
- */
58
- send(input: MessageSendInput): Message {
59
- const receiver = this.registry.getAgent(input.to);
60
- if (!receiver) {
61
- throw new Error(`Unknown receiver agent: ${input.to}`);
62
- }
63
- if (receiver.status !== 'active') {
64
- throw new Error(`Receiver agent is inactive: ${receiver.name}`);
65
- }
66
-
67
- const msg: Message = {
68
- id: randomUUID(),
69
- from: input.from,
70
- to: input.to,
71
- type: input.type,
72
- content: input.content,
73
- timestamp: new Date(),
74
- read: false,
75
- };
76
-
77
- const inbox = this.inboxes.get(input.to) ?? [];
78
- inbox.push(msg);
79
-
80
- // Evict oldest read messages if inbox exceeds limit
81
- if (inbox.length > MAX_INBOX_SIZE) {
82
- const readIndices: number[] = [];
83
- for (let i = 0; i < inbox.length; i++) {
84
- if (inbox[i].read) readIndices.push(i);
85
- }
86
- // Remove oldest read messages first
87
- const toRemove = inbox.length - MAX_INBOX_SIZE;
88
- for (let i = Math.min(toRemove, readIndices.length) - 1; i >= 0; i--) {
89
- inbox.splice(readIndices[i], 1);
90
- }
91
- // If still over limit, remove oldest unread
92
- while (inbox.length > MAX_INBOX_SIZE) {
93
- inbox.shift();
94
- }
95
- }
96
-
97
- this.inboxes.set(input.to, inbox);
98
- return msg;
99
- }
100
-
101
- /**
102
- * Broadcast a message to all active agents except the sender.
103
- */
104
- broadcast(input: MessageBroadcastInput): Message[] {
105
- const activeAgents = this.registry.listAgents({ status: 'active' });
106
- const messages: Message[] = [];
107
-
108
- for (const agent of activeAgents) {
109
- if (agent.id === input.from) continue;
110
-
111
- const msg = this.send({
112
- from: input.from,
113
- to: agent.id,
114
- type: input.type,
115
- content: input.content,
116
- });
117
- messages.push(msg);
118
- }
119
-
120
- return messages;
121
- }
122
-
123
- /**
124
- * Get all messages in an agent's inbox (both read and unread).
125
- */
126
- getInbox(agentId: string): Message[] {
127
- return [...(this.inboxes.get(agentId) ?? [])];
128
- }
129
-
130
- /**
131
- * Mark specific messages as read.
132
- */
133
- markRead(agentId: string, messageIds: string[]): number {
134
- const inbox = this.inboxes.get(agentId);
135
- if (!inbox) return 0;
136
-
137
- const idSet = new Set(messageIds);
138
- let count = 0;
139
- for (const msg of inbox) {
140
- if (idSet.has(msg.id) && !msg.read) {
141
- msg.read = true;
142
- count++;
143
- }
144
- }
145
- return count;
146
- }
147
-
148
- /**
149
- * Count unread messages for an agent.
150
- */
151
- getUnreadCount(agentId: string): number {
152
- const inbox = this.inboxes.get(agentId);
153
- if (!inbox) return 0;
154
- return inbox.filter(m => !m.read).length;
155
- }
156
-
157
- /**
158
- * Remove all read messages from an agent's inbox.
159
- * Returns the number of messages pruned.
160
- */
161
- pruneRead(agentId: string): number {
162
- const inbox = this.inboxes.get(agentId);
163
- if (!inbox) return 0;
164
- const before = inbox.length;
165
- const unread = inbox.filter(m => !m.read);
166
- this.inboxes.set(agentId, unread);
167
- return before - unread.length;
168
- }
169
-
170
- /**
171
- * Clear all messages for an agent (used on agent leave).
172
- */
173
- clearInbox(agentId: string): void {
174
- this.inboxes.delete(agentId);
175
- }
176
-
177
- /** Max allowed content length */
178
- static get MAX_CONTENT_LENGTH() { return MAX_CONTENT_LENGTH; }
179
-
180
- /** Serialize state for file persistence */
181
- serialize(): { inboxes: Record<string, unknown[]> } {
182
- const inboxes: Record<string, unknown[]> = {};
183
- for (const [agentId, msgs] of this.inboxes) {
184
- inboxes[agentId] = msgs.map(m => ({
185
- ...m,
186
- timestamp: m.timestamp.toISOString(),
187
- }));
188
- }
189
- return { inboxes };
190
- }
191
-
192
- /** Hydrate state from file persistence */
193
- hydrate(data: { inboxes?: Record<string, any[]> }): void {
194
- this.inboxes.clear();
195
- if (!data?.inboxes) return;
196
- for (const [agentId, msgs] of Object.entries(data.inboxes)) {
197
- this.inboxes.set(agentId, msgs.map(m => ({
198
- ...m,
199
- timestamp: new Date(m.timestamp),
200
- })));
201
- }
202
- }
203
- }
1
+ /**
2
+ * Message Bus — Agent-to-agent communication
3
+ *
4
+ * In-memory message queue for team coordination.
5
+ * Messages are temporary (not persisted as observations).
6
+ * Supports direct send, broadcast, inbox retrieval, and read tracking.
7
+ */
8
+
9
+ import { randomUUID } from 'node:crypto';
10
+ import type { AgentRegistry } from './registry.js';
11
+
12
+ // ─── Types ───────────────────────────────────────────────────────────
13
+
14
+ export type MessageType = 'request' | 'response' | 'info' | 'announcement' | 'contract' | 'error';
15
+
16
+ export interface MessageSendInput {
17
+ from: string;
18
+ to: string;
19
+ type: MessageType;
20
+ content: string;
21
+ }
22
+
23
+ export interface MessageBroadcastInput {
24
+ from: string;
25
+ type: MessageType;
26
+ content: string;
27
+ }
28
+
29
+ export interface Message {
30
+ id: string;
31
+ from: string;
32
+ to: string;
33
+ type: MessageType;
34
+ content: string;
35
+ timestamp: Date;
36
+ read: boolean;
37
+ }
38
+
39
+ // ─── Message Bus ─────────────────────────────────────────────────────
40
+
41
+ /** Max messages per inbox. Oldest read messages are evicted first. */
42
+ const MAX_INBOX_SIZE = 200;
43
+ /** Max message content length in bytes */
44
+ const MAX_CONTENT_LENGTH = 10_000;
45
+
46
+ export class MessageBus {
47
+ private inboxes = new Map<string, Message[]>();
48
+ private registry: AgentRegistry;
49
+
50
+ constructor(registry: AgentRegistry) {
51
+ this.registry = registry;
52
+ }
53
+
54
+ /**
55
+ * Send a message to a specific agent.
56
+ * Throws if receiver is unknown.
57
+ */
58
+ send(input: MessageSendInput): Message {
59
+ const receiver = this.registry.getAgent(input.to);
60
+ if (!receiver) {
61
+ throw new Error(`Unknown receiver agent: ${input.to}`);
62
+ }
63
+ if (receiver.status !== 'active') {
64
+ throw new Error(`Receiver agent is inactive: ${receiver.name}`);
65
+ }
66
+
67
+ const msg: Message = {
68
+ id: randomUUID(),
69
+ from: input.from,
70
+ to: input.to,
71
+ type: input.type,
72
+ content: input.content,
73
+ timestamp: new Date(),
74
+ read: false,
75
+ };
76
+
77
+ const inbox = this.inboxes.get(input.to) ?? [];
78
+ inbox.push(msg);
79
+
80
+ // Evict oldest read messages if inbox exceeds limit
81
+ if (inbox.length > MAX_INBOX_SIZE) {
82
+ const readIndices: number[] = [];
83
+ for (let i = 0; i < inbox.length; i++) {
84
+ if (inbox[i].read) readIndices.push(i);
85
+ }
86
+ // Remove oldest read messages first
87
+ const toRemove = inbox.length - MAX_INBOX_SIZE;
88
+ for (let i = Math.min(toRemove, readIndices.length) - 1; i >= 0; i--) {
89
+ inbox.splice(readIndices[i], 1);
90
+ }
91
+ // If still over limit, remove oldest unread
92
+ while (inbox.length > MAX_INBOX_SIZE) {
93
+ inbox.shift();
94
+ }
95
+ }
96
+
97
+ this.inboxes.set(input.to, inbox);
98
+ return msg;
99
+ }
100
+
101
+ /**
102
+ * Broadcast a message to all active agents except the sender.
103
+ */
104
+ broadcast(input: MessageBroadcastInput): Message[] {
105
+ const activeAgents = this.registry.listAgents({ status: 'active' });
106
+ const messages: Message[] = [];
107
+
108
+ for (const agent of activeAgents) {
109
+ if (agent.id === input.from) continue;
110
+
111
+ const msg = this.send({
112
+ from: input.from,
113
+ to: agent.id,
114
+ type: input.type,
115
+ content: input.content,
116
+ });
117
+ messages.push(msg);
118
+ }
119
+
120
+ return messages;
121
+ }
122
+
123
+ /**
124
+ * Get all messages in an agent's inbox (both read and unread).
125
+ */
126
+ getInbox(agentId: string): Message[] {
127
+ return [...(this.inboxes.get(agentId) ?? [])];
128
+ }
129
+
130
+ /**
131
+ * Mark specific messages as read.
132
+ */
133
+ markRead(agentId: string, messageIds: string[]): number {
134
+ const inbox = this.inboxes.get(agentId);
135
+ if (!inbox) return 0;
136
+
137
+ const idSet = new Set(messageIds);
138
+ let count = 0;
139
+ for (const msg of inbox) {
140
+ if (idSet.has(msg.id) && !msg.read) {
141
+ msg.read = true;
142
+ count++;
143
+ }
144
+ }
145
+ return count;
146
+ }
147
+
148
+ /**
149
+ * Count unread messages for an agent.
150
+ */
151
+ getUnreadCount(agentId: string): number {
152
+ const inbox = this.inboxes.get(agentId);
153
+ if (!inbox) return 0;
154
+ return inbox.filter(m => !m.read).length;
155
+ }
156
+
157
+ /**
158
+ * Remove all read messages from an agent's inbox.
159
+ * Returns the number of messages pruned.
160
+ */
161
+ pruneRead(agentId: string): number {
162
+ const inbox = this.inboxes.get(agentId);
163
+ if (!inbox) return 0;
164
+ const before = inbox.length;
165
+ const unread = inbox.filter(m => !m.read);
166
+ this.inboxes.set(agentId, unread);
167
+ return before - unread.length;
168
+ }
169
+
170
+ /**
171
+ * Clear all messages for an agent (used on agent leave).
172
+ */
173
+ clearInbox(agentId: string): void {
174
+ this.inboxes.delete(agentId);
175
+ }
176
+
177
+ /** Max allowed content length */
178
+ static get MAX_CONTENT_LENGTH() { return MAX_CONTENT_LENGTH; }
179
+
180
+ /** Serialize state for file persistence */
181
+ serialize(): { inboxes: Record<string, unknown[]> } {
182
+ const inboxes: Record<string, unknown[]> = {};
183
+ for (const [agentId, msgs] of this.inboxes) {
184
+ inboxes[agentId] = msgs.map(m => ({
185
+ ...m,
186
+ timestamp: m.timestamp.toISOString(),
187
+ }));
188
+ }
189
+ return { inboxes };
190
+ }
191
+
192
+ /** Hydrate state from file persistence */
193
+ hydrate(data: { inboxes?: Record<string, any[]> }): void {
194
+ this.inboxes.clear();
195
+ if (!data?.inboxes) return;
196
+ for (const [agentId, msgs] of Object.entries(data.inboxes)) {
197
+ this.inboxes.set(agentId, msgs.map(m => ({
198
+ ...m,
199
+ timestamp: new Date(m.timestamp),
200
+ })));
201
+ }
202
+ }
203
+ }
package/src/team/poll.ts CHANGED
@@ -1,132 +1,132 @@
1
- /**
2
- * Team Poll — Situational awareness for agents.
3
- *
4
- * Phase 4b: Provides `computeWatermark` (extracted from server.ts session_start)
5
- * and `computePoll` (full situational awareness in one call).
6
- *
7
- * Architectural boundary (B3): These are cross-process consistency mechanisms.
8
- * They read from SQLite (canonical truth), never from EventBus.
9
- */
10
-
11
- import type { TeamStore, TeamAgentRow, TeamTaskRow, TeamMessageRow } from './team-store.js';
12
-
13
- // ── Types ──────────────────────────────────────────────────────────
14
-
15
- export interface WatermarkResult {
16
- lastSeenGeneration: number;
17
- currentGeneration: number;
18
- newObservationCount: number;
19
- }
20
-
21
- export interface PollResult {
22
- agent: {
23
- agentId: string;
24
- status: string;
25
- lastHeartbeat: number;
26
- } | null;
27
- watermark: WatermarkResult;
28
- inbox: {
29
- unreadCount: number;
30
- messages: TeamMessageRow[];
31
- };
32
- tasks: {
33
- myInProgress: TeamTaskRow[];
34
- availableToClaim: TeamTaskRow[];
35
- recentlyCompleted: TeamTaskRow[];
36
- recentlyFailed: TeamTaskRow[];
37
- };
38
- team: {
39
- activeAgents: TeamAgentRow[];
40
- totalAgents: number;
41
- };
42
- }
43
-
44
- // ── computeWatermark ───────────────────────────────────────────────
45
-
46
- /**
47
- * Compute the watermark for an agent: how many new observations
48
- * have appeared in this project since the agent's last session.
49
- *
50
- * Extracted from server.ts session_start to be shared by poll and session_start.
51
- *
52
- * @param lastSeenGeneration - Agent's last_seen_obs_generation from TeamStore
53
- * @param currentGeneration - Current global generation from observation store
54
- * @param projectObservations - Pre-filtered observations for this project
55
- * with writeGeneration > lastSeenGeneration
56
- */
57
- export function computeWatermark(
58
- lastSeenGeneration: number,
59
- currentGeneration: number,
60
- newObservationCount: number,
61
- ): WatermarkResult {
62
- return {
63
- lastSeenGeneration,
64
- currentGeneration,
65
- newObservationCount,
66
- };
67
- }
68
-
69
- // ── computePoll ────────────────────────────────────────────────────
70
-
71
- /**
72
- * Compute full situational awareness for an agent in one call.
73
- * Reads only from SQLite (TeamStore) — never from EventBus.
74
- *
75
- * @param teamStore - The TeamStore instance
76
- * @param projectId - Current project ID
77
- * @param agentId - The requesting agent's ID (null if no agent registered)
78
- * @param watermark - Pre-computed watermark result
79
- */
80
- export function computePoll(
81
- teamStore: TeamStore,
82
- projectId: string,
83
- agentId: string | null,
84
- watermark: WatermarkResult,
85
- ): PollResult {
86
- // Agent info
87
- let agent: PollResult['agent'] = null;
88
- if (agentId) {
89
- const row = teamStore.getAgent(agentId);
90
- if (row) {
91
- agent = {
92
- agentId: row.agent_id,
93
- status: row.status,
94
- lastHeartbeat: row.last_heartbeat,
95
- };
96
- }
97
- }
98
-
99
- // Inbox
100
- const unreadCount = agentId ? teamStore.getUnreadCount(projectId, agentId) : 0;
101
- const messages = agentId ? teamStore.getInbox(projectId, agentId) : [];
102
-
103
- // Tasks
104
- const allTasks = teamStore.listTasks(projectId);
105
- const myInProgress = agentId
106
- ? allTasks.filter(t => t.assignee_agent_id === agentId && t.status === 'in_progress')
107
- : [];
108
-
109
- // Available to claim: pending, no assignee, all deps met
110
- const pendingTasks = allTasks.filter(t => t.status === 'pending' && !t.assignee_agent_id);
111
- const availableToClaim = pendingTasks.filter(t => {
112
- const unmet = teamStore.getTaskDeps(t.task_id)
113
- .map(depId => teamStore.getTask(depId))
114
- .filter(dep => dep && dep.status !== 'completed');
115
- return unmet.length === 0;
116
- });
117
-
118
- const recentlyCompleted = allTasks.filter(t => t.status === 'completed');
119
- const recentlyFailed = allTasks.filter(t => t.status === 'failed');
120
-
121
- // Team
122
- const allAgents = teamStore.listAgents(projectId);
123
- const activeAgents = allAgents.filter(a => a.status === 'active');
124
-
125
- return {
126
- agent,
127
- watermark,
128
- inbox: { unreadCount, messages },
129
- tasks: { myInProgress, availableToClaim, recentlyCompleted, recentlyFailed },
130
- team: { activeAgents, totalAgents: allAgents.length },
131
- };
132
- }
1
+ /**
2
+ * Team Poll — Situational awareness for agents.
3
+ *
4
+ * Phase 4b: Provides `computeWatermark` (extracted from server.ts session_start)
5
+ * and `computePoll` (full situational awareness in one call).
6
+ *
7
+ * Architectural boundary (B3): These are cross-process consistency mechanisms.
8
+ * They read from SQLite (canonical truth), never from EventBus.
9
+ */
10
+
11
+ import type { TeamStore, TeamAgentRow, TeamTaskRow, TeamMessageRow } from './team-store.js';
12
+
13
+ // ── Types ──────────────────────────────────────────────────────────
14
+
15
+ export interface WatermarkResult {
16
+ lastSeenGeneration: number;
17
+ currentGeneration: number;
18
+ newObservationCount: number;
19
+ }
20
+
21
+ export interface PollResult {
22
+ agent: {
23
+ agentId: string;
24
+ status: string;
25
+ lastHeartbeat: number;
26
+ } | null;
27
+ watermark: WatermarkResult;
28
+ inbox: {
29
+ unreadCount: number;
30
+ messages: TeamMessageRow[];
31
+ };
32
+ tasks: {
33
+ myInProgress: TeamTaskRow[];
34
+ availableToClaim: TeamTaskRow[];
35
+ recentlyCompleted: TeamTaskRow[];
36
+ recentlyFailed: TeamTaskRow[];
37
+ };
38
+ team: {
39
+ activeAgents: TeamAgentRow[];
40
+ totalAgents: number;
41
+ };
42
+ }
43
+
44
+ // ── computeWatermark ───────────────────────────────────────────────
45
+
46
+ /**
47
+ * Compute the watermark for an agent: how many new observations
48
+ * have appeared in this project since the agent's last session.
49
+ *
50
+ * Extracted from server.ts session_start to be shared by poll and session_start.
51
+ *
52
+ * @param lastSeenGeneration - Agent's last_seen_obs_generation from TeamStore
53
+ * @param currentGeneration - Current global generation from observation store
54
+ * @param projectObservations - Pre-filtered observations for this project
55
+ * with writeGeneration > lastSeenGeneration
56
+ */
57
+ export function computeWatermark(
58
+ lastSeenGeneration: number,
59
+ currentGeneration: number,
60
+ newObservationCount: number,
61
+ ): WatermarkResult {
62
+ return {
63
+ lastSeenGeneration,
64
+ currentGeneration,
65
+ newObservationCount,
66
+ };
67
+ }
68
+
69
+ // ── computePoll ────────────────────────────────────────────────────
70
+
71
+ /**
72
+ * Compute full situational awareness for an agent in one call.
73
+ * Reads only from SQLite (TeamStore) — never from EventBus.
74
+ *
75
+ * @param teamStore - The TeamStore instance
76
+ * @param projectId - Current project ID
77
+ * @param agentId - The requesting agent's ID (null if no agent registered)
78
+ * @param watermark - Pre-computed watermark result
79
+ */
80
+ export function computePoll(
81
+ teamStore: TeamStore,
82
+ projectId: string,
83
+ agentId: string | null,
84
+ watermark: WatermarkResult,
85
+ ): PollResult {
86
+ // Agent info
87
+ let agent: PollResult['agent'] = null;
88
+ if (agentId) {
89
+ const row = teamStore.getAgent(agentId);
90
+ if (row) {
91
+ agent = {
92
+ agentId: row.agent_id,
93
+ status: row.status,
94
+ lastHeartbeat: row.last_heartbeat,
95
+ };
96
+ }
97
+ }
98
+
99
+ // Inbox
100
+ const unreadCount = agentId ? teamStore.getUnreadCount(projectId, agentId) : 0;
101
+ const messages = agentId ? teamStore.getInbox(projectId, agentId) : [];
102
+
103
+ // Tasks
104
+ const allTasks = teamStore.listTasks(projectId);
105
+ const myInProgress = agentId
106
+ ? allTasks.filter(t => t.assignee_agent_id === agentId && t.status === 'in_progress')
107
+ : [];
108
+
109
+ // Available to claim: pending, no assignee, all deps met
110
+ const pendingTasks = allTasks.filter(t => t.status === 'pending' && !t.assignee_agent_id);
111
+ const availableToClaim = pendingTasks.filter(t => {
112
+ const unmet = teamStore.getTaskDeps(t.task_id)
113
+ .map(depId => teamStore.getTask(depId))
114
+ .filter(dep => dep && dep.status !== 'completed');
115
+ return unmet.length === 0;
116
+ });
117
+
118
+ const recentlyCompleted = allTasks.filter(t => t.status === 'completed');
119
+ const recentlyFailed = allTasks.filter(t => t.status === 'failed');
120
+
121
+ // Team
122
+ const allAgents = teamStore.listAgents(projectId);
123
+ const activeAgents = allAgents.filter(a => a.status === 'active');
124
+
125
+ return {
126
+ agent,
127
+ watermark,
128
+ inbox: { unreadCount, messages },
129
+ tasks: { myInProgress, availableToClaim, recentlyCompleted, recentlyFailed },
130
+ team: { activeAgents, totalAgents: allAgents.length },
131
+ };
132
+ }