@songsid/agend 2.2.0-alpha.1 → 2.2.0-alpha.2

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 (150) hide show
  1. package/README.md +2 -0
  2. package/README.zh-TW.md +2 -0
  3. package/dist/agent-cli.js +6 -1
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.js +68 -12
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-token-header.d.ts +13 -0
  8. package/dist/agent-token-header.js +16 -0
  9. package/dist/agent-token-header.js.map +1 -0
  10. package/dist/backend/antigravity.js +4 -3
  11. package/dist/backend/antigravity.js.map +1 -1
  12. package/dist/backend/grok.js +1 -1
  13. package/dist/backend/grok.js.map +1 -1
  14. package/dist/backend/kiro.d.ts +7 -0
  15. package/dist/backend/kiro.js +82 -33
  16. package/dist/backend/kiro.js.map +1 -1
  17. package/dist/backend/muse.js +1 -1
  18. package/dist/backend/muse.js.map +1 -1
  19. package/dist/backend/types.d.ts +6 -0
  20. package/dist/backend/types.js.map +1 -1
  21. package/dist/cache-analysis.d.ts +92 -0
  22. package/dist/cache-analysis.js +147 -0
  23. package/dist/cache-analysis.js.map +1 -0
  24. package/dist/cache-ledger.d.ts +108 -0
  25. package/dist/cache-ledger.js +230 -0
  26. package/dist/cache-ledger.js.map +1 -0
  27. package/dist/cache-prices.d.ts +32 -0
  28. package/dist/cache-prices.js +73 -0
  29. package/dist/cache-prices.js.map +1 -0
  30. package/dist/cache-scan.d.ts +39 -0
  31. package/dist/cache-scan.js +115 -0
  32. package/dist/cache-scan.js.map +1 -0
  33. package/dist/cache-service.d.ts +115 -0
  34. package/dist/cache-service.js +417 -0
  35. package/dist/cache-service.js.map +1 -0
  36. package/dist/cli-entry.d.ts +19 -0
  37. package/dist/cli-entry.js +36 -0
  38. package/dist/cli-entry.js.map +1 -0
  39. package/dist/cli.js +251 -24
  40. package/dist/cli.js.map +1 -1
  41. package/dist/daemon.d.ts +6 -0
  42. package/dist/daemon.js +90 -37
  43. package/dist/daemon.js.map +1 -1
  44. package/dist/event-log.d.ts +4 -1
  45. package/dist/event-log.js +6 -3
  46. package/dist/event-log.js.map +1 -1
  47. package/dist/fleet-control-audit.d.ts +5 -0
  48. package/dist/fleet-control-audit.js +7 -0
  49. package/dist/fleet-control-audit.js.map +1 -1
  50. package/dist/fleet-level-config.js +22 -0
  51. package/dist/fleet-level-config.js.map +1 -1
  52. package/dist/fleet-manager.d.ts +46 -0
  53. package/dist/fleet-manager.js +287 -40
  54. package/dist/fleet-manager.js.map +1 -1
  55. package/dist/full-restart.js +2 -3
  56. package/dist/full-restart.js.map +1 -1
  57. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
  58. package/dist/install-lock.d.ts +49 -0
  59. package/dist/install-lock.js +200 -0
  60. package/dist/install-lock.js.map +1 -0
  61. package/dist/instance-config-impact.js +3 -2
  62. package/dist/instance-config-impact.js.map +1 -1
  63. package/dist/locale.js +14 -0
  64. package/dist/locale.js.map +1 -1
  65. package/dist/outbound-schemas.js +1 -1
  66. package/dist/outbound-schemas.js.map +1 -1
  67. package/dist/package-preimage.d.ts +26 -0
  68. package/dist/package-preimage.js +96 -0
  69. package/dist/package-preimage.js.map +1 -0
  70. package/dist/public-web-gateway.js +2 -2
  71. package/dist/public-web-gateway.js.map +1 -1
  72. package/dist/quickstart.js +10 -4
  73. package/dist/quickstart.js.map +1 -1
  74. package/dist/restart-guard.d.ts +52 -0
  75. package/dist/restart-guard.js +109 -0
  76. package/dist/restart-guard.js.map +1 -0
  77. package/dist/scheduler/db.js +6 -2
  78. package/dist/scheduler/db.js.map +1 -1
  79. package/dist/service-activation.d.ts +47 -5
  80. package/dist/service-activation.js +156 -51
  81. package/dist/service-activation.js.map +1 -1
  82. package/dist/service-installer.d.ts +56 -1
  83. package/dist/service-installer.js +134 -12
  84. package/dist/service-installer.js.map +1 -1
  85. package/dist/service-plan.d.ts +65 -0
  86. package/dist/service-plan.js +108 -0
  87. package/dist/service-plan.js.map +1 -0
  88. package/dist/settings-api.js +46 -5
  89. package/dist/settings-api.js.map +1 -1
  90. package/dist/settings-change.js +34 -6
  91. package/dist/settings-change.js.map +1 -1
  92. package/dist/setup-host.js +2 -3
  93. package/dist/setup-host.js.map +1 -1
  94. package/dist/setup-wizard.js +2 -1
  95. package/dist/setup-wizard.js.map +1 -1
  96. package/dist/sqlite-open-errors.d.ts +12 -0
  97. package/dist/sqlite-open-errors.js +13 -0
  98. package/dist/sqlite-open-errors.js.map +1 -0
  99. package/dist/tmux-control.d.ts +22 -0
  100. package/dist/tmux-control.js +89 -5
  101. package/dist/tmux-control.js.map +1 -1
  102. package/dist/topic-commands.js +14 -5
  103. package/dist/topic-commands.js.map +1 -1
  104. package/dist/ui/chat-commands.js +43 -0
  105. package/dist/ui/chat-render.js +2 -2
  106. package/dist/ui/chat-store.js +30 -1
  107. package/dist/ui/chat-strings.js +22 -2
  108. package/dist/ui/chat-thread.js +47 -10
  109. package/dist/ui/fleet-cache.js +168 -0
  110. package/dist/ui/panel-chat.js +104 -18
  111. package/dist/ui/panel-fleet.js +26 -12
  112. package/dist/ui/panel-settings.js +12 -8
  113. package/dist/ui/preview-panel.js +202 -0
  114. package/dist/ui/preview.js +7 -3
  115. package/dist/ui/settings-strings.js +4 -2
  116. package/dist/ui/shared/app-route.js +1 -1
  117. package/dist/ui/shared/app-shell.js +6 -4
  118. package/dist/ui/shared/app.css +147 -3
  119. package/dist/ui/shared/signin.js +13 -1
  120. package/dist/ui/shared/ui-icons.js +2 -0
  121. package/dist/ui/signin.html +2 -1
  122. package/dist/update-dispatch.d.ts +28 -0
  123. package/dist/update-dispatch.js +73 -0
  124. package/dist/update-dispatch.js.map +1 -0
  125. package/dist/update-install.d.ts +65 -7
  126. package/dist/update-install.js +126 -26
  127. package/dist/update-install.js.map +1 -1
  128. package/dist/web-api.d.ts +14 -0
  129. package/dist/web-api.js +65 -0
  130. package/dist/web-api.js.map +1 -1
  131. package/dist/web-commands.d.ts +77 -0
  132. package/dist/web-commands.js +81 -0
  133. package/dist/web-commands.js.map +1 -0
  134. package/dist/web-shell-routes.d.ts +1 -1
  135. package/dist/web-shell-routes.js +1 -1
  136. package/dist/web-shell-routes.js.map +1 -1
  137. package/launcher/agend +110 -0
  138. package/launcher/agend-agent +110 -0
  139. package/launcher/agend-agent.cjs +4 -0
  140. package/launcher/agend.cjs +4 -0
  141. package/launcher/install-admission.cjs +105 -0
  142. package/launcher/launch.cjs +64 -0
  143. package/launcher/postinstall.cjs +114 -0
  144. package/launcher/run-in-process.cjs +13 -0
  145. package/launcher/runtime-platform.cjs +86 -0
  146. package/launcher/runtime-select.cjs +217 -0
  147. package/package.json +12 -4
  148. package/scripts/preinstall-guard.cjs +40 -21
  149. package/templates/launchd.plist.ejs +2 -2
  150. package/templates/systemd.service.ejs +1 -1
