@songsid/agend 2.2.0-beta.6 → 2.2.0-beta.8

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 (134) hide show
  1. package/dist/agent-cli-instructions.md +6 -2
  2. package/dist/agent-cli.js +9 -5
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-whats-new.js +9 -3
  5. package/dist/agent-whats-new.js.map +1 -1
  6. package/dist/backend/kiro.d.ts +9 -0
  7. package/dist/backend/kiro.js +23 -0
  8. package/dist/backend/kiro.js.map +1 -1
  9. package/dist/backend/muse.d.ts +26 -0
  10. package/dist/backend/muse.js +131 -0
  11. package/dist/backend/muse.js.map +1 -1
  12. package/dist/channel/adapters/discord.js +12 -1
  13. package/dist/channel/adapters/discord.js.map +1 -1
  14. package/dist/channel/adapters/telegram.js +6 -0
  15. package/dist/channel/adapters/telegram.js.map +1 -1
  16. package/dist/channel/mcp-tools.js +3 -3
  17. package/dist/channel/mcp-tools.js.map +1 -1
  18. package/dist/channel/types.d.ts +3 -0
  19. package/dist/config-validator.js +3 -0
  20. package/dist/config-validator.js.map +1 -1
  21. package/dist/cross-instance-envelope.js +2 -0
  22. package/dist/cross-instance-envelope.js.map +1 -1
  23. package/dist/daemon.d.ts +8 -0
  24. package/dist/daemon.js +126 -54
  25. package/dist/daemon.js.map +1 -1
  26. package/dist/delivery-outbox.d.ts +27 -0
  27. package/dist/delivery-outbox.js +175 -6
  28. package/dist/delivery-outbox.js.map +1 -1
  29. package/dist/fleet-manager.d.ts +40 -1
  30. package/dist/fleet-manager.js +248 -40
  31. package/dist/fleet-manager.js.map +1 -1
  32. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +16 -1
  33. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +4 -0
  34. package/dist/human-delivery-receipts.d.ts +83 -0
  35. package/dist/human-delivery-receipts.js +194 -0
  36. package/dist/human-delivery-receipts.js.map +1 -0
  37. package/dist/instance-config-impact.js +1 -0
  38. package/dist/instance-config-impact.js.map +1 -1
  39. package/dist/instructions.d.ts +1 -1
  40. package/dist/instructions.js +1 -1
  41. package/dist/instructions.js.map +1 -1
  42. package/dist/locale.js +8 -0
  43. package/dist/locale.js.map +1 -1
  44. package/dist/log-service.d.ts +99 -0
  45. package/dist/log-service.js +235 -0
  46. package/dist/log-service.js.map +1 -0
  47. package/dist/log-steps.d.ts +143 -0
  48. package/dist/log-steps.js +646 -0
  49. package/dist/log-steps.js.map +1 -0
  50. package/dist/outbound-handlers.d.ts +3 -0
  51. package/dist/outbound-handlers.js +20 -4
  52. package/dist/outbound-handlers.js.map +1 -1
  53. package/dist/outbound-schemas.d.ts +15 -13
  54. package/dist/outbound-schemas.js +5 -0
  55. package/dist/outbound-schemas.js.map +1 -1
  56. package/dist/public-link-progress.d.ts +1 -1
  57. package/dist/public-link-progress.js +8 -2
  58. package/dist/public-link-progress.js.map +1 -1
  59. package/dist/public-preview.d.ts +2 -1
  60. package/dist/public-preview.js +8 -5
  61. package/dist/public-preview.js.map +1 -1
  62. package/dist/public-web-gateway.js +1 -1
  63. package/dist/public-web-gateway.js.map +1 -1
  64. package/dist/public-web-link.d.ts +4 -1
  65. package/dist/public-web-link.js +42 -13
  66. package/dist/public-web-link.js.map +1 -1
  67. package/dist/reply-buttons-controller.d.ts +3 -0
  68. package/dist/reply-buttons-controller.js +3 -2
  69. package/dist/reply-buttons-controller.js.map +1 -1
  70. package/dist/settings-api.js +4 -0
  71. package/dist/settings-api.js.map +1 -1
  72. package/dist/settings-change.js +2 -0
  73. package/dist/settings-change.js.map +1 -1
  74. package/dist/settings-effect.js +2 -0
  75. package/dist/settings-effect.js.map +1 -1
  76. package/dist/tool-progress.js +3 -2
  77. package/dist/tool-progress.js.map +1 -1
  78. package/dist/topic-commands.d.ts +3 -2
  79. package/dist/topic-commands.js +14 -4
  80. package/dist/topic-commands.js.map +1 -1
  81. package/dist/transcript-jsonl.d.ts +80 -3
  82. package/dist/transcript-jsonl.js +147 -10
  83. package/dist/transcript-jsonl.js.map +1 -1
  84. package/dist/transcript-monitor.d.ts +11 -0
  85. package/dist/transcript-monitor.js +61 -13
  86. package/dist/transcript-monitor.js.map +1 -1
  87. package/dist/transcript-sources.d.ts +24 -0
  88. package/dist/transcript-sources.js +205 -55
  89. package/dist/transcript-sources.js.map +1 -1
  90. package/dist/tunnel/cloudflared.d.ts +7 -15
  91. package/dist/tunnel/cloudflared.js +36 -1
  92. package/dist/tunnel/cloudflared.js.map +1 -1
  93. package/dist/tunnel/types.d.ts +5 -1
  94. package/dist/tunnel/types.js.map +1 -1
  95. package/dist/types.d.ts +6 -0
  96. package/dist/ui/chat-strings.js +8 -2
  97. package/dist/ui/chat-thread.js +24 -4
  98. package/dist/ui/chat-time.js +80 -0
  99. package/dist/ui/panel-log.js +388 -0
  100. package/dist/ui/panel-settings.js +6 -1
  101. package/dist/ui/preview-panel.js +7 -1
  102. package/dist/ui/preview.js +5 -1
  103. package/dist/ui/settings-strings.js +2 -0
  104. package/dist/ui/shared/app-i18n.js +2 -2
  105. package/dist/ui/shared/app-route.js +6 -1
  106. package/dist/ui/shared/app-shell.js +3 -3
  107. package/dist/ui/shared/app-stream.js +2 -1
  108. package/dist/ui/shared/app.css +44 -6
  109. package/dist/ui/shared/app.js +5 -0
  110. package/dist/ui/shared/fonts/README.md +1 -1
  111. package/dist/ui/shared/instance-nav.js +9 -6
  112. package/dist/ui/shared/instance-switch.js +4 -4
  113. package/dist/ui/shared/tokens.css +1 -1
  114. package/dist/web-api.d.ts +5 -0
  115. package/dist/web-api.js +49 -1
  116. package/dist/web-api.js.map +1 -1
  117. package/dist/web-auth.js +2 -1
  118. package/dist/web-auth.js.map +1 -1
  119. package/dist/web-commands.d.ts +2 -2
  120. package/dist/web-commands.js +3 -2
  121. package/dist/web-commands.js.map +1 -1
  122. package/dist/web-reply-files.d.ts +45 -0
  123. package/dist/web-reply-files.js +223 -0
  124. package/dist/web-reply-files.js.map +1 -0
  125. package/dist/web-settings-patch.d.ts +3 -1
  126. package/dist/web-settings-patch.js +4 -1
  127. package/dist/web-settings-patch.js.map +1 -1
  128. package/dist/web-shell-routes.d.ts +4 -0
  129. package/dist/web-shell-routes.js +9 -0
  130. package/dist/web-shell-routes.js.map +1 -1
  131. package/dist/web-upload.d.ts +17 -4
  132. package/dist/web-upload.js +64 -13
  133. package/dist/web-upload.js.map +1 -1
  134. package/package.json +1 -1
