amalgm 0.1.84 → 0.1.85

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.
package/lib/supervisor.js CHANGED
@@ -402,9 +402,17 @@ function sleep(ms) {
402
402
 
403
403
  function writeAutoUpdateState(update) {
404
404
  const current = readJson(AUTO_UPDATE_STATE_FILE, {});
405
+ const updateValue = (key, fallback = null) => (
406
+ Object.prototype.hasOwnProperty.call(update, key) ? update[key] : fallback
407
+ );
405
408
  const next = {
406
409
  ...current,
407
410
  ...update,
411
+ active_turns: updateValue('active_turns'),
412
+ tempstore_count: updateValue('tempstore_count'),
413
+ mcp_active: updateValue('mcp_active'),
414
+ tunnel_active: updateValue('tunnel_active'),
415
+ idle_blocked_reason: updateValue('idle_blocked_reason'),
408
416
  updated_at: new Date().toISOString(),
409
417
  };
410
418
  writeJsonSecret(AUTO_UPDATE_STATE_FILE, next);
@@ -419,6 +427,7 @@ function writeAutoUpdateState(update) {
419
427
  last_checked_at: next.last_checked_at || null,
420
428
  last_error: next.last_error || null,
421
429
  active_turns: next.active_turns ?? null,
430
+ tempstore_count: next.tempstore_count ?? null,
422
431
  mcp_active: next.mcp_active ?? null,
423
432
  tunnel_active: next.tunnel_active ?? null,
424
433
  },
@@ -462,20 +471,43 @@ function localJsonRequest(port, requestPath, runtimeToken, timeoutMs) {
462
471
  });
463
472
  }
464
473
 
