let-them-talk 5.2.5 → 5.4.0

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 (166) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +158 -592
  3. package/SECURITY.md +3 -3
  4. package/USAGE.md +151 -0
  5. package/agent-contracts.js +447 -0
  6. package/api-agents.js +760 -0
  7. package/autonomy/decision-v2.js +380 -0
  8. package/autonomy/watchdog-policy.js +572 -0
  9. package/cli.js +454 -298
  10. package/conversation-templates/autonomous-feature.json +83 -22
  11. package/conversation-templates/code-review.json +69 -21
  12. package/conversation-templates/debug-squad.json +69 -21
  13. package/conversation-templates/feature-build.json +69 -21
  14. package/conversation-templates/research-write.json +69 -21
  15. package/dashboard.html +3148 -174
  16. package/dashboard.js +823 -786
  17. package/data-dir.js +58 -0
  18. package/docs/architecture/branch-semantics.md +157 -0
  19. package/docs/architecture/canonical-event-schema.md +88 -0
  20. package/docs/architecture/markdown-workspace.md +183 -0
  21. package/docs/architecture/runtime-contract.md +459 -0
  22. package/docs/architecture/runtime-migration-hardening.md +64 -0
  23. package/events/hooks.js +154 -0
  24. package/events/log.js +457 -0
  25. package/events/replay.js +33 -0
  26. package/events/schema.js +432 -0
  27. package/managed-team-integration.js +261 -0
  28. package/office/agents.js +704 -597
  29. package/office/animation.js +1 -1
  30. package/office/assets/arcade-cabinet.js +141 -0
  31. package/office/assets/archway.js +77 -0
  32. package/office/assets/bar-counter.js +91 -0
  33. package/office/assets/bar-stool.js +71 -0
  34. package/office/assets/beanbag.js +64 -0
  35. package/office/assets/bench.js +99 -0
  36. package/office/assets/bollard.js +87 -0
  37. package/office/assets/cactus.js +100 -0
  38. package/office/assets/carpet-tile.js +46 -0
  39. package/office/assets/chair.js +123 -0
  40. package/office/assets/chandelier.js +107 -0
  41. package/office/assets/coffee-machine.js +95 -0
  42. package/office/assets/coffee-table.js +81 -0
  43. package/office/assets/column.js +95 -0
  44. package/office/assets/desk-lamp.js +102 -0
  45. package/office/assets/desk.js +76 -0
  46. package/office/assets/dining-table.js +105 -0
  47. package/office/assets/door.js +70 -0
  48. package/office/assets/dual-monitor.js +72 -0
  49. package/office/assets/fence.js +76 -0
  50. package/office/assets/filing-cabinet.js +111 -0
  51. package/office/assets/floor-lamp.js +69 -0
  52. package/office/assets/floor-tile.js +54 -0
  53. package/office/assets/flower-pot.js +76 -0
  54. package/office/assets/foosball.js +95 -0
  55. package/office/assets/fridge.js +99 -0
  56. package/office/assets/gaming-chair.js +154 -0
  57. package/office/assets/gaming-desk.js +105 -0
  58. package/office/assets/glass-door.js +72 -0
  59. package/office/assets/glass-wall.js +64 -0
  60. package/office/assets/half-wall.js +49 -0
  61. package/office/assets/hanging-plant.js +112 -0
  62. package/office/assets/index.js +151 -0
  63. package/office/assets/indoor-tree.js +90 -0
  64. package/office/assets/l-sofa.js +153 -0
  65. package/office/assets/marble-floor.js +64 -0
  66. package/office/assets/materials.js +40 -0
  67. package/office/assets/meeting-table.js +88 -0
  68. package/office/assets/microwave.js +94 -0
  69. package/office/assets/monitor.js +67 -0
  70. package/office/assets/neon-strip.js +73 -0
  71. package/office/assets/painting.js +84 -0
  72. package/office/assets/palm-tree.js +108 -0
  73. package/office/assets/pc-tower.js +91 -0
  74. package/office/assets/pendant-light.js +67 -0
  75. package/office/assets/ping-pong.js +114 -0
  76. package/office/assets/plant.js +72 -0
  77. package/office/assets/planter-box.js +95 -0
  78. package/office/assets/pool-table.js +94 -0
  79. package/office/assets/printer.js +113 -0
  80. package/office/assets/reception-desk.js +133 -0
  81. package/office/assets/rug.js +78 -0
  82. package/office/assets/sculpture.js +85 -0
  83. package/office/assets/server-rack.js +98 -0
  84. package/office/assets/sink.js +109 -0
  85. package/office/assets/sofa.js +106 -0
  86. package/office/assets/speaker.js +83 -0
  87. package/office/assets/spotlight.js +83 -0
  88. package/office/assets/street-lamp.js +97 -0
  89. package/office/assets/trash-can.js +83 -0
  90. package/office/assets/treadmill.js +126 -0
  91. package/office/assets/trophy.js +89 -0
  92. package/office/assets/tv-screen.js +79 -0
  93. package/office/assets/vase.js +84 -0
  94. package/office/assets/wall-clock.js +84 -0
  95. package/office/assets/wall.js +53 -0
  96. package/office/assets/water-cooler.js +146 -0
  97. package/office/assets/whiteboard.js +115 -0
  98. package/office/assets.js +3 -431
  99. package/office/builder.js +791 -355
  100. package/office/campus-env.js +1012 -1119
  101. package/office/environment.js +2 -0
  102. package/office/gallery.js +997 -0
  103. package/office/index.js +165 -61
  104. package/office/navigation.js +173 -152
  105. package/office/player.js +178 -68
  106. package/office/robot-character.js +272 -0
  107. package/office/spectator-camera.js +33 -10
  108. package/office/state.js +2 -0
  109. package/office/world-save.js +35 -4
  110. package/package.json +57 -3
  111. package/providers/comfyui.js +383 -0
  112. package/providers/dalle.js +79 -0
  113. package/providers/gemini.js +181 -0
  114. package/providers/ollama.js +184 -0
  115. package/providers/replicate.js +115 -0
  116. package/providers/zai.js +183 -0
  117. package/runtime-descriptor.js +270 -0
  118. package/scripts/check-agent-contract-advisory.js +132 -0
  119. package/scripts/check-api-agent-parity.js +277 -0
  120. package/scripts/check-autonomy-v2-decision.js +207 -0
  121. package/scripts/check-autonomy-v2-execution.js +588 -0
  122. package/scripts/check-autonomy-v2-watchdog.js +224 -0
  123. package/scripts/check-branch-fork-snapshot.js +337 -0
  124. package/scripts/check-branch-isolation.js +787 -0
  125. package/scripts/check-branch-semantics.js +139 -0
  126. package/scripts/check-dashboard-control-plane.js +1304 -0
  127. package/scripts/check-docs-onboarding.js +490 -0
  128. package/scripts/check-event-schema.js +276 -0
  129. package/scripts/check-evidence-completion.js +239 -0
  130. package/scripts/check-invariants.js +992 -0
  131. package/scripts/check-lifecycle-hooks.js +525 -0
  132. package/scripts/check-managed-team-integration.js +166 -0
  133. package/scripts/check-markdown-workspace-export.js +548 -0
  134. package/scripts/check-markdown-workspace-safety.js +347 -0
  135. package/scripts/check-markdown-workspace.js +136 -0
  136. package/scripts/check-message-replay.js +429 -0
  137. package/scripts/check-migration-hardening.js +300 -0
  138. package/scripts/check-performance-indexing.js +272 -0
  139. package/scripts/check-provider-capabilities.js +316 -0
  140. package/scripts/check-runtime-contract.js +109 -0
  141. package/scripts/check-session-aware-context.js +172 -0
  142. package/scripts/check-session-lifecycle.js +210 -0
  143. package/scripts/export-markdown-workspace.js +84 -0
  144. package/scripts/fixtures/message-replay/clean.jsonl +2 -0
  145. package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
  146. package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
  147. package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
  148. package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
  149. package/scripts/migrate-legacy-to-canonical.js +201 -0
  150. package/scripts/run-verification-suite.js +242 -0
  151. package/scripts/sync-packaged-docs.js +69 -0
  152. package/server.js +9546 -7214
  153. package/state/agents.js +161 -0
  154. package/state/canonical.js +3068 -0
  155. package/state/dashboard-queries.js +441 -0
  156. package/state/evidence.js +56 -0
  157. package/state/io.js +69 -0
  158. package/state/markdown-workspace.js +951 -0
  159. package/state/messages.js +669 -0
  160. package/state/sessions.js +683 -0
  161. package/state/tasks-workflows.js +92 -0
  162. package/templates/debate.json +2 -2
  163. package/templates/managed.json +4 -4
  164. package/templates/pair.json +2 -2
  165. package/templates/review.json +2 -2
  166. package/templates/team.json +3 -3
