@agentprojectcontext/apx 1.40.0 → 1.41.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.40.0",
3
+ "version": "1.41.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -277,6 +277,32 @@ export async function cmdDesktopStop(_args = {}) {
277
277
  }
278
278
  }
279
279
 
280
+ // True when the desktop Electron process is alive. Used by `apx restart` so it
281
+ // only re-launches the desktop if the user already had it running.
282
+ export function desktopRunning() {
283
+ return pidAlive(readPid());
284
+ }
285
+
286
+ // Stop (if running) then start — the only way the desktop picks up new
287
+ // renderer/main.js code after a pull, since the Electron process holds the
288
+ // old bundle in memory. Token re-sync after a daemon restart is handled
289
+ // automatically by the WS reconnect (it re-reads daemon.token), so this is
290
+ // purely about refreshing stale desktop CODE.
291
+ export async function cmdDesktopRestart(args = {}) {
292
+ const pid = readPid();
293
+ if (pidAlive(pid)) {
294
+ try { process.kill(pid, "SIGTERM"); } catch {}
295
+ clearPid();
296
+ // Give Electron a moment to release the tray/shortcut before relaunch.
297
+ await new Promise((r) => setTimeout(r, 600));
298
+ process.stderr.write("apx: restarting desktop...\n");
299
+ } else {
300
+ clearPid();
301
+ process.stderr.write("apx: desktop not running — starting...\n");
302
+ }
303
+ await cmdDesktopStart(args);
304
+ }
305
+
280
306
  export async function cmdDesktopStatus(_args = {}) {
281
307
  const pid = readPid();
282
308
  const alive = pidAlive(pid);
@@ -99,7 +99,7 @@ import {
99
99
  cmdPermission,
100
100
  } from "./commands/config.js";
101
101
  import { cmdPluginsList, cmdPluginStatus } from "./commands/plugins.js";
102
- import { cmdDesktopStart, cmdDesktopStop, cmdDesktopStatus, cmdDesktopInstall, cmdDesktopUninstall } from "./commands/desktop.js";
102
+ import { cmdDesktopStart, cmdDesktopStop, cmdDesktopRestart, cmdDesktopStatus, cmdDesktopInstall, cmdDesktopUninstall, desktopRunning } from "./commands/desktop.js";
103
103
  import { cmdVoiceSay, cmdVoiceListen, cmdVoiceProviders } from "./commands/voice.js";
104
104
  import { cmdSkillsAdd, cmdSkillsList, cmdSkillsStatus, cmdSkillsSync, cmdSkillsIndex, cmdSkillsInspect, cmdSkillsInspector } from "./commands/skills.js";
105
105
  import { cmdIdentity } from "./commands/identity.js";
@@ -2615,6 +2615,18 @@ async function dispatch(cmd, rest) {
2615
2615
  await cmdUpdate(parseArgs(rest), VERSION);
2616
2616
  return; // skip checkForUpdate after an update
2617
2617
 
2618
+ // Refresh everything held in memory after a code change (e.g. a `git
2619
+ // pull` in a dev checkout): restart the daemon, and restart the desktop
2620
+ // too if it was running. The daemon picks up new code/prompts; the
2621
+ // desktop picks up its new renderer/main.js. Token re-sync is automatic
2622
+ // (the desktop WS re-reads daemon.token on reconnect).
2623
+ case "restart": {
2624
+ const a = parseArgs(rest);
2625
+ await cmdDaemonRestart(a);
2626
+ if (desktopRunning()) await cmdDesktopRestart(a);
2627
+ return;
2628
+ }
2629
+
2618
2630
  case "overlay":
2619
2631
  console.error(" apx overlay has been renamed to apx desktop — forwarding.");
2620
2632
  /* falls through */
@@ -2623,10 +2635,11 @@ async function dispatch(cmd, rest) {
2623
2635
  const oArgs = parseArgs(oRest);
2624
2636
  if (!sub || sub === "start") { await cmdDesktopStart(oArgs); return; }
2625
2637
  if (sub === "stop") { await cmdDesktopStop(oArgs); return; }
2638
+ if (sub === "restart") { await cmdDesktopRestart(oArgs); return; }
2626
2639
  if (sub === "status") { await cmdDesktopStatus(oArgs);return; }
2627
2640
  if (sub === "install") { await cmdDesktopInstall(oArgs); return; }
2628
2641
  if (sub === "uninstall") { await cmdDesktopUninstall(oArgs);return; }
2629
- die(`unknown desktop sub-command: ${sub}\nUsage: apx desktop <start|stop|status|install|uninstall>`);
2642
+ die(`unknown desktop sub-command: ${sub}\nUsage: apx desktop <start|stop|restart|status|install|uninstall>`);
2630
2643
  return;
2631
2644
  }
2632
2645
 
@@ -2656,7 +2669,7 @@ const [topCmd, ...topRest] = argv;
2656
2669
  // of the compact line.
2657
2670
  // Suppress everything with APX_QUIET=1 / APX_NO_BANNER=1 (see branding.js).
2658
2671
  const SELF_BRANDED = new Set([
2659
- "status", "setup", "install", "daemon", "update", "upgrade", "help",
2672
+ "status", "setup", "install", "daemon", "update", "upgrade", "help", "restart",
2660
2673
  ]);
2661
2674
  const BANNERED = new Set(["init"]);
2662
2675
 
@@ -561,10 +561,16 @@ function connectDaemon() {
561
561
  return;
562
562
  }
563
563
 
564
- const token = readToken();
565
564
  const url = `ws://${DAEMON_HOST}:${DAEMON_PORT}/desktop/ws`;
566
565
 
567
566
  function connect() {
567
+ // Re-read the token on EVERY attempt — the daemon regenerates
568
+ // ~/.apx/daemon.token on each restart, so a token captured once at
569
+ // startup goes stale the moment the daemon is restarted (e.g. after a
570
+ // pull / `apx daemon restart`) and every reconnect 401s forever. Reading
571
+ // it fresh here lets the desktop self-heal: the next retry picks up the
572
+ // new token and reconnects on its own.
573
+ const token = readToken();
568
574
  try {
569
575
  wsConn = new WS(url, {
570
576
  headers: token ? { Authorization: `Bearer ${token}` } : {},
@@ -210,10 +210,17 @@
210
210
 
211
211
  function initialCaption(shortcut) {
212
212
  const sc = formatShortcut(shortcut);
213
+ // Empty-idle state has no other affordance to dismiss the floating window
214
+ // (the session bar's "Cerrar" only shows once a conversation exists, and
215
+ // the window doesn't auto-hide on blur). Pair the hint with a translucent
216
+ // "Cerrar ventana" pill in the same glass language so the user can always
217
+ // close it without hunting for the tray icon.
213
218
  $captionSlot.innerHTML = `
214
219
  <div class="caption">Mantené <span class="kbd">${sc}</span> para hablar
215
220
  <span class="kbd">⌥ /</span> para escribir</div>
221
+ <button class="cap-pill" id="btn-close-idle" title="Cerrar ventana">${ICON.close()}<span>Cerrar ventana</span></button>
216
222
  `;
223
+ $captionSlot.querySelector("#btn-close-idle")?.addEventListener("click", closeWindow);
217
224
  }
218
225
 
219
226
  // ── Render: capsule center + actions vary by mode ────────────────────────
@@ -193,6 +193,27 @@ button { font-family: inherit; }
193
193
  }
194
194
  [data-theme="dark"] .kbd { color: color-mix(in oklch, var(--accent) 60%, white); }
195
195
 
196
+ /* Stack the hint + the close pill, centered, in the empty-idle state.
197
+ render() toggles this slot's display between "" (→ flex) and "none". */
198
+ #caption-slot { display: flex; flex-direction: column; align-items: center; gap: 7px; }
199
+
200
+ /* Translucent "Cerrar ventana" pill — same glass language as .caption so the
201
+ empty-idle state always has a way to dismiss the window. */
202
+ .cap-pill {
203
+ display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
204
+ font: inherit; font-size: 11px; font-weight: 600; color: rgba(255,255,255,.92);
205
+ padding: 5px 12px; border-radius: 9px;
206
+ background: rgba(20,22,32,.28); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
207
+ border: 1px solid rgba(255,255,255,.14);
208
+ text-shadow: 0 0.5px 1.5px rgba(0,0,0,.3);
209
+ transition: transform .14s ease, background .15s ease;
210
+ animation: captionIn .4s ease both;
211
+ }
212
+ .cap-pill svg { width: 13px; height: 13px; opacity: .9; }
213
+ .cap-pill:hover { transform: translateY(-1px); background: rgba(20,22,32,.42); }
214
+ .cap-pill:active { transform: scale(.96); }
215
+ [data-theme="dark"] .cap-pill { background: rgba(0,0,0,.34); }
216
+
196
217
  /* ============================================================
197
218
  Conversation card
198
219
  ============================================================ */