groove-dev 0.27.197 → 0.27.198

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 (67) hide show
  1. package/axom-integration/Screenshot_2026-07-25_at_5.25.30_PM.png +0 -0
  2. package/axom-integration/Screenshot_2026-07-25_at_5.33.26_PM.png +0 -0
  3. package/axom-integration/Screenshot_2026-07-25_at_6.11.53_PM.png +0 -0
  4. package/node_modules/@groove-dev/cli/package.json +1 -1
  5. package/node_modules/@groove-dev/daemon/package.json +1 -1
  6. package/node_modules/@groove-dev/daemon/src/api.js +2 -0
  7. package/node_modules/@groove-dev/daemon/src/axom-connector.js +340 -0
  8. package/node_modules/@groove-dev/daemon/src/axom-install.js +140 -0
  9. package/node_modules/@groove-dev/daemon/src/axom-server.js +229 -0
  10. package/node_modules/@groove-dev/daemon/src/federation.js +6 -0
  11. package/node_modules/@groove-dev/daemon/src/index.js +11 -0
  12. package/node_modules/@groove-dev/daemon/src/innerchat-docs.js +8 -1
  13. package/node_modules/@groove-dev/daemon/src/innerchat-relay.js +89 -0
  14. package/node_modules/@groove-dev/daemon/src/innerchat.js +261 -1
  15. package/node_modules/@groove-dev/daemon/src/introducer.js +1 -1
  16. package/node_modules/@groove-dev/daemon/src/network-guard.js +56 -0
  17. package/node_modules/@groove-dev/daemon/src/process.js +1 -1
  18. package/node_modules/@groove-dev/daemon/src/providers/axom.js +66 -0
  19. package/node_modules/@groove-dev/daemon/src/providers/index.js +2 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/axom.js +196 -0
  21. package/node_modules/@groove-dev/daemon/src/routes/innerchat.js +152 -20
  22. package/node_modules/@groove-dev/daemon/test/axom-connector.test.js +412 -0
  23. package/node_modules/@groove-dev/daemon/test/axom-server.test.js +187 -0
  24. package/node_modules/@groove-dev/daemon/test/innerchat-relay.test.js +251 -0
  25. package/node_modules/@groove-dev/gui/dist/assets/index-RbtaI6l7.css +1 -0
  26. package/node_modules/@groove-dev/gui/dist/assets/{index-Da2nbd6M.js → index-b9dKN6cq.js} +259 -228
  27. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  28. package/node_modules/@groove-dev/gui/package.json +1 -1
  29. package/node_modules/@groove-dev/gui/src/App.jsx +2 -0
  30. package/node_modules/@groove-dev/gui/src/app.css +53 -0
  31. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -1
  32. package/node_modules/@groove-dev/gui/src/stores/groove.js +19 -0
  33. package/node_modules/@groove-dev/gui/src/stores/slices/axom-slice.js +197 -0
  34. package/node_modules/@groove-dev/gui/src/views/axom.jsx +1060 -0
  35. package/node_modules/@groove-dev/gui/src/views/settings.jsx +178 -3
  36. package/package.json +2 -2
  37. package/packages/cli/package.json +1 -1
  38. package/packages/daemon/package.json +1 -1
  39. package/packages/daemon/src/api.js +2 -0
  40. package/packages/daemon/src/axom-connector.js +340 -0
  41. package/packages/daemon/src/axom-install.js +140 -0
  42. package/packages/daemon/src/axom-server.js +229 -0
  43. package/packages/daemon/src/federation.js +6 -0
  44. package/packages/daemon/src/index.js +11 -0
  45. package/packages/daemon/src/innerchat-docs.js +8 -1
  46. package/packages/daemon/src/innerchat-relay.js +89 -0
  47. package/packages/daemon/src/innerchat.js +261 -1
  48. package/packages/daemon/src/introducer.js +1 -1
  49. package/packages/daemon/src/network-guard.js +56 -0
  50. package/packages/daemon/src/process.js +1 -1
  51. package/packages/daemon/src/providers/axom.js +66 -0
  52. package/packages/daemon/src/providers/index.js +2 -0
  53. package/packages/daemon/src/routes/axom.js +196 -0
  54. package/packages/daemon/src/routes/innerchat.js +152 -20
  55. package/packages/gui/dist/assets/index-RbtaI6l7.css +1 -0
  56. package/packages/gui/dist/assets/{index-Da2nbd6M.js → index-b9dKN6cq.js} +259 -228
  57. package/packages/gui/dist/index.html +2 -2
  58. package/packages/gui/package.json +1 -1
  59. package/packages/gui/src/App.jsx +2 -0
  60. package/packages/gui/src/app.css +53 -0
  61. package/packages/gui/src/components/layout/activity-bar.jsx +2 -1
  62. package/packages/gui/src/stores/groove.js +19 -0
  63. package/packages/gui/src/stores/slices/axom-slice.js +197 -0
  64. package/packages/gui/src/views/axom.jsx +1060 -0
  65. package/packages/gui/src/views/settings.jsx +178 -3
  66. package/node_modules/@groove-dev/gui/dist/assets/index-zmrIbwNm.css +0 -1
  67. package/packages/gui/dist/assets/index-zmrIbwNm.css +0 -1
