@songsid/agend 2.3.1 → 2.3.2-beta.1

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 (159) hide show
  1. package/dist/authority/authority-state.d.ts +4 -0
  2. package/dist/authority/authority-state.js +16 -10
  3. package/dist/authority/authority-state.js.map +1 -1
  4. package/dist/authority/session-authorizations.d.ts +7 -1
  5. package/dist/authority/session-authorizations.js +6 -4
  6. package/dist/authority/session-authorizations.js.map +1 -1
  7. package/dist/authority/web-authority.d.ts +19 -6
  8. package/dist/authority/web-authority.js +22 -8
  9. package/dist/authority/web-authority.js.map +1 -1
  10. package/dist/channel/adapters/discord.d.ts +2 -0
  11. package/dist/channel/adapters/discord.js +33 -3
  12. package/dist/channel/adapters/discord.js.map +1 -1
  13. package/dist/channel/mcp-tools.js +5 -5
  14. package/dist/channel/mcp-tools.js.map +1 -1
  15. package/dist/channel/types.d.ts +11 -0
  16. package/dist/config-validator.js +40 -3
  17. package/dist/config-validator.js.map +1 -1
  18. package/dist/daemon.d.ts +34 -3
  19. package/dist/daemon.js +227 -46
  20. package/dist/daemon.js.map +1 -1
  21. package/dist/delivery-outbox.d.ts +12 -1
  22. package/dist/delivery-outbox.js +67 -15
  23. package/dist/delivery-outbox.js.map +1 -1
  24. package/dist/doctor.d.ts +3 -0
  25. package/dist/doctor.js +9 -0
  26. package/dist/doctor.js.map +1 -1
  27. package/dist/event-log.d.ts +5 -0
  28. package/dist/event-log.js +13 -0
  29. package/dist/event-log.js.map +1 -1
  30. package/dist/fleet-manager.d.ts +140 -0
  31. package/dist/fleet-manager.js +414 -29
  32. package/dist/fleet-manager.js.map +1 -1
  33. package/dist/fleet-process-handlers.d.ts +1 -0
  34. package/dist/fleet-process-handlers.js +9 -1
  35. package/dist/fleet-process-handlers.js.map +1 -1
  36. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +4 -4
  37. package/dist/general-knowledge/skills/web-appearance/SKILL.md +7 -7
  38. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +106 -55
  39. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +107 -56
  40. package/dist/guest-seen-people.d.ts +38 -0
  41. package/dist/guest-seen-people.js +78 -0
  42. package/dist/guest-seen-people.js.map +1 -0
  43. package/dist/instance-config-impact.js +4 -1
  44. package/dist/instance-config-impact.js.map +1 -1
  45. package/dist/instance-lifecycle.d.ts +1 -0
  46. package/dist/instance-lifecycle.js +16 -1
  47. package/dist/instance-lifecycle.js.map +1 -1
  48. package/dist/locale.js +38 -10
  49. package/dist/locale.js.map +1 -1
  50. package/dist/mac-auto-restore.d.ts +117 -0
  51. package/dist/mac-auto-restore.js +294 -0
  52. package/dist/mac-auto-restore.js.map +1 -0
  53. package/dist/outbound-schemas.d.ts +2 -0
  54. package/dist/outbound-schemas.js +2 -0
  55. package/dist/outbound-schemas.js.map +1 -1
  56. package/dist/public-web-gateway.js +9 -1
  57. package/dist/public-web-gateway.js.map +1 -1
  58. package/dist/settings-api.d.ts +26 -0
  59. package/dist/settings-api.js +107 -1
  60. package/dist/settings-api.js.map +1 -1
  61. package/dist/settings-change.js +37 -3
  62. package/dist/settings-change.js.map +1 -1
  63. package/dist/settings-effect.js +5 -2
  64. package/dist/settings-effect.js.map +1 -1
  65. package/dist/start-attempt.d.ts +9 -5
  66. package/dist/start-attempt.js +27 -8
  67. package/dist/start-attempt.js.map +1 -1
  68. package/dist/start-failure-chat.d.ts +4 -0
  69. package/dist/start-failure-chat.js +23 -6
  70. package/dist/start-failure-chat.js.map +1 -1
  71. package/dist/start-failure-notice.d.ts +4 -0
  72. package/dist/start-failure-notice.js +8 -0
  73. package/dist/start-failure-notice.js.map +1 -1
  74. package/dist/start-failure.d.ts +25 -0
  75. package/dist/start-failure.js +75 -2
  76. package/dist/start-failure.js.map +1 -1
  77. package/dist/turn-reply-guard.d.ts +25 -0
  78. package/dist/turn-reply-guard.js +111 -12
  79. package/dist/turn-reply-guard.js.map +1 -1
  80. package/dist/types.d.ts +18 -1
  81. package/dist/ui/backend-signin.js +5 -2
  82. package/dist/ui/chat-ambient.js +1 -1
  83. package/dist/ui/chat-effects.js +4 -4
  84. package/dist/ui/chat-render.js +5 -2
  85. package/dist/ui/chat-strings.js +2 -2
  86. package/dist/ui/chat-thread.js +2 -2
  87. package/dist/ui/first-run.js +2 -2
  88. package/dist/ui/fleet-canvas-view.js +114 -0
  89. package/dist/ui/fleet-canvas.js +15 -77
  90. package/dist/ui/fleet-office-editor.js +80 -5
  91. package/dist/ui/fleet-office-layout.js +197 -0
  92. package/dist/ui/fleet-office-model.js +6 -3
  93. package/dist/ui/fleet-office-pack.d.ts +39 -0
  94. package/dist/ui/fleet-office-pack.js +240 -29
  95. package/dist/ui/fleet-office-skin.js +130 -21
  96. package/dist/ui/fleet-office-store.js +75 -4
  97. package/dist/ui/fleet-office-view.js +154 -0
  98. package/dist/ui/fleet-office-zones.js +95 -33
  99. package/dist/ui/fleet-office.js +530 -94
  100. package/dist/ui/fleet-usage.js +7 -6
  101. package/dist/ui/panel-chat.js +1 -1
  102. package/dist/ui/panel-details.js +2 -2
  103. package/dist/ui/panel-fleet.js +11 -77
  104. package/dist/ui/panel-log.js +2 -2
  105. package/dist/ui/panel-settings.js +578 -163
  106. package/dist/ui/search-dialog.js +26 -7
  107. package/dist/ui/settings-appearance.js +82 -29
  108. package/dist/ui/settings-apply.js +42 -7
  109. package/dist/ui/settings-dialogs.js +68 -22
  110. package/dist/ui/settings-draft.js +51 -0
  111. package/dist/ui/settings-guests.js +244 -78
  112. package/dist/ui/settings-learn.js +2 -2
  113. package/dist/ui/settings-model.js +2 -0
  114. package/dist/ui/settings-registry.js +142 -0
  115. package/dist/ui/settings-strings.js +102 -44
  116. package/dist/ui/settings-web-authority.js +34 -23
  117. package/dist/ui/shared/app-i18n.js +14 -0
  118. package/dist/ui/shared/app-nav.js +10 -5
  119. package/dist/ui/shared/app-prefs.js +80 -2
  120. package/dist/ui/shared/app-route.js +39 -5
  121. package/dist/ui/shared/app-shell.js +59 -4
  122. package/dist/ui/shared/app-stream.js +2 -1
  123. package/dist/ui/shared/app.css +242 -17
  124. package/dist/ui/shared/app.js +3 -0
  125. package/dist/ui/shared/effect-stock-trust.js +1 -1
  126. package/dist/ui/shared/identity-color.js +4 -4
  127. package/dist/ui/shared/signin.js +2 -2
  128. package/dist/ui/shared/ui-dialog.js +6 -1
  129. package/dist/ui/shared/ui-icons.js +2 -0
  130. package/dist/ui/shared/web-authorize.js +13 -12
  131. package/dist/web-api.d.ts +8 -2
  132. package/dist/web-api.js +160 -5
  133. package/dist/web-api.js.map +1 -1
  134. package/dist/web-auth.js +7 -1
  135. package/dist/web-auth.js.map +1 -1
  136. package/dist/web-experimental.d.ts +1 -1
  137. package/dist/web-experimental.js +1 -1
  138. package/dist/web-experimental.js.map +1 -1
  139. package/dist/web-mod-agent.js +2 -2
  140. package/dist/web-mod-agent.js.map +1 -1
  141. package/dist/web-office-skins.d.ts +108 -0
  142. package/dist/web-office-skins.js +298 -0
  143. package/dist/web-office-skins.js.map +1 -0
  144. package/dist/web-office-store.d.ts +8 -2
  145. package/dist/web-office-store.js +37 -7
  146. package/dist/web-office-store.js.map +1 -1
  147. package/dist/web-settings-patch.d.ts +6 -0
  148. package/dist/web-settings-patch.js +61 -4
  149. package/dist/web-settings-patch.js.map +1 -1
  150. package/dist/web-shell-routes.d.ts +4 -2
  151. package/dist/web-shell-routes.js +17 -7
  152. package/dist/web-shell-routes.js.map +1 -1
  153. package/dist/working-recovery.d.ts +104 -0
  154. package/dist/working-recovery.js +263 -0
  155. package/dist/working-recovery.js.map +1 -0
  156. package/dist/working-state-journal.d.ts +3 -1
  157. package/dist/working-state-journal.js +15 -2
  158. package/dist/working-state-journal.js.map +1 -1
  159. package/package.json +1 -1