@@ -208,7 +208,7 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
208
208
  :root { --text-scale: 1; }
209
209
  :root[data-text-size="s"] { --text-scale: 0.9; }
210
210
  :root[data-text-size="l"] { --text-scale: 1.15; }
211
- .thread, .p-details .panel-body, .p-fleet .panel-body {
211
+ .thread, .p-details .panel-body, .p-fleet .panel-body, .p-log .panel-body {
212
212
  --fs-xs: calc(12px * var(--text-scale)); --lh-xs: calc(16px * var(--text-scale));
213
213
  --fs-sm: calc(13px * var(--text-scale)); --lh-sm: calc(18px * var(--text-scale));
214
214
  --fs-md: calc(15px * var(--text-scale)); --lh-md: calc(24px * var(--text-scale));
@@ -217,10 +217,40 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
217
217
  }
218
218
  /* #1523 N2: Details. */
219
219
  .p-details .d-tags { display: inline-flex; flex-wrap: wrap; gap: var(--s-1); }
220
+ /* #1218: an instance's Log — one column of steps, newest at the bottom; the filter bar above it. Classes only (#1300). */
221
+ .p-log .log-bar { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-2) var(--s-4); border-bottom: 1px solid var(--border); flex: none; }
222
+ .p-log .log-bar .chip-btn.on { color: var(--text); background: var(--surface-2); font-weight: var(--fw-semibold); }
223
+ .p-log .log-live { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-3); }
224
+ .p-log .log-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-1); }
225
+ .p-log .log-item { padding: var(--s-2) var(--s-3); border-radius: var(--r-md); background: var(--surface); min-width: 0; overflow-wrap: anywhere; }
226
+ .p-log .log-item.turn-start { margin-top: var(--s-4); }
227
+ .p-log .log-item.k-user { background: var(--user-bubble); }
228
+ .p-log .log-item.k-turn_end, .p-log .log-item.k-state, .p-log .log-item.k-event { background: transparent; color: var(--text-3); padding-block: 2px; font-size: var(--fs-sm); }
229
+ .p-log .log-item.k-error, .p-log .log-item.s-failed { border-left: 3px solid var(--danger); }
230
+ .p-log .log-head { display: flex; align-items: baseline; gap: var(--s-2); min-width: 0; }
231
+ .p-log .log-icon { flex: none; }
232
+ .p-log .log-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
233
+ .p-log .log-cat { font-weight: var(--fw-semibold); }
234
+ .p-log .log-subject { font-size: var(--fs-sm); }
235
+ .p-log .log-time { flex: none; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; }
236
+ .p-log .log-status { flex: none; font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; }
237
+ .p-log .log-status.done { color: var(--ok); }
238
+ .p-log .log-status.failed { color: var(--danger); }
239
+ .p-log .log-status.interrupted { color: var(--warn, var(--text-3)); }
240
+ .p-log .log-status .spinner { display: inline-block; width: 8px; height: 8px; margin-right: 4px; border-radius: 50%; background: var(--accent); animation: pulse 1.2s ease-in-out infinite; }
241
+ .p-log .log-repeat { flex: none; font-size: var(--fs-xs); padding: 0 6px; border-radius: 8px; background: var(--surface-2); color: var(--text-2); }
242
+ .p-log .log-text { white-space: pre-wrap; margin-top: var(--s-1); font-size: var(--fs-sm); }
243
+ .p-log .log-text.mono { font-family: var(--font-mono); font-size: var(--fs-xs); }
244
+ .p-log .log-text.folded { max-height: 10.5em; overflow: hidden; mask-image: linear-gradient(to bottom, #000 70%, transparent); -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent); }
245
+ .p-log .link-btn { border: 0; background: none; padding: 2px 0; color: var(--accent); cursor: pointer; font-size: var(--fs-xs); }
246
+ .p-log .log-detail { margin-top: var(--s-1); display: flex; flex-direction: column; gap: var(--s-2); }
247
+ .p-log .log-sec-h { font-size: var(--fs-xs); color: var(--text-3); text-transform: uppercase; letter-spacing: 0.04em; }
248
+ .p-log .log-older, .p-log .log-start { text-align: center; margin: var(--s-2) 0; }
249
+ .p-log .log-off { padding: var(--s-6) var(--s-4); }
220
250
  .p-details .d-edit { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-2); }
221
251
  @media (max-width: 899px) {
222
252
  .panel-head:has(.inst-switch) { flex-wrap: wrap; }
223
- .inst-switch.seg { order: 3; flex-basis: 100%; display: grid; grid-template-columns: repeat(3, 1fr); padding: 0 0 var(--s-1); }
253
+ .inst-switch.seg { order: 3; flex-basis: 100%; display: grid; grid-template-columns: repeat(4, 1fr); padding: 0 0 var(--s-1); }
224
254
  .inst-switch .seg-item { justify-content: center; border-radius: var(--r-sm); }
225
255
  }
226
256
  .panel-body { flex: 1; min-height: 0; overflow-y: auto; }
@@ -336,7 +366,10 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
336
366
  .msg .time { font-family: var(--font-mono); white-space: nowrap; flex: none; }
337
367
  .msg.user { align-self: flex-end; max-width: min(80%, 620px); }
338
368
  .msg.user .meta { justify-content: flex-end; }
339
- .msg.user .body { background: var(--user-bubble); border-radius: var(--r-xl); padding: 10px var(--s-4); }
369
+ /* #1773: the bubble hugs its text. The message is as wide as its widest child — usually the meta row (sender, a time
370
+ such as 「昨天 下午03:58」, ticks) — and a block body would stretch to it; fit-content keeps a short message short, on
371
+ the right under its meta, and a long one still wraps at the message's max-width. */
372
+ .msg.user .body { background: var(--user-bubble); border-radius: var(--r-xl); padding: 10px var(--s-4); width: fit-content; max-width: 100%; margin-left: auto; }
340
373
  .msg.agent { align-self: stretch; }
341
374
  /* #1672: a status line (another instance's message, a daemon notice): muted plain text, no bubble; a long one folds.
342
375
  Its class is `status-line`, never `status`: that is the header's inline-flex badge class, and on a message it turned
@@ -652,11 +685,16 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
652
685
  .v-filter-clear { width: 28px; height: 28px; }
653
686
  .v-filter .note { padding: var(--s-1) var(--s-1) 0; font-variant-numeric: tabular-nums; }
654
687
  /* alpha.2 (N1): status and CLI chips under the filter box — each a group of checkboxes under its header (#1700). */
655
- .nav-facets { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-1) var(--s-2); padding: var(--s-2) var(--s-1) 0; }
656
- .nav-facet { font-size: var(--fs-xs); color: var(--text-2); }
688
+ /* #1776: the "N / M" count is on the headers' row, at its right end — not a row of its own. The headers (and an open
689
+ group's list, the reset) wrap in their own box beside it, so a filter's summary or a narrow sidebar moves a header
690
+ down, never the count; the count is as tall as a header, so the row does not grow. */
691
+ .nav-facets { display: flex; align-items: flex-start; gap: var(--s-2); padding: var(--s-2) var(--s-1) 0; }
692
+ .nav-facet-groups { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-1) var(--s-2); flex: 1 1 auto; min-width: 0; }
693
+ .nav-facets .nav-shown { flex: none; margin: 0 0 0 auto; padding: 2px 0; white-space: nowrap; }
694
+ .nav-facet { font-size: var(--fs-xs); color: var(--text-2); min-width: 0; max-width: 100%; }
657
695
  /* #1700: a facet group's header — the whole row toggles its list; folded with a filter set, a short summary. */