@@ -127,7 +127,7 @@ a { color: inherit; text-decoration: none; }
127
127
  .dot.warn { background: var(--warn); }
128
128
  .dot.bad { background: var(--danger); }
129
129
  .dot.off { background: var(--text-3); }
130
- @keyframes pulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
130
+ @keyframes pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } } /* never near-invisible on a dark surface */
131
131
 
132
132
  .scrim { display: none; }
133
133
  .main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; position: relative; }
@@ -295,6 +295,48 @@ a { color: inherit; text-decoration: none; }
295
295
  .pv-holder:empty { display: none; }
296
296
  .pv-holder { margin-top: var(--s-2); }
297
297
  .preview-frame { display: block; width: 100%; border: 2px solid var(--warn-line); border-radius: var(--r-sm); background: #ffffff; }
298
+ /* #1481: the preview beside the conversation. A split on a wide screen (width: --pv-w, set by the divider through
299
+ CSSOM), a full-screen sheet on a narrow one. The frame is the card's (preview.js), sized by the panel here. */
300
+ .chat-split { flex: 1; min-height: 0; display: flex; }
301
+ .chat-main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
302
+ .pv-divider { flex: 0 0 8px; cursor: col-resize; background: var(--bg); border-left: 1px solid var(--border); touch-action: none; position: relative; }
303
+ .pv-divider::after { content: ""; position: absolute; top: 50%; left: 2px; width: 3px; height: 32px; margin-top: -16px; border-radius: 2px; background: var(--border-strong); }
304
+ .pv-divider:hover::after, .pv-divider:focus-visible::after, .chat-split.resizing .pv-divider::after { background: var(--accent); }
305
+ .pv-divider:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
306
+ .chat-split.resizing { cursor: col-resize; user-select: none; }
307
+ .chat-split.resizing .preview-frame { pointer-events: none; } /* a frame under the pointer would end the drag */
308
+ .pv-panel { flex: 0 0 var(--pv-w, 45%); width: var(--pv-w, 45%); min-width: 320px; max-width: calc(100% - 368px); min-height: 0;
309
+ display: flex; flex-direction: column; background: var(--bg); container-type: inline-size; }
310
+ .pv-panel-head { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--border); }
311
+ .pv-panel-title { flex: 1; min-width: 0; display: flex; flex-direction: column; font-size: var(--fs-sm); }
312
+ .pv-source { all: unset; cursor: pointer; font-size: var(--fs-xs); color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
313
+ .pv-source:hover { color: var(--text); text-decoration: underline; }
314
+ .pv-source:focus-visible { outline: 2px solid var(--accent); border-radius: var(--r-sm); }
315
+ .pv-panel-bar { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-3); flex-wrap: wrap; }
316
+ .pv-panel-bar .grow { flex: 1; }
317
+ .pv-panel > .pv-banner, .pv-panel > .pv-note, .pv-newer { margin: 0 var(--s-3) var(--s-2); }
318
+ .pv-panel > .pv-note { margin-top: 0; }
319
+ .pv-newer { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; padding: var(--s-2) var(--s-3); border-radius: var(--r-sm);
320
+ background: var(--surface); font-size: var(--fs-xs); }
321
+ .pv-newer > span { flex: 1; min-width: 0; }
322
+ .pv-panel-body { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 0 var(--s-3) var(--s-3); }
323
+ .pv-panel-body .pv-holder { flex: 1; min-height: 0; margin: 0; display: flex; flex-direction: column; }
324
+ .pv-panel-body.show-code .pv-holder { display: none; }
325
+ .preview-frame.fill { flex: 1; height: 100%; min-height: 0; }
326
+ .pv-code { flex: 1; min-height: 0; margin: 0; overflow: auto; font-family: var(--font-mono); font-size: var(--fs-xs); line-height: 1.5;
327
+ background: var(--surface); border-radius: var(--r-sm); padding: var(--s-3); white-space: pre-wrap; overflow-wrap: anywhere; color: var(--text); }
328
+ .msg.flash { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--r-sm); transition: outline-color var(--t-med); }
329
+ @media (max-width: 899px) {
330
+ .only-wide { display: none; }
331
+ .pv-divider { display: none; }
332
+ .pv-panel { position: fixed; inset: 0; z-index: 37; width: auto; min-width: 0; max-width: none;
333
+ padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); }
334
+ }
335
+ /* A narrow panel (a phone, or the divider dragged right): the run controls keep their icons, titles and screen-reader
336
+ names — except Preview, which the panel's note names. */
337
+ @container (max-width: 560px) {
338
+ .pv-panel-bar :not(.pv-panel-run) > .lbl { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
339
+ }
298
340
  .pv-menu { position: relative; }
299
341
  .pv-menu > summary { list-style: none; cursor: pointer; padding: 2px var(--s-2); border-radius: var(--r-sm); color: var(--text-3); }
300
342
  .pv-menu > summary::-webkit-details-marker { display: none; }
@@ -334,6 +376,8 @@ a { color: inherit; text-decoration: none; }
334
376
  .composer textarea::placeholder { color: var(--text-3); }
335
377
  .btn-send { width: 36px; padding: 0; }
336
378
  .btn-send .send-label { display: none; }
379
+ /* A narrow phone: Stop reply beside Send leaves the message box no room — the label is for screen readers there. */
380
+ @media (max-width: 479px) { .btn-stop .stop-label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } }
337
381
  .btn-stop { background: var(--bg); }