465
- async function runtimeIdleStatus({ ports, runtimeToken, tunnels }) {
466
- const tunnelActive = tunnels.reduce((sum, tunnel) => (
474
+ function runtimeTunnelActive(tunnels = []) {
475
+ return tunnels.reduce((sum, tunnel) => (
467
476
  sum + (typeof tunnel.activeCount === 'function' ? Number(tunnel.activeCount()) || 0 : 0)
468
477
  ), 0);
478
+ }
479
+
480
+ function autoUpdateIdleBlockedReason(idle) {
481
+ if (idle.idle) return null;
482
+ if (idle.reason) return idle.reason;
483
+
484
+ const tempstoreCount = Number(idle.tempstore_count ?? idle.active_turns) || 0;
485
+ if (tempstoreCount > 0) {
486
+ return `tempstore has ${tempstoreCount} active turn${tempstoreCount === 1 ? '' : 's'}`;
487
+ }
488
+
489
+ const mcpActive = Number(idle.mcp_active) || 0;
490
+ if (mcpActive > 0) {
491
+ return `mcp has ${mcpActive} active agent conversation${mcpActive === 1 ? '' : 's'}`;
492
+ }
493
+
494
+ return 'runtime is busy';
495
+ }
496
+
497
+ async function runtimeIdleStatus({ ports, runtimeToken, tunnels }) {
498
+ const tunnelActive = runtimeTunnelActive(tunnels);
469
499
  const timeoutMs = numberFromEnv('AMALGM_UPDATE_IDLE_TIMEOUT_MS', AUTO_UPDATE_POLICY.idleTimeoutMs, 500);
470
500
  const [chat, mcp] = await Promise.all([
471
501
  localJsonRequest(ports.chatServer, '/runtime-idle', runtimeToken, timeoutMs),
472
502
  localJsonRequest(ports.amalgmMcp, '/healthz', runtimeToken, timeoutMs),
473
503
  ]);
474
- const activeTurns = Number(chat?.active_turns) || 0;
504
+ const tempstoreCount = Number(chat?.tempstore_count ?? chat?.active_turns) || 0;
505
+ const activeTurns = Number(chat?.active_turns ?? tempstoreCount) || 0;
475
506
  const mcpActive = Number(mcp?.activeAgentConversations) || 0;
476
507
  return {
477
- idle: activeTurns === 0 && mcpActive === 0 && tunnelActive === 0,
508
+ idle: tempstoreCount === 0 && mcpActive === 0,
478
509
  active_turns: activeTurns,
510
+ tempstore_count: tempstoreCount,
479
511
  mcp_active: mcpActive,
480
512
  tunnel_active: tunnelActive,
481
513
  };
@@ -573,8 +605,9 @@ function createAutoUpdateManager({ ports, runtimeToken, tunnels, foreground, req
573
605
  idle = {
574
606
  idle: false,
575
607
  active_turns: null,
608
+ tempstore_count: null,
576
609
  mcp_active: null,
577
- tunnel_active: null,
610
+ tunnel_active: runtimeTunnelActive(tunnels),
578
611
  reason: error.message,
579
612
  };
580
613
  }
@@ -587,9 +620,10 @@ function createAutoUpdateManager({ ports, runtimeToken, tunnels, foreground, req
587
620
  critical: false,
588
621
  service_managed: true,
589
622
  active_turns: idle.active_turns,
623
+ tempstore_count: idle.tempstore_count,
590
624
  mcp_active: idle.mcp_active,
591
625
  tunnel_active: idle.tunnel_active,
592
- idle_blocked_reason: idle.idle ? null : idle.reason || 'runtime is busy',
626
+ idle_blocked_reason: autoUpdateIdleBlockedReason(idle),
593
627
  });
594
628
 
595
629
  if (idle.idle) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.84",
3
+ "version": "0.1.85",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -109,10 +109,11 @@ class ChatCore {
109
109
  }
110
110
 
111
111
  idleStatus() {
112
- const activeTurns = typeof this.turns.activeCount === 'function' ? this.turns.activeCount() : 0;
112
+ const tempstoreCount = typeof this.turns.activeCount === 'function' ? this.turns.activeCount() : 0;
113
113
  return {
114
- idle: activeTurns === 0,
115
- active_turns: activeTurns,
114
+ idle: tempstoreCount === 0,
115
+ active_turns: tempstoreCount,
116
+ tempstore_count: tempstoreCount,
116
117
  };
117
118
  }
118
119
 
@@ -118,7 +118,7 @@ test('POST /chat streams from the same indexed raw chunks as /raw-temp', async (
118
118
  }
119
119
  });
120
120
 
121
- test('GET /runtime-idle reports active turn count', async () => {
121
+ test('GET /runtime-idle reports tempstore count', async () => {
122
122
  process.env.AMALGM_RUNTIME_AUTH = 'disabled';
123
123
 
124
124
  const core = new ChatCore({
@@ -137,7 +137,7 @@ test('GET /runtime-idle reports active turn count', async () => {
137
137
  try {
138
138
  let res = await fetch(`${baseUrl}/runtime-idle`);
139
139
  assert.equal(res.status, 200);
140
- assert.deepEqual(await res.json(), { idle: true, active_turns: 0 });
140
+ assert.deepEqual(await res.json(), { idle: true, active_turns: 0, tempstore_count: 0 });
141
141
 
142
142
  const entry = core.turns.start({
143
143
  sessionId: 'session-test',
@@ -147,12 +147,12 @@ test('GET /runtime-idle reports active turn count', async () => {
147
147
  });
148
148
  res = await fetch(`${baseUrl}/runtime-idle`);
149
149
  assert.equal(res.status, 200);
150
- assert.deepEqual(await res.json(), { idle: false, active_turns: 1 });
150
+ assert.deepEqual(await res.json(), { idle: false, active_turns: 1, tempstore_count: 1 });
151
151
 
152
152
  core.turns.mark(entry.turnId, 'complete');
153
153
  res = await fetch(`${baseUrl}/runtime-idle`);
154
154
  assert.equal(res.status, 200);
155
- assert.deepEqual(await res.json(), { idle: true, active_turns: 0 });
155
+ assert.deepEqual(await res.json(), { idle: true, active_turns: 0, tempstore_count: 0 });
156
156
  } finally {
157
157
  await new Promise((resolve) => server.close(resolve));
158
158
  }