package/dist/web-api.d.ts CHANGED
@@ -9,6 +9,7 @@ import type { ModStore } from "./web-mod-store.js";
9
9
  import type { WebEffectService } from "./web-effect-service.js";
10
10
  import type { ThemeStore } from "./web-theme-store.js";
11
11
  import type { OfficeStore, OfficeWriteLimiter } from "./web-office-store.js";
12
+ import { type OfficeSkinStore } from "./web-office-skins.js";
12
13
  import type { WebAppearance } from "./web-appearance.js";
13
14
  import type { SendOpts } from "./channel/types.js";
14
15
  import type { IncomingMessage, ServerResponse } from "node:http";
@@ -195,6 +196,10 @@ export interface WebApiContext {
195
196
  readonly officeStore?: OfficeStore;
196
197
  /** How often a person may save the offices (absent: no limit, as in a test without it). */
197
198
  readonly officeWrites?: OfficeWriteLimiter;
199
+ /** #2513: the Office view's uploaded skin packs (web-office-skins/); absent: /ui/office-skins is not offered. */
200
+ readonly officeSkins?: OfficeSkinStore;
201
+ /** How often a person may upload or delete a skin pack (absent: no limit). */
202
+ readonly officeSkinWrites?: OfficeWriteLimiter;
198
203
  readonly webAppearance?: WebAppearance;
199
204
  /** #1629 r4-5: installed mods and a person's staged import. */
200
205
  readonly mods?: ModStore;
@@ -402,8 +407,9 @@ export interface WebPerson {
402
407
  readonly platformName?: string;
403
408
  }
404
409
  /**
405
- * #1939 §3.4 / #2453 §0.2: where this request came from — the admin's named link by the gateway's own provenance (its
406
- * exact hostname), else what the TCP peer and Host say (a quick tunnel's request is `lan`, and refused as public_link).
410
+ * #1939 §3.4 / #2453 §0.2 / §0.3: where this request came from — over the public gateway, the link itself by the
411
+ * gateway's own provenance (never a header): the admin's named tunnel on its exact hostname, else the temporary
412
+ * quick-tunnel link. Off the gateway, what the TCP peer and Host say.
407
413
  */
408
414
  export declare function webPeerOf(req: IncomingMessage): WebPeer;
409
415
  export declare function handleWebRequest(req: IncomingMessage, res: ServerResponse, url: URL, ctx: WebApiContext): boolean;
package/dist/web-api.js CHANGED
@@ -15,6 +15,7 @@ import { AGENT_WIDGET_SLOTS, checkWidgetData, checkWidgetSpec } from "./web-widg
15
15
  import { checkOwnerCss, ownerCssAssetRefs } from "./web-owner-css.js";
16
16
  import { canonicalText } from "./web-theme.js";
17
17
  import { EFFECT_WIRE_LIMIT } from "./web-effect-upload.js";
18
+ import { SKIN_WIRE_BYTES } from "./web-office-skins.js";
18
19
  import { gatewayRequestContext } from "./web-request-context.js";
19
20
  import { crossInstanceVisibility } from "./cross-instance-notice.js";
20
21
  import { permitWebContinuation } from "./web-continuation.js";
@@ -447,6 +448,141 @@ function handleAppIcon(req, res, url, ctx, method) {
447
448
  });