338
382
  .hint { font-size: var(--fs-xs); color: var(--text-3); text-align: center; }
339
383
  .pending-files { display: flex; flex-wrap: wrap; gap: var(--s-2); padding: var(--s-2) var(--s-2) 0; }
@@ -401,7 +445,8 @@ a { color: inherit; text-decoration: none; }
401
445
  .dlg-box { max-height: 90vh; }
402
446
  .dlg-actions { padding-bottom: max(var(--s-5), env(safe-area-inset-bottom)); }
403
447
  .dlg-actions .btn { flex: 1; min-height: var(--tap); }
404
- .row-item .btn { min-height: var(--tap); }
448
+ .row-item .btn, .s-row .btn, .n-actions .btn { min-height: var(--tap); }
449
+ .s-row .icon-btn { min-width: var(--tap); min-height: var(--tap); }
405
450
  .kv { flex-direction: column; align-items: flex-start; gap: 2px; }
406
451
  }
407
452
  @media (prefers-reduced-motion: reduce) {
@@ -511,6 +556,11 @@ a { color: inherit; text-decoration: none; }
511
556
  .s-row.s-conn { grid-template-columns: minmax(0, 1fr) auto; }
512
557
  .s-conn .s-facts { grid-column: 1 / -1; grid-row: 2; }
513
558
  }
559
+ /* A narrow phone: the cluster (token, state, Settings) goes under the facts, so the name keeps its line (alpha.2 sweep). */
560
+ @media (max-width: 479px) {
561
+ .s-row.s-conn { grid-template-columns: minmax(0, 1fr); }
562
+ .s-conn .s-actions { grid-column: 1; grid-row: 3; flex-wrap: wrap; }
563
+ }
514
564
  .s-state { font-size: var(--fs-xs); }
515
565
  .s-state.ok { color: var(--ok); }
516
566
  .s-state.bad { color: var(--danger); }
@@ -549,6 +599,30 @@ a { color: inherit; text-decoration: none; }
549
599
  .s-yaml { font-family: var(--font-mono); font-size: var(--fs-xs); line-height: 1.5; background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-sm); padding: var(--s-3);
550
600
  overflow: auto; max-height: 60vh; white-space: pre; margin: 0; color: var(--text); }
551
601
  textarea.s-yaml { width: 100%; min-height: 50vh; resize: vertical; }