658
696
  .nav-facet-head { display: inline-flex; align-items: center; gap: var(--s-1); max-width: 100%; padding: 2px var(--s-2); border: 0; border-radius: var(--r-sm);
659
- background: transparent; color: inherit; font: inherit; cursor: pointer; text-align: left; }
697
+ background: transparent; color: inherit; font: inherit; cursor: pointer; text-align: left; white-space: nowrap; }
660
698
  .nav-facet-head:hover { background: var(--hover); }
661
699
  .nav-facet-head:focus-visible { outline: 2px solid var(--accent); }
662
700
  .nav-facet-head .caret { color: var(--text-3); flex: none; }
@@ -51,6 +51,8 @@ if (mode === "full") {
51
51
  stream.on("hydration", (hydration) => appStore.set({ hydration }));
52
52
  appStore.set({ retryHydration: () => stream.catchUp() });
53
53
  stream.on("needs", (d) => appStore.set({ needs: Array.isArray(d && d.items) ? d.items : [] }));
54
+ // #1218: a panel's way to hear one stream event (the Log's steps_tick), unsubscribed by its lease.
55
+ appStore.set({ streamOn: (name, fn) => stream.on(name, fn) });
54
56
  appStore.set({ connection: stream.connection() });
55
57
 
56
58
  // The chat's store must hear every message from the first frame, whatever panel is open: its module boots once,
@@ -67,6 +69,7 @@ if (mode === "full") {
67
69
  const loadSettings = retryable((a) => import(retryUrl("/ui/js/panel-settings.js", a)));
68
70
  const loadNeeds = retryable((a) => import(retryUrl("/ui/js/panel-needs.js", a)));
69
71
  const loadDetails = retryable((a) => import(retryUrl("/ui/js/panel-details.js", a)));
72
+ const loadLog = retryable((a) => import(retryUrl("/ui/js/panel-log.js", a)));
70
73
  // The stream opens once the chat listens, so the frames sent on connect (status, open prompts, ticks) reach it too.
71
74
  // If the chat cannot load (a session that just ended), the stream still opens for the sidebar.
72
75
  const chatBoot = loadChat();
@@ -80,6 +83,8 @@ if (mode === "full") {
80
83
  panels.set("needs", { load: () => loadNeeds().then((m) => m.NeedsPanel) });
81
84
  // #1523 N2: one instance's Details (/ui/fleet/agent/<name>).
82
85
  panels.set("details", { load: () => loadDetails().then((m) => m.DetailsPanel) });
86
+ // #1218: one instance's Log (/ui/log/<name>).
87
+ panels.set("log", { load: () => loadLog().then((m) => m.LogPanel) });
83
88
  // #1386 §6.3: desktop notifications for new "Needs you" items, in any panel, while this signed-in page is open.
84
89
  startNeedsNotifier({ open: (instance) => navigate(chatPath(instance)) });
85
90
  // #1665: this device's first upload of its own choices (once), then another device's changes at each status frame.
@@ -8,7 +8,7 @@
8
8
  - The optical-size axis is fixed at its text size (`opsz=14`).
9
9
  - The font is subset to Latin, with the same range as `unicode-range` in `tokens.css`.
10
10
  - It is saved as WOFF2. About 44 KB, against 352 KB for the full variable font.
11
- - Chinese and other scripts fall back to the system fonts named in `--font-ui` (PingFang TC → Noto Sans TC → Microsoft JhengHei → the system stack).
11
+ - Chinese and other scripts fall back to the system fonts named in `--font-ui` (PingFang TC → PingFang SC → Noto Sans TC → Noto Sans SC → Noto Sans CJK TC → Noto Sans CJK SC → Microsoft JhengHei → Microsoft YaHei → the system stack).
12
12
 
13
13
  Rebuilt with fontTools 4.59 (and `brotli` for WOFF2):
14
14
 
@@ -10,7 +10,7 @@
10
10
  import { html, useLayoutEffect, useRef, useState } from "./app-html.js";
11
11
  import { t } from "./app-i18n.js";
12
12
  import { appStore, createStore, useStore } from "./app-store.js";
13
- import { chatPath, detailsPath, viewPath } from "./app-route.js";
13
+ import { chatPath, detailsPath, logPath, viewPath } from "./app-route.js";
14
14
  import { Icon } from "./ui-icons.js";
15
15
  import { Skeleton } from "./ui-states.js";
16
16
  import { viewStore } from "./view-roster-store.js";
@@ -353,8 +353,9 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
353
353
  // Where a row goes: the view the page is in (#1523 §3.1). View → View; Details, or a Fleet tab (Q4 = A), → Details;
354
354
  // everywhere else → Chat. The anonymous reader only ever has View.
355
355
  const rowPath = viewOnly || (route && route.panel === "view") ? viewPath
356
+ : route && route.panel === "log" ? logPath // #1218: Log → Log
356
357
  : route && (route.panel === "details" || route.panel === "fleet") ? detailsPath : chatPath;
357
- const active = route && (route.panel === "chat" || route.panel === "view" || route.panel === "details") ? route.instance : null;
358
+ const active = route && (route.panel === "chat" || route.panel === "view" || route.panel === "log" || route.panel === "details") ? route.instance : null;
358
359
  useLayoutEffect(() => { keepActiveInView(list.current); }, [active, items.length, loaded]);
359
360
  const own = (map, name) => Object.prototype.hasOwnProperty.call(map, name);
360
361
  const byName = new Map(items.map((it) => [nameOf(it), it]));
@@ -478,11 +479,13 @@ export function InstanceNav({ route, items, loaded = true, exec = {}, awaiting =
478
479
  onClick=${() => { setNavFilter({ q: "" }); filterRef.current && filterRef.current.focus(); }}><${Icon} name="close" size=${14} /></button>` : null}
479
480
  </label>
480
481
  <div class="nav-facets">
481
- ${viewOnly ? null : chips("status", STATUS_FACETS, tn("filterStatus"), statusText)}
482
- ${backends.length > 1 || f.cli.length ? chips("cli", backends, tn("filterCli"), backendLabel) : null}
483
- ${f.status.length || f.cli.length ? html`<button type="button" class="chip-btn nav-facet-reset" onClick=${() => setNavFilter({ status: [], cli: [] })}>${tn("filterFacetsReset")}</button>` : null}
482
+ <div class="nav-facet-groups">
483
+ ${viewOnly ? null : chips("status", STATUS_FACETS, tn("filterStatus"), statusText)}
484
+ ${backends.length > 1 || f.cli.length ? chips("cli", backends, tn("filterCli"), backendLabel) : null}
485
+ ${f.status.length || f.cli.length ? html`<button type="button" class="chip-btn nav-facet-reset" onClick=${() => setNavFilter({ status: [], cli: [] })}>${tn("filterFacetsReset")}</button>` : null}
486
+ </div>
487
+ <p class="note nav-shown" aria-live="polite">${shown.shown} / ${shown.total}${shown.active ? ` ${tn("filterShown")}` : ""}</p>
484
488
  </div>
485
- <p class="note" aria-live="polite">${shown.shown} / ${shown.total}${shown.active ? ` ${tn("filterShown")}` : ""}</p>
486
489
  </div>
487
490
  </div>`;
488
491
  }
@@ -1,16 +1,16 @@
1
1
  // #1523 N2 (§3.2): the instance view switch — Chat | View | Details of ONE instance. Each segment is a link to that
2
2
  // view's URL for the same instance, so switching keeps the instance (and the sidebar, which is never remounted).
3
- // Desktop: links in the header; a phone: the same three as top tabs under it (app.css). The anonymous View reader
3
+ // Desktop: links in the header; a phone: the same four as top tabs under it (app.css). The anonymous View reader
4
4
  // gets none (Chat and Details need a session): the caller does not render it there.
5
5
  import { html } from "./app-html.js";
6
6
  import { t } from "./app-i18n.js";
7
- import { chatPath, detailsPath, viewPath } from "./app-route.js";
7
+ import { chatPath, detailsPath, logPath, viewPath } from "./app-route.js";
8
8
 
9
- /** current: "chat" | "view" | "details". */
9
+ /** current: "chat" | "view" | "log" | "details". #1218: Log sits between View and Details. */
10
10
  export function InstanceSwitch({ name, current }) {
11
11
  const seg = (key, href, label) => html`<a key=${key} class=${`seg-item${current === key ? " active" : ""}`} href=${href}
12
12
  aria-current=${current === key ? "page" : undefined}>${label}</a>`;
13
13
  return html`<nav class="inst-switch seg" aria-label=${t("app.instanceViews", name)}>
14
- ${seg("chat", chatPath(name), t("app.chat"))}${seg("view", viewPath(name), t("app.view"))}${seg("details", detailsPath(name), t("app.details"))}
14
+ ${seg("chat", chatPath(name), t("app.chat"))}${seg("view", viewPath(name), t("app.view"))}${seg("log", logPath(name), t("app.log"))}${seg("details", detailsPath(name), t("app.details"))}
15
15
  </nav>`;
16
16
  }
@@ -22,7 +22,7 @@
22
22
 
23
23
  :root {
24
24
  /* ── Type ── */
25
- --font-ui: Inter, "PingFang TC", "Noto Sans TC", "Microsoft JhengHei", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
25
+ --font-ui: Inter, "PingFang TC", "PingFang SC", "Noto Sans TC", "Noto Sans SC", "Noto Sans CJK TC", "Noto Sans CJK SC", "Microsoft JhengHei", "Microsoft YaHei", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
26
26
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
27
27
  --fs-xs: 12px; --lh-xs: 16px;
28
28
  --fs-sm: 13px; --lh-sm: 18px;
package/dist/web-api.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type HumanOrigin } from "./human-delivery-receipts.js";
1
2
  import type { CachedImage } from "./web-images.js";
2
3
  import type { SettingsExecution } from "./settings-transaction.js";
3
4
  import type { PaneContextSource } from "./pane-context-cache.js";
@@ -106,6 +107,7 @@ export interface WebApiContext {
106
107
  sendClassicWebEcho?(instance: string, text: string): Promise<number>;
107
108
  /** false: definitely not delivered (the instance's IPC is gone, or it was restarted meanwhile). */
108
109
  deliverToInstance(instanceName: string, payload: Record<string, unknown>): Promise<boolean | void>;
110
+ deliverHumanToInstance?(instanceName: string, payload: Record<string, unknown>, origin: HumanOrigin, originalBody: string): Promise<boolean | void>;
109
111
  getUiStatus(): unknown;
110
112
  /** Current owned pane reader shared with status; absent means context is unavailable. */
111
113
  getPaneContextSource?(name: string): PaneContextSource | null;
@@ -146,6 +148,8 @@ export interface WebApiContext {
146
148
  needsYouItems?(): unknown[];
147
149
  /** #1389: the org chart's structure (fleet.yaml's teams, General, descriptions, thread links); absent: an empty chart. */
148
150
  orgChart?(): unknown;
151
+ /** #1218: an instance's Log steps, read on demand (null: no such instance; `{ enabled: false }`: the Log is off). */
152
+ logSteps?(name: string, q: import("./log-service.js").LogQuery): Promise<unknown>;
149
153
  /** #1468: the prompt-cache expiry analysis for a window ("24h" | "7d" | "30d"); absent: not offered. */
150
154
  cacheReport?(window: string): Promise<unknown>;
151
155
  /** #1269: an instance's chat command from the web chat (web-commands.ts); absent: not offered. */
@@ -251,6 +255,7 @@ export interface AppShellContext {
251
255
  readonly fleetConfig?: {
252
256
  web?: {
253
257
  usage_panel?: boolean;
258
+ log_tab?: boolean;
254
259
  };
255
260
  } | null;
256
261
  /** #1665: the host's theme and language go into the page itself, so a device paints them before any script reads. */
package/dist/web-api.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { webHumanOrigin } from "./human-delivery-receipts.js";
1
2
  import { withSpawnHoldReply } from "./spawn-hold-reply.js";
2
3
  import { binaryProbe } from "./binary-probe.js";
3
4
  import { settingsRequestExecution, settingsWrite, isSettingsReplay } from "./settings-request-capability.js";
@@ -32,6 +33,7 @@ import { commitTeamCreate, commitTeamUpdate } from "./outbound-handlers.js";
32
33
  import { parseMentionQuery } from "./web-mentions.js";
33
34
  import { authorizeExplicitInstanceRemoval } from "./instance-removal.js";
34
35
  import { measureSyncWork } from "./sync-work-attribution.js";
36
+ import { resolveTz } from "./tz-utils.js";
35
37
  // ── Strict public-facing schemas ────────────────────────────────────────────
36
38
  // web-api endpoints must reject unknown fields so the dashboard cannot inject
37
39
  // internal-only flags that would reach handleCreate/scheduler/config writers.
@@ -345,6 +347,45 @@ export function handleWebRequest(req, res, url, ctx) {
345
347
  json(res, 200, { messages: ctx.webChatHistory?.list(instance, limit) ?? [], boot: ctx.webChatHistory?.boot ?? null, lastId: ctx.webChatHistory?.lastId ?? 0 });
346
348
  return true;
347
349
  }
350
+ // ── #1218: the Log tab ─────────────────────────────────
351
+ // An instance's steps, read on demand from its own transcript (log-service.ts). Read-only, and offered on the public
352
+ // link on purpose (the user's decision): the same masking applies to every viewer. Passive (web-auth.ts).
353
+ if (method === "GET" && path === "/ui/steps") {
354
+ const p = url.searchParams;
355
+ const instance = p.get("instance") ?? "";
356
+ const num = (k) => {
357
+ const v = p.get(k);
358
+ if (v === null)
359
+ return undefined;
360
+ const n = Number(v);
361
+ return /^\d{1,15}$/.test(v) && Number.isSafeInteger(n) ? n : null;
362
+ };
363
+ const after = num("after"), before = num("before"), ev = num("ev"), limit = num("limit");
364
+ const key = p.get("key");
365
+ if (!instance || instance.length > 128 || after === null || before === null || ev === null || limit === null
366
+ || (limit !== undefined && (limit < 1 || limit > 200)) || (key !== null && !/^\d{1,20}-\d{1,20}$/.test(key))) {
367
+ json(res, 400, { error: "instance (1-128 chars); after, before, ev: offsets; limit 1-200; key: <dev>-<ino>" });
368
+ return true;
369
+ }
370
+ if (!ctx.logSteps) {
371
+ json(res, 404, { error: "not available" });
372
+ return true;
373
+ }
374
+ ctx.logSteps(instance, {
375
+ ...(after !== undefined ? { after } : {}), ...(before !== undefined ? { before } : {}), ...(ev !== undefined ? { ev } : {}),
376
+ ...(limit !== undefined ? { limit } : {}), ...(key !== null ? { key } : {}),
377
+ skip: p.get("skip") === "1", watch: p.get("watch") === "1",
378
+ }).then(answer => {
379
+ if (answer === null)
380
+ json(res, 404, { error: `Instance not found: ${instance}` });
381
+ else
382
+ json(res, 200, answer);
383
+ }).catch(err => {
384
+ ctx.logger.error({ err: err.message, instance }, "Log steps read failed");
385
+ json(res, 500, { error: "could not read the steps" });
386
+ });
387
+ return true;
388
+ }
348
389
  // ── Send message ───────────────────────────────────────
349
390
  if (method === "POST" && path === "/ui/send") {
350
391
  handleSendMessage(req, res, ctx);
@@ -1403,7 +1444,10 @@ function handleSendMessage(req, res, ctx) {
1403
1444
  }) : undefined;
1404
1445
  let delivered;
1405
1446
  try {
1406
- delivered = await ctx.deliverToInstance(instance, {
1447
+ const deliver = (name, payload) => ctx.deliverHumanToInstance
1448
+ ? ctx.deliverHumanToInstance(name, payload, webHumanOrigin(), typed)
1449
+ : ctx.deliverToInstance(name, payload);
1450
+ delivered = await deliver(instance, {
1407
1451
  type: "fleet_inbound",
1408
1452
  content: delivery.text,
1409
1453
  targetSession: instance,
@@ -1607,6 +1651,10 @@ function shellBodyTag(req, ctx, mode) {
1607
1651
  + `${p?.detail ? ` data-preview-detail="${attr(p.detail)}"` : ""}`
1608
1652
  // #1523 N3: whether the fleet serves /api/ai-usage (web.usage_panel), for every page's ◔.
1609
1653
  + ` data-usage-panel="${ctx.fleetConfig?.web?.usage_panel === false ? "0" : "1"}"`
1654
+ // #1218: whether the Log tab is on (web.log_tab; off by default): the tab says so and offers to turn it on.
1655
+ + ` data-log-tab="${ctx.fleetConfig?.web?.log_tab === true ? "1" : "0"}"`
1656
+ // #1770: the fleet's zone (chat-logs' and fleet.log's), in which the chat reads its message times and days.
1657
+ + ` data-fleet-tz="${attr(resolveTz())}"`
1610
1658
  // #1589: the public link shows attachments as downloads only (no inline text or media): its scope is unchanged.
1611
1659
  + `${gatewayRequestContext(req) ? ' data-public-link="1"' : ""}>`;
1612
1660
  }