@@ -6,12 +6,12 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <link rel="icon" type="image/png" href="/favicon.png" />
8
8
  <title>Groove GUI</title>
9
- <script type="module" crossorigin src="/assets/index-Da2nbd6M.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-b9dKN6cq.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BYKpdS2W.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
14
- <link rel="stylesheet" crossorigin href="/assets/index-zmrIbwNm.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-RbtaI6l7.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.197",
3
+ "version": "0.27.198",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -25,6 +25,7 @@ import NetworkView from './views/network';
25
25
  import MemoryView from './views/memory';
26
26
  import FleetView from './views/fleet';
27
27
  import AutoAgentsView from './views/auto-agents';
28
+ import AxomView from './views/axom';
28
29
 
29
30
  // Agent components
30
31
  import { AgentPanel } from './components/agents/agent-panel';
@@ -79,6 +80,7 @@ function ViewRouter() {
79
80
  case 'federation': content = <FederationView />; break;
80
81
  case 'settings': content = <SettingsView />; break;
81
82
  case 'fleet': content = <FleetView />; break;
83
+ case 'axom': content = <AxomView />; break;
82
84
  case 'memory': content = <MemoryView />; break;
83
85
  case 'network': content = networkUnlocked ? <NetworkView /> : <AgentsView />; break;
84
86
  default: content = <AgentsView />;
@@ -309,6 +309,59 @@ html {
309
309
  box-shadow: 0 0 60px rgba(51,175,188,0.15), 0 0 120px rgba(51,175,188,0.04);
310
310
  }
311
311
 
312
+ /* ── Axom Onboarding ─────────────────────────────────────── */
313
+
314
+ /* Front door backdrop — one cool bloom behind the mark on the identity pane,
315
+ a faint cold wash under the operations pane. Nothing else. */
316
+ .axom-hero-bg {
317
+ background:
318
+ radial-gradient(ellipse 760px 620px at 20% 38%, rgba(51, 175, 188, 0.11) 0%, transparent 68%),
319
+ radial-gradient(ellipse 900px 700px at 88% 62%, rgba(122, 176, 223, 0.045) 0%, transparent 70%),
320
+ var(--color-surface-1);
321
+ }
322
+
323
+ /* Panels read as instrument faces: light at the top edge, settling downward. */
324
+ .axom-panel {
325
+ background: linear-gradient(180deg, var(--color-surface-3) 0%, var(--color-surface-1) 100%);
326
+ }
327
+
328
+ /* Hairline that carries the accent only where it matters — a top edge. */
329
+ .axom-edge-accent {
330
+ background: linear-gradient(90deg, transparent 0%, rgba(51, 175, 188, 0.6) 50%, transparent 100%);
331
+ }
332
+ .axom-edge-muted {
333
+ background: linear-gradient(90deg, transparent 0%, var(--color-border) 50%, transparent 100%);
334
+ }
335
+
336
+ /* Display type — a soft vertical falloff, no color, just depth. */
337
+ .axom-display {
338
+ background: linear-gradient(180deg, #f2f4f7 0%, #9aa3b0 100%);
339
+ -webkit-background-clip: text;
340
+ background-clip: text;
341
+ color: transparent;
342
+ }
343
+
344
+ .axom-ring {
345
+ border-radius: 50%;
346
+ border: 1px solid rgba(51, 175, 188, 0.22);
347
+ }
348
+
349
+ @keyframes axom-orbit { to { transform: rotate(360deg); } }
350
+ .animate-axom-orbit { animation: axom-orbit 26s linear infinite; }
351
+ .animate-axom-orbit-slow { animation: axom-orbit 44s linear infinite reverse; }
352
+
353
+ @keyframes axom-breathe {
354
+ 0%, 100% { opacity: 0.45; }
355
+ 50% { opacity: 0.85; }
356
+ }
357
+ .animate-axom-breathe { animation: axom-breathe 6s ease-in-out infinite; }
358
+
359
+ @media (prefers-reduced-motion: reduce) {
360
+ .animate-axom-orbit,
361
+ .animate-axom-orbit-slow,
362
+ .animate-axom-breathe { animation: none; }
363
+ }
364
+
312
365
  /* ── React Flow Overrides ─────────────────────────────────── */
313
366
 
314
367
  /* Suppress node fly-in animation — nodes appear in position instantly */
@@ -1,5 +1,5 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, BookOpen, LayoutList, Bot } from 'lucide-react';
2
+ import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, BookOpen, LayoutList, Bot, Atom } from 'lucide-react';
3
3
  import { cn } from '../../lib/cn';