602
+ /* Lines too long for the box scroll sideways; a shadow at an edge says there is more that way (alpha.2 sweep). */
603
+ .s-yaml {
604
+ background:
605
+ linear-gradient(to right, var(--bg) 30%, transparent) left / 24px 100% no-repeat local,
606
+ linear-gradient(to left, var(--bg) 30%, transparent) right / 24px 100% no-repeat local,
607
+ radial-gradient(farthest-side at 0 50%, color-mix(in srgb, var(--text) 22%, transparent), transparent) left / 10px 100% no-repeat scroll,
608
+ radial-gradient(farthest-side at 100% 50%, color-mix(in srgb, var(--text) 22%, transparent), transparent) right / 10px 100% no-repeat scroll,
609
+ var(--bg);
610
+ }
611
+ textarea.s-yaml { white-space: pre; overflow-x: auto; }
612
+ .s-br { display: none; }
613
+ @media (max-width: 479px) {
614
+ /* Settings → Developer: one toolbar row, icons only (names stay as titles and for screen readers). */
615
+ .s-dev-bar { flex-wrap: nowrap; }
616
+ .s-dev-bar .lbl { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
617
+ /* An agent's row: name and model on the first line, its tags and buttons from the second, on every row alike. */
618
+ .s-br { display: block; flex-basis: 100%; height: 0; }
619
+ /* View: the profile line wraps to two lines instead of hiding the model and context. */
620
+ .v-card-meta { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
621
+ /* A text button in Needs you lines up with the text above it, first in its row or wrapped onto a line of its own;
622
+ a row's trailing Delete sits at the right. */
623
+ .n-actions > .btn-ghost { padding-left: 0; padding-right: 0; margin-left: -1px; }
624
+ .row-item > .btn-ghost:last-child { margin-left: auto; margin-right: calc(-1 * var(--s-3) - 1px); }
625
+ }
552
626
  .s-pending { position: sticky; bottom: 0; display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; padding: var(--s-3) var(--s-5);
553
627
  background: var(--bg); border-top: 1px solid var(--border); box-shadow: var(--shadow-2); }
554
628
  .s-pending .grow { flex: 1; }
@@ -575,6 +649,8 @@ textarea.s-yaml { width: 100%; min-height: 50vh; resize: vertical; }
575
649
  .se-default { opacity: 0.7; }
576
650
  /* #1423: requests waiting for a fleet admin, in any panel */
577
651
  .pending-stack { position: fixed; right: var(--s-4); top: calc(var(--header-h) + var(--s-2)); z-index: 40; display: flex; flex-direction: column; gap: var(--s-2); width: min(360px, calc(100vw - 32px)); }
652
+ /* In Settings the cards sit in the column, right under the operation — every width (alpha.2 sweep). */
653
+ .pending-stack.inline, .shell:has(.s-pending) .pending-stack.inline { position: static; width: auto; margin-bottom: var(--s-4); }
578
654
  .pending-card { background: var(--bg); border: 1px solid var(--border-strong); border-radius: var(--r-md); box-shadow: var(--shadow-2); padding: var(--s-3) var(--s-4); display: flex; flex-direction: column; gap: var(--s-1); font-size: var(--fs-sm); }
579
655
  .pending-card.applied { border-color: var(--ok); }
580
656
  .pending-card.rejected, .pending-card.expired, .pending-card.stale, .pending-card.failed { border-color: var(--danger-line); }
@@ -586,6 +662,8 @@ textarea.s-yaml { width: 100%; min-height: 50vh; resize: vertical; }
586
662
  .pending-actions { display: flex; justify-content: flex-end; }
587
663
  /* Settings' pending bar sits at the bottom of the page: toasts and request cards stand above it, never on its buttons. */
588
664
  .shell:has(.s-pending) .toasts { bottom: calc(var(--s-6) + 64px); }
665
+ /* In a chat the composer owns the bottom (Stop reply, Send): toasts come in under the header instead (alpha.2 sweep). */
666
+ @media (min-width: 900px) { .shell:has(.p-chat) .toasts { top: calc(var(--header-h) + var(--s-2)); bottom: auto; } }
589
667
  .op-line { display: flex; align-items: center; justify-content: center; gap: var(--s-2); }
590
668
  .op-line.ok { color: var(--ok); }
591
669
  .op-line.bad { color: var(--danger); }
@@ -672,5 +750,71 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
672
750
  }
673
751
  /* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
674
752
  @media (max-width: 479px) {
675
- .p-fleet .seg-item:not(.active) > span { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
753
+ .p-fleet .seg-item:not(.active) > span, .p-settings .seg-item:not(.active) > span { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
754
+ }
755
+
756
+ /* ── Cache analysis (#1468): Fleet → Cache. Bars are SVG attributes; nothing here is an inline style. ── */
757
+ .ch { display: flex; flex-direction: column; gap: var(--s-4); }
758
+ .ch-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-2) var(--s-4); margin-top: var(--s-2); }
759
+ .ch-scan { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); font-size: var(--fs-xs); color: var(--text-2); }
760
+ .ch-fleet { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-3); }
761
+ .ch-notes { display: flex; flex-direction: column; gap: var(--s-1); font-size: var(--fs-xs); }
762
+ .ch-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 380px), 1fr)); gap: var(--s-4); align-items: start; }
763
+ .ch-card { display: flex; flex-direction: column; gap: var(--s-3); min-width: 0; padding: var(--s-4); border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--bg); }
764
+ .ch-card.on { border-color: var(--ok); }
765
+ .ch-head { display: flex; align-items: flex-start; gap: var(--s-3); }
766
+ .ch-id { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
767
+ .ch-id h3 { font-size: var(--fs-sm); font-weight: var(--fw-semibold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
768
+ .ch-models { font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
769
+ .ch-rec { flex-shrink: 0; font-size: 11px; line-height: 20px; padding: 0 var(--s-2); border-radius: var(--r-full); border: 1px solid var(--border); color: var(--text-2); white-space: nowrap; }
770
+ .ch-rec.on { color: var(--ok); border-color: var(--ok); font-weight: var(--fw-semibold); }
771
+ .ch-reason { font-size: var(--fs-sm); color: var(--text-2); overflow-wrap: anywhere; }
772
+ .ch-tag { display: inline-block; font-size: 11px; line-height: 18px; padding: 0 var(--s-2); border-radius: var(--r-full); background: var(--surface-2); color: var(--text-2); }
773
+ .ch-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-2) var(--s-3); }
774
+ .ch-metric { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
775
+ .ch-metric dt { font-size: 11px; color: var(--text-3); line-height: var(--lh-xs); }
776
+ .ch-metric dd { margin: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; display: flex; flex-direction: column; }
777
+ .ch-sub { font-size: 11px; font-weight: var(--fw-regular); color: var(--text-3); }
778
+ .ch-fleet .ch-metric dd { font-size: var(--fs-lg); }
779
+ .ch-hist { display: block; width: 100%; height: 96px; color: var(--text-2); }
780
+ .ch-col rect { fill: var(--accent); opacity: 0.55; }
781
+ .ch-col.past rect { fill: var(--warn); opacity: 0.85; }
782
+ .ch-n, .ch-l { font-size: 9px; fill: currentColor; }
783
+ .ch-ttl line { stroke: var(--danger); stroke-width: 1.5; stroke-dasharray: 3 3; }
784
+ .ch-ttl text { font-size: 9px; fill: var(--danger); }
785
+ .ch-rest .row-item { font-size: var(--fs-xs); }
786
+ @media (max-width: 899px) {
787
+ .ch-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
788
+ .ch-fleet { grid-template-columns: minmax(0, 1fr); }
789
+ .ch-bar .btn { min-height: var(--tap); }
790
+ }
791
+
792
+ /* ── Chat commands and quick actions (#1269) ── */
793
+ .hd-chip { margin-left: var(--s-2); padding: 0 var(--s-2); border: 1px solid var(--border); border-radius: var(--r-full); background: transparent; color: var(--text-2);
794
+ font-size: var(--fs-xs); line-height: 20px; cursor: pointer; max-width: 16em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
795
+ .hd-chip:hover { background: var(--surface); color: var(--text); }
796
+ .composer-box { position: relative; }
797
+ .cmd-palette { position: absolute; left: 0; right: 0; bottom: calc(100% + var(--s-1)); z-index: 20; list-style: none; max-height: 280px; overflow-y: auto;
798
+ padding: var(--s-1); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--bg); box-shadow: var(--shadow-2); }
799
+ .cmd-palette li { display: flex; align-items: baseline; gap: var(--s-3); padding: var(--s-2) var(--s-3); border-radius: var(--r-sm); cursor: pointer; font-size: var(--fs-sm); }
800
+ .cmd-palette li.on, .cmd-palette li:hover { background: var(--surface-2); }
801
+ .cmd-palette .cmd-name { flex-shrink: 0; white-space: nowrap; }
802
+ .cmd-palette .cmd-desc { color: var(--text-3); font-size: var(--fs-xs); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
803
+ .cmd-palette .cmd-arg { color: var(--text-3); }
804
+ .cmd-palette .cmd-none { color: var(--text-3); cursor: default; }
805
+ .cmd-card { display: flex; flex-direction: column; gap: var(--s-2); margin-bottom: var(--s-2); padding: var(--s-2) var(--s-3); border-radius: var(--r-md); background: var(--surface); font-size: var(--fs-sm); }
806
+ .cmd-card.bad { border-left: 3px solid var(--danger); }
807
+ .cmd-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
808
+ .cmd-name { font-weight: var(--fw-semibold); }
809
+ .cmd-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--font-ui); color: var(--text-2); }
810
+ .cmd-choices { display: flex; flex-wrap: wrap; gap: var(--s-1); max-height: 180px; overflow-y: auto; }
811
+ .cmd-choices .btn.current { border-color: var(--accent); color: var(--accent); }
812
+ .quick-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); font-size: var(--fs-xs); color: var(--text-2); }
813
+ /* A phone: the instance's name keeps the header; /model and /effort are a "/" away. */
814
+ @media (max-width: 479px) { .hd-chip { display: none; } }
815
+ @media (max-width: 899px) {
816
+ .hd-chip { max-width: 9em; }
817
+ .cmd-palette li { min-height: var(--tap); align-items: center; flex-wrap: wrap; row-gap: 0; }
818
+ .cmd-palette .cmd-desc { white-space: normal; flex-basis: 100%; }
819
+ .cmd-choices .btn, .quick-actions .btn { min-height: var(--tap); }
676
820
  }
