groove-dev 0.27.143 → 0.27.144

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 (186) hide show
  1. package/CLAUDE.md +0 -7
  2. package/node_modules/@groove-dev/cli/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  5. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  6. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  7. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  8. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  9. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  10. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  11. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  12. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  13. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  14. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  21. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  22. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  23. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  24. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  25. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  26. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  28. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  29. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  30. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  32. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  33. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  34. package/node_modules/@groove-dev/gui/package.json +1 -1
  35. package/node_modules/@groove-dev/gui/src/{app.jsx → App.jsx} +0 -2
  36. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  39. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  40. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  41. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  42. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  43. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  45. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  54. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  55. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  56. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  57. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
  58. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
  59. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  60. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  61. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  62. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  63. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  64. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  65. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  66. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  67. package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
  68. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  69. package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
  70. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  71. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
  72. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  73. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
  74. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  75. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  76. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  77. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  78. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  79. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  81. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  82. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  83. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  84. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
  85. package/node_modules/@groove-dev/gui/src/views/models.jsx +409 -507
  86. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  87. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  88. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  89. package/package.json +1 -1
  90. package/packages/cli/package.json +1 -1
  91. package/packages/daemon/package.json +1 -1
  92. package/packages/daemon/src/api.js +1086 -6532
  93. package/packages/daemon/src/gateways/manager.js +35 -1
  94. package/packages/daemon/src/index.js +3 -0
  95. package/packages/daemon/src/journalist.js +23 -13
  96. package/packages/daemon/src/mlx-server.js +365 -0
  97. package/packages/daemon/src/model-lab.js +308 -12
  98. package/packages/daemon/src/pm.js +1 -1
  99. package/packages/daemon/src/process.js +2 -2
  100. package/packages/daemon/src/providers/local.js +36 -8
  101. package/packages/daemon/src/registry.js +21 -5
  102. package/packages/daemon/src/routes/agents.js +889 -0
  103. package/packages/daemon/src/routes/coordination.js +318 -0
  104. package/packages/daemon/src/routes/files.js +751 -0
  105. package/packages/daemon/src/routes/integrations.js +485 -0
  106. package/packages/daemon/src/routes/network.js +1784 -0
  107. package/packages/daemon/src/routes/providers.js +755 -0
  108. package/packages/daemon/src/routes/schedules.js +110 -0
  109. package/packages/daemon/src/routes/teams.js +650 -0
  110. package/packages/daemon/src/scheduler.js +456 -24
  111. package/packages/daemon/src/teams.js +1 -1
  112. package/packages/daemon/src/validate.js +38 -1
  113. package/packages/daemon/templates/mlx-setup.json +12 -0
  114. package/packages/daemon/templates/tgi-setup.json +1 -1
  115. package/packages/daemon/templates/vllm-setup.json +1 -1
  116. package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  117. package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  118. package/packages/gui/dist/index.html +2 -2
  119. package/packages/gui/package.json +1 -1
  120. package/packages/gui/src/{app.jsx → App.jsx} +0 -2
  121. package/packages/gui/src/app.css +35 -0
  122. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  123. package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
  124. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  125. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  126. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  127. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  128. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  129. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  130. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  131. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  132. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  133. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  134. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  135. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  136. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  137. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  138. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  139. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  140. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  141. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  142. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  143. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  144. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  145. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  146. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  147. package/packages/gui/src/components/network/network-health.jsx +2 -2
  148. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  149. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  150. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  151. package/packages/gui/src/lib/cron.js +64 -0
  152. package/packages/gui/src/lib/status.js +24 -24
  153. package/packages/gui/src/lib/theme-hex.js +1 -0
  154. package/packages/gui/src/stores/groove.js +34 -3144
  155. package/packages/gui/src/stores/helpers.js +10 -0
  156. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  157. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  158. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  159. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  160. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  161. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  162. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  163. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  164. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  165. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  166. package/packages/gui/src/views/agents.jsx +5 -5
  167. package/packages/gui/src/views/dashboard.jsx +12 -13
  168. package/packages/gui/src/views/marketplace.jsx +191 -3
  169. package/packages/gui/src/views/model-lab.jsx +17 -6
  170. package/packages/gui/src/views/models.jsx +409 -507
  171. package/packages/gui/src/views/network.jsx +3 -3
  172. package/packages/gui/src/views/settings.jsx +81 -94
  173. package/packages/gui/src/views/teams.jsx +40 -483
  174. package/SECURITY_SWEEP.md +0 -228
  175. package/TRAINING_DATA_v4.md +0 -6
  176. package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +0 -1
  177. package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +0 -984
  178. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -379
  179. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  180. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  181. package/packages/gui/dist/assets/index-CCVvAoQn.css +0 -1
  182. package/packages/gui/dist/assets/index-DGIv_TRm.js +0 -984
  183. package/packages/gui/src/components/agents/agent-chat.jsx +0 -379
  184. package/packages/gui/src/views/preview.jsx +0 -6
  185. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  186. package/test.py +0 -571