4
4
  import { Tooltip } from '../ui/tooltip';
5
5
  import { useGrooveStore } from '../../stores/groove';
@@ -7,6 +7,7 @@ import { isElectron, getPlatform } from '../../lib/electron';
7
7
 
8
8
  const BASE_NAV_ITEMS = [
9
9
  { id: 'agents', icon: Network, label: 'Agents' },
10
+ { id: 'axom', icon: Atom, label: 'Axom' },
10
11
  { id: 'fleet', icon: LayoutList, label: 'Fleet' },
11
12
  { id: 'editor', icon: Code2, label: 'Editor' },
12
13
  { id: 'dashboard', icon: ChartSpline, label: 'Dashboard' },
@@ -14,6 +14,7 @@ import { createPreviewSlice } from './slices/preview-slice.js';
14
14
  import { createMarketplaceSlice } from './slices/marketplace-slice.js';
15
15
  import { createAutomationsSlice } from './slices/automations-slice.js';
16
16
  import { createAutoAgentsSlice } from './slices/auto-agents-slice.js';
17
+ import { createAxomSlice } from './slices/axom-slice.js';
17
18
 
18
19
  const WS_URL = `ws://${window.location.hostname}:${window.location.port || 31415}`;
19
20
 
@@ -39,6 +40,7 @@ export const useGrooveStore = create((set, get) => ({
39
40
  ...createMarketplaceSlice(set, get),
40
41
  ...createAutomationsSlice(set, get),
41
42
  ...createAutoAgentsSlice(set, get),
43
+ ...createAxomSlice(set, get),
42
44
 
43
45
  // ── Connection ────────────────────────────────────────────
44
46
  connected: false,
@@ -73,6 +75,7 @@ export const useGrooveStore = create((set, get) => ({
73
75
  get().fetchTunnels();
74
76
  get().fetchBetaStatus();
75
77
  get().fetchNetworkInstallStatus();
78
+ get().fetchAxomStatus();
76
79
  get().fetchTrainingStatus();
77
80
  api.get('/config').then((cfg) => {
78
81
  if (cfg?.dataSharingDismissed) set({ dataSharingDismissed: true });
@@ -653,6 +656,22 @@ export const useGrooveStore = create((set, get) => ({
653
656
  set({ gateways: msg.data || [] });
654
657
  break;
655
658
 
659
+ case 'axom:status':
660
+ set({ axomStatus: msg.data || { endpoints: [] } });
661
+ break;
662
+
663
+ case 'axom:event':
664
+ get().ingestAxomEvent(msg.endpoint, msg.session, msg.envelope);
665
+ break;
666
+
667
+ case 'axom:instances':
668
+ set({ axomInstances: msg.data || [] });
669
+ break;
670
+
671
+ case 'axom:install:progress':
672
+ set({ axomInstall: msg.data });
673
+ break;
674
+
656
675
  case 'provider:status-changed':
657
676
  set({ _providerRefreshTick: Date.now() });
658
677
  break;
@@ -0,0 +1,197 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ //
3
+ // Axom provider state. Everything rendered from this slice is
4
+ // telemetry-grounded: interrupt/stop UI states flip on EVENTS from the
5
+ // runtime, never on our own optimism (contract §2/§7 — fail-deceptive is
6
+ // worse than fail-open).
7
+
8
+ import { api } from '../../lib/api';
9
+
10
+ const EVENT_BUFFER = 2000;
11
+
12
+ const sessionKey = (endpoint, session) => `${endpoint}/${session}`;
13
+
14
+ export const createAxomSlice = (set, get) => ({
15
+ // ── Axom Provider ─────────────────────────────────────────
16
+ axomStatus: { endpoints: [] },
17
+ axomSelected: null, // { endpoint, session }
18
+ axomEvents: {}, // sessionKey -> [envelope] (bounded)
19
+ // Local interrupt ledger: id -> {text, state}. state: 'sent' (POST accepted,
20
+ // not yet seen on the stream) | 'heard' (interrupt event arrived) |
21
+ // 'acked' (interrupt_ack arrived). Rollup states come from pipeline_done.
22
+ axomInterrupts: {}, // sessionKey -> { [id]: {text, state, truncated} }
23
+ // Stop states: null | 'pending' (POST sent, nothing effected yet) |
24
+ // 'effected' (stop_effected seen) | 'resolved-before-stop' (§7 edge:
25
+ // pipeline_done arrived with no stop_effected).
26
+ axomStops: {}, // sessionKey -> state
27
+ // Contract-violation log (§8): e.g. an interrupt_ack without interrupt_id.
28
+ // A fallback that fires must announce itself — never degrade silently.
29
+ axomAnomalies: {}, // sessionKey -> [{eventId, message}]
30
+ axomInstances: [], // managed local instances (contract §11)
31
+ axomInstall: { phase: 'idle', file: null, receivedBytes: 0, totalBytes: 0, error: null },
32
+ axomHardware: null, // machine readiness report for local inference
33
+ axomMyEndpoint: null, // this GROOVE's own Axom endpoint (share/copy)
34
+
35
+ async fetchAxomStatus() {
36
+ try {
37
+ const data = await api.get('/axom/status');
38
+ set({ axomStatus: data || { endpoints: [] } });
39
+ const instances = await api.get('/axom/instances');
40
+ set({ axomInstances: instances || [] });
41
+ } catch { /* daemon predates the axom routes */ }
42
+ },
43
+
44
+ async fetchAxomHardware() {
45
+ try {
46
+ const data = await api.get('/axom/hardware');
47
+ set({ axomHardware: data });
48
+ } catch { /* daemon predates the route */ }
49
+ try {
50
+ const me = await api.get('/axom/my-endpoint');
51
+ set({ axomMyEndpoint: me });
52
+ } catch { /* daemon predates the route */ }
53
+ },
54
+
55
+ async startAxomInstall(manifestUrl) {
56
+ await api.post('/axom/install', manifestUrl ? { manifestUrl } : {});
57
+ },
58
+
59
+ async startAxomInstance(id = 'default') {
60
+ const instance = await api.post('/axom/instances', { id });
61
+ await get().fetchAxomStatus();
62
+ return instance;
63
+ },
64
+
65
+ async stopAxomInstance(id) {
66
+ await api.delete(`/axom/instances/${encodeURIComponent(id)}`);
67
+ await get().fetchAxomStatus();
68
+ },
69
+
70
+ async saveAxomEndpoints(endpoints) {
71
+ const data = await api.patch('/axom/config', { endpoints });
72
+ await get().fetchAxomStatus();
73
+ return data;
74
+ },
75
+
76
+ async selectAxomSession(endpoint, session) {
77
+ set({ axomSelected: { endpoint, session } });
78
+ // Backfill from the daemon's ring so a freshly opened tab has history.
79
+ try {
80
+ const data = await api.get(`/axom/sessions/${encodeURIComponent(session)}/events?endpoint=${encodeURIComponent(endpoint)}`);
81
+ const key = sessionKey(endpoint, session);
82
+ set((s) => {
83
+ const existing = s.axomEvents[key] || [];
84
+ const seen = new Set(existing.map((e) => e.id));
85
+ const merged = [...(data.events || []).filter((e) => !seen.has(e.id)), ...existing]
86
+ .sort((a, b) => (a.id < b.id ? -1 : 1))
87
+ .slice(-EVENT_BUFFER);
88
+ return { axomEvents: { ...s.axomEvents, [key]: merged } };
89
+ });
90
+ } catch { /* session may have no ring yet — live events still flow */ }
91
+ },
92
+
93
+ ingestAxomEvent(endpoint, session, envelope) {
94
+ const key = sessionKey(endpoint, session);
95
+ set((s) => {
96
+ const buf = s.axomEvents[key] || [];
97
+ if (buf.length && buf[buf.length - 1].id === envelope.id) return {}; // WS echo guard
98
+ const updates = {
99
+ axomEvents: { ...s.axomEvents, [key]: [...buf, envelope].slice(-EVENT_BUFFER) },
100
+ };
101
+
102
+ // Interrupt lifecycle — driven only by what the runtime says happened.
103
+ // §8 pins interrupt_ack.payload.interrupt_id as ALWAYS present; the
104
+ // oldest-pending fallback stays as defense but surfaces as an anomaly
105
+ // when it fires — an ack without an id is a runtime bug to expose.
106
+ if (envelope.kind === 'interrupt' || envelope.kind === 'interrupt_ack') {
107
+ const ledger = { ...(s.axomInterrupts[key] || {}) };
108
+ const evId = envelope.payload?.interrupt_id ?? envelope.payload?.id;
109
+ const nextState = envelope.kind === 'interrupt' ? 'heard' : 'acked';
110
+ if (evId && ledger[evId]) {
111
+ ledger[evId] = { ...ledger[evId], state: nextState };
112
+ } else if (evId) {
113
+ // Known id, not ours — an interrupt posted by another client (e.g.
114
+ // typed straight into the REPL). Normal; it lives in the ticker,
115
+ // our ledger stays untouched.
116
+ } else {
117
+ // No id at all — a shape violation per §8. Advance the oldest
118
+ // pending entry as defense, and announce that the fallback fired.
119
+ const pending = Object.entries(ledger).find(([, v]) => v.state !== nextState && v.state !== 'acked');
120
+ if (pending) ledger[pending[0]] = { ...pending[1], state: nextState };
121
+ updates.axomAnomalies = {
122
+ ...s.axomAnomalies,
123
+ [key]: [
124
+ ...(s.axomAnomalies[key] || []),
125
+ { eventId: envelope.id, message: `${envelope.kind} arrived without an interrupt id (§8 violation)${pending ? ` — fallback advanced "${pending[1].text?.slice(0, 40)}"` : ''}` },
126
+ ].slice(-50),
127
+ };
128
+ }
129
+ updates.axomInterrupts = { ...s.axomInterrupts, [key]: ledger };
130
+ }
131
+
132
+ if (envelope.kind === 'stop_effected' && s.axomStops[key] === 'pending') {
133
+ updates.axomStops = { ...s.axomStops, [key]: 'effected' };
134
+ }
135
+ // §7 stop edge: the run can resolve before stop lands. Release the
136
+ // sticky pressed state on pipeline_done and say what actually happened.
137
+ if (envelope.kind === 'pipeline_done' && s.axomStops[key] === 'pending') {
138
+ updates.axomStops = { ...s.axomStops, [key]: 'resolved-before-stop' };
139
+ }
140
+ return updates;
141
+ });
142
+ },
143
+
144
+ // §12: message starts a turn on a caller-chosen session id (first message
145
+ // creates the session). 409 means a turn is in flight — steer instead.
146
+ async sendAxomMessage(text) {
147
+ const sel = get().axomSelected;
148
+ if (!sel) throw new Error('No Axom session selected');
149
+ try {
150
+ await api.post(`/axom/sessions/${encodeURIComponent(sel.session)}/message`, {
151
+ endpoint: sel.endpoint, text,
152
+ });
153
+ await get().fetchAxomStatus();
154
+ return { ok: true };
155
+ } catch (err) {
156
+ if (err.status === 409) return { busy: true };
157
+ if (err.status === 413) return { tooLong: true, max: err.body?.max };
158
+ throw err;
159
+ }
160
+ },
161
+
162
+ async sendAxomInterrupt(text) {
163
+ const sel = get().axomSelected;
164
+ if (!sel) throw new Error('No Axom session selected');
165
+ const result = await api.post(`/axom/sessions/${encodeURIComponent(sel.session)}/interrupt`, {
166
+ endpoint: sel.endpoint, text,
167
+ });
168
+ const key = sessionKey(sel.endpoint, sel.session);
169
+ set((s) => ({
170
+ axomInterrupts: {
171
+ ...s.axomInterrupts,
172
+ [key]: {
173
+ ...(s.axomInterrupts[key] || {}),
174
+ [result.id]: { text, state: 'sent', truncated: !!result.truncated },
175
+ },
176
+ },
177
+ }));
178
+ return result;
179
+ },
180
+
181
+ async sendAxomStop() {
182
+ const sel = get().axomSelected;
183
+ if (!sel) throw new Error('No Axom session selected');
184
+ const key = sessionKey(sel.endpoint, sel.session);
185
+ set((s) => ({ axomStops: { ...s.axomStops, [key]: 'pending' } }));
186
+ try {
187
+ return await api.post(`/axom/sessions/${encodeURIComponent(sel.session)}/stop`, { endpoint: sel.endpoint });
188
+ } catch (err) {
189
+ // The POST failed — nothing was requested, so the button must not stay
190
+ // pressed pretending it was.
191
+ set((s) => ({ axomStops: { ...s.axomStops, [key]: null } }));
192
+ throw err;
193
+ }
194
+ },
195
+ });
196
+
197
+ export { sessionKey as axomSessionKey };