@songsid/agend 2.3.0-beta.11 → 2.3.0-beta.13

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 (111) hide show
  1. package/README.md +6 -0
  2. package/README.zh-TW.md +6 -0
  3. package/dist/agent-whats-new.js +14 -9
  4. package/dist/agent-whats-new.js.map +1 -1
  5. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  6. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  7. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  8. package/dist/channel/adapters/discord.d.ts +2 -0
  9. package/dist/channel/adapters/discord.js +14 -4
  10. package/dist/channel/adapters/discord.js.map +1 -1
  11. package/dist/cli.js +12 -79
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts +12 -0
  14. package/dist/config.js +13 -4
  15. package/dist/config.js.map +1 -1
  16. package/dist/daemon.d.ts +3 -0
  17. package/dist/daemon.js +25 -0
  18. package/dist/daemon.js.map +1 -1
  19. package/dist/fleet-manager.d.ts +44 -2
  20. package/dist/fleet-manager.js +229 -63
  21. package/dist/fleet-manager.js.map +1 -1
  22. package/dist/fleet-process-handlers.d.ts +18 -0
  23. package/dist/fleet-process-handlers.js +69 -0
  24. package/dist/fleet-process-handlers.js.map +1 -0
  25. package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
  26. package/dist/general-knowledge/skills/web-appearance/SKILL.md +20 -3
  27. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +27 -5
  28. package/dist/instance-lifecycle.d.ts +2 -0
  29. package/dist/instance-lifecycle.js +11 -6
  30. package/dist/instance-lifecycle.js.map +1 -1
  31. package/dist/outbound-schemas.d.ts +14 -1
  32. package/dist/outbound-schemas.js +8 -2
  33. package/dist/outbound-schemas.js.map +1 -1
  34. package/dist/public-web-gateway.js +4 -0
  35. package/dist/public-web-gateway.js.map +1 -1
  36. package/dist/restart-progress.d.ts +16 -1
  37. package/dist/restart-progress.js +74 -15
  38. package/dist/restart-progress.js.map +1 -1
  39. package/dist/settings-api.d.ts +4 -0
  40. package/dist/settings-api.js +27 -1
  41. package/dist/settings-api.js.map +1 -1
  42. package/dist/settings-confirmation.d.ts +3 -1
  43. package/dist/settings-confirmation.js +2 -0
  44. package/dist/settings-confirmation.js.map +1 -1
  45. package/dist/settings-effect.js +2 -1
  46. package/dist/settings-effect.js.map +1 -1
  47. package/dist/settings-http-confirmation.d.ts +11 -0
  48. package/dist/settings-http-confirmation.js +10 -0
  49. package/dist/settings-http-confirmation.js.map +1 -1
  50. package/dist/ui/app-effects.js +13 -1
  51. package/dist/ui/chat-effects.js +10 -3
  52. package/dist/ui/chat-store.js +32 -10
  53. package/dist/ui/chat-strings.js +18 -0
  54. package/dist/ui/chat-thread.js +5 -2
  55. package/dist/ui/effects-waker.js +70 -0
  56. package/dist/ui/fleet-canvas-layout.js +10 -4
  57. package/dist/ui/fleet-canvas.js +32 -2
  58. package/dist/ui/fleet-route-scene.js +207 -0
  59. package/dist/ui/fleet-route-worker.js +21 -0
  60. package/dist/ui/fleet-route.js +88 -0
  61. package/dist/ui/fleet-traffic.js +27 -11
  62. package/dist/ui/fleet-usage.js +35 -1
  63. package/dist/ui/panel-chat.js +67 -26
  64. package/dist/ui/panel-fleet.js +3 -3
  65. package/dist/ui/panel-settings.js +24 -0
  66. package/dist/ui/settings-appearance.js +2 -1
  67. package/dist/ui/settings-apply.js +3 -3
  68. package/dist/ui/settings-confirm.js +7 -2
  69. package/dist/ui/settings-owner-effects.js +6 -4
  70. package/dist/ui/settings-strings.js +2 -0
  71. package/dist/ui/shared/app-i18n.js +4 -0
  72. package/dist/ui/shared/app-prefs.js +12 -0
  73. package/dist/ui/shared/app-shell.js +38 -2
  74. package/dist/ui/shared/app-themes.js +116 -1
  75. package/dist/ui/shared/app.css +47 -3
  76. package/dist/ui/shared/card-strings.js +4 -0
  77. package/dist/ui/shared/effect-owner-source.js +8 -1
  78. package/dist/ui/shared/effect-registry.js +10 -2
  79. package/dist/ui/shared/identity-card.js +24 -6
  80. package/dist/ui/shared/identity-color.js +11 -7
  81. package/dist/ui/shared/owner-css.js +30 -1
  82. package/dist/ui/shared/ui-icons.js +2 -0
  83. package/dist/ui/shared/ui-menu.js +9 -3
  84. package/dist/ui/shared/web-authorize.js +47 -0
  85. package/dist/web-api.d.ts +6 -2
  86. package/dist/web-api.js +68 -15
  87. package/dist/web-api.js.map +1 -1
  88. package/dist/web-appearance.js +3 -1
  89. package/dist/web-appearance.js.map +1 -1
  90. package/dist/web-effect.d.ts +1 -1
  91. package/dist/web-host-guard.d.ts +11 -0
  92. package/dist/web-host-guard.js +11 -0
  93. package/dist/web-host-guard.js.map +1 -1
  94. package/dist/web-libavoid.d.ts +14 -0
  95. package/dist/web-libavoid.js +46 -0
  96. package/dist/web-libavoid.js.map +1 -0
  97. package/dist/web-owner-css-store.d.ts +44 -1
  98. package/dist/web-owner-css-store.js +70 -9
  99. package/dist/web-owner-css-store.js.map +1 -1
  100. package/dist/web-prefs.js +13 -0
  101. package/dist/web-prefs.js.map +1 -1
  102. package/dist/web-settings-tools.d.ts +5 -0
  103. package/dist/web-settings-tools.js +38 -7
  104. package/dist/web-settings-tools.js.map +1 -1
  105. package/dist/web-theme.d.ts +29 -0
  106. package/dist/web-theme.js +89 -1
  107. package/dist/web-theme.js.map +1 -1
  108. package/dist/yaml-shared-nodes.d.ts +25 -0
  109. package/dist/yaml-shared-nodes.js +185 -0
  110. package/dist/yaml-shared-nodes.js.map +1 -0
  111. package/package.json +2 -1
