@nac3/forge-cli 1.14.0 → 1.15.0

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 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/chat/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/chat/server.ts"],"names":[],"mappings":"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"}
@@ -48,6 +48,7 @@ import { isAllowedSlot, listAllowedSlots } from '../core/provider_keys.js';
48
48
  import { toolStatusSection, startToolStatusRefresh, environmentLines } from './tool_status.js';
49
49
  import { RelayPublisher } from '../relay/relay_publisher.js';
50
50
  import { setInFlightTurn, clearInFlightTurn, inFlightTurn } from '../relay/in_flight_turn.js';
51
+ import { decideBeat, presenceSignature, PRESENCE_TICK_MS } from '../relay/presence_cadence.js';
51
52
  import { getInstanceIdentity } from '../relay/instance_identity.js';
52
53
  import { isMobilePaired, relayGate } from '../core/mobile_pairing_state.js';
53
54
  import { relayPoll } from '../relay/relay_client.js';
@@ -863,6 +864,18 @@ function broadcastToPanel(event, data) {
863
864
  catch { /* best-effort */ }
864
865
  }
865
866
  }
867
+ /* El latido de presencia, forzable desde el camino del turno. El tick de
868
+ * evaluacion (5s) ya detecta el cambio de estado, pero forzarlo en el arranque
869
+ * y el cierre del turno lo hace INSTANTANEO: es la informacion por la que el
870
+ * telefono existe ("que esta haciendo ahora"), y no tiene que pagar ni la
871
+ * resolucion del tick. Null mientras no hay consumidores de relay corriendo. */
872
+ let _forcePresenceBeat = null;
873
+ function forcePresenceBeat() {
874
+ try {
875
+ _forcePresenceBeat?.(true);
876
+ }
877
+ catch { /* best-effort */ }
878
+ }
866
879
  let _relayPub = null;
867
880
  function getRelayPublisher() {
868
881
  if (!relayEnabled())
@@ -1127,29 +1140,73 @@ function startDesktopCommandConsumer(port, coord = {}) {
1127
1140
  pollCommands, runTurn, onControl, reply,
1128
1141
  onError: () => { },
1129
1142
  });
1130
- /* Item 2 (cross-machine): advertise this instance's presence on a slow
1131
- * heartbeat (gated by relayEnabled -> only when a phone is paired) so the
1132
- * phone can list + pick instances across machines and TTL-expire dead ones. */
1133
- const presenceTimer = setInterval(() => {
1143
+ /* Item 2 (cross-machine): advertise this instance's presence so the phone
1144
+ * can list + pick instances across machines and TTL-expire dead ones.
1145
+ * Gated by relayEnabled -> only when a phone is paired.
1146
+ *
1147
+ * La CADENCIA la decide relay/presence_cadence.ts, no este timer: el timer
1148
+ * solo marca el pulso de EVALUACION. Medido contra el D1 (12-oct-2026), el
1149
+ * latido fijo de 15s era el 85% de la ventana retenida del relay y 34 de
1150
+ * los 40 eventos que trae un refresco -- ocioso, inundaba su propia ventana
1151
+ * y dejaba al telefono sin nada que mostrar. Ahora: 15s con turno en vuelo,
1152
+ * 300s ocioso, e inmediato cuando el estado cambia (que es lo que mantiene
1153
+ * instantaneo el anuncio del pedido en proceso). */
1154
+ let lastBeatAt = null;
1155
+ let lastSig = null;
1156
+ const beatPresence = (force = false) => {
1134
1157
  try {
1135
1158
  const pub = getRelayPublisher();
1136
1159
  if (!pub)
1137
1160
  return;
1138
1161
  const idn = getInstanceIdentity();
1139
1162
  const slug = _relayBoardCtx?.slug ?? null;
1163
+ const turn = inFlightTurn();
1164
+ const info = { hostname: idn.hostname, project_slug: slug, turn };
1165
+ const sig = presenceSignature(info);
1166
+ if (!force) {
1167
+ const d = decideBeat({
1168
+ now: Date.now(), lastBeatAt, lastSig, sig, working: !!turn,
1169
+ });
1170
+ if (!d.beat)
1171
+ return;
1172
+ }
1173
+ lastBeatAt = Date.now();
1174
+ lastSig = sig;
1140
1175
  pub.presence({
1141
1176
  hostname: idn.hostname, pid: idn.pid, port,
1142
1177
  ...(slug ? { project_slug: slug, project_name: slug } : {}),
1143
- turn: inFlightTurn(),
1178
+ turn,
1144
1179
  });
1145
1180
  void pub.flush();
1146
1181
  }
1147
1182
  catch { /* best-effort */ }
1148
- }, 15000);
1183
+ };
1184
+ const presenceTimer = setInterval(() => { beatPresence(); }, PRESENCE_TICK_MS);
1149
1185
  if (typeof presenceTimer.unref === 'function')
1150
1186
  presenceTimer.unref();
1187
+ _forcePresenceBeat = beatPresence;
1151
1188
  return {
1152
- stop() { clearInterval(peerTimer); clearInterval(presenceTimer); chat.stop(); control.stop(); },
1189
+ stop() {
1190
+ clearInterval(peerTimer);
1191
+ clearInterval(presenceTimer);
1192
+ /* Despedida: con la cadencia ociosa en 300s el TTL del telefono son 15
1193
+ * minutos, asi que una instancia que se cierra quedaria fantasma en el
1194
+ * selector todo ese rato. Un ultimo latido marcado `gone` la borra al
1195
+ * instante. Best-effort -- el TTL sigue siendo el respaldo si el
1196
+ * proceso muere sin pasar por aca. */
1197
+ try {
1198
+ const pub = getRelayPublisher();
1199
+ if (pub) {
1200
+ const idn = getInstanceIdentity();
1201
+ pub.presence({ hostname: idn.hostname, pid: idn.pid, port, gone: true });
1202
+ void pub.flush();
1203
+ }
1204
+ }
1205
+ catch { /* best-effort */ }
1206
+ _forcePresenceBeat = null;
1207
+ chat.stop();
1208
+ control.stop();
1209
+ },
1153
1210
  tickOnce: async () => { await refreshPeers(); await chat.tickOnce(); await control.tickOnce(); },
1154
1211
  };
1155
1212
  }
@@ -1366,10 +1423,17 @@ function relayForward(event, data) {
1366
1423
  pub.status('working', { tier: d.tier, provider: d.provider, model: d.model });
1367
1424
  pub.activity('remote running...', { kind: 'heartbeat' }); /* first line now; then every 60s */
1368
1425
  startRelayHeartbeat();
1426
+ /* El turno en vuelo viaja DENTRO del latido (in_flight_turn.ts): hay que
1427
+ * publicarlo ya, sin esperar el tick, o el telefono no sabe que hay algo
1428
+ * corriendo hasta la proxima evaluacion. */
1429
+ forcePresenceBeat();
1369
1430
  }
1370
1431
  else if (event === 'turn_complete') {
1371
1432
  stopRelayHeartbeat();
1372
1433
  pub.status('round_done');
1434
+ /* Simetrico: el latido tiene que dejar de anunciar el turno, o el
1435
+ * telefono sigue mostrando la burbuja "pensando" hasta el proximo. */
1436
+ forcePresenceBeat();
1373
1437
  /* refresh the phone's board after each turn (workflow/pendings may
1374
1438
  * have changed) -- the pizarron is the value the user keeps. */
1375
1439
  publishBoardSnapshot();