@@ -0,0 +1,441 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const DASHBOARD_QUERY_PROJECTION_SCHEMA_VERSION = 1;
5
+
6
+ function cloneJsonValue(value) {
7
+ return value == null ? value : JSON.parse(JSON.stringify(value));
8
+ }
9
+
10
+ function readJsonlObjects(filePath) {
11
+ if (!filePath || !fs.existsSync(filePath)) return [];
12
+ const raw = fs.readFileSync(filePath, 'utf8').trim();
13
+ if (!raw) return [];
14
+
15
+ return raw
16
+ .split(/\r?\n/)
17
+ .map((line) => {
18
+ try {
19
+ return JSON.parse(line);
20
+ } catch {
21
+ return null;
22
+ }
23
+ })
24
+ .filter(Boolean);
25
+ }
26
+
27
+ function readFileFingerprint(filePath) {
28
+ if (!filePath || !fs.existsSync(filePath)) {
29
+ return {
30
+ exists: false,
31
+ size: 0,
32
+ mtime_ms: 0,
33
+ };
34
+ }
35
+
36
+ const stats = fs.statSync(filePath);
37
+ return {
38
+ exists: true,
39
+ size: stats.size,
40
+ mtime_ms: stats.mtimeMs,
41
+ };
42
+ }
43
+
44
+ function sameFileFingerprint(left, right) {
45
+ return !!left
46
+ && !!right
47
+ && left.exists === right.exists
48
+ && left.size === right.size
49
+ && left.mtime_ms === right.mtime_ms;
50
+ }
51
+
52
+ function normalizeFileFingerprint(fingerprint) {
53
+ return {
54
+ exists: !!(fingerprint && fingerprint.exists),
55
+ size: fingerprint && Number.isInteger(fingerprint.size) ? fingerprint.size : 0,
56
+ mtime_ms: fingerprint && Number.isFinite(fingerprint.mtime_ms) ? fingerprint.mtime_ms : 0,
57
+ };
58
+ }
59
+
60
+ function compareMessagesByTime(left, right) {
61
+ const leftTime = Date.parse(left && left.timestamp || '');
62
+ const rightTime = Date.parse(right && right.timestamp || '');
63
+
64
+ if (leftTime !== rightTime) {
65
+ return leftTime - rightTime;
66
+ }
67
+
68
+ return (left && Number.isInteger(left._sort_index) ? left._sort_index : 0)
69
+ - (right && Number.isInteger(right._sort_index) ? right._sort_index : 0);
70
+ }
71
+
72
+ function normalizeProjectionMessage(message) {
73
+ if (!message || typeof message !== 'object' || Array.isArray(message)) return null;
74
+ return {
75
+ ...message,
76
+ channel: typeof message.channel === 'string' && message.channel ? message.channel : 'general',
77
+ acked: !!message.acked,
78
+ };
79
+ }
80
+
81
+ function normalizeProjectionChannels(channels) {
82
+ const source = channels && typeof channels === 'object' && !Array.isArray(channels) ? channels : {};
83
+ return Object.fromEntries(Object.entries(source).map(([channelName, channel]) => {
84
+ const normalized = channel && typeof channel === 'object' && !Array.isArray(channel) ? channel : {};
85
+ const count = Number.isInteger(normalized.message_count)
86
+ ? normalized.message_count
87
+ : (Number.isInteger(normalized.count) ? normalized.count : 0);
88
+
89
+ return [channelName, {
90
+ name: channelName,
91
+ ...normalized,
92
+ count,
93
+ message_count: count,
94
+ }];
95
+ }));
96
+ }
97
+
98
+ function normalizeProjection(projection) {
99
+ const source = projection && projection.source && typeof projection.source === 'object' && !Array.isArray(projection.source)
100
+ ? projection.source
101
+ : {};
102
+ const channelHistories = source.channel_histories && typeof source.channel_histories === 'object' && !Array.isArray(source.channel_histories)
103
+ ? source.channel_histories
104
+ : {};
105
+
106
+ return {
107
+ schema_version: DASHBOARD_QUERY_PROJECTION_SCHEMA_VERSION,
108
+ branch_id: projection && typeof projection.branch_id === 'string' ? projection.branch_id : 'main',
109
+ updated_at: projection && typeof projection.updated_at === 'string' ? projection.updated_at : null,
110
+ source: {
111
+ history: normalizeFileFingerprint(source.history),
112
+ acks: normalizeFileFingerprint(source.acks),
113
+ channels: normalizeFileFingerprint(source.channels),
114
+ channel_histories: Object.fromEntries(
115
+ Object.entries(channelHistories).map(([channelName, fingerprint]) => [channelName, normalizeFileFingerprint(fingerprint)])
116
+ ),
117
+ },
118
+ messages: Array.isArray(projection && projection.messages)
119
+ ? projection.messages.map(normalizeProjectionMessage).filter(Boolean)
120
+ : [],
121
+ channels: normalizeProjectionChannels(projection && projection.channels),
122
+ };
123
+ }
124
+
125
+ function projectPlanWorkflow(workflow) {
126
+ if (!workflow || typeof workflow !== 'object' || Array.isArray(workflow)) return null;
127
+ return {
128
+ ...workflow,
129
+ status: workflow.status === 'active' && workflow.paused ? 'paused' : workflow.status,
130
+ };
131
+ }
132
+
133
+ function createDashboardQueries(options = {}) {
134
+ const {
135
+ dataDir,
136
+ readJson,
137
+ agentsFile,
138
+ profilesFile,
139
+ getTasksFile,
140
+ getWorkflowsFile,
141
+ getAcksFile,
142
+ getHistoryFile,
143
+ getChannelsFile,
144
+ getChannelHistoryFile,
145
+ getBranchDashboardProjectionFile,
146
+ sanitizeBranchName,
147
+ } = options;
148
+
149
+ const projectionCache = new Map();
150
+
151
+ function readObject(filePath) {
152
+ const value = readJson(filePath, {});
153
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
154
+ }
155
+
156
+ function readArray(filePath) {
157
+ const value = readJson(filePath, []);
158
+ return Array.isArray(value) ? value : [];
159
+ }
160
+
161
+ function resolveBranch(branchName = 'main') {
162
+ return sanitizeBranchName(branchName || 'main');
163
+ }
164
+
165
+ function getProjectionFile(branchName = 'main') {
166
+ const branch = resolveBranch(branchName);
167
+ if (typeof getBranchDashboardProjectionFile === 'function') {
168
+ return getBranchDashboardProjectionFile(branch);
169
+ }
170
+ return path.join(dataDir, 'runtime', 'branches', branch, 'dashboard-query-projection.json');
171
+ }
172
+
173
+ function listProjectionChannelNames(branchName, channels) {
174
+ return Object.keys(channels || {})
175
+ .filter((channelName) => channelName && channelName !== 'general')
176
+ .sort();
177
+ }
178
+
179
+ function readProjectionFile(filePath) {
180
+ const projection = readJson(filePath, null);
181
+ if (!projection || typeof projection !== 'object' || Array.isArray(projection)) return null;
182
+ return normalizeProjection(projection);
183
+ }
184
+
185
+ function persistProjection(branchName, projection) {
186
+ const projectionFile = getProjectionFile(branchName);
187
+ fs.mkdirSync(path.dirname(projectionFile), { recursive: true });
188
+ fs.writeFileSync(projectionFile, JSON.stringify(projection));
189
+ return projection;
190
+ }
191
+
192
+ function isProjectionFresh(branchName, projection, channels) {
193
+ const branch = resolveBranch(branchName);
194
+ const channelNames = listProjectionChannelNames(branch, channels);
195
+ const projectionChannelNames = Object.keys(projection.source.channel_histories || {}).sort();
196
+
197
+ if (channelNames.length !== projectionChannelNames.length) return false;
198
+ for (let index = 0; index < channelNames.length; index += 1) {
199
+ if (channelNames[index] !== projectionChannelNames[index]) return false;
200
+ }
201
+
202
+ if (!sameFileFingerprint(projection.source.history, readFileFingerprint(getHistoryFile(branch)))) return false;
203
+ if (!sameFileFingerprint(projection.source.acks, readFileFingerprint(getAcksFile(branch)))) return false;
204
+ if (!sameFileFingerprint(projection.source.channels, readFileFingerprint(getChannelsFile(branch)))) return false;
205
+
206
+ for (const channelName of channelNames) {
207
+ if (!sameFileFingerprint(
208
+ projection.source.channel_histories[channelName],
209
+ readFileFingerprint(getChannelHistoryFile(channelName, branch))
210
+ )) {
211
+ return false;
212
+ }
213
+ }
214
+
215
+ return true;
216
+ }
217
+
218
+ function cacheProjection(branchName, projection) {
219
+ const normalized = normalizeProjection(projection);
220
+ projectionCache.set(resolveBranch(branchName), normalized);
221
+ return normalized;
222
+ }
223
+
224
+ function buildBranchConversationProjection(branchName = 'main') {
225
+ const branch = resolveBranch(branchName);
226
+ const acks = readObject(getAcksFile(branch));
227
+ const channels = readObject(getChannelsFile(branch));
228
+ const channelNames = listProjectionChannelNames(branch, channels);
229
+ const projectionMessages = [];
230
+ let sortIndex = 0;
231
+
232
+ for (const message of readJsonlObjects(getHistoryFile(branch))) {
233
+ projectionMessages.push({
234
+ ...message,
235
+ channel: typeof message.channel === 'string' && message.channel ? message.channel : 'general',
236
+ acked: !!acks[message.id],
237
+ _sort_index: sortIndex,
238
+ });
239
+ sortIndex += 1;
240
+ }
241
+
242
+ for (const channelName of channelNames) {
243
+ for (const message of readJsonlObjects(getChannelHistoryFile(channelName, branch))) {
244
+ projectionMessages.push({
245
+ ...message,
246
+ channel: typeof message.channel === 'string' && message.channel ? message.channel : channelName,
247
+ acked: !!acks[message.id],
248
+ _sort_index: sortIndex,
249
+ });
250
+ sortIndex += 1;
251
+ }
252
+ }
253
+
254
+ projectionMessages.sort(compareMessagesByTime);
255
+
256
+ const channelCounts = {};
257
+ for (const message of projectionMessages) {
258
+ const channelName = typeof message.channel === 'string' && message.channel ? message.channel : 'general';
259
+ channelCounts[channelName] = (channelCounts[channelName] || 0) + 1;
260
+ }
261
+
262
+ const projectionChannels = {};
263
+ const channelNamesInProjection = new Set(['general', ...Object.keys(channels || {}), ...Object.keys(channelCounts)]);
264
+ for (const channelName of [...channelNamesInProjection].sort()) {
265
+ const metadata = channels && channels[channelName] && typeof channels[channelName] === 'object' && !Array.isArray(channels[channelName])
266
+ ? channels[channelName]
267
+ : {};
268
+ const count = channelCounts[channelName] || 0;
269
+ projectionChannels[channelName] = {
270
+ name: channelName,
271
+ ...metadata,
272
+ count,
273
+ message_count: count,
274
+ };
275
+ }
276
+
277
+ const projection = {
278
+ schema_version: DASHBOARD_QUERY_PROJECTION_SCHEMA_VERSION,
279
+ branch_id: branch,
280
+ updated_at: new Date().toISOString(),
281
+ source: {
282
+ history: readFileFingerprint(getHistoryFile(branch)),
283
+ acks: readFileFingerprint(getAcksFile(branch)),
284
+ channels: readFileFingerprint(getChannelsFile(branch)),
285
+ channel_histories: Object.fromEntries(
286
+ channelNames.map((channelName) => [channelName, readFileFingerprint(getChannelHistoryFile(channelName, branch))])
287
+ ),
288
+ },
289
+ messages: projectionMessages.map((message) => {
290
+ const normalized = { ...message };
291
+ delete normalized._sort_index;
292
+ return normalized;
293
+ }),
294
+ channels: projectionChannels,
295
+ };
296
+
297
+ persistProjection(branch, projection);
298
+ return cacheProjection(branch, projection);
299
+ }
300
+
301
+ function loadBranchConversationProjection(branchName = 'main') {
302
+ const branch = resolveBranch(branchName);
303
+ const projectionFile = getProjectionFile(branch);
304
+ const channels = readObject(getChannelsFile(branch));
305
+ const cachedProjection = projectionCache.get(branch);
306
+ if (cachedProjection && isProjectionFresh(branch, cachedProjection, channels)) {
307
+ if (!fs.existsSync(projectionFile)) {
308
+ persistProjection(branch, cachedProjection);
309
+ }
310
+ return cachedProjection;
311
+ }
312
+
313
+ const persistedProjection = readProjectionFile(projectionFile);
314
+ if (persistedProjection && isProjectionFresh(branch, persistedProjection, channels)) {
315
+ return cacheProjection(branch, persistedProjection);
316
+ }
317
+
318
+ return buildBranchConversationProjection(branch);
319
+ }
320
+
321
+ function getConversationMessages(params = {}) {
322
+ const projection = loadBranchConversationProjection(params.branch || 'main');
323
+ return cloneJsonValue(projection.messages);
324
+ }
325
+
326
+ function getHistoryView(params = {}) {
327
+ const projection = loadBranchConversationProjection(params.branch || 'main');
328
+ const limit = Math.min(Math.max(parseInt(params.limit || '500', 10), 1), 1000);
329
+ const page = Math.max(parseInt(params.page || '0', 10), 0);
330
+ const threadId = params.threadId || params.thread_id || null;
331
+
332
+ const history = threadId
333
+ ? projection.messages.filter((message) => message.id === threadId || message.thread_id === threadId || message.reply_to === threadId)
334
+ : projection.messages;
335
+
336
+ if (page > 0) {
337
+ const pages = history.length > 0 ? Math.ceil(history.length / limit) : 0;
338
+ const boundedPage = pages > 0 ? Math.min(page, pages) : 1;
339
+ const endIndex = Math.max(0, history.length - ((boundedPage - 1) * limit));
340
+ const startIndex = Math.max(0, endIndex - limit);
341
+ return {
342
+ messages: cloneJsonValue(history.slice(startIndex, endIndex)),
343
+ page: boundedPage,
344
+ pages,
345
+ total_messages: history.length,
346
+ };
347
+ }
348
+
349
+ return cloneJsonValue(history.slice(-limit));
350
+ }
351
+
352
+ function getChannelsView(params = {}) {
353
+ const projection = loadBranchConversationProjection(params.branch || 'main');
354
+ return cloneJsonValue(projection.channels);
355
+ }
356
+
357
+ function getSearchResultsView(params = {}) {
358
+ const projection = loadBranchConversationProjection(params.branch || 'main');
359
+ const query = String(params.query || '').trim().toLowerCase();
360
+ const from = params.from ? String(params.from).trim().toLowerCase() : null;
361
+ const limit = Math.min(Math.max(parseInt(params.limit || '50', 10), 1), 200);
362
+ const matches = [];
363
+
364
+ for (let index = projection.messages.length - 1; index >= 0 && matches.length < limit; index -= 1) {
365
+ const message = projection.messages[index];
366
+ if (!message || typeof message !== 'object') continue;
367
+ if (from && String(message.from || '').trim().toLowerCase() !== from) continue;
368
+
369
+ const haystacks = [message.content, message.from, message.to, message.channel, message.id];
370
+ if (haystacks.some((value) => String(value || '').toLowerCase().includes(query))) {
371
+ matches.push(cloneJsonValue(message));
372
+ }
373
+ }
374
+
375
+ return matches.reverse();
376
+ }
377
+
378
+ function listAgents() {
379
+ return readObject(agentsFile);
380
+ }
381
+
382
+ function listProfiles() {
383
+ return readObject(profilesFile);
384
+ }
385
+
386
+ function listTasks(branchName = 'main') {
387
+ return readArray(getTasksFile(resolveBranch(branchName)));
388
+ }
389
+
390
+ function listWorkflows(branchName = 'main') {
391
+ return readArray(getWorkflowsFile(resolveBranch(branchName)));
392
+ }
393
+
394
+ function getPlanStatusView(params = {}) {
395
+ const branch = resolveBranch(params.branch || 'main');
396
+ return {
397
+ workflows: listWorkflows(branch)
398
+ .map(projectPlanWorkflow)
399
+ .filter((workflow) => workflow && (workflow.status === 'active' || workflow.status === 'paused') && Array.isArray(workflow.steps)),
400
+ };
401
+ }
402
+
403
+ function getPlanReportView(params = {}) {
404
+ const branch = resolveBranch(params.branch || 'main');
405
+ const workflows = listWorkflows(branch).map(projectPlanWorkflow).filter(Boolean);
406
+ if (workflows.length === 0) return null;
407
+
408
+ const activeWorkflows = workflows.filter((workflow) => workflow.status === 'active');
409
+ const completedWorkflows = workflows.filter((workflow) => workflow.status === 'completed');
410
+ const pausedWorkflows = workflows.filter((workflow) => workflow.status === 'paused');
411
+
412
+ return {
413
+ generated_at: new Date().toISOString(),
414
+ totals: {
415
+ workflows: workflows.length,
416
+ active_workflows: activeWorkflows.length,
417
+ completed_workflows: completedWorkflows.length,
418
+ paused_workflows: pausedWorkflows.length,
419
+ },
420
+ workflows: cloneJsonValue(workflows),
421
+ };
422
+ }
423
+
424
+ return {
425
+ getChannelsView,
426
+ getConversationMessages,
427
+ getHistoryView,
428
+ getPlanReportView,
429
+ getPlanStatusView,
430
+ getSearchResultsView,
431
+ listAgents,
432
+ listProfiles,
433
+ listTasks,
434
+ listWorkflows,
435
+ };
436
+ }
437
+
438
+ module.exports = {
439
+ createDashboardQueries,
440
+ DASHBOARD_QUERY_PROJECTION_SCHEMA_VERSION,
441
+ };
@@ -0,0 +1,56 @@
1
+ const EVIDENCE_STORE_SCHEMA_VERSION = 1;
2
+
3
+ function normalizeEvidenceStore(store) {
4
+ return {
5
+ schema_version: EVIDENCE_STORE_SCHEMA_VERSION,
6
+ updated_at: store && store.updated_at ? store.updated_at : null,
7
+ records: Array.isArray(store && store.records) ? [...store.records] : [],
8
+ };
9
+ }
10
+
11
+ function createEvidenceState(options = {}) {
12
+ const { io } = options;
13
+
14
+ if (!io) throw new Error('createEvidenceState requires io');
15
+
16
+ function readEvidenceStore(filePath) {
17
+ return normalizeEvidenceStore(io.readJsonFile(filePath, null));
18
+ }
19
+
20
+ function saveEvidenceStore(filePath, store, writeOptions = {}) {
21
+ return io.withLock(filePath, () => io.writeJson(filePath, normalizeEvidenceStore(store), {
22
+ space: writeOptions.space,
23
+ }));
24
+ }
25
+
26
+ function mutateEvidence(filePath, mutator, writeOptions = {}) {
27
+ return io.withLock(filePath, () => {
28
+ const store = readEvidenceStore(filePath);
29
+ const result = mutator(store);
30
+ io.writeJson(filePath, store, {
31
+ space: writeOptions.space,
32
+ });
33
+ return result;
34
+ });
35
+ }
36
+
37
+ function findEvidenceRecord(filePath, evidenceId) {
38
+ if (!evidenceId) return null;
39
+ const store = readEvidenceStore(filePath);
40
+ return store.records.find((record) => record && record.evidence_id === evidenceId) || null;
41
+ }
42
+
43
+ return {
44
+ EVIDENCE_STORE_SCHEMA_VERSION,
45
+ findEvidenceRecord,
46
+ mutateEvidence,
47
+ readEvidenceStore,
48
+ saveEvidenceStore,
49
+ };
50
+ }
51
+
52
+ module.exports = {
53
+ EVIDENCE_STORE_SCHEMA_VERSION,
54
+ createEvidenceState,
55
+ normalizeEvidenceStore,
56
+ };
package/state/io.js ADDED
@@ -0,0 +1,69 @@
1
+ const fs = require('fs');
2
+
3
+ function ensureDir(dirPath) {
4
+ if (!dirPath) return;
5
+ if (!fs.existsSync(dirPath)) {
6
+ fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
7
+ }
8
+ }
9
+
10
+ function createStateIo(options = {}) {
11
+ const { dataDir, invalidateCache, withFileLock } = options;
12
+
13
+ function ensureDataDir() {
14
+ ensureDir(dataDir);
15
+ }
16
+
17
+ function readJsonFile(filePath, fallback = null) {
18
+ if (!fs.existsSync(filePath)) return fallback;
19
+ try {
20
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
21
+ } catch {
22
+ return fallback;
23
+ }
24
+ }
25
+
26
+ function appendJsonl(filePath, value) {
27
+ ensureDataDir();
28
+ fs.appendFileSync(filePath, JSON.stringify(value) + '\n');
29
+ return value;
30
+ }
31
+
32
+ function writeJson(filePath, data, options = {}) {
33
+ const { cacheKey = null, space } = options;
34
+
35
+ ensureDataDir();
36
+ fs.writeFileSync(filePath, JSON.stringify(data, null, space));
37
+
38
+ if (cacheKey && typeof invalidateCache === 'function') {
39
+ invalidateCache(cacheKey);
40
+ }
41
+
42
+ return data;
43
+ }
44
+
45
+ function writeJsonl(filePath, rows) {
46
+ ensureDataDir();
47
+ const lines = Array.isArray(rows) ? rows.map((row) => JSON.stringify(row)) : [];
48
+ fs.writeFileSync(filePath, lines.length > 0 ? `${lines.join('\n')}\n` : '');
49
+ return rows;
50
+ }
51
+
52
+ function withLock(filePath, fn) {
53
+ if (typeof withFileLock === 'function') {
54
+ return withFileLock(filePath, fn);
55
+ }
56
+ return fn();
57
+ }
58
+
59
+ return {
60
+ ensureDataDir,
61
+ readJsonFile,
62
+ appendJsonl,
63
+ writeJson,
64
+ writeJsonl,
65
+ withLock,
66
+ };
67
+ }
68
+
69
+ module.exports = { createStateIo };