package/dist/web-api.js CHANGED
@@ -21,7 +21,8 @@ import { formatWebChannelEcho } from "./web-channel-echo.js";
21
21
  import { t } from "./locale.js";
22
22
  import { isSafeInstanceName } from "./web-shell-routes.js";
23
23
  import { MAX_COMMAND_ARGS } from "./web-commands.js";
24
- import { sendPanelHtml } from "./web-host-guard.js";
24
+ import { ROUTE_WORKER_CSP, ROUTE_WORKER_PATH, sendPanelHtml } from "./web-host-guard.js";
25
+ import { libavoidFile } from "./web-libavoid.js";
25
26
  import { shellRoute } from "./web-shell-routes.js";
26
27
  import { createReadStream, createWriteStream, readFileSync } from "node:fs";
27
28
  import { join, dirname } from "node:path";
@@ -280,7 +281,7 @@ function handleOwnerCss(req, res, url, ctx, path, method) {
280
281
  json(res, removed.ok ? 200 : removed.status, removed.ok ? { ok: true } : { error: removed.error });
281
282
  return true;
282
283
  }
283
- if (method !== "POST" || !["/ui/owner-css", "/ui/owner-css/keep", "/ui/owner-css/revert", "/ui/owner-css/restore", "/ui/owner-css/asset"].includes(path)) {
284
+ if (method !== "POST" || !["/ui/owner-css", "/ui/owner-css/shown", "/ui/owner-css/keep", "/ui/owner-css/revert", "/ui/owner-css/restore", "/ui/owner-css/asset"].includes(path)) {
284
285
  json(res, 404, { error: "not found" });
285
286
  return true;
286
287
  }
@@ -329,6 +330,17 @@ function handleOwnerCss(req, res, url, ctx, path, method) {
329
330
  announce();
330
331
  json(res, 200, { sha: saved.sha, previous: saved.previous, trial: saved.trial });
331
332
  }
333
+ else if (path === "/ui/owner-css/shown") {
334
+ // #2198: this page shows the trial (its stylesheet loaded): an agent's waiting trial starts its keep window now.
335
+ const shown = store.shown(person, body.trial);
336
+ if (!shown.ok) {
337
+ json(res, shown.status, { error: shown.error });
338
+ return;
339
+ }
340
+ if (shown.started)
341
+ announce(); // the person's other devices take the new deadline
342
+ json(res, 200, { remaining_ms: shown.remaining_ms });
343
+ }
332
344
  else if (path === "/ui/owner-css/keep") {
333
345
  const kept = store.keep(person, body.trial);
334
346
  if (!kept.ok) {
@@ -1010,6 +1022,9 @@ export function handleWebRequest(req, res, url, ctx) {
1010
1022
  try {
1011
1023
  const js = readFileSync(join(__dirname, "ui", fileName), "utf-8");
1012
1024
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
1025
+ // #2182: the route worker's own, narrower-reaching policy (ROUTE_WORKER_CSP); every other file keeps the page's.
1026
+ if (path === ROUTE_WORKER_PATH)
1027
+ res.setHeader("Content-Security-Policy", ROUTE_WORKER_CSP);
1013
1028
  res.writeHead(200);
1014
1029
  res.end(js);
1015
1030
  }
@@ -1018,6 +1033,19 @@ export function handleWebRequest(req, res, url, ctx) {
1018
1033
  }
1019
1034
  return true;
1020
1035
  }
1036
+ // #2182: libavoid (libavoid-js, LGPL-2.1-or-later) — the npm package's own two files, unmodified, at a versioned path.
1037
+ const vendor = method === "GET" ? libavoidFile(path) : null;
1038
+ if (vendor) {
1039
+ try {
1040
+ const body = readFileSync(vendor.file);
1041
+ res.writeHead(200, { "Content-Type": vendor.type, "Cache-Control": "private, max-age=31536000, immutable" });
1042
+ res.end(body);
1043
+ }
1044
+ catch {
1045
+ json(res, 404, { error: "File not found" });
1046
+ }
1047
+ return true;
1048
+ }
1021
1049
  // ── Backend detection ─────────────────────────────────
1022
1050
  // #2072: the model a Settings field offers — the cached catalog of that backend (that instance's account's). Read only.
1023
1051
  if (method === "GET" && path === "/ui/models") {
@@ -1908,7 +1936,7 @@ export function handleWebRequest(req, res, url, ctx) {
1908
1936
  return true;
1909
1937
  }
1910
1938
  // Own keys only: "constructor" is in every object and names no instance.
1911
- const known = (ctx.fleetConfig ? Object.hasOwn(ctx.fleetConfig.instances, name) : false) || ctx.daemons.has(name);
1939
+ const known = registeredChatInstance(ctx, name) || ctx.daemons.has(name);
1912
1940
  if (!ctx.cancelInstance || !known) {
1913
1941
  json(res, 404, { error: `Instance not found: ${name}` });
1914
1942
  return true;
@@ -2578,6 +2606,27 @@ function webEchoPreview(message, names) {
2578
2606
  const truncated = message.length > 500 || preview.length > 1400;
2579
2607
  return wellFormed(preview.slice(0, 1400)) + (truncated ? ` … (${t("web.echo_full_text")})` : "");
2580
2608
  }
2609
+ /** Registration and liveness are different: a paused CLI may have neither daemon nor IPC. */
2610
+ function registeredChatInstance(ctx, name) {
2611
+ return (!!ctx.fleetConfig && Object.hasOwn(ctx.fleetConfig.instances, name)) || ctx.isClassicInstance?.(name) === true;
2612
+ }
2613
+ function uploadRecipient(ctx, name) {
2614
+ return registeredChatInstance(ctx, name)
2615
+ && (ctx.instanceIpcClients.has(name) || ctx.getInstanceStatus?.(name) === "paused");
2616
+ }
2617
+ /** Closed, path-free explanations; arbitrary launch errors stay in the host log. */
2618
+ function webDeliveryFailure(err) {
2619
+ const message = err instanceof Error ? err.message : "";
2620
+ if (/No warm slot is free/.test(message))
2621
+ return { reason: "wake_capacity", error: "No slot is free to wake this instance. Retry when another instance is idle." };
2622
+ if (/login|log in|sign in|\bauth(?:entication|orization)?\b|credential|api.?key/i.test(message))
2623
+ return { reason: "wake_credentials", error: "The instance could not sign in. Check its account on the host, then retry." };
2624
+ if (/timed? out|timeout/i.test(message))
2625
+ return { reason: "wake_timeout", error: "The instance did not become ready in time. Retry, or check its status on the host." };
2626
+ if (/Cannot wake stopped|no longer configured/.test(message))
2627
+ return { reason: "instance_stopped", error: "This instance was stopped or removed. Start it on the host before retrying." };
2628
+ return { reason: "delivery_unavailable", error: "The instance could not receive this message. Check its status on the host, then retry." };
2629
+ }
2581
2630
  /** Handle POST /ui/send — extracted for readability. */
2582
2631
  function handleSendMessage(req, res, ctx) {
2583
2632
  const arrival = webAuthorityOf(req, new URL(req.url ?? "/", "http://localhost"), ctx); // #1939 §3: again after the body
@@ -2615,8 +2664,8 @@ function handleSendMessage(req, res, ctx) {
2615
2664
  json(res, 403, { error: "guest_no_commands" });
2616
2665
  return;
2617
2666
  }
2618
- const ipc = ctx.instanceIpcClients.get(instance);
2619
- if (!ipc) {
2667
+ const registered = registeredChatInstance(ctx, instance);
2668
+ if (!registered && !ctx.instanceIpcClients.has(instance)) {
2620
2669
  json(res, 404, { error: `Instance not found: ${instance}` });
2621
2670
  return;
2622
2671
  }
@@ -2639,6 +2688,12 @@ function handleSendMessage(req, res, ctx) {
2639
2688
  json(res, 403, { error: t("web.raw_public_refused") });
2640
2689
  return;
2641
2690
  }
2691
+ // Recheck the same admission all the way through a wake, before the facade's actual IPC handoff.
2692
+ const requestUrl = new URL(req.url ?? "/", "http://localhost");
2693
+ const admission = admissionOf(req, requestUrl, ctx);
2694
+ const stillCurrent = () => !res.destroyed && admission !== null && admissionOf(req, requestUrl, ctx) === admission
2695
+ && (!guest || (guest.current() && !guestChatRefusal(guest, instance)))
2696
+ && (registered ? registeredChatInstance(ctx, instance) : ctx.instanceIpcClients.has(instance));
2642
2697
  const message = typed;
2643
2698
  // #1827: who is writing — the name agents and chats see, and the id they tell people apart by. Asked now that the
2644
2699
  // body is in: a session revoked meanwhile sends nothing (not as the operator).
@@ -2685,8 +2740,8 @@ function handleSendMessage(req, res, ctx) {
2685
2740
  let delivered;
2686
2741
  try {
2687
2742
  const deliver = (name, payload) => ctx.deliverHumanToInstance
2688
- ? ctx.deliverHumanToInstance(name, payload, webHumanOrigin(false, authority), typed)
2689
- : ctx.deliverToInstance(name, payload);
2743
+ ? ctx.deliverHumanToInstance(name, payload, webHumanOrigin(false, authority), typed, { stillCurrent })
2744
+ : ctx.deliverToInstance(name, payload, { stillCurrent });
2690
2745
  delivered = await deliver(instance, {
2691
2746
  type: "fleet_inbound",
2692
2747
  content: delivery.text,
@@ -2712,7 +2767,7 @@ function handleSendMessage(req, res, ctx) {
2712
2767
  settleClassicEcho?.(false);
2713
2768
  ctx.webFiles?.release(files); // not delivered: the same ids can be sent again
2714
2769
  ctx.logger.error({ err, instance }, "Web message delivery failed");
2715
- json(res, 503, { error: "Instance delivery failed" });
2770
+ json(res, 503, webDeliveryFailure(err));
2716
2771
  return;
2717
2772
  }
2718
2773
  if (delivered === false) {
@@ -2720,7 +2775,7 @@ function handleSendMessage(req, res, ctx) {
2720
2775
  settleClassicEcho?.(false);
2721
2776
  ctx.webFiles?.release(files);
2722
2777
  ctx.logger.error({ instance }, "Web message not delivered (the instance went away or restarted)");
2723
- json(res, 503, { error: "Instance delivery failed" });
2778
+ json(res, 503, { reason: "delivery_changed", error: "The session or instance changed before this message was sent. Sign in again if needed, then retry." });
2724
2779
  return;
2725
2780
  }
2726
2781
  settleEcho?.(true);
@@ -2767,7 +2822,7 @@ function handleUpload(req, res, url, ctx) {
2767
2822
  json(res, guestRefused.status, { error: guestRefused.error });
2768
2823
  return;
2769
2824
  }
2770
- if (!instance || instance.length > 128 || !safeInstanceSegment(instance) || !ctx.instanceIpcClients.has(instance)) {
2825
+ if (!instance || instance.length > 128 || !safeInstanceSegment(instance) || !uploadRecipient(ctx, instance)) {
2771
2826
  json(res, 404, { error: `Instance not found: ${instance}` });
2772
2827
  return;
2773
2828
  }
@@ -2830,11 +2885,9 @@ function handleUpload(req, res, url, ctx) {
2830
2885
  req.on("error", () => drop()); // the client went away
2831
2886
  req.on("close", () => { if (!over && req.complete === false)
2832
2887
  drop(); });
2833
- // The instance is checked again now the whole body is here: it may have been deleted (and its files
2834
- // forgotten) or stopped while the upload streamed in; storing now would bring its id back (#1252 review).
2835
- // Still deliverable means: its IPC is up (a paused one is woken by the send) and it is still registered —
2836
- // in fleet.yaml, or as a ClassicBot room in classicBot.yaml. Asked when the body ends and again just before storing.
2837
- const deliverable = () => ctx.instanceIpcClients.has(instance) && (!!ctx.fleetConfig?.instances?.[instance] || ctx.isClassicInstance?.(instance) === true);
2888
+ // Recheck registration and delivery readiness after both waits. A paused recipient may have no IPC;
2889
+ // staging a file never wakes it. A stopped or deleted recipient must not regain an upload id.
2890
+ const deliverable = () => uploadRecipient(ctx, instance);
2838
2891
  const gone = () => drop(() => json(res, 404, { error: `Instance not found: ${instance}` }));
2839
2892
  req.on("end", () => {
2840
2893
  if (over)