@@ -39,7 +39,17 @@
39
39
  history.replaceState(null, "", here.pathname + here.search + here.hash);
40
40
  }
41
41
  } catch { /* nothing to clean */ }
42
- document.querySelectorAll("[data-i18n]").forEach((el) => { el.textContent = t(el.getAttribute("data-i18n")); });
42
+ // The command in the hint is a <code> that never breaks across lines (a break inside "--code" reads as two dashes
43
+ // and a word, alpha.2 sweep); the text around it is text.
44
+ const CMD = "agend web --code";
45
+ document.querySelectorAll("[data-i18n]").forEach((el) => {
46
+ const text = t(el.getAttribute("data-i18n"));
47
+ const at = text.indexOf(CMD);
48
+ if (at < 0) { el.textContent = text; return; }
49
+ const code = document.createElement("code");
50
+ code.textContent = CMD;
51
+ el.replaceChildren(text.slice(0, at), code, text.slice(at + CMD.length));
52
+ });
43
53
 
44
54
  // An old deep link, /ui#instance=<name> (#1408 §3): the fragment never reaches the server, and the sign-in page is
45
55
  // served at the URL that was asked for, so it is still in location.hash here. Exactly that one form becomes the new
@@ -94,6 +104,8 @@
94
104
  // navigation; going round again would loop, so stop and let the person type a code.
