groove-dev 0.27.142 → 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 (187) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  4. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  7. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  8. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  9. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  10. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  11. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  12. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  13. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  14. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  21. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  22. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  23. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  24. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  25. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  26. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  28. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  29. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  30. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  32. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  33. package/node_modules/@groove-dev/gui/package.json +1 -1
  34. package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
  35. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  36. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  39. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  40. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  41. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  43. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  54. package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
  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 +410 -509
  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/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/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/editor/selection-menu.jsx +2 -0
  141. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  142. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  143. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  144. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  145. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  146. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  147. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  148. package/packages/gui/src/components/network/network-health.jsx +2 -2
  149. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  150. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  151. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  152. package/packages/gui/src/lib/cron.js +64 -0
  153. package/packages/gui/src/lib/status.js +24 -24
  154. package/packages/gui/src/lib/theme-hex.js +1 -0
  155. package/packages/gui/src/stores/groove.js +34 -3144
  156. package/packages/gui/src/stores/helpers.js +10 -0
  157. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  158. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  159. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  160. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  161. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  162. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  163. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  164. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  165. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  166. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  167. package/packages/gui/src/views/agents.jsx +5 -5
  168. package/packages/gui/src/views/dashboard.jsx +12 -13
  169. package/packages/gui/src/views/marketplace.jsx +191 -3
  170. package/packages/gui/src/views/model-lab.jsx +17 -6
  171. package/packages/gui/src/views/models.jsx +410 -509
  172. package/packages/gui/src/views/network.jsx +3 -3
  173. package/packages/gui/src/views/settings.jsx +81 -94
  174. package/packages/gui/src/views/teams.jsx +40 -483
  175. package/SECURITY_SWEEP.md +0 -228
  176. package/TRAINING_DATA_v4.md +0 -6
  177. package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
  178. package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
  179. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
  180. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  181. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  182. package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
  183. package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
  184. package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
  185. package/packages/gui/src/views/preview.jsx +0 -6
  186. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  187. 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
+ }