groove-dev 0.27.203 → 0.27.205

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.203",
3
+ "version": "0.27.205",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.203",
3
+ "version": "0.27.205",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -174,6 +174,7 @@ export class AxomConnector {
174
174
  async _pollSessions(ep) {
175
175
  const list = await this._fetch(ep, '/sessions');
176
176
  if (!Array.isArray(list)) return;
177
+ let changed = false;
177
178
  for (const info of list) {
178
179
  const id = info.session;
179
180
  if (!id || typeof id !== 'string') continue;
@@ -191,12 +192,19 @@ export class AxomConnector {
191
192
  reconnectTimer: null,
192
193
  };
193
194
  ep.sessions.set(id, s);
195
+ changed = true;
194
196
  }
195
197
  // §12: `live` means a turn is in flight; sessions persist between turns
196
198
  // and events can start at any moment — stay attached regardless.
199
+ const wasLive = s.live;
197
200
  s.live = !!info.live;
201
+ // A liveness change is news. Without this, the GUI's copy of `live`
202
+ // stays true after a turn ends until something else happens to refetch
203
+ // status — which is how a spinner outlives its turn.
204
+ if (wasLive !== s.live) changed = true;
198
205
  if (!s.ws) this._watchSession(ep, s);
199
206
  }
207
+ if (changed) this._broadcastStatus();
200
208
  }
201
209
 
202
210
  _watchSession(ep, s) {
@@ -423,6 +423,18 @@ describe('AxomConnector', () => {
423
423
  assert.deepEqual(bridge.shutdowns, [{ force: true }]);
424
424
  });
425
425
 
426
+ it('broadcasts when a session stops being live — a spinner must not outlive its turn', async () => {
427
+ connect();
428
+ await waitFor(() => connector.status().endpoints[0]?.sessions[0]?.watching);
429
+ const before = daemon.broadcasts.filter((b) => b.type === 'axom:status').length;
430
+
431
+ // Turn ends runtime-side; the next poll must tell the GUI, not sit on it.
432
+ bridge.sessions['s-test0001'].live = false;
433
+ await waitFor(() => daemon.broadcasts.filter((b) => b.type === 'axom:status').length > before, 3000);
434
+ const latest = daemon.broadcasts.filter((b) => b.type === 'axom:status').pop();
435
+ assert.equal(latest.data.endpoints[0].sessions[0].live, false);
436
+ });
437
+
426
438
  it('reports an unreachable endpoint honestly and recovers by retry', async () => {
427
439
  const deadUrl = bridge.url;
428
440
  const port = Number(new URL(deadUrl).port);