448
449
  return true;
449
450
  }
451
+ /**
452
+ * #2513: the Office view's skin packs (web-office-skins.ts). The reads are the offices' (GET /ui/offices): whoever may
453
+ * open the org chart, the public link too (its manifest lists them; a guest's routes do not): the list, a pack's
454
+ * manifest (?v=<sha>: immutable, else no-store) and its pictures (by the sha of their bytes: immutable, image/png,
455
+ * nosniff, sandboxed). The writes are PUT /ui/offices's: an admin session only — never the CLI token, never the public
456
+ * link (absent from its manifest, and refused here too) — asked again once the body is in; rate limited per person.
457
+ * Every page hears `office_skins` on the stream and reads the list again.
458
+ */
459
+ function handleOfficeSkins(req, res, url, ctx, path, method) {
460
+ const store = ctx.officeSkins;
461
+ if (!store) {
462
+ json(res, 404, { error: "Skin packs are not kept on this fleet" });
463
+ return true;
464
+ }
465
+ const one = /^\/ui\/office-skins\/([A-Za-z0-9_-]{1,40})$/.exec(path);
466
+ const picture = /^\/ui\/office-skins\/([A-Za-z0-9_-]{1,40})\/img\/([0-9a-f]{32})\.png$/.exec(path);
467
+ if (method === "GET" || method === "HEAD") {
468
+ res.setHeader("X-Content-Type-Options", "nosniff");
469
+ if (path === "/ui/office-skins") {
470
+ res.setHeader("Cache-Control", "private, no-store");
471
+ json(res, 200, store.list());
472
+ return true;
473
+ }
474
+ if (one) {
475
+ const pack = store.get(one[1]);
476
+ if (!pack) {
477
+ res.setHeader("Cache-Control", "private, no-store");
478
+ json(res, 404, { error: "skin_unknown" });
479
+ return true;
480
+ }
481
+ const v = url.searchParams.get("v");
482
+ res.setHeader("Cache-Control", v === pack.sha ? "private, max-age=31536000, immutable" : "private, no-store");
483
+ json(res, 200, pack);
484
+ return true;
485
+ }
486
+ if (picture) {
487
+ const bytes = store.picture(picture[1], picture[2]);
488
+ if (!bytes) {
489
+ res.setHeader("Cache-Control", "private, no-store");
490
+ json(res, 404, { error: "No such image" });
491
+ return true;
492
+ }
493
+ res.setHeader("Content-Type", "image/png");
494
+ // Keyed by the sha of its bytes: a picture at this URL never changes.
495
+ res.setHeader("Cache-Control", "private, max-age=31536000, immutable");
496
+ res.setHeader("Content-Security-Policy", "default-src 'none'; sandbox");
497
+ res.writeHead(200);
498
+ res.end(method === "HEAD" ? undefined : bytes);
499
+ return true;
500
+ }
501
+ json(res, 404, { error: "not found" });
502
+ return true;
503
+ }
504
+ const upload = method === "POST" && path === "/ui/office-skins", removal = method === "DELETE" && !!one;
505
+ if (!upload && !removal) {
506
+ json(res, 404, { error: "not found" });
507
+ return true;
508
+ }
509
+ if (gatewayRequestContext(req)) {
510
+ json(res, 403, { error: "skins_not_over_public_link" });
511
+ return true;
512
+ }
513
+ const who = () => {
514
+ const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
515
+ if (verdict.kind !== "allow")
516
+ return { status: 401 };
517
+ return verdict.via === "session" && verdict.session?.tier === "admin" ? { person: verdict.session.owner ? webPersonKey(verdict.session.owner) : "admin-session" } : { status: 403 };
518
+ };
519
+ const refuse = (status) => json(res, status, { error: status === 401 ? WEB_TOKEN_INVALID_MESSAGE : "skins_need_an_admin_session" });
520
+ const first = who();
521
+ if ("status" in first) {
522
+ refuse(first.status);
523
+ return true;
524
+ }
525
+ const limited = (person) => {
526
+ const allowed = ctx.officeSkinWrites ? ctx.officeSkinWrites.check(person) : { ok: true };
527
+ if (allowed.ok)
528
+ return false;
529
+ res.setHeader("Retry-After", String(allowed.retry_after_s));
530
+ json(res, 429, { error: "skins_rate_limited", retry_after_s: allowed.retry_after_s });
531
+ return true;
532
+ };
533
+ const answer = (r, body, person) => {
534
+ if (!r.ok) {
535
+ json(res, r.status, { error: r.error, ...(r.detail ? { detail: r.detail } : {}) });
536
+ return;
537
+ }
538
+ ctx.officeSkinWrites?.record(person);
539
+ ctx.emitSseEvent("office_skins", { rev: r.rev });
540
+ json(res, 200, body());
541
+ };
542
+ if (removal) {
543
+ if (limited(first.person))
544
+ return true;
545
+ const r = store.remove(one[1]);
546
+ answer(r, () => ({ rev: r.ok ? r.rev : "" }), first.person);
547
+ return true;
548
+ }
549
+ (async () => {
550
+ let bytes;
551
+ try {
552
+ bytes = await readBoundedWebBody(req, SKIN_WIRE_BYTES);
553
+ }
554
+ catch {
555
+ json(res, 413, { error: "skin_too_large", detail: `at most ${Math.floor(SKIN_WIRE_BYTES / 1024 / 1024)} MiB a request` });
556
+ return;
557
+ }
558
+ if (!permitWebContinuation(req, res, ctx))
559
+ return;
560
+ const now = who();
561
+ if ("status" in now) {
562
+ refuse(now.status);
563
+ return;
564
+ }
565
+ let body;
566
+ try {
567
+ body = JSON.parse(bytes.toString("utf8"));
568
+ }
569
+ catch {
570
+ json(res, 400, { error: "Invalid JSON" });
571
+ return;
572
+ }
573
+ if (limited(now.person))
574
+ return;
575
+ const r = store.add(body, now.person);
576
+ answer(r, () => (r.ok ? { rev: r.rev, pack: r.pack } : null), now.person);
577
+ })().catch(err => {
578
+ ctx.logger.error({ err: err.message }, "Uploading a skin pack failed");
579
+ try {
580
+ json(res, 500, { error: "Uploading a skin pack failed" });
581
+ }
582
+ catch { /* already answered */ }
583
+ });
584
+ return true;
585
+ }
450
586
  /**
451
587
  * #1629 r4 §3: the owner-CSS routes (see their call site). `who()`, asked first and again after any body: the person a
452
588
  * signed-in human session is, or the status to answer.
@@ -1311,11 +1447,13 @@ function webAuthorityOf(req, url, ctx, instance) {
1311
1447
  peer: webPeerOf(req), ...(instance !== undefined ? { instance } : {}) });
1312
1448
  }
1313
1449
  /**
1314
- * #1939 §3.4 / #2453 §0.2: where this request came from — the admin's named link by the gateway's own provenance (its
1315
- * exact hostname), else what the TCP peer and Host say (a quick tunnel's request is `lan`, and refused as public_link).
1450
+ * #1939 §3.4 / #2453 §0.2 / §0.3: where this request came from — over the public gateway, the link itself by the
1451
+ * gateway's own provenance (never a header): the admin's named tunnel on its exact hostname, else the temporary
1452
+ * quick-tunnel link. Off the gateway, what the TCP peer and Host say.
1316
1453
  */
