@songsid/agend 2.2.0-beta.1 → 2.2.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/agent-cli-instructions.md +10 -7
- package/dist/agent-endpoint.js +21 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +47 -0
- package/dist/agent-whats-new.js +141 -0
- package/dist/agent-whats-new.js.map +1 -0
- package/dist/auth-api.d.ts +2 -2
- package/dist/auth-api.js +4 -2
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.d.ts +5 -0
- package/dist/backend/kiro-engine-ledger.js +20 -0
- package/dist/backend/kiro-engine-ledger.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +6 -0
- package/dist/backend/kiro-identity.js +19 -5
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +14 -2
- package/dist/backend/kiro.js +84 -9
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +4 -2
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +11 -1
- package/dist/channel/adapters/discord.js +23 -1
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +19 -1
- package/dist/channel/adapters/telegram.js +60 -9
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-server.js +9 -1
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +11 -0
- package/dist/channel/mcp-tools.js +23 -12
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/telegram-markdown.d.ts +6 -0
- package/dist/channel/telegram-markdown.js +104 -0
- package/dist/channel/telegram-markdown.js.map +1 -0
- package/dist/channel/tool-router.js +9 -3
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +14 -2
- package/dist/cli.js +55 -16
- package/dist/cli.js.map +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +49 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +15 -2
- package/dist/cross-instance-envelope.js +27 -5
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +48 -0
- package/dist/daemon.js +250 -22
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +17 -2
- package/dist/delivery-outbox.js +13 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +18 -1
- package/dist/delivery-reconciliation.js +67 -2
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/fleet-manager.d.ts +63 -1
- package/dist/fleet-manager.js +314 -20
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/backend-providers/SKILL.md +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +9 -6
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +11 -2
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +8 -8
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -5
- package/dist/general-knowledge/skills/fleet-health/SKILL.md +4 -4
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +13 -13
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +6 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +9 -9
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +8 -2
- package/dist/general-knowledge/skills/scheduling/SKILL.md +5 -3
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -5
- package/dist/general-knowledge/skills/tui-effort/SKILL.md +2 -2
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +4 -3
- package/dist/general-knowledge/steering/core-rules.md +8 -7
- package/dist/instance-avatar.d.ts +36 -0
- package/dist/instance-avatar.js +57 -0
- package/dist/instance-avatar.js.map +1 -0
- package/dist/instance-config-impact.js +5 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +4 -0
- package/dist/instance-lifecycle.js +3 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.js +10 -7
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +36 -26
- package/dist/locale.js.map +1 -1
- package/dist/next-steps.js +1 -1
- package/dist/next-steps.js.map +1 -1
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +28 -15
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +10 -2
- package/dist/outbound-schemas.js +15 -5
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +7 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/service-activation.js +4 -3
- package/dist/service-activation.js.map +1 -1
- package/dist/settings-api.js +71 -21
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +70 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +26 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +7 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +1 -1
- package/dist/status-emojis.d.ts +10 -3
- package/dist/status-emojis.js +11 -2
- package/dist/status-emojis.js.map +1 -1
- package/dist/tips.js +4 -4
- package/dist/tips.js.map +1 -1
- package/dist/transcript-delta-reader.d.ts +3 -3
- package/dist/transcript-delta-reader.js +8 -8
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-monitor.d.ts +8 -0
- package/dist/transcript-monitor.js +26 -2
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +54 -0
- package/dist/transcript-sources.js +188 -27
- package/dist/transcript-sources.js.map +1 -1
- package/dist/transcript-turns.d.ts +15 -2
- package/dist/transcript-turns.js +15 -2
- package/dist/transcript-turns.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/ui/chat-emojis.js +33 -0
- package/dist/ui/chat-render.js +12 -0
- package/dist/ui/chat-store.js +1 -1
- package/dist/ui/chat-strings.js +12 -12
- package/dist/ui/chat-thread.js +23 -4
- package/dist/ui/panel-chat.js +16 -3
- package/dist/ui/panel-needs.js +5 -2
- package/dist/ui/panel-settings.js +129 -31
- package/dist/ui/settings-dialogs.js +7 -2
- package/dist/ui/settings-model.js +2 -1
- package/dist/ui/settings-strings.js +44 -4
- package/dist/ui/shared/app-i18n.js +37 -15
- package/dist/ui/shared/app-prefs.js +249 -0
- package/dist/ui/shared/app-route.js +1 -1
- package/dist/ui/shared/app-shell.js +17 -4
- package/dist/ui/shared/app-store.js +3 -1
- package/dist/ui/shared/app.css +55 -0
- package/dist/ui/shared/app.js +6 -1
- package/dist/ui/shared/header-tools.js +55 -5
- package/dist/ui/shared/instance-nav.js +169 -32
- package/dist/ui/shared/panel-view.js +6 -0
- package/dist/ui/shared/signin.js +7 -7
- package/dist/ui/shared/theme.js +39 -7
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/usage-dialog.js +13 -4
- package/dist/ui/shared/view-strings.js +22 -4
- package/dist/ui/signin.html +1 -1
- package/dist/update-install.d.ts +15 -1
- package/dist/update-install.js +6 -0
- package/dist/update-install.js.map +1 -1
- package/dist/update-preflight.d.ts +100 -0
- package/dist/update-preflight.js +264 -0
- package/dist/update-preflight.js.map +1 -0
- package/dist/updater-caller.d.ts +31 -0
- package/dist/updater-caller.js +109 -0
- package/dist/updater-caller.js.map +1 -0
- package/dist/view-api.d.ts +1 -1
- package/dist/view-api.js +38 -8
- package/dist/view-api.js.map +1 -1
- package/dist/wake-coordinator.d.ts +11 -0
- package/dist/wake-coordinator.js +38 -4
- package/dist/wake-coordinator.js.map +1 -1
- package/dist/web-api.d.ts +21 -1
- package/dist/web-api.js +157 -1
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +2 -2
- package/dist/web-auth.js +7 -4
- package/dist/web-auth.js.map +1 -1
- package/dist/web-host-guard.d.ts +3 -11
- package/dist/web-host-guard.js +3 -12
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-images.d.ts +47 -0
- package/dist/web-images.js +169 -0
- package/dist/web-images.js.map +1 -0
- package/dist/web-prefs.d.ts +63 -0
- package/dist/web-prefs.js +263 -0
- package/dist/web-prefs.js.map +1 -0
- package/dist/web-session.d.ts +36 -1
- package/dist/web-session.js +53 -6
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +21 -0
- package/dist/web-settings-patch.js +74 -0
- package/dist/web-settings-patch.js.map +1 -0
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
package/dist/ui/chat-strings.js
CHANGED
|
@@ -21,8 +21,8 @@ register("chat", {
|
|
|
21
21
|
dropHere: "Drop files to attach them", asText: "As text", pasteAttached: "A long paste ({0} characters) was attached as a text file", pasteKeptAsText: "No room for another attachment: the long paste stays as text",
|
|
22
22
|
send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line · / for commands · Esc stops the reply", attachTitle: "Attach files (images, PDF, text — or paste / drop them)",
|
|
23
23
|
pvPreview: "Preview", pvStop: "Stop", pvDownload: "Download", pvMenu: "Preview options", pvHtml: "HTML",
|
|
24
|
-
pvAllowButton: "Allow previews", pvAllow: "Allow HTML previews
|
|
25
|
-
pvConfirm: "Allow HTML previews on
|
|
24
|
+
pvAllowButton: "Allow previews", pvAllow: "Allow HTML previews — they may be able to send data out", pvDisallow: "Stop allowing HTML previews",
|
|
25
|
+
pvConfirm: "Allow HTML previews?\n\nThis applies on every device where you are signed in. A preview runs the agent's HTML in an isolated frame. It cannot use your login, but it may be able to send data out, and it can slow or freeze this tab. You still click Preview each time.",
|
|
26
26
|
pvNever: "Never preview HTML on this device", pvNeverUndo: "Let this device preview HTML again", pvStarting: "Starting the preview…",
|
|
27
27
|
pvChanged: "Preview stopped — the message changed. Preview again.", pvTruncated: "Truncated — ask the agent to send it as a .html file.",
|
|
28
28
|
pvLoading: "Reading the file…", pvAttOver: "This file is over 1 MiB; it is not previewed.",
|
|
@@ -37,8 +37,8 @@ register("chat", {
|
|
|
37
37
|
pvNewer: "A newer version is in the reply from {0}.", pvNewerShow: "Show it", pvPanelIdle: "Click Preview to run it here.",
|
|
38
38
|
lbLabel: "Image: {0}", lbLabelNoName: "Image", lbClose: "Close", lbOpenOriginal: "Open original", lbPrev: "Previous image", lbNext: "Next image", lbCount: "{0} / {1}",
|
|
39
39
|
// #1554: preview.js's words (its English is the fallback there), and the server's reasons by code.
|
|
40
|
-
pvWhyOptin: "HTML previews are off
|
|
41
|
-
pvWhyOptinPanel: "HTML previews are off
|
|
40
|
+
pvWhyOptin: "HTML previews are off. Allow them from this card's ⋯ menu or Settings → Preferences.",
|
|
41
|
+
pvWhyOptinPanel: "HTML previews are off.",
|
|
42
42
|
pvWhyNever: "Previews are switched off on this device for this session.",
|
|
43
43
|
pvWhyOrigin: "Previews are off: this page is at {0}, but the fleet believes it is at {1} (a proxy that rewrites Host). Pass the external Host through, or set web.preview_origin.",
|
|
44
44
|
pvUnknownAddress: "an unknown address", pvWhyServer: "Previews are not available here.",
|
|
@@ -55,8 +55,8 @@ register("chat", {
|
|
|
55
55
|
pvServer_sameOrigin: "The preview origin cannot be the dashboard's own.",
|
|
56
56
|
pvServer_publicLink: "Previews are not offered over the public link. Open the dashboard on the machine running AgEnD to preview.",
|
|
57
57
|
pvServer_notOffered: "Previews are not available on this page.",
|
|
58
|
-
previews: "Allow HTML previews
|
|
59
|
-
copy: "Copy", copyMessage: "Copy this reply", copied: "Copied", copyFailed: "Copy failed", wrap: "Wrap", showAll: "Show all {0} lines", showLess: "Show less", jumpLatest: "Latest", newBelow: "{0} new",
|
|
58
|
+
previews: "Allow HTML previews",
|
|
59
|
+
copy: "Copy", copyMessage: "Copy this reply", copied: "Copied", copyFailed: "Copy failed", wrap: "Wrap", showAll: "Show all {0} lines", showMore: "Show more", showLess: "Show less", jumpLatest: "Latest", newBelow: "{0} new",
|
|
60
60
|
disconnected: "disconnected", tick_queued: "Waiting behind another message", tick_processing: "Handed to the agent", tick_delivered: "The agent has it", tick_failed: "Not delivered", tick_cancelled: "Dropped by Stop",
|
|
61
61
|
promptAnswering: "Answering…", promptGone: "This prompt is no longer open", promptFor: "{0}: {1}",
|
|
62
62
|
working: "{0} is working…", stuck: "{0} looks stuck", stopReply: "Stop reply", stopReplyTitle: "Stop the current reply (like Esc) and drop the messages still waiting. The instance keeps running.", stopSent: "Stopped",
|
|
@@ -96,8 +96,8 @@ register("chat", {
|
|
|
96
96
|
dropHere: "放開即可附加檔案", asText: "改為文字", pasteAttached: "貼上的長文字({0} 個字元)已改為文字檔附加", pasteKeptAsText: "附件已滿,貼上的長文字保留為一般文字",
|
|
97
97
|
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行 · / 叫出指令 · Esc 中止回覆", attachTitle: "附加檔案(圖片、PDF、文字檔,也可以貼上或拖曳)",
|
|
98
98
|
pvPreview: "預覽", pvStop: "停止", pvDownload: "下載", pvMenu: "預覽選項", pvHtml: "HTML",
|
|
99
|
-
pvAllowButton: "允許預覽", pvAllow: "
|
|
100
|
-
pvConfirm: "
|
|
99
|
+
pvAllowButton: "允許預覽", pvAllow: "允許預覽 HTML(預覽可能把資料傳出去)", pvDisallow: "不再允許預覽 HTML",
|
|
100
|
+
pvConfirm: "要允許預覽 HTML 嗎?\n\n這會套用到你登入的每一台裝置。預覽會在隔離的框架裡執行 agent 的 HTML。它無法使用你的登入,但可能把資料傳出去,也可能讓這個分頁變慢或卡住。每次仍需要你按「預覽」。",
|
|
101
101
|
pvNever: "這台裝置永不預覽 HTML", pvNeverUndo: "讓這台裝置再次可以預覽 HTML", pvStarting: "正在啟動預覽…",
|
|
102
102
|
pvChanged: "預覽已停止——訊息內容變了。請再預覽一次。", pvTruncated: "內容被截斷——請 agent 改用 .html 檔案傳送。",
|
|
103
103
|
pvLoading: "正在讀取檔案…", pvAttOver: "這個檔案超過 1 MiB,不提供預覽。",
|
|
@@ -111,8 +111,8 @@ register("chat", {
|
|
|
111
111
|
pvPage: "頁面", pvCode: "程式碼", pvReload: "重新載入", pvClose: "關閉面板", pvBack: "回到對話", pvResize: "調整預覽面板寬度",
|
|
112
112
|
pvNewer: "{0} 的回覆裡有較新的版本。", pvNewerShow: "顯示", pvPanelIdle: "按「預覽」在這裡執行。",
|
|
113
113
|
lbLabel: "圖片:{0}", lbLabelNoName: "圖片", lbClose: "關閉", lbOpenOriginal: "開啟原圖", lbPrev: "上一張", lbNext: "下一張", lbCount: "{0} / {1}",
|
|
114
|
-
pvWhyOptin: "
|
|
115
|
-
pvWhyOptinPanel: "
|
|
114
|
+
pvWhyOptin: "還沒允許 HTML 預覽。可從這張卡片的 ⋯ 選單或「設定 → 偏好設定」允許。",
|
|
115
|
+
pvWhyOptinPanel: "還沒允許 HTML 預覽。",
|
|
116
116
|
pvWhyNever: "這台裝置在這個工作階段已關閉預覽。",
|
|
117
117
|
pvWhyOrigin: "預覽已關閉:這個頁面位於 {0},但艦隊認為它位於 {1}(有會改寫 Host 的代理)。請讓外部 Host 原樣傳入,或設定 web.preview_origin。",
|
|
118
118
|
pvUnknownAddress: "未知的網址", pvWhyServer: "這裡無法使用預覽。",
|
|
@@ -129,8 +129,8 @@ register("chat", {
|
|
|
129
129
|
pvServer_sameOrigin: "預覽的來源不能和儀表板相同。",
|
|
130
130
|
pvServer_publicLink: "公開連結不提供預覽。要預覽,請在執行 AgEnD 的那台機器上開啟儀表板。",
|
|
131
131
|
pvServer_notOffered: "這個頁面不提供預覽。",
|
|
132
|
-
previews: "
|
|
133
|
-
copy: "複製", copyMessage: "複製這則回覆", copied: "已複製", copyFailed: "複製失敗", wrap: "換行", showAll: "展開全部 {0} 行", showLess: "收合", jumpLatest: "最新", newBelow: "{0} 則新訊息",
|
|
132
|
+
previews: "允許預覽 HTML",
|
|
133
|
+
copy: "複製", copyMessage: "複製這則回覆", copied: "已複製", copyFailed: "複製失敗", wrap: "換行", showAll: "展開全部 {0} 行", showMore: "顯示更多", showLess: "收合", jumpLatest: "最新", newBelow: "{0} 則新訊息",
|
|
134
134
|
disconnected: "已斷線", tick_queued: "排在前一則訊息之後", tick_processing: "已交給 agent", tick_delivered: "agent 已收到", tick_failed: "未送達", tick_cancelled: "已被中止取消",
|
|
135
135
|
promptAnswering: "回覆中…", promptGone: "這個提示已經結束", promptFor: "{0}:{1}",
|
|
136
136
|
working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
|
package/dist/ui/chat-thread.js
CHANGED
|
@@ -22,6 +22,9 @@ export const TEXT_PREVIEW_NODES = 4000;
|
|
|
22
22
|
/** A csv/tsv wider than this is shown as text, as Markdown tables are capped at 20 columns. */
|
|
23
23
|
export const TEXT_PREVIEW_COLUMNS = 50;
|
|
24
24
|
const P = () => globalThis.AgendPreview;
|
|
25
|
+
/** #1672: a status line longer than this (lines, or characters) is folded until it is opened. */
|
|
26
|
+
export const STATUS_FOLD_LINES = 6;
|
|
27
|
+
export const STATUS_FOLD_CHARS = 600;
|
|
25
28
|
const COPY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
26
29
|
|
|
27
30
|
/** Escapes & " ' < >: safe for text and inside a quoted attribute. */
|
|
@@ -50,13 +53,29 @@ export function createThread(list, scroller, opts) {
|
|
|
50
53
|
const TICKS = () => ({ queued: tr("chat.tick_queued"), processing: tr("chat.tick_processing"), delivered: tr("chat.tick_delivered"), failed: tr("chat.tick_failed"), cancelled: tr("chat.tick_cancelled") });
|
|
51
54
|
|
|
52
55
|
function msgHtml(x) {
|
|
53
|
-
const u = opts.isUser(x);
|
|
54
56
|
const time = x.ts ? new Date(x.ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
|
|
57
|
+
if (x.role === "status") return statusHtml(x, time);
|
|
58
|
+
const u = opts.isUser(x);
|
|
55
59
|
// renderMarkdown escapes the whole text before it adds any tag of its own (chat-render.js).
|
|
56
60
|
const ticks = u && x.delivery ? R().deliveryHtml(x.delivery, TICKS()) : "";
|
|
57
61
|
const tools = u ? "" : `<div class="msg-tools"><button type="button" class="chip-btn icon-only" data-act="copyMsg" data-arg="${escAttr(msgKey(x))}" title="${escAttr(tr("chat.copyMessage"))}" aria-label="${escAttr(tr("chat.copyMessage"))}">${COPY}</button></div>`;
|
|
58
62
|
const buttons = x.role === "agent" ? replyButtonsHtml(x.buttons) : "";
|
|
59
|
-
return `<div class="msg ${u ? "user" : "agent"}"><div class="meta"><span class="sender">${escAttr(x.sender)}</span><span class="time">${time}</span>${ticks}</div><div class="body"><div class="md">${R().renderMarkdown(x.text, x.role === "agent"
|
|
63
|
+
return `<div class="msg ${u ? "user" : "agent"}"><div class="meta"><span class="sender">${escAttr(x.sender)}</span><span class="time">${time}</span>${ticks}</div><div class="body"><div class="md">${R().renderMarkdown(x.text, { htmlCards: x.role === "agent", emojis: opts.emojis })}</div>${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK() })}${buttons}</div>${tools}</div>`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* #1672: a status line (another instance's message, a daemon notice) is muted, read-only plain text — never Markdown,
|
|
67
|
+
* a preview card, a button or a person's bubble. Checked before isUser, which reads the sender: a fleet name need not
|
|
68
|
+
* start with "agend". A long one is folded until it is opened.
|
|
69
|
+
*/
|
|
70
|
+
function statusHtml(x, time) {
|
|
71
|
+
const text = String(x.text == null ? "" : x.text);
|
|
72
|
+
const lines = R().lineCount(text);
|
|
73
|
+
const manyLines = lines > STATUS_FOLD_LINES;
|
|
74
|
+
const more = manyLines || text.length > STATUS_FOLD_CHARS
|
|
75
|
+
? `<button type="button" class="st-more" data-act="toggleFold"${manyLines ? ` data-lines="${escAttr(lines)}"` : ""}>${escAttr(manyLines ? trf("chat.showAll", lines) : tr("chat.showMore"))}</button>`
|
|
76
|
+
: "";
|
|
77
|
+
const tools = `<div class="msg-tools"><button type="button" class="chip-btn icon-only" data-act="copyMsg" data-arg="${escAttr(msgKey(x))}" title="${escAttr(tr("chat.copyMessage"))}" aria-label="${escAttr(tr("chat.copyMessage"))}">${COPY}</button></div>`;
|
|
78
|
+
return `<div class="msg status${more ? " folded" : ""}"><div class="meta"><span class="time">${time}</span></div><div class="body"><div class="st-text">${escAttr(text)}</div>${more}</div>${tools}</div>`;
|
|
60
79
|
}
|
|
61
80
|
/**
|
|
62
81
|
* #1266: an agent reply's buttons. Labels are text (escaped, never Markdown); the click names the set and the index,
|
|
@@ -426,9 +445,9 @@ export function createThread(list, scroller, opts) {
|
|
|
426
445
|
if (act === "copyMsg") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) copyFrom(el, x.text); }
|
|
427
446
|
else if (act === "copyCode") { const pre = el.closest(".codeblock")?.querySelector("pre"); if (pre) copyFrom(el, pre.textContent); }
|
|
428
447
|
else if (act === "toggleFold") {
|
|
429
|
-
const box = el.closest(".codeblock"); if (!box) return;
|
|
448
|
+
const box = el.closest(".codeblock, .msg.status"); if (!box) return;
|
|
430
449
|
const folded = box.classList.toggle("folded");
|
|
431
|
-
el.textContent = folded ? trf("chat.showAll", el.dataset.lines) : tr("chat.
|
|
450
|
+
el.textContent = !folded ? tr("chat.showLess") : el.dataset.lines ? trf("chat.showAll", el.dataset.lines) : tr("chat.showMore");
|
|
432
451
|
} else if (act === "toggleWrap") opts.toggleWrap();
|
|
433
452
|
else if (act === "replyButton") {
|
|
434
453
|
const m = /^([0-9a-f]{32}):(\d{1,2})$/.exec(el.dataset.arg || "");
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -10,6 +10,7 @@ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-
|
|
|
10
10
|
import { t } from "/assets/app-i18n.js";
|
|
11
11
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
12
12
|
import { useLease } from "/assets/app-ctx.js";
|
|
13
|
+
import { labelOf } from "/assets/instance-nav.js";
|
|
13
14
|
import { PanelHeader, onPanelKey, setTitle, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
|
|
14
15
|
import { detailsPath } from "/assets/app-route.js";
|
|
15
16
|
import { InstanceSwitch } from "/assets/instance-switch.js";
|
|
@@ -21,10 +22,12 @@ import { Empty, Skeleton } from "/assets/ui-states.js";
|
|
|
21
22
|
import { Icon } from "/assets/ui-icons.js";
|
|
22
23
|
import { toast } from "/assets/ui-toast.js";
|
|
23
24
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
25
|
+
import { savePref } from "/assets/app-prefs.js";
|
|
24
26
|
import "./chat-strings.js";
|
|
25
27
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
26
28
|
import { createChatStore } from "./chat-store.js";
|
|
27
29
|
import { createThread, msgKey } from "./chat-thread.js";
|
|
30
|
+
import { createEmojiIndex } from "./chat-emojis.js";
|
|
28
31
|
import { PreviewPanel, openPanel, panelStore, shownKey } from "./preview-panel.js";
|
|
29
32
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
30
33
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
@@ -40,6 +43,8 @@ export let store = null;
|
|
|
40
43
|
// ── Announcements: one polite, visually hidden line for the coarse events only (a reply arrived, the agent started,
|
|
41
44
|
// finished, or waits on you). Messages themselves are not read out as they arrive; the list is a log to browse.
|
|
42
45
|
let announceTimer = null;
|
|
46
|
+
/** #1630: which Discord server emoji this page may draw (created with the store, in boot). */
|
|
47
|
+
let emojiIndex = null;
|
|
43
48
|
function announce(text) {
|
|
44
49
|
let el = document.getElementById("announcer");
|
|
45
50
|
if (!el) { el = document.createElement("div"); el.id = "announcer"; el.className = "sr-only"; el.setAttribute("role", "status"); el.setAttribute("aria-live", "polite"); el.setAttribute("aria-atomic", "true"); document.body.appendChild(el); }
|
|
@@ -56,6 +61,8 @@ function announce(text) {
|
|
|
56
61
|
export async function setPreviewOptIn(on) {
|
|
57
62
|
if (on && !P().optedIn() && !(await confirmDialog({ message: t("chat.pvConfirm"), confirmLabel: t("chat.pvAllowButton") }))) on = false;
|
|
58
63
|
P().setOptIn(on);
|
|
64
|
+
// #1665: kept on the host for every device (only a signed-in page writes it; never the public link).
|
|
65
|
+
savePref("preview.optIn", P().optedIn());
|
|
59
66
|
return P().optedIn();
|
|
60
67
|
}
|
|
61
68
|
|
|
@@ -65,6 +72,7 @@ export function boot({ stream, boot: bootData, deps = {} }) {
|
|
|
65
72
|
store = createChatStore({
|
|
66
73
|
fetch: (...a) => fetch(...a), toast, announce, t, setTimeout: (fn, ms) => setTimeout(fn, ms), confirm: confirmDialog, ...deps,
|
|
67
74
|
});
|
|
75
|
+
emojiIndex = createEmojiIndex((url) => (deps.fetch || fetch)(url, { credentials: "same-origin" }));
|
|
68
76
|
store.attach(stream);
|
|
69
77
|
// #1306: what the server said about previews for this load. The page-wide message and storage listeners this adds
|
|
70
78
|
// belong to the app, once (they are never per mount).
|
|
@@ -206,7 +214,7 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
206
214
|
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", inst.model)} aria-label=${t("chat.chipModel", inst.model)} onClick=${() => pick("model")}>${inst.model}</button>` : null}
|
|
207
215
|
${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
|
|
208
216
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
|
|
209
|
-
<${PanelHeader} title=${name} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="chat" />`}><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
217
|
+
<${PanelHeader} title=${labelOf(inst) !== name ? html`<span title=${name}>${labelOf(inst)}</span>` : name} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="chat" />`}><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
210
218
|
<div class="chat-split" ref=${split}>
|
|
211
219
|
<div class="chat-main">
|
|
212
220
|
<${Thread} name=${name} th=${thread} />
|
|
@@ -234,12 +242,17 @@ function Thread({ name, th }) {
|
|
|
234
242
|
replyButtonBusy: (id) => store.state.rbBusy.has(id),
|
|
235
243
|
panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o) }, // #1481
|
|
236
244
|
openImage: openLightbox, // an image, full size, in the page
|
|
245
|
+
emojis: (id) => !!emojiIndex && emojiIndex.known(id), // #1630: the fleet's server emoji
|
|
237
246
|
});
|
|
238
247
|
th.current = thread;
|
|
248
|
+
// #1630: the server-emoji list is read once a message shows a tag; when it arrives, those messages are drawn again.
|
|
249
|
+
const wantEmojis = (msgs) => { if (emojiIndex && emojiIndex.wants(msgs)) emojiIndex.ensure(); };
|
|
250
|
+
const offEmojis = emojiIndex ? emojiIndex.subscribe(() => thread.render(store.state.msgs[name] || [])) : () => {};
|
|
239
251
|
thread.render(store.state.msgs[name] || [], { restore: store.state.scrollMemo[name] ?? null });
|
|
252
|
+
wantEmojis(store.state.msgs[name]);
|
|
240
253
|
const off = store.subscribe((instance, kind) => {
|
|
241
254
|
if (instance !== name) return;
|
|
242
|
-
if (kind === "msgs") thread.render(store.state.msgs[name] || []);
|
|
255
|
+
if (kind === "msgs") { thread.render(store.state.msgs[name] || []); wantEmojis(store.state.msgs[name]); }
|
|
243
256
|
else if (kind === "sent") thread.jumpLatest();
|
|
244
257
|
});
|
|
245
258
|
const offPv = P() ? P().onChange(() => thread.refreshCards()) : () => {};
|
|
@@ -249,7 +262,7 @@ function Thread({ name, th }) {
|
|
|
249
262
|
sc.addEventListener("scroll", onScroll, { passive: true });
|
|
250
263
|
ls.addEventListener("click", thread.onClick);
|
|
251
264
|
return () => {
|
|
252
|
-
off(); offPv(); offPanel();
|
|
265
|
+
off(); offPv(); offPanel(); offEmojis();
|
|
253
266
|
sc.removeEventListener("scroll", onScroll);
|
|
254
267
|
ls.removeEventListener("click", thread.onClick);
|
|
255
268
|
thread.dispose(); // the whole thread goes: every preview in it stops first
|
package/dist/ui/panel-needs.js
CHANGED
|
@@ -15,6 +15,7 @@ import { appStore, createStore, useStore } from "/assets/app-store.js";
|
|
|
15
15
|
import { useLease } from "/assets/app-ctx.js";
|
|
16
16
|
import { PanelHeader, setTitle } from "/assets/app-shell.js";
|
|
17
17
|
import { chatPath } from "/assets/app-route.js";
|
|
18
|
+
import { labelOf } from "/assets/instance-nav.js";
|
|
18
19
|
import { Empty } from "/assets/ui-states.js";
|
|
19
20
|
import { Icon } from "/assets/ui-icons.js";
|
|
20
21
|
import { toast } from "/assets/ui-toast.js";
|
|
@@ -107,7 +108,9 @@ export function groupNeeds(items) {
|
|
|
107
108
|
|
|
108
109
|
export function NeedsPanel({ navKey }) {
|
|
109
110
|
const lease = useLease(navKey);
|
|
110
|
-
const { needs, chatOwner: owner } = useStore(appStore);
|
|
111
|
+
const { needs, chatOwner: owner, instances } = useStore(appStore);
|
|
112
|
+
// #1630: a group is headed by the instance's display name (its name on hover), as the sidebar shows it.
|
|
113
|
+
const nameFor = (instance) => { const it = (instances || []).find((i) => i && i.name === instance); return it ? labelOf(it) : instance; };
|
|
111
114
|
const items = Array.isArray(needs) ? needs : [];
|
|
112
115
|
const [, tick] = useState(0);
|
|
113
116
|
useEffect(() => { setTitle(tn("title")); }, [navKey]);
|
|
@@ -122,7 +125,7 @@ export function NeedsPanel({ navKey }) {
|
|
|
122
125
|
<div class="panel-body"><div class="col">
|
|
123
126
|
${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn("emptyHint")} />`
|
|
124
127
|
: groups.map((g) => html`<section key=${g.instance} class="n-group" aria-label=${g.instance}>
|
|
125
|
-
<h2 class="n-group-head"><a href=${chatPath(g.instance)}>${g.instance}</a></h2>
|
|
128
|
+
<h2 class="n-group-head"><a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a></h2>
|
|
126
129
|
${g.items.map((item) => html`<${NeedsItem} key=${item.id} item=${item} nowMs=${nowMs} owner=${owner} />`)}
|
|
127
130
|
</section>`)}
|
|
128
131
|
<${NotifyToggle} />
|
|
@@ -43,7 +43,7 @@ import { startTour } from "./chat-tour.js";
|
|
|
43
43
|
import "./chat-strings.js";
|
|
44
44
|
|
|
45
45
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
46
|
-
const ICONS = { device: "user", agents: "bot", bots: "plug", classic: "room", general: "sliders", advanced: "code" };
|
|
46
|
+
const ICONS = { device: "user", agents: "bot", bots: "plug", classic: "room", general: "sliders", web: "globe", advanced: "code" };
|
|
47
47
|
const RELEASES = "https://github.com/songsid/AgEnD/releases";
|
|
48
48
|
|
|
49
49
|
/** GET under the panel's lease → { ok, status, body }. */
|
|
@@ -229,7 +229,8 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
229
229
|
: section === "agents" ? html`<${Agents} ctx=${ctx} search=${search} openDialog=${(d) => setDialog({ ...d, key: navKey })} />`
|
|
230
230
|
: section === "bots" ? html`<${Bots} ctx=${ctx} search=${search} openDialog=${(d) => setDialog({ ...d, key: navKey })} />`
|
|
231
231
|
: section === "classic" ? html`<${Classic} ctx=${ctx} search=${search} openDialog=${(d) => setDialog({ ...d, key: navKey })} />`
|
|
232
|
-
: section === "general" ? html`<${General} key=${data.loadedAt} ctx=${ctx}
|
|
232
|
+
: section === "general" ? html`<${General} key=${data.loadedAt} ctx=${ctx} />`
|
|
233
|
+
: section === "web" ? html`<${WebSection} key=${data.loadedAt} ctx=${ctx} staged=${staged} />`
|
|
233
234
|
: html`<${Developer} ctx=${ctx} />`;
|
|
234
235
|
// A dialog belongs to the navigation that opened it (its draft names one object, never another).
|
|
235
236
|
const open = dialog && dialog.key === navKey ? dialog : null;
|
|
@@ -257,9 +258,10 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
/**
|
|
260
|
-
* #1604:
|
|
261
|
-
*
|
|
262
|
-
* the chat tour (replayed on the
|
|
261
|
+
* #1604, #1665: Preferences. Synced across the person's devices (kept on the host, app-prefs.js): theme (theme.js),
|
|
262
|
+
* language (app-i18n.js) and HTML previews (preview.js, through the app's confirm). On the public link they are
|
|
263
|
+
* read-only: a change there stays in that browser, and it says so. This device only: the chat tour (replayed on the
|
|
264
|
+
* chat it points at), and the layout choices named there (their controls are where they always were).
|
|
263
265
|
*/
|
|
264
266
|
export function DeviceSection() {
|
|
265
267
|
const P = () => globalThis.AgendPreview;
|
|
@@ -290,11 +292,15 @@ export function DeviceSection() {
|
|
|
290
292
|
};
|
|
291
293
|
setTimeout(go, 0);
|
|
292
294
|
};
|
|
293
|
-
|
|
294
|
-
|
|
295
|
+
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
296
|
+
return html`<section class="card s-device s-synced"><h3>${tn("syncedTitle")}</h3>
|
|
297
|
+
<p class="sub">${publicLink ? tn("syncedPublicLink") : tn("syncedLead")}</p>
|
|
295
298
|
<${DevicePrefs} labels=${true} />
|
|
296
299
|
${P() ? html`<label class="check"><input type="checkbox" checked=${on} onChange=${(e) => { const want = e.target.checked; setPreviewOptIn(want).then(setOn); }} />
|
|
297
300
|
<span>${t("chat.previews")}</span></label>` : null}
|
|
301
|
+
</section>
|
|
302
|
+
<section class="card s-device s-local"><h3>${tn("deviceTitle")}</h3>
|
|
303
|
+
<p class="sub">${tn("deviceLead")}</p>
|
|
298
304
|
<div class="s-device-row"><button type="button" id="tourBtn" class="btn" title=${t("chat.tourBtnTitle")} onClick=${replay}><${Icon} name="info" size=${16} />${t("chat.tour")}</button></div>
|
|
299
305
|
</section>`;
|
|
300
306
|
}
|
|
@@ -570,17 +576,13 @@ function Classic({ ctx, search, openDialog }) {
|
|
|
570
576
|
* ClassicBot defaults, web) — each only with what differs from what this form showed, so changing one hot default
|
|
571
577
|
* never also stages untouched settings as a fleet restart.
|
|
572
578
|
*/
|
|
573
|
-
function General({ ctx
|
|
579
|
+
function General({ ctx }) {
|
|
574
580
|
const { schema } = ctx;
|
|
575
581
|
const d = ctx.fleet.defaults || {}, c = (ctx.classic && ctx.classic.defaults) || {};
|
|
576
582
|
const hang = d.hang_detector || {}, startup = d.startup || {};
|
|
577
583
|
const primary = channelsOf(ctx.fleet)[0];
|
|
578
|
-
const
|
|
579
|
-
const originalPublic = useMemo(() => ({ allow_public: (web.public_link && web.public_link.allow_public) ?? true, ttl_minutes: (web.public_link && web.public_link.ttl_minutes) ?? 120, protocol: (web.public_link && web.public_link.protocol) ?? "http2" }), []);
|
|
580
|
-
const originalEcho = web.echo_to_channel ?? true;
|
|
581
|
-
const original = useMemo(() => ({ c: structuredClone(c), web: structuredClone(ctx.fleet.web) }), []);
|
|
584
|
+
const original = useMemo(() => ({ c: structuredClone(c) }), []);
|
|
582
585
|
const [f, setF] = useState(() => ({
|
|
583
|
-
echo: originalEcho, pub: originalPublic.allow_public, ttl: String(originalPublic.ttl_minutes), protocol: originalPublic.protocol,
|
|
584
586
|
backend: d.backend || "claude-code", model: d.model || "", locale: d.locale || "", auto: String(d.auto_pause_after ?? 0),
|
|
585
587
|
agent_mode: d.agent_mode || "mcp", tool_set: d.tool_set || "full", tool_progress: d.tool_progress ?? "off",
|
|
586
588
|
visibility: visibilityDefault(d), reply: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
|
|
@@ -598,7 +600,6 @@ function General({ ctx, staged }) {
|
|
|
598
600
|
auto: nonNegative(f.auto), hang: positive(f.hang),
|
|
599
601
|
concurrency: !Number.isInteger(num(f.concurrency)) || num(f.concurrency) < 1 || num(f.concurrency) > 20 ? "concurrencyRange" : "",
|
|
600
602
|
stagger: !Number.isFinite(num(f.stagger)) || f.stagger.trim() === "" || num(f.stagger) < 0 || num(f.stagger) > 30000 ? "staggerRange" : "",
|
|
601
|
-
ttl: !Number.isInteger(num(f.ttl)) || num(f.ttl) < 1 || num(f.ttl) > 480 ? "publicTtl" : "",
|
|
602
603
|
};
|
|
603
604
|
const warnings = { concurrency: num(f.concurrency) > 4 ? "concurrencyWarning" : "", stagger: num(f.stagger) < 100 ? "staggerWarning" : "" };
|
|
604
605
|
const ready = !Object.values(errors).some(Boolean);
|
|
@@ -638,29 +639,12 @@ function General({ ctx, staged }) {
|
|
|
638
639
|
ctx.stage("defaults:classic", { label: tn("updateClassicDefaults"), impact: hotOnly ? "now" : "fleet",
|
|
639
640
|
request: { method: "PUT", url: "/api/settings/classic/defaults", body: classicPatch } });
|
|
640
641
|
}
|
|
641
|
-
const publicPatch = changedFields({ allow_public: f.pub, ttl_minutes: num(f.ttl), protocol: f.protocol }, originalPublic);
|
|
642
|
-
const webPatch = { ...(f.echo !== originalEcho ? { echo_to_channel: f.echo } : {}), ...(Object.keys(publicPatch).length ? { public_link: publicPatch } : {}) };
|
|
643
|
-
if (Object.keys(webPatch).length) {
|
|
644
|
-
ctx.stage("web:echo", { label: tn("publicLink"),
|
|
645
|
-
impact: batchImpact(schema, [...(webPatch.echo_to_channel !== undefined ? ["web.echo_to_channel"] : []), ...Object.keys(publicPatch).map((k) => `web.public_link.${k}`)], (k) => impactOf(schema, k)),
|
|
646
|
-
request: { method: "PUT", url: "/api/settings/fleet/web", body: webPatch } });
|
|
647
|
-
} else if (staged.has("web:echo")) ctx.unstage("web:echo");
|
|
648
642
|
setMsg(tn("readyApply"));
|
|
649
643
|
};
|
|
650
644
|
const L = (text, field, id) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${id} />`;
|
|
651
645
|
const fleetReply = replyGuardSupported(f.backend, f.agent_mode, d.kiro_ui) ? tn("replyGuardSupported") : tn("replyGuardUnsupported", f.backend, f.agent_mode);
|
|
652
646
|
const classicReply = replyGuardSupported(f.classicBackend, f.agent_mode, d.kiro_ui) ? tn("replyGuardSupported") : tn("replyGuardUnsupported", f.classicBackend, f.agent_mode);
|
|
653
647
|
return html`<div class="form s-general">
|
|
654
|
-
<section class="card"><h3>${tn("webChat")}</h3>
|
|
655
|
-
<label class="check"><input id="webEchoToChannel" type="checkbox" checked=${f.echo} onChange=${(e) => set("echo")(e.target.checked)} /><span>${tn("webEcho")}</span><${Impact} schema=${schema} field="web.echo_to_channel" /></label>
|
|
656
|
-
<p class="note">${tn("webEchoHint")}</p>
|
|
657
|
-
<label class="check"><input id="publicWebLink" type="checkbox" checked=${f.pub} onChange=${(e) => set("pub")(e.target.checked)} /><span>${tn("publicLink")}</span><${Impact} schema=${schema} field="web.public_link.allow_public" /></label>
|
|
658
|
-
<p class="note">${tn("publicLinkHint")}</p>
|
|
659
|
-
<div class="grid2">
|
|
660
|
-
<div class="field"><label for="publicWebTtl">${tn("publicTtl")}</label><input id="publicWebTtl" type="number" min="1" max="480" class=${errors.ttl ? "invalid" : ""} value=${f.ttl} onInput=${(e) => set("ttl")(e.target.value)} /><${Feedback} error=${errors.ttl} /></div>
|
|
661
|
-
<div class="field"><label for="publicWebProtocol">${tn("publicProtocol")}</label><${Select} id="publicWebProtocol" value=${f.protocol} onChange=${set("protocol")} options=${["http2", "quic", "auto"]} /></div>
|
|
662
|
-
</div>
|
|
663
|
-
</section>
|
|
664
648
|
<section class="card"><h3>${tn("defaultsTitle")}</h3>
|
|
665
649
|
<div class="grid2">
|
|
666
650
|
<div class="field">${L(tn("language"), "defaults.locale", "g-locale")}<${Select} id="g-locale" value=${f.locale} onChange=${set("locale")}
|
|
@@ -714,6 +698,120 @@ function General({ ctx, staged }) {
|
|
|
714
698
|
</div>`;
|
|
715
699
|
}
|
|
716
700
|
|
|
701
|
+
/**
|
|
702
|
+
* #1620: Settings → Web — the dashboard's own settings, in one write (PUT /api/settings/fleet/web). Editable: the local
|
|
703
|
+
* session limits, the browser terminal (on/off, its minutes), web echo and the public link's menu; everything else
|
|
704
|
+
* under web: / web_terminal: is listed read-only (it decides what the dashboard listens on and exposes: fleet.yaml, then
|
|
705
|
+
* agend reload). The server judges each change: loosening one waits for a fleet admin in General, tightening applies at
|
|
706
|
+
* once. Over
|
|
707
|
+
* the temporary public link none of it can be changed — the form says so and stays disabled (the server refuses too).
|
|
708
|
+
*/
|
|
709
|
+
export const WEB_READ_ONLY = [
|
|
710
|
+
"web.allowed_hosts", "web.view_access", "web.notify_login", "web.preview", "web.preview_port", "web.preview_origin",
|
|
711
|
+
"web.usage_panel", "web.provider_secrets", "web_terminal.bind", "web_terminal.tunnel",
|
|
712
|
+
];
|
|
713
|
+
function WebSection({ ctx, staged }) {
|
|
714
|
+
const { schema } = ctx;
|
|
715
|
+
const web = ctx.fleet.web || {}, local = (web.session && web.session.local) || {}, term = ctx.fleet.web_terminal || {};
|
|
716
|
+
const overPublicLink = globalThis.document?.body?.dataset?.publicLink === "1";
|
|
717
|
+
const original = useMemo(() => ({
|
|
718
|
+
echo: web.echo_to_channel ?? true,
|
|
719
|
+
pub: { allow_public: (web.public_link && web.public_link.allow_public) ?? true, ttl_minutes: (web.public_link && web.public_link.ttl_minutes) ?? 120, protocol: (web.public_link && web.public_link.protocol) ?? "http2" },
|
|
720
|
+
session: { absolute_hours: local.absolute_hours ?? null, idle_hours: local.idle_hours ?? null },
|
|
721
|
+
terminal: { enabled: term.enabled ?? null, ttl_minutes: term.ttl_minutes ?? null },
|
|
722
|
+
}), []);
|
|
723
|
+
const text = (v) => (v === null || v === undefined ? "" : String(v));
|
|
724
|
+
const [f, setF] = useState(() => ({
|
|
725
|
+
echo: original.echo, pub: original.pub.allow_public, ttl: String(original.pub.ttl_minutes), protocol: original.pub.protocol,
|
|
726
|
+
abs: text(original.session.absolute_hours), idle: text(original.session.idle_hours),
|
|
727
|
+
termOn: original.terminal.enabled !== false, termTtl: text(original.terminal.ttl_minutes),
|
|
728
|
+
}));
|
|
729
|
+
const [msg, setMsg] = useState(null);
|
|
730
|
+
const set = (k) => (v) => setF((x) => ({ ...x, [k]: v }));
|
|
731
|
+
// Empty = the default (null in the write: the key is removed from fleet.yaml).
|
|
732
|
+
const value = (v) => (v.trim() === "" ? null : Number(v));
|
|
733
|
+
const hours = (v) => { const n = value(v); return n !== null && (!Number.isFinite(n) || n < 1 || n > 720) ? "sessionRange" : ""; };
|
|
734
|
+
// The defaults (#1670: 48 h / 48 h, DEFAULT_SESSION_POLICY); an idle time left blank follows a shorter session, as
|
|
735
|
+
// the store and the validator apply it — so only an idle time that is typed can exceed the session length.
|
|
736
|
+
const abs = value(f.abs) ?? 48, idle = value(f.idle) ?? Math.min(48, abs);
|
|
737
|
+
const errors = {
|
|
738
|
+
ttl: !Number.isInteger(Number(f.ttl)) || Number(f.ttl) < 1 || Number(f.ttl) > 480 ? "publicTtl" : "",
|
|
739
|
+
abs: hours(f.abs),
|
|
740
|
+
idle: hours(f.idle) || (!hours(f.abs) && idle > abs ? "sessionIdleOver" : ""),
|
|
741
|
+
termTtl: value(f.termTtl) !== null && (!Number.isInteger(value(f.termTtl)) || value(f.termTtl) < 1 || value(f.termTtl) > 20) ? "webTerminalTtlRange" : "",
|
|
742
|
+
};
|
|
743
|
+
const ready = !overPublicLink && !Object.values(errors).some(Boolean);
|
|
744
|
+
const review = () => {
|
|
745
|
+
if (!ready) return;
|
|
746
|
+
const publicPatch = changedFields({ allow_public: f.pub, ttl_minutes: Number(f.ttl), protocol: f.protocol }, original.pub);
|
|
747
|
+
const sessionPatch = changedFields({ absolute_hours: value(f.abs), idle_hours: value(f.idle) }, original.session);
|
|
748
|
+
// The checkbox shows the default (on) for an unset key: only a real toggle is written; on again = back to the default.
|
|
749
|
+
const terminalPatch = changedFields({ enabled: f.termOn === (original.terminal.enabled !== false) ? original.terminal.enabled : f.termOn ? null : false,
|
|
750
|
+
ttl_minutes: value(f.termTtl) }, original.terminal);
|
|
751
|
+
const body = {
|
|
752
|
+
...(f.echo !== original.echo ? { echo_to_channel: f.echo } : {}),
|
|
753
|
+
...(Object.keys(publicPatch).length ? { public_link: publicPatch } : {}),
|
|
754
|
+
...(Object.keys(sessionPatch).length ? { session: { local: sessionPatch } } : {}),
|
|
755
|
+
...(Object.keys(terminalPatch).length ? { web_terminal: terminalPatch } : {}),
|
|
756
|
+
};
|
|
757
|
+
const fields = [
|
|
758
|
+
...(body.echo_to_channel !== undefined ? ["web.echo_to_channel"] : []),
|
|
759
|
+
...Object.keys(publicPatch).map((k) => `web.public_link.${k}`),
|
|
760
|
+
...Object.keys(sessionPatch).map((k) => `web.session.local.${k}`),
|
|
761
|
+
...Object.keys(terminalPatch).map((k) => `web_terminal.${k}`),
|
|
762
|
+
];
|
|
763
|
+
if (fields.length) {
|
|
764
|
+
ctx.stage("web", { label: tn("sec_web"), impact: batchImpact(schema, fields, (k) => impactOf(schema, k)),
|
|
765
|
+
request: { method: "PUT", url: "/api/settings/fleet/web", body } });
|
|
766
|
+
} else if (staged.has("web")) ctx.unstage("web");
|
|
767
|
+
setMsg(tn("readyApply"));
|
|
768
|
+
};
|
|
769
|
+
const shown = (path) => {
|
|
770
|
+
const [top, key] = path.split(".");
|
|
771
|
+
const v = (top === "web" ? web : term)[key];
|
|
772
|
+
return v === undefined ? tn("notSet") : typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
773
|
+
};
|
|
774
|
+
return html`<div class="form s-web">
|
|
775
|
+
<p class="sub">${tn("webLead")}</p>
|
|
776
|
+
${overPublicLink ? html`<p class="note" role="status" id="webPublicRefused">${tn("webPublicRefused")}</p>` : null}
|
|
777
|
+
<fieldset class="s-web-fields" disabled=${overPublicLink}>
|
|
778
|
+
<section class="card"><h3>${tn("webSessionTitle")}</h3>
|
|
779
|
+
<p class="note">${tn("webSessionHint")}</p>
|
|
780
|
+
<div class="grid2">
|
|
781
|
+
<div class="field"><label for="webSessionAbsolute">${tn("sessionAbsolute")}</label><input id="webSessionAbsolute" type="number" min="1" max="720" step="any" placeholder=${tn("defaultHours", 48)}
|
|
782
|
+
class=${errors.abs ? "invalid" : ""} value=${f.abs} onInput=${(e) => set("abs")(e.target.value)} /><${Feedback} error=${errors.abs} /></div>
|
|
783
|
+
<div class="field"><label for="webSessionIdle">${tn("sessionIdle")}</label><input id="webSessionIdle" type="number" min="1" max="720" step="any" placeholder=${tn("defaultHours", 48)}
|
|
784
|
+
class=${errors.idle ? "invalid" : ""} value=${f.idle} onInput=${(e) => set("idle")(e.target.value)} /><${Feedback} error=${errors.idle} /></div>
|
|
785
|
+
</div>
|
|
786
|
+
</section>
|
|
787
|
+
<section class="card"><h3>${tn("webTerminalTitle")}</h3>
|
|
788
|
+
<p class="note">${tn("webTerminalHint")}</p>
|
|
789
|
+
<label class="check"><input id="webTerminalEnabled" type="checkbox" checked=${f.termOn} onChange=${(e) => set("termOn")(e.target.checked)} /><span>${tn("webTerminalEnabled")}</span><${Impact} schema=${schema} field="web_terminal.enabled" /></label>
|
|
790
|
+
<div class="grid2">
|
|
791
|
+
<div class="field"><label for="webTerminalTtl">${tn("webTerminalTtl")}</label><input id="webTerminalTtl" type="number" min="1" max="20" step="1" placeholder=${tn("defaultHours", 10)}
|
|
792
|
+
class=${errors.termTtl ? "invalid" : ""} value=${f.termTtl} onInput=${(e) => set("termTtl")(e.target.value)} /><${Feedback} error=${errors.termTtl} /></div>
|
|
793
|
+
</div>
|
|
794
|
+
</section>
|
|
795
|
+
<section class="card"><h3>${tn("webChat")}</h3>
|
|
796
|
+
<label class="check"><input id="webEchoToChannel" type="checkbox" checked=${f.echo} onChange=${(e) => set("echo")(e.target.checked)} /><span>${tn("webEcho")}</span><${Impact} schema=${schema} field="web.echo_to_channel" /></label>
|
|
797
|
+
<p class="note">${tn("webEchoHint")}</p>
|
|
798
|
+
<label class="check"><input id="publicWebLink" type="checkbox" checked=${f.pub} onChange=${(e) => set("pub")(e.target.checked)} /><span>${tn("publicLink")}</span><${Impact} schema=${schema} field="web.public_link.allow_public" /></label>
|
|
799
|
+
<p class="note">${tn("publicLinkHint")}</p>
|
|
800
|
+
<div class="grid2">
|
|
801
|
+
<div class="field"><label for="publicWebTtl">${tn("publicTtl")}</label><input id="publicWebTtl" type="number" min="1" max="480" class=${errors.ttl ? "invalid" : ""} value=${f.ttl} onInput=${(e) => set("ttl")(e.target.value)} /><${Feedback} error=${errors.ttl} /></div>
|
|
802
|
+
<div class="field"><label for="publicWebProtocol">${tn("publicProtocol")}</label><${Select} id="publicWebProtocol" value=${f.protocol} onChange=${set("protocol")} options=${["http2", "quic", "auto"]} /></div>
|
|
803
|
+
</div>
|
|
804
|
+
</section>
|
|
805
|
+
</fieldset>
|
|
806
|
+
<section class="card s-web-readonly"><h3>${tn("webReadOnlyTitle")}</h3>
|
|
807
|
+
<p class="note">${tn("webReadOnlyLead")}</p>
|
|
808
|
+
<dl class="s-web-ro">${WEB_READ_ONLY.map((k) => html`<div key=${k}><dt><code>${k}</code></dt><dd>${shown(k)}</dd></div>`)}</dl>
|
|
809
|
+
</section>
|
|
810
|
+
<div class="save-row"><button type="button" class="btn btn-primary" disabled=${!ready} onClick=${review}>${tn("reviewChanges")}</button>
|
|
811
|
+
${msg ? html`<span class="note" role="status">${msg}</span>` : null}</div>
|
|
812
|
+
</div>`;
|
|
813
|
+
}
|
|
814
|
+
|
|
717
815
|
/** Developer: the fleet model as YAML or JSON, to read, copy, download — or edit and apply as one operation. */
|
|
718
816
|
function Developer({ ctx }) {
|
|
719
817
|
const [fmt, setFmt] = useState("yaml");
|
|
@@ -108,8 +108,13 @@ export function orderedStatusMap(map, keys) {
|
|
|
108
108
|
for (const key of keys) { const v = map && map[key]; if (typeof v === "string" && v.trim()) out[key] = v.trim(); }
|
|
109
109
|
return Object.keys(out).length ? out : null;
|
|
110
110
|
}
|
|
111
|
-
//
|
|
112
|
-
|
|
111
|
+
// #1630: from the dashboard's own route (/ui/img/emoji/<id>). An emoji the fleet's servers do not have is not served
|
|
112
|
+
// there: then its :name: as text, never a broken image.
|
|
113
|
+
const EmojiImg = ({ url, alt }) => {
|
|
114
|
+
const [broken, setBroken] = useState("");
|
|
115
|
+
return broken === url ? html`<span class="se-alt">${alt}</span>`
|
|
116
|
+
: html`<img src=${url} alt=${alt} referrerpolicy="no-referrer" loading="lazy" onError=${() => setBroken(url)} />`;
|
|
117
|
+
};
|
|
113
118
|
|
|
114
119
|
/**
|
|
115
120
|
* One status_emojis map. `platform` and `channel` say where it applies; `previewBody(map)` is what the preview resolves.
|
|
@@ -24,8 +24,9 @@ export function changedFields(next, baseline) {
|
|
|
24
24
|
/** #1302: cross-instance notices. Unset, or a value the fleet does not know, means full. */
|
|
25
25
|
export const visibilityDefault = (defaults) => (VISIBILITY_MODES.includes(defaults && defaults.cross_instance_visibility) ? defaults.cross_instance_visibility : "full");
|
|
26
26
|
|
|
27
|
+
/** The backends whose turn end the reply guard can see (backend replyCompletionGuard: claude-code, codex and muse since #1510, kiro legacy/tui), with the MCP server it needs. */
|
|
27
28
|
export const replyGuardSupported = (backend, mode, kiroUi = "legacy") =>
|
|
28
|
-
mode === "mcp" && (
|
|
29
|
+
mode === "mcp" && (["claude-code", "codex", "muse"].includes(backend) || (backend === "kiro-cli" && ["legacy", "tui"].includes(kiroUi || "legacy")));
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* The backend picker's options. A current value it does not offer (the removed gemini-cli, #1280 review) stays first
|
|
@@ -75,7 +75,7 @@ register("settings", {
|
|
|
75
75
|
crossInstanceVisibility: "Bot-to-bot messages in topics",
|
|
76
76
|
crossInstanceVisibilityHint: "full: the whole message · summary: one line · hidden: nothing. Messages are always delivered; the Mirror Topic still shows them.",
|
|
77
77
|
replyCompletionGuard: "Require a delivered reply",
|
|
78
|
-
replyGuardSupported: "Available for Claude Code and Kiro legacy/TUI in MCP mode.",
|
|
78
|
+
replyGuardSupported: "Available for Claude Code, Codex, Muse and Kiro legacy/TUI in MCP mode.",
|
|
79
79
|
replyGuardUnsupported: "Stored but inactive: {0} in {1} mode does not support reply-drop recovery.",
|
|
80
80
|
logLevel: "Log level",
|
|
81
81
|
concurrency: "Startup concurrency",
|
|
@@ -217,7 +217,9 @@ register("settings", {
|
|
|
217
217
|
concurrencyWarning: "May exceed CPU capacity on smaller hosts",
|
|
218
218
|
staggerRange: "Must be between 0 and 30000",
|
|
219
219
|
staggerWarning: "Very low values can cause startup spikes",
|
|
220
|
-
sec_device: "
|
|
220
|
+
sec_device: "Preferences", syncedTitle: "Synced across your devices", syncedLead: "Changes here apply on every device where you are signed in.",
|
|
221
|
+
syncedPublicLink: "On the public link these are read-only: a change here stays in this browser.",
|
|
222
|
+
deviceTitle: "This device only", deviceLead: "The tour, text size, view fit, the sidebar shown or hidden, panel widths and desktop notifications stay on each device.",
|
|
221
223
|
sec_agents: "Agents",
|
|
222
224
|
sec_bots: "Connections",
|
|
223
225
|
sec_classic: "ClassicBot",
|
|
@@ -277,6 +279,24 @@ register("settings", {
|
|
|
277
279
|
keyRollbackFailed: "Rollback failed",
|
|
278
280
|
accessTitle: "Access",
|
|
279
281
|
webChat: "Web chat",
|
|
282
|
+
sec_web: "Web",
|
|
283
|
+
webLead: "How the web dashboard behaves. A change that loosens access (longer sessions, the browser terminal back on or for longer, the public link) waits for a fleet admin to confirm it in General; one that tightens it applies at once.",
|
|
284
|
+
webPublicRefused: "Web settings can't be changed over a temporary public link. Open the dashboard at its own address to change them.",
|
|
285
|
+
webSessionTitle: "Sign-in sessions",
|
|
286
|
+
webSessionHint: "For sign-ins at the dashboard's own address (this computer, the LAN, a tailnet). A temporary public link always keeps 4 h / 30 min. Shortening ends the sessions it now outlives; lengthening applies to new sign-ins and to an existing one's idle time on its next use. Empty = the default.",
|
|
287
|
+
sessionAbsolute: "Session length, hours (1–720)",
|
|
288
|
+
sessionIdle: "Sign out after unused for, hours (1–720)",
|
|
289
|
+
sessionRange: "Must be a number of hours from 1 to 720",
|
|
290
|
+
sessionIdleOver: "Must not exceed the session length",
|
|
291
|
+
defaultHours: "{0} (default)",
|
|
292
|
+
webTerminalTitle: "Browser terminal",
|
|
293
|
+
webTerminalHint: "The token-gated terminal /login opens to sign a CLI in. Off: /login refuses. Applies to the next /login.",
|
|
294
|
+
webTerminalEnabled: "Allow the browser terminal",
|
|
295
|
+
webTerminalTtl: "Terminal session minutes (1–20)",
|
|
296
|
+
webTerminalTtlRange: "Must be a whole number from 1 to 20",
|
|
297
|
+
webReadOnlyTitle: "Set in fleet.yaml",
|
|
298
|
+
webReadOnlyLead: "Read-only here: these decide what the dashboard listens on and exposes. Edit fleet.yaml, then run agend reload (web.preview_port and web.preview_origin take a restart of AgEnD).",
|
|
299
|
+
notSet: "not set (default)",
|
|
280
300
|
defaultsTitle: "Defaults",
|
|
281
301
|
classicBackend: "ClassicBot default backend",
|
|
282
302
|
whoUsesClassic: "Who can use ClassicBot",
|
|
@@ -420,7 +440,7 @@ register("settings", {
|
|
|
420
440
|
crossInstanceVisibility: "Agent 之間的訊息在主題中",
|
|
421
441
|
crossInstanceVisibilityHint: "full:完整訊息 · summary:一行摘要 · hidden:不顯示。訊息一律照常送達,Mirror Topic 也照常顯示。",
|
|
422
442
|
replyCompletionGuard: "要求送出回覆",
|
|
423
|
-
replyGuardSupported: "Claude Code 與 Kiro legacy/TUI 的 MCP 模式可用。",
|
|
443
|
+
replyGuardSupported: "Claude Code、Codex、Muse 與 Kiro legacy/TUI 的 MCP 模式可用。",
|
|
424
444
|
replyGuardUnsupported: "設定會保留,但不會生效:{0} 的 {1} 模式不支援回覆遺失補救。",
|
|
425
445
|
logLevel: "Log 等級",
|
|
426
446
|
concurrency: "啟動併發數",
|
|
@@ -562,7 +582,9 @@ register("settings", {
|
|
|
562
582
|
concurrencyWarning: "在較小的主機上可能超出 CPU 負荷",
|
|
563
583
|
staggerRange: "必須介於 0 到 30000",
|
|
564
584
|
staggerWarning: "數值太低可能造成啟動尖峰",
|
|
565
|
-
sec_device: "
|
|
585
|
+
sec_device: "偏好設定", syncedTitle: "在你所有的裝置同步", syncedLead: "這裡的變更會套用到你登入的每一台裝置。",
|
|
586
|
+
syncedPublicLink: "公開連結只能讀取這些設定:在這裡變更只會留在這個瀏覽器。",
|
|
587
|
+
deviceTitle: "只限這台裝置", deviceLead: "導覽、文字大小、視圖寬度、側欄顯示或隱藏、面板寬度與桌面通知,每台裝置各自保留。",
|
|
566
588
|
sec_agents: "Agent",
|
|
567
589
|
sec_bots: "連線",
|
|
568
590
|
sec_classic: "ClassicBot",
|
|
@@ -622,6 +644,24 @@ register("settings", {
|
|
|
622
644
|
keyRollbackFailed: "還原失敗",
|
|
623
645
|
accessTitle: "存取",
|
|
624
646
|
webChat: "網頁聊天",
|
|
647
|
+
sec_web: "網頁",
|
|
648
|
+
webLead: "網頁儀表板的行為。放寬存取的變更(延長 session、重新開啟或延長瀏覽器終端機、公開連結)需要 fleet admin 在 General 確認;收緊的變更會立即套用。",
|
|
649
|
+
webPublicRefused: "無法透過臨時公開連結變更網頁設定。請從儀表板本身的網址開啟後再變更。",
|
|
650
|
+
webSessionTitle: "登入 session",
|
|
651
|
+
webSessionHint: "適用於從儀表板本身網址登入的 session(本機、區域網路、tailnet)。臨時公開連結固定為 4 小時 / 30 分鐘。縮短會讓已超過新限制的 session 結束;延長會套用到新的登入,以及既有 session 下次使用時的閒置時間。留空 = 預設值。",
|
|
652
|
+
sessionAbsolute: "Session 時長,小時(1–720)",
|
|
653
|
+
sessionIdle: "閒置多久後登出,小時(1–720)",
|
|
654
|
+
sessionRange: "必須是 1 到 720 之間的小時數",
|
|
655
|
+
sessionIdleOver: "不能超過 session 時長",
|
|
656
|
+
defaultHours: "{0}(預設)",
|
|
657
|
+
webTerminalTitle: "瀏覽器終端機",
|
|
658
|
+
webTerminalHint: "/login 用來登入 CLI 的 token 保護終端機。關閉時 /login 會拒絕。下一次 /login 起生效。",
|
|
659
|
+
webTerminalEnabled: "允許瀏覽器終端機",
|
|
660
|
+
webTerminalTtl: "終端機 session 分鐘數(1–20)",
|
|
661
|
+
webTerminalTtlRange: "必須是 1 到 20 之間的整數",
|
|
662
|
+
webReadOnlyTitle: "在 fleet.yaml 設定",
|
|
663
|
+
webReadOnlyLead: "這裡僅供檢視:這些設定決定儀表板監聽和公開的內容。請編輯 fleet.yaml 後執行 agend reload(web.preview_port 與 web.preview_origin 需要重新啟動 AgEnD)。",
|
|
664
|
+
notSet: "未設定(預設)",
|
|
625
665
|
defaultsTitle: "預設值",
|
|
626
666
|
classicBackend: "ClassicBot 預設 backend",
|
|
627
667
|
whoUsesClassic: "誰可以使用 ClassicBot",
|