@@ -0,0 +1,318 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { Keeper, KEEPER_COMMANDS } from '../keeper.js';
3
+
4
+ const FILE_READ_TOOLS = new Set(['Read', 'read_file']);
5
+ const FILE_WRITE_TOOLS = new Set(['Write', 'Edit', 'write_file', 'edit_file', 'create_file']);
6
+
7
+ export function registerCoordinationRoutes(app, daemon) {
8
+ // Lock management
9
+ app.get('/api/locks', (req, res) => {
10
+ res.json(daemon.locks.getAll());
11
+ });
12
+
13
+ // Knock protocol: Claude Code PreToolUse hook POSTs every Bash/Write/Edit
14
+ // tool call here. The daemon checks the target path (for file ops) against
15
+ // the agent's declared scope and against other agents' active locks, and
16
+ // allows or denies. Non-Claude providers don't hit this path.
17
+ app.post('/api/knock', (req, res) => {
18
+ const body = req.body || {};
19
+ const agentId = body.grooveAgentId;
20
+ const toolName = body.tool_name || body.toolName || '';
21
+ const toolInput = body.tool_input || body.toolInput || {};
22
+
23
+ // Unknown / no agent id → fail open (don't wedge an agent we can't identify)
24
+ if (!agentId) return res.json({ allow: true });
25
+ const agent = daemon.registry.get(agentId);
26
+ if (!agent) return res.json({ allow: true });
27
+
28
+ // Extract the target file paths from the tool input
29
+ const targets = [];
30
+ if (toolInput.file_path) targets.push(String(toolInput.file_path));
31
+ if (toolInput.path) targets.push(String(toolInput.path));
32
+ if (Array.isArray(toolInput.edits)) {
33
+ for (const e of toolInput.edits) if (e?.file_path) targets.push(String(e.file_path));
34
+ }
35
+
36
+ // Scope guard: if agent has a declared scope and the op targets a path,
37
+ // verify the path matches the scope or belongs to no one.
38
+ if (agent.scope && agent.scope.length > 0 && targets.length > 0) {
39
+ for (const target of targets) {
40
+ const conflict = daemon.locks.check(agentId, target, agent.workingDir);
41
+ if (conflict.conflict) {
42
+ daemon.audit.log('knock.denied', { agentId, toolName, target, owner: conflict.owner, pattern: conflict.pattern });
43
+ daemon.broadcast({ type: 'knock:denied', agentId, agentName: agent.name, toolName, target, owner: conflict.owner, reason: 'scope_conflict' });
44
+ return res.json({
45
+ allow: false,
46
+ reason: `GROOVE PM: ${target} is owned by another agent (pattern ${conflict.pattern}). Use the handoff protocol (write .groove/handoffs/<role>.md) or request approval instead of editing it directly.`,
47
+ });
48
+ }
49
+ }
50
+ }
51
+
52
+ // Track file operations for the files-touched API
53
+ if (targets.length > 0) {
54
+ const op = FILE_WRITE_TOOLS.has(toolName) ? 'write' : FILE_READ_TOOLS.has(toolName) ? 'read' : null;
55
+ if (op) {
56
+ for (const t of targets) daemon.registry.trackFileOp(agentId, t, op);
57
+ }
58
+ }
59
+
60
+ daemon.audit.log('knock.allowed', { agentId, toolName, targets });
61
+ res.json({ allow: true });
62
+ });
63
+
64
+ // Coordination protocol — agents declare intent on shared resources
65
+ // (npm install, server restart, package.json edit) to prevent races.
66
+ // Returns 423 Locked if another agent holds a conflicting resource.
67
+ app.post('/api/coordination/declare', (req, res) => {
68
+ const { agentId, operation, resources, ttlMs } = req.body || {};
69
+ if (!agentId || !operation || !Array.isArray(resources) || resources.length === 0) {
70
+ return res.status(400).json({ error: 'agentId, operation, and resources[] required' });
71
+ }
72
+ const result = daemon.locks.declareOperation(agentId, operation, resources, ttlMs);
73
+ if (result.conflict) {
74
+ daemon.audit.log('coordination.conflict', { agentId, operation, resource: result.resource, owner: result.owner });
75
+ return res.status(423).json(result);
76
+ }
77
+ daemon.audit.log('coordination.declared', { agentId, operation, resources });
78
+ daemon.broadcast({ type: 'coordination:declared', agentId, operation, resources });
79
+ res.json({ declared: true, operation, resources });
80
+ });
81
+
82
+ app.post('/api/coordination/complete', (req, res) => {
83
+ const { agentId } = req.body || {};
84
+ if (!agentId) return res.status(400).json({ error: 'agentId required' });
85
+ const removed = daemon.locks.completeOperation(agentId);
86
+ daemon.audit.log('coordination.completed', { agentId });
87
+ daemon.broadcast({ type: 'coordination:completed', agentId });
88
+ res.json({ completed: removed });
89
+ });
90
+
91
+ app.get('/api/coordination', (req, res) => {
92
+ res.json({ operations: daemon.locks.getOperations() });
93
+ });
94
+
95
+ // --- Persistent Memory (Layer 7) ---
96
+ // Constraints: project rules discovered by agents / set by user
97
+ app.get('/api/memory/constraints', (req, res) => {
98
+ res.json({ constraints: daemon.memory.listConstraints() });
99
+ });
100
+
101
+ app.post('/api/memory/constraints', (req, res) => {
102
+ const { text, category } = req.body || {};
103
+ const result = daemon.memory.addConstraint({ text, category });
104
+ if (!result.added && result.error) {
105
+ return res.status(400).json(result);
106
+ }
107
+ if (result.added) {
108
+ daemon.audit.log('memory.constraint.added', { hash: result.hash, category });
109
+ daemon.broadcast({ type: 'memory:constraint:added', hash: result.hash });
110
+ }
111
+ res.json(result);
112
+ });
113
+
114
+ app.delete('/api/memory/constraints/:hash', (req, res) => {
115
+ const removed = daemon.memory.removeConstraint(req.params.hash);
116
+ if (removed) {
117
+ daemon.audit.log('memory.constraint.removed', { hash: req.params.hash });
118
+ daemon.broadcast({ type: 'memory:constraint:removed', hash: req.params.hash });
119
+ }
120
+ res.json({ removed });
121
+ });
122
+
123
+ // Handoff chains (per role, optionally scoped by workspace)
124
+ app.get('/api/memory/handoff-chain/:role', (req, res) => {
125
+ res.json({
126
+ role: req.params.role,
127
+ workspace: req.query.workspace || null,
128
+ entries: daemon.memory.getHandoffChain(req.params.role, req.query.workspace),
129
+ });
130
+ });
131
+
132
+ app.get('/api/memory/handoff-chain/:role/recent', (req, res) => {
133
+ const count = Math.min(parseInt(req.query.count) || 3, 10);
134
+ res.json({
135
+ role: req.params.role,
136
+ workspace: req.query.workspace || null,
137
+ markdown: daemon.memory.getRecentHandoffMarkdown(req.params.role, count, 10_000, req.query.workspace),
138
+ });
139
+ });
140
+
141
+ app.get('/api/memory/handoff-chain', (req, res) => {
142
+ res.json({ roles: daemon.memory.listHandoffRoles(req.query.workspace) });
143
+ });
144
+
145
+ // Discoveries (error → fix pairs)
146
+ app.get('/api/memory/discoveries', (req, res) => {
147
+ const role = req.query.role;
148
+ const teamId = req.query.teamId;
149
+ const limit = Math.min(parseInt(req.query.limit) || 100, 500);
150
+ res.json({ discoveries: daemon.memory.listDiscoveries({ role, teamId, limit }) });
151
+ });
152
+
153
+ app.post('/api/memory/discoveries', (req, res) => {
154
+ const { agentId, role, trigger, fix, outcome, teamId } = req.body || {};
155
+ const result = daemon.memory.addDiscovery({ agentId, role, trigger, fix, outcome, teamId });
156
+ if (!result.added && result.error) {
157
+ return res.status(400).json(result);
158
+ }
159
+ if (result.added) {
160
+ daemon.audit.log('memory.discovery.added', { agentId, role });
161
+ daemon.broadcast({ type: 'memory:discovery:added', agentId, role });
162
+ }
163
+ res.json(result);
164
+ });
165
+
166
+ // Specializations (per-agent + per-role quality profiles)
167
+ app.get('/api/memory/specializations', (req, res) => {
168
+ res.json(daemon.memory.getAllSpecializations());
169
+ });
170
+
171
+ app.get('/api/memory/specializations/:agentId', (req, res) => {
172
+ const spec = daemon.memory.getSpecialization(req.params.agentId);
173
+ if (!spec) return res.status(404).json({ error: 'No specialization data for this agent' });
174
+ res.json(spec);
175
+ });
176
+
177
+ // ── Keeper (tagged memory) ──────────────────────────────────
178
+
179
+ app.get('/api/keeper', (req, res) => {
180
+ res.json({ items: daemon.keeper.list() });
181
+ });
182
+
183
+ app.get('/api/keeper/tree', (req, res) => {
184
+ res.json({ tree: daemon.keeper.tree() });
185
+ });
186
+
187
+ app.get('/api/keeper/search', (req, res) => {
188
+ const q = req.query.q || '';
189
+ res.json({ results: daemon.keeper.search(q) });
190
+ });
191
+
192
+ app.get('/api/keeper/commands', (_req, res) => {
193
+ res.json({ commands: KEEPER_COMMANDS });
194
+ });
195
+
196
+ app.get('/api/keeper/:tag(*)', (req, res) => {
197
+ const item = daemon.keeper.get(req.params.tag);
198
+ if (!item) return res.status(404).json({ error: `Memory #${req.params.tag} not found` });
199
+ res.json(item);
200
+ });
201
+
202
+ app.post('/api/keeper', (req, res) => {
203
+ try {
204
+ const { tag, content } = req.body || {};
205
+ const item = daemon.keeper.save(tag, content);
206
+ daemon.audit.log('keeper.save', { tag: item.tag });
207
+ daemon.broadcast({ type: 'keeper:saved', item });
208
+ res.status(201).json(item);
209
+ } catch (err) {
210
+ res.status(400).json({ error: err.message });
211
+ }
212
+ });
213
+
214
+ app.post('/api/keeper/append', (req, res) => {
215
+ try {
216
+ const { tag, content } = req.body || {};
217
+ const item = daemon.keeper.append(tag, content);
218
+ daemon.audit.log('keeper.append', { tag: item.tag });
219
+ daemon.broadcast({ type: 'keeper:updated', item });
220
+ res.json(item);
221
+ } catch (err) {
222
+ res.status(400).json({ error: err.message });
223
+ }
224
+ });
225
+
226
+ app.post('/api/keeper/pull', (req, res) => {
227
+ try {
228
+ const { tags } = req.body || {};
229
+ if (!Array.isArray(tags) || tags.length === 0) {
230
+ return res.status(400).json({ error: 'Tags array is required' });
231
+ }
232
+ const brief = daemon.keeper.pull(tags);
233
+ if (!brief) return res.status(404).json({ error: 'No memories found for the given tags' });
234
+ res.json({ brief, tags });
235
+ } catch (err) {
236
+ res.status(400).json({ error: err.message });
237
+ }
238
+ });
239
+
240
+ app.patch('/api/keeper/:tag(*)', (req, res) => {
241
+ try {
242
+ const { content } = req.body || {};
243
+ const item = daemon.keeper.update(req.params.tag, content);
244
+ daemon.audit.log('keeper.update', { tag: item.tag });
245
+ daemon.broadcast({ type: 'keeper:updated', item });
246
+ res.json(item);
247
+ } catch (err) {
248
+ res.status(err.message.includes('does not exist') ? 404 : 400).json({ error: err.message });
249
+ }
250
+ });
251
+
252
+ app.delete('/api/keeper/link/:tag(*)', (req, res) => {
253
+ try {
254
+ const { docPath } = req.body || {};
255
+ daemon.keeper.unlink(req.params.tag, docPath);
256
+ daemon.audit.log('keeper.unlink', { tag: req.params.tag, docPath });
257
+ res.json({ ok: true });
258
+ } catch (err) {
259
+ res.status(400).json({ error: err.message });
260
+ }
261
+ });
262
+
263
+ app.delete('/api/keeper/:tag(*)', (req, res) => {
264
+ try {
265
+ const removed = daemon.keeper.delete(req.params.tag);
266
+ if (!removed) return res.status(404).json({ error: `Memory #${req.params.tag} not found` });
267
+ daemon.audit.log('keeper.delete', { tag: req.params.tag });
268
+ daemon.broadcast({ type: 'keeper:deleted', tag: req.params.tag });
269
+ res.json({ ok: true });
270
+ } catch (err) {
271
+ res.status(400).json({ error: err.message });
272
+ }
273
+ });
274
+
275
+ app.post('/api/keeper/doc', async (req, res) => {
276
+ try {
277
+ const { tag, chatHistory, agentId } = req.body || {};
278
+ if (!tag) return res.status(400).json({ error: 'Tag is required' });
279
+ if (!chatHistory || !Array.isArray(chatHistory) || chatHistory.length === 0) {
280
+ return res.status(400).json({ error: 'Chat history is required' });
281
+ }
282
+ const transcript = chatHistory
283
+ .map(m => `**${m.from === 'user' ? 'User' : 'Agent'}:** ${m.text}`)
284
+ .join('\n\n');
285
+ const prompt = `You are a technical writer. Below is a conversation exploring an idea or feature. Write a comprehensive document that captures:\n\n1. The core idea and motivation\n2. Key decisions made during the discussion\n3. Architecture / design choices\n4. Implementation plan (if discussed)\n5. Open questions or next steps\n\nWrite in clear, structured markdown with headers. Be thorough — this document will be the reference for future work on this topic. Do not include a meta-summary about the conversation itself.\n\n---\n\nConversation:\n\n${transcript.slice(0, 40000)}`;
286
+ let doc;
287
+ if (daemon.journalist && typeof daemon.journalist.callHeadless === 'function') {
288
+ doc = await daemon.journalist.callHeadless(prompt, { trackAs: '__keeper_doc__' });
289
+ } else {
290
+ doc = `# ${tag}\n\n*Auto-generated document from conversation*\n\n${transcript.slice(0, 5000)}`;
291
+ }
292
+ const item = daemon.keeper.saveDoc(tag, doc);
293
+ daemon.audit.log('keeper.doc', { tag: item.tag, agentId });
294
+ daemon.broadcast({ type: 'keeper:saved', item });
295
+ res.status(201).json({ ...item, content: doc });
296
+ } catch (err) {
297
+ res.status(500).json({ error: err.message });
298
+ }
299
+ });
300
+
301
+ app.post('/api/keeper/link', (req, res) => {
302
+ try {
303
+ const { tag, docPath } = req.body || {};
304
+ const item = daemon.keeper.link(tag, docPath);
305
+ daemon.audit.log('keeper.link', { tag: item.tag, docPath });
306
+ daemon.broadcast({ type: 'keeper:updated', item });
307
+ res.json(item);
308
+ } catch (err) {
309
+ res.status(400).json({ error: err.message });
310
+ }
311
+ });
312
+
313
+ app.post('/api/keeper/parse', (req, res) => {
314
+ const { text } = req.body || {};
315
+ const parsed = Keeper.parseCommand(text || '');
316
+ res.json({ parsed });
317
+ });
318
+ }