1317
1454
  export function webPeerOf(req) {
1318
- return gatewayRequestContext(req)?.named === true ? "named_link" : classifyWebPeer(req);
1455
+ const gateway = gatewayRequestContext(req);
1456
+ return gateway ? (gateway.named === true ? "named_link" : "quick_link") : classifyWebPeer(req);
1319
1457
  }
1320
1458
  /**
1321
1459
  * #2122: what let this request in, asked now — its session (by handle, under the token it was signed in with) or the
@@ -2074,6 +2212,8 @@ export function handleWebRequest(req, res, url, ctx) {
2074
2212
  });
2075
2213
  return true;
2076
2214
  }
2215
+ if (path === "/ui/office-skins" || path.startsWith("/ui/office-skins/"))
2216
+ return handleOfficeSkins(req, res, url, ctx, path, method);
2077
2217
  // ── #1630: avatars and Discord server emoji, same-origin (the page never loads a platform's CDN; img-src stays 'self') ──
2078
2218
  const imageMatch = path.match(/^\/ui\/img\/(bot|avatar|emoji)\/([^/]+)$/);
2079
2219
  if (method === "GET" && imageMatch) {
@@ -3268,10 +3408,10 @@ export function handleWebRequest(req, res, url, ctx) {
3268
3408
  return [403, "usage_report_not_over_public_link"];
3269
3409
  return null;
3270
3410
  };
3271
- // Over the public link, where to turn it on: Settings → Web on the dashboard reached directly (this computer's own
3411
+ // Over the public link, where to turn it on: Settings → Web dashboard on the dashboard reached directly (this computer's own
3272
3412
  // address — the public link's Settings only reads). Never the public origin's own Settings, which cannot change it.
3273
3413
  const answer = ([status, error]) => json(res, status, {
3274
- error, ...(status === 403 ? { settings: `http://localhost:${ctx.fleetConfig?.health_port ?? 19280}/settings/web` } : {})
3414
+ error, ...(status === 403 ? { settings: `http://localhost:${ctx.fleetConfig?.health_port ?? 19280}/settings/web-dashboard` } : {})
3275
3415
  });
3276
3416
  const first = refusal();
3277
3417
  if (first) {
@@ -3866,6 +4006,11 @@ function handleUpload(req, res, url, ctx) {
3866
4006
  // staging a file never wakes it. A stopped or deleted recipient must not regain an upload id.
3867
4007
  const deliverable = () => uploadRecipient(ctx, instance);
3868
4008
  const gone = () => drop(() => json(res, 404, { error: `Instance not found: ${instance}` }));
4009
+ // #2366: a guest's scope (and whether guests may chat) is asked again at each commit point — narrowed, revoked or made
4010
+ // read-only while the body was arriving, the upload is refused and its part file removed, as it would have been at
4011
+ // the start. Read now: the scope is never stored (guestRequestOf's scope() is the fleet's guestScope as it is now).
4012
+ const guestNow = () => guestChatRefusal(guestRequestOf(req), instance);
4013
+ const refuseGuest = (r) => drop(() => json(res, r.status, { error: r.error }));
3869
4014
  req.on("end", () => {
3870
4015
  if (over)
3871
4016
  return;
@@ -3880,6 +4025,11 @@ function handleUpload(req, res, url, ctx) {
3880
4025
  drop(() => json(res, 415, { error: "the file is empty" }));
3881
4026
  return;
3882
4027
  }
4028
+ const refusedAtEnd = guestNow();
4029
+ if (refusedAtEnd) {
4030
+ refuseGuest(refusedAtEnd);
4031
+ return;
4032
+ }
3883
4033
  if (!deliverable()) {
3884
4034
  gone();
3885
4035
  return;
@@ -3892,6 +4042,11 @@ function handleUpload(req, res, url, ctx) {
3892
4042
  drop();
3893
4043
  return;
3894
4044
  }
4045
+ const refusedAtFinish = guestNow(); // #2366: and the guest's scope may have changed in it
4046
+ if (refusedAtFinish) {
4047
+ refuseGuest(refusedAtFinish);
4048
+ return;
4049
+ }
3895
4050
  if (!deliverable()) {
3896
4051
  gone();
3897
4052
  return;