95
105
  if (recentlyBounced()) return false;
96
106
  try {
107
+ // Signed out, this answers 401 — and the browser logs that request in red in DevTools. That is expected: the
108
+ // probe asks "is there a session?", and no is a normal answer here (decided against reshaping the auth API).
97
109
  const r = await fetch("/auth/session", { credentials: "same-origin", cache: "no-store" });
98
110
  return r.ok;
99
111
  } catch { return false; }
@@ -50,6 +50,8 @@ const P = {
50
50
  pause: () => html`<path d="M8 5v14M16 5v14"/>`,
51
51
  download: () => html`<path d="M12 3v12M7 10l5 5 5-5M5 21h14"/>`,
52
52
  org: () => html`<rect x="9" y="2" width="6" height="5" rx="1"/><rect x="2" y="17" width="6" height="5" rx="1"/><rect x="16" y="17" width="6" height="5" rx="1"/><path d="M12 7v5M5 17v-5h14v5"/>`,
53
+ panel: () => html`<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M14 4v16"/>`,
54
+ back: () => html`<path d="M19 12H5M12 19l-7-7 7-7"/>`,
53
55
  external: () => html`<path d="M14 4h6v6M20 4l-9 9"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>`,
54
56
  };
55
57
 
@@ -27,6 +27,7 @@
27
27
  button:disabled { opacity: .6; cursor: default; }
28
28
  #msg { min-height: 1.5em; margin-top: 14px; font-size: var(--fs-sm); color: var(--danger); }
29
29
  .hint { margin-top: 18px; font-size: var(--fs-xs); color: var(--text-3); }
30
+ .hint code { white-space: nowrap; font-family: var(--font-mono); }
30
31
  [hidden] { display: none !important; }
31
32
  </style>
32
33
  </head>
@@ -42,7 +43,7 @@
42
43
  autocorrect="off" spellcheck="false" maxlength="12" placeholder="XXXX-XXXX" required>
43
44
  <button id="go" type="submit" data-i18n="submit">Sign in</button>
44
45
  <div id="msg" role="alert" aria-live="polite"></div>
45
- <div class="hint" data-i18n="hint">Send /dashboard to your AgEnD bot, or run “agend web --code” on the host. A code works once and expires in 5 minutes.</div>
46
+ <div class="hint" data-i18n="hint">Send /dashboard to your AgEnD bot, or run “<code>agend web --code</code>” on the host. A code works once and expires in 5 minutes.</div>
46
47
  </form>
47
48
  <noscript>This page needs JavaScript to sign you in.</noscript>
48
49
  </main>
@@ -0,0 +1,28 @@
1
+ export interface DispatchDeps {
2
+ run(command: string, args: string[], timeoutMs: number): Promise<{
3
+ status: number | null;
4
+ stdout: string;
5
+ }>;
6
+ readFile(path: string): Promise<string>;
7
+ realpath(path: string): Promise<string>;
8
+ }
9
+ export declare const defaultDispatchDeps: DispatchDeps;
10
+ export type InstalledAgend = {
11
+ ok: true;
12
+ agend: string;
13
+ version: string;
14
+ } | {
15
+ ok: false;
16
+ reason: string;
17
+ };
18
+ /** npm's global root and prefix → the installed package, its bin target, and the bin link that must lead to it. */
19
+ export declare function resolveInstalledAgend(deps?: DispatchDeps): Promise<InstalledAgend>;
20
+ /**
21
+ * `<installed agend> update`, 2 s from now, from a detached `sh` that outlives this fleet (the update restarts it). The
22
+ * path is a positional argument, never spliced into the script.
23
+ */
24
+ export declare const DELAYED_UPDATE_SCRIPT = "sleep 2 && exec \"$1\" update";
25
+ export declare function updateCommand(agend: string): {
26
+ command: string;
27
+ args: string[];
28
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * #1450 C5: chat `/update` runs the INSTALLED `agend` — the one npm put in its global prefix, verified by identity —
3
+ * by absolute path, never whichever `agend` comes first on the fleet's PATH (a checkout, another prefix). That keeps
4
+ * the deliberate choice "update what is installed, on its own channel" (the CLI picks the channel from the version it
5
+ * replaces). No unverified fallback: when npm cannot say where it installed AgEnD, or what it says does not hold
6
+ * together, `/update` refuses and says to run `agend update` from a shell.
7
+ *
8
+ * Runs on the fleet's event loop: every step is async and bounded.
9
+ */
10
+ import { execFile } from "node:child_process";
11
+ import { readFile, realpath } from "node:fs/promises";
12
+ import { join } from "node:path";
13
+ import { promisify } from "node:util";
14
+ export const defaultDispatchDeps = {
15
+ run: async (command, args, timeoutMs) => {
16
+ try {
17
+ const { stdout } = await promisify(execFile)(command, args, { encoding: "utf8", timeout: timeoutMs });
18
+ return { status: 0, stdout: String(stdout) };
19
+ }
20
+ catch (err) {
21
+ const code = err.code;
22
+ return { status: typeof code === "number" ? code : null, stdout: "" };
23
+ }
24
+ },
25
+ readFile: path => readFile(path, "utf8"),
26
+ realpath: path => realpath(path),
27
+ };
28
+ /** npm's global root and prefix → the installed package, its bin target, and the bin link that must lead to it. */
29
+ export async function resolveInstalledAgend(deps = defaultDispatchDeps) {
30
+ const last = (text) => text.trim().split("\n").pop()?.trim() ?? "";
31
+ const [root, prefix] = await Promise.all([deps.run("npm", ["root", "-g"], 15_000), deps.run("npm", ["prefix", "-g"], 15_000)]);
32
+ if (root.status !== 0 || prefix.status !== 0 || !last(root.stdout) || !last(prefix.stdout)) {
33
+ return { ok: false, reason: "npm could not say where AgEnD is installed" };
34
+ }
35
+ const pkgDir = join(last(root.stdout), "@songsid", "agend");
36
+ const notAgend = { ok: false, reason: `${pkgDir} is not an installed AgEnD package` };
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(await deps.readFile(join(pkgDir, "package.json")));
40
+ }
41
+ catch {
42
+ return notAgend;
43
+ }
44
+ // Whatever parsed (null, an array, a string…) is judged by shape, never dereferenced blindly.
45
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
46
+ return notAgend;
47
+ const manifest = parsed;
48
+ const bin = manifest.bin;
49
+ const binRel = typeof bin === "string" ? bin
50
+ : typeof bin === "object" && bin !== null && !Array.isArray(bin) && typeof bin.agend === "string" ? bin.agend : null;
51
+ if (manifest.name !== "@songsid/agend" || typeof manifest.version !== "string" || !manifest.version || !binRel)
52
+ return notAgend;
53
+ const agend = join(last(prefix.stdout), "bin", "agend");
54
+ let target, linked;
55
+ try {
56
+ [target, linked] = await Promise.all([deps.realpath(join(pkgDir, binRel)), deps.realpath(agend)]);
57
+ }
58
+ catch {
59
+ return { ok: false, reason: `${agend} does not lead to the installed package` };
60
+ }
61
+ if (target !== linked)
62
+ return { ok: false, reason: `${agend} leads to ${linked}, not the installed package's ${target}` };
63
+ return { ok: true, agend, version: manifest.version };
64
+ }
65
+ /**
66
+ * `<installed agend> update`, 2 s from now, from a detached `sh` that outlives this fleet (the update restarts it). The
67
+ * path is a positional argument, never spliced into the script.
68
+ */
69
+ export const DELAYED_UPDATE_SCRIPT = 'sleep 2 && exec "$1" update';
70
+ export function updateCommand(agend) {
71
+ return { command: "sh", args: ["-c", DELAYED_UPDATE_SCRIPT, "sh", agend] };
72
+ }
73
+ //# sourceMappingURL=update-dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-dispatch.js","sourceRoot":"","sources":["../src/update-dispatch.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { type PackagePreimage } from "./package-preimage.js";
1
2
  export interface CommandResult {
2
3
  status: number | null;
3
4
  signal: NodeJS.Signals | null;
@@ -5,10 +6,14 @@ export interface CommandResult {
5
6
  stderr: string;
6
7
  }
7
8
  export interface CommandRunner {
8
- /** Run a program; `inherit` streams its output to the terminal (npm's progress), otherwise it is captured. */
9
+ /**
10
+ * Run a program; `inherit` streams its output to the terminal (npm's progress), otherwise it is captured. `env` is
11
+ * added to this process's environment for that one program (the install token goes to npm and nothing else).
12
+ */
9
13
  run(command: string, args: string[], options?: {
10
14
  inherit?: boolean;
11
15
  timeoutMs?: number;
16
+ env?: Record<string, string>;
12
17
  }): CommandResult;
13
18
  /** Progress for the operator, printed when it happens. */
14
19
  log(message: string): void;
@@ -22,6 +27,28 @@ export interface UpdateInstallPlan {
22
27
  viaNvm: boolean;
23
28
  /** `~/.nvm/nvm.sh`. */
24
29
  nvmSh: string;
30
+ /**
31
+ * #1450 C1: take the prefix lock for the realpath'd npm prefix this install will change, before npm runs; its token
32
+ * is handed to npm as AGEND_INSTALL_TOKEN. Omitted: no lock (tests of the order alone).
33
+ */
34
+ lock?(prefix: string): {
35
+ ok: true;
36
+ token: string;
37
+ } | {
38
+ ok: false;
39
+ reason: string;
40
+ };
41
+ /**
42
+ * #1450 C6: copy the installed package aside before npm (requires `lock`), and put it back when the new one does not
43
+ * verify. The successful outcome carries the preimage, so a later service failure can restore it too.
44
+ */
45
+ rollback?: boolean;
46
+ now?(): Date;
47
+ /**
48
+ * nvm's Node 22 bin directory, resolved ONCE (runUpdateInstall sets it): locking, installing and verifying then run
49
+ * on that exact selection, never on a fresh `nvm use 22` that could pick another Node 22 in between (#1472 review).
50
+ */
51
+ nvmBin?: string;
25
52
  }
26
53
  export type UpdateInstallOutcome = {
27
54
  ok: true;
@@ -29,10 +56,17 @@ export type UpdateInstallOutcome = {
29
56
  version: string;
30
57
  dir: string;
31
58
  bin: string;
59
+ entry: string;
32
60
  node: string;
61
+ rollback?: {
62
+ root: string;
63
+ prefix: string;
64
+ preimage: PackagePreimage | null;
65
+ }; /** nvm transition: retire the old system copy once activation settles. */
66
+ retireSystemCopy?: true;
33
67
  } | {
34
68
  ok: false;
35
- stage: "install" | "verify";
69
+ stage: "lock" | "install" | "verify";
36
70
  message: string;
37
71
  };
38
72
  /**
@@ -42,8 +76,12 @@ export type UpdateInstallOutcome = {
42
76
  * arguments or switching to its default before `nvm use 22`.
43
77
  */
44
78
  export declare const NVM_RUN_SCRIPT = "n=$1; shift; . \"$n\" --no-use >/dev/null 2>&1 && nvm use 22 >/dev/null 2>&1 && \"$@\"";
45
- /** The install itself, also with every path and spec as data: `$1` = nvm.sh, `$2` = the npm spec. */
46
- export declare const NVM_INSTALL_SCRIPT = "n=$1; p=$2; . \"$n\" --no-use && nvm install 22 && nvm use 22 && npm install -g \"$p\"";
79
+ /** Getting nvm's Node 22, before its prefix can be locked and installed into: `$1` = nvm.sh, as data. */
80
+ export declare const NVM_PREPARE_SCRIPT = "n=$1; . \"$n\" --no-use && nvm install 22";
81
+ /** Which Node `nvm use 22` selects: its absolute path, printed once. `$1` = nvm.sh, as data. */
82
+ export declare const NVM_SELECT_SCRIPT = "n=$1; . \"$n\" --no-use >/dev/null 2>&1 && nvm use 22 >/dev/null 2>&1 && command -v node";
83
+ /** Run the remaining arguments with a frozen nvm bin directory first on PATH (`$1`, as data). */
84
+ export declare const NVM_BIN_SCRIPT = "d=$1; shift; PATH=\"$d:$PATH\"; export PATH; exec \"$@\"";
47
85
  /**
48
86
  * How to run the NEW `agend` after the install: directly, or — for an nvm install — inside nvm's Node 22, because its
49
87
  * `#!/usr/bin/env node` would otherwise pick the parent's (old) Node from PATH (#1446 item 1). Append the subcommand.
@@ -55,11 +93,15 @@ export declare function newAgendInvocation(plan: Pick<UpdateInstallPlan, "viaNvm
55
93
  /** `npm link` leaves a symlink to a source checkout outside any node_modules tree; a registry install is a directory. */
56
94
  export declare function isLocalLinkTarget(entryIsSymlink: boolean, realTarget: string): boolean;
57
95
  /**
58
- * The identity of what npm installed: `$1` is the package directory in npm's global root. Prints its name, version and
59
- * the REAL path of its `agend` bin target, so PATH's `agend` can be held to that exact file.
96
+ * The identity of what npm installed: `$1` is the package directory in npm's global root. Prints its name, version, the
97
+ * REAL path of its `agend` bin target, so PATH's `agend` can be held to that exact file, and of its canonical inner
98
+ * entry `dist/cli.js` (#1450 C4: what a service starts; since the launcher, not the bin).
60
99
  */
61
100
  export declare const PACKAGE_IDENTITY_SCRIPT: string;
62
- /** Proves the native module loads AND works: on Node 20 better-sqlite3 13 imports fine and then SIGSEGVs on open. */
101
+ /**
102
+ * Proves the native module loads AND works, in the main thread and in a worker (the fleet opens databases in both):
103
+ * on Node 20 better-sqlite3 13 imports fine and then SIGSEGVs on open. `$1` is the package directory.
104
+ */
63
105
  export declare const NATIVE_CHECK_SCRIPT: string;
64
106
  /**
65
107
  * Prove the package npm installed — read from npm's own global root and prefix in the install environment — is the
@@ -69,3 +111,19 @@ export declare const NATIVE_CHECK_SCRIPT: string;
69
111
  */
70
112
  export declare function verifyInstalledPackage(plan: UpdateInstallPlan, runner: CommandRunner): UpdateInstallOutcome;
71
113
  export declare function runUpdateInstall(plan: UpdateInstallPlan, runner: CommandRunner): UpdateInstallOutcome;
114
+ /**
115
+ * Has the activation POSITIVELY settled on the new install — the moment the old system copy may go? launchd: the new
116
+ * job was proven running. Otherwise only a restart that reported "restarted": "pending" (systemd still running the
117
+ * job) and "failed" keep the old copy, as does any failed activation (#1473 review).
118
+ */
119
+ export declare function activationSettled(outcome: {
120
+ ok: true;
121
+ via: "restart" | "launchd-activation";
122
+ } | {
123
+ ok: false;
124
+ }, restart: "restarted" | "pending" | "failed" | null): boolean;
125
+ /**
126
+ * After an nvm transition's activation SETTLED (the fleet runs the new install): remove the old system copy. Best
127
+ * effort, and never waits for a password. Before that point the old copy is the rollback for the old service.
128
+ */
129
+ export declare function retireSystemCopy(runner: CommandRunner): void;