@songsid/agend 2.2.0-beta.8 → 2.2.0
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 +40 -24
- package/README.zh-TW.md +40 -24
- package/dist/agent-endpoint.js +3 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +7 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +3 -1
- package/dist/auth-api.js +15 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +33 -0
- package/dist/backend/antigravity.js +126 -18
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +3 -0
- package/dist/backend/claude-code.js +9 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +37 -1
- package/dist/backend/effort-metadata.js +86 -15
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +4 -0
- package/dist/backend/grok.js +14 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +18 -1
- package/dist/backend/kiro.js +35 -8
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +61 -0
- package/dist/backend/launch-effort.js +170 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +17 -0
- package/dist/backend/muse.js +86 -16
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +40 -1
- package/dist/backend/types.js +12 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +12 -0
- package/dist/channel/adapters/discord.js +49 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +18 -0
- package/dist/channel/adapters/telegram.js +24 -3
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +16 -0
- package/dist/cli.js +124 -24
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +5 -2
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.d.ts +86 -0
- package/dist/compact-watch.js +288 -0
- package/dist/compact-watch.js.map +1 -0
- package/dist/config-validator.js +39 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +13 -1
- package/dist/daemon.js +160 -13
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +8 -1
- package/dist/delivery-outbox.js +6 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/doctor.js +7 -0
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-context.d.ts +7 -0
- package/dist/fleet-manager.d.ts +160 -10
- package/dist/fleet-manager.js +837 -121
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +4 -3
- package/dist/forged-envelope-scan.js +4 -3
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
- package/dist/install-lock.d.ts +18 -1
- package/dist/install-lock.js +30 -3
- package/dist/install-lock.js.map +1 -1
- package/dist/instance-avatar.d.ts +5 -4
- package/dist/instance-avatar.js +12 -8
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-lifecycle.js +3 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.d.ts +38 -0
- package/dist/kiro-compact-pane.js +57 -0
- package/dist/kiro-compact-pane.js.map +1 -0
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- package/dist/launchd-handoff.d.ts +43 -0
- package/dist/launchd-handoff.js +56 -0
- package/dist/launchd-handoff.js.map +1 -0
- package/dist/locale.js +202 -30
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +10 -2
- package/dist/log-service.js.map +1 -1
- package/dist/log-steps.d.ts +26 -0
- package/dist/log-steps.js +215 -3
- package/dist/log-steps.js.map +1 -1
- package/dist/login-shell.d.ts +21 -0
- package/dist/login-shell.js +98 -0
- package/dist/login-shell.js.map +1 -0
- package/dist/outbound-handlers.d.ts +27 -0
- package/dist/outbound-handlers.js +120 -36
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +12 -0
- package/dist/outbound-schemas.js +15 -4
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/provider-probe.d.ts +29 -0
- package/dist/provider-probe.js +198 -2
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +11 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.d.ts +1 -1
- package/dist/quickstart-api.js +8 -6
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +12 -0
- package/dist/quickstart.js +36 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +86 -5
- package/dist/reply-buttons-controller.js +271 -7
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.d.ts +0 -2
- package/dist/restart-progress.js +0 -2
- package/dist/restart-progress.js.map +1 -1
- package/dist/sd-notify.d.ts +22 -1
- package/dist/sd-notify.js +63 -10
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +42 -26
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +24 -0
- package/dist/service-installer.js +49 -1
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +11 -0
- package/dist/service-plan.js +23 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tool-permissions.js +3 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +68 -2
- package/dist/topic-commands.js +141 -27
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +7 -2
- package/dist/transcript-jsonl.js +13 -7
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-sources.js +6 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-render.js +55 -10
- package/dist/ui/chat-store.js +69 -5
- package/dist/ui/chat-strings.js +28 -16
- package/dist/ui/chat-thread.js +132 -14
- package/dist/ui/chat-tour.js +17 -14
- package/dist/ui/panel-chat.js +16 -10
- package/dist/ui/panel-fleet.js +15 -2
- package/dist/ui/panel-log.js +55 -15
- package/dist/ui/panel-needs.js +10 -7
- package/dist/ui/panel-settings.js +58 -7
- package/dist/ui/settings-dialogs.js +11 -6
- package/dist/ui/settings-model.js +3 -1
- package/dist/ui/settings-strings.js +24 -10
- package/dist/ui/settings-token.js +7 -0
- package/dist/ui/settings-wizard.js +5 -3
- package/dist/ui/shared/app-i18n.js +1 -1
- package/dist/ui/shared/app-stream.js +4 -1
- package/dist/ui/shared/app.css +52 -10
- package/dist/ui/shared/header-tools.js +34 -14
- package/dist/ui/shared/instance-switch.js +5 -2
- package/dist/ui/shared/panel-view.js +15 -4
- package/dist/ui/shared/signin.js +15 -9
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/view-strings.js +10 -2
- package/dist/ui/signin.html +8 -3
- package/dist/unrecognised-prompt.js +2 -2
- package/dist/unrecognised-prompt.js.map +1 -1
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-launch.d.ts +48 -1
- package/dist/update-launch.js +159 -5
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/providers.d.ts +28 -8
- package/dist/usage/providers.js +96 -41
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +26 -9
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +44 -2
- package/dist/web-api.js +265 -57
- package/dist/web-api.js.map +1 -1
- package/dist/web-chat-history.d.ts +26 -1
- package/dist/web-chat-history.js +47 -8
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +39 -3
- package/dist/web-commands.js +22 -5
- package/dist/web-commands.js.map +1 -1
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-prefs.d.ts +18 -0
- package/dist/web-prefs.js +36 -4
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-session.d.ts +14 -0
- package/dist/web-session.js +10 -0
- package/dist/web-session.js.map +1 -1
- package/dist/web-upload.d.ts +119 -4
- package/dist/web-upload.js +252 -39
- package/dist/web-upload.js.map +1 -1
- package/launcher/install-admission.cjs +61 -6
- package/package.json +3 -3
|
@@ -10,7 +10,7 @@ register("settings", {
|
|
|
10
10
|
classicChannels: "ClassicBot Channels",
|
|
11
11
|
noClassic: "No ClassicBot channels — start one with /start in a chat.",
|
|
12
12
|
publicLink: "Offer temporary public web links",
|
|
13
|
-
publicLinkHint: "Explicit admin consent on every request. Cloudflare handles TLS; link users have full web-admin access.
|
|
13
|
+
publicLinkHint: "Explicit admin consent on every request. Cloudflare handles TLS; link users have full web-admin access. HTML previews get a second temporary tunnel unless web.preview_on_public_link is false.",
|
|
14
14
|
publicTtl: "Public link minutes (1–480)",
|
|
15
15
|
publicProtocol: "Tunnel protocol",
|
|
16
16
|
webEcho: "Web echo",
|
|
@@ -164,7 +164,9 @@ register("settings", {
|
|
|
164
164
|
wizardWaitingStart: "Waiting for a message in the group…",
|
|
165
165
|
wizardNoStartYet: "No group message yet — post one and press Detect again.",
|
|
166
166
|
wizardGeneralChannel: "General channel id",
|
|
167
|
-
wizardAdminUser: "Your user
|
|
167
|
+
wizardAdminUser: "Your user ID (required; becomes the first allowed user)",
|
|
168
|
+
wizardNeedDiscordAdmin: "Enter your Discord user ID: 17–20 digits, without a leading zero.",
|
|
169
|
+
wizardNeedTelegramAdmin: "Enter your Telegram user ID: 1–20 positive digits, without a leading zero.",
|
|
168
170
|
wizardNeedVerify: "Verify the token before continuing.",
|
|
169
171
|
wizardPlanLoading: "Building the plan…",
|
|
170
172
|
wizardWillWrite: "This will be written:",
|
|
@@ -176,7 +178,7 @@ register("settings", {
|
|
|
176
178
|
deleteBot: "Delete this bot",
|
|
177
179
|
noAllowedUsers: "no allowed users",
|
|
178
180
|
searchAll: "Search agents, bots, channels…",
|
|
179
|
-
accessMovedHint: "Who may use the bot is set on the connection itself — open Settings on \"{0}\" under Connections
|
|
181
|
+
accessMovedHint: "Who may use the bot is set on the connection itself — open Settings on \"{0}\" under Connections.",
|
|
180
182
|
applyRestartNeeded: "Saved — restart AgEnD to apply",
|
|
181
183
|
applyNoChange: "no change needed",
|
|
182
184
|
applyRestartHint: "This change needs AgEnD itself restarted. You can restart it here, or run /restart in your chat channel.",
|
|
@@ -219,6 +221,11 @@ register("settings", {
|
|
|
219
221
|
staggerRange: "Must be between 0 and 30000",
|
|
220
222
|
staggerWarning: "Very low values can cause startup spikes",
|
|
221
223
|
sec_device: "Preferences", syncedTitle: "Synced across your devices", syncedLead: "Changes here apply on every device where you are signed in.",
|
|
224
|
+
profileTitle: "Your name",
|
|
225
|
+
profileLeadBound: "Agents see your web messages as the Discord or Telegram account your sign-in code was sent to: its id, and its name unless you choose another here. It follows you to every device you sign in on. It is for recognition only: it grants nothing.",
|
|
226
|
+
profileLeadUnbound: "This browser was not signed in with a code sent to your Discord or Telegram account, so agents see your web messages as \"web-user\" (or the name you set here). Sign in with a code from /web to be recognised as your account.",
|
|
227
|
+
profileName: "Name", profileHelp: "Agents see: {0} · id {1}", profileSave: "Save", profileClear: "Use web-user", profileUseAccount: "Use {0}",
|
|
228
|
+
profileSaved: "Agents now see you as {0}", profileFailed: "Could not save the name",
|
|
222
229
|
syncedPublicLink: "On the public link these are read-only: a change here stays in this browser.",
|
|
223
230
|
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.",
|
|
224
231
|
sec_agents: "Agents",
|
|
@@ -281,7 +288,7 @@ register("settings", {
|
|
|
281
288
|
accessTitle: "Access",
|
|
282
289
|
webChat: "Web chat",
|
|
283
290
|
sec_web: "Web",
|
|
284
|
-
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.",
|
|
291
|
+
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 chat (General topic) or on the host; one that tightens it applies at once.",
|
|
285
292
|
webPublicRefused: "Web settings can't be changed over a temporary public link. Open the dashboard at its own address to change them.",
|
|
286
293
|
webSessionTitle: "Sign-in sessions",
|
|
287
294
|
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.",
|
|
@@ -365,7 +372,7 @@ register("settings", {
|
|
|
365
372
|
help1: "Agents: each agent's settings, Start/Stop, Pause/Wake, and New agent.",
|
|
366
373
|
help2: "Connections: who may use each bot, its binding, its status emojis, a new token; and provider API keys.",
|
|
367
374
|
help3: "ClassicBot: each room's backend, model and options.",
|
|
368
|
-
help4: "General: defaults for every agent
|
|
375
|
+
help4: "General: defaults for every agent and ClassicBot defaults. Web: the dashboard's sign-in sessions, browser terminal, web echo, Log and public link. Review changes stages them.",
|
|
369
376
|
help5: "Changes are staged, then applied together with Apply changes. Each shows what it costs: immediately, an agent restart, or an AgEnD restart.",
|
|
370
377
|
help6: "A change to access, secrets or connections waits for a fleet admin to confirm it in chat before it applies.",
|
|
371
378
|
},
|
|
@@ -401,7 +408,7 @@ register("settings", {
|
|
|
401
408
|
classicChannels: "ClassicBot 頻道",
|
|
402
409
|
noClassic: "沒有 ClassicBot 頻道 — 在聊天中用 /start 建立。",
|
|
403
410
|
publicLink: "提供臨時公開網頁連結",
|
|
404
|
-
publicLinkHint: "每次都需管理員明確啟用。Cloudflare 終止 TLS
|
|
411
|
+
publicLinkHint: "每次都需管理員明確啟用。Cloudflare 終止 TLS;登入者具有完整網頁管理權限。HTML 預覽會另開一條臨時通道,除非設定 web.preview_on_public_link: false。",
|
|
405
412
|
publicTtl: "公開連結分鐘數(1–480)",
|
|
406
413
|
publicProtocol: "Tunnel 協定",
|
|
407
414
|
webEcho: "Web 同步",
|
|
@@ -530,7 +537,9 @@ register("settings", {
|
|
|
530
537
|
wizardWaitingStart: "等待群組訊息中…",
|
|
531
538
|
wizardNoStartYet: "還沒收到群組訊息 —— 發一則之後再按一次。",
|
|
532
539
|
wizardGeneralChannel: "General 頻道 id",
|
|
533
|
-
wizardAdminUser: "
|
|
540
|
+
wizardAdminUser: "你的使用者 ID(必填;會成為第一個允許使用者)",
|
|
541
|
+
wizardNeedDiscordAdmin: "請輸入 Discord 使用者 ID:17–20 位數字,開頭不能是 0。",
|
|
542
|
+
wizardNeedTelegramAdmin: "請輸入 Telegram 使用者 ID:1–20 位正整數字,開頭不能是 0。",
|
|
534
543
|
wizardNeedVerify: "請先驗證 token 再繼續。",
|
|
535
544
|
wizardPlanLoading: "正在產生方案…",
|
|
536
545
|
wizardWillWrite: "將寫入:",
|
|
@@ -542,7 +551,7 @@ register("settings", {
|
|
|
542
551
|
deleteBot: "刪除這個機器人",
|
|
543
552
|
noAllowedUsers: "沒有允許的使用者",
|
|
544
553
|
searchAll: "搜尋 agent、機器人、頻道…",
|
|
545
|
-
accessMovedHint: "誰可以使用 bot 是在連線本身設定 ——
|
|
554
|
+
accessMovedHint: "誰可以使用 bot 是在連線本身設定 —— 請到「連線」開啟「{0}」的設定。",
|
|
546
555
|
applyRestartNeeded: "已儲存 —— 需要重新啟動 AgEnD 才會生效",
|
|
547
556
|
applyNoChange: "無需變更",
|
|
548
557
|
applyRestartHint: "此變更需要重新啟動 AgEnD 本身。可以在這裡重新啟動,或在聊天頻道執行 /restart。",
|
|
@@ -585,6 +594,11 @@ register("settings", {
|
|
|
585
594
|
staggerRange: "必須介於 0 到 30000",
|
|
586
595
|
staggerWarning: "數值太低可能造成啟動尖峰",
|
|
587
596
|
sec_device: "偏好設定", syncedTitle: "在你所有的裝置同步", syncedLead: "這裡的變更會套用到你登入的每一台裝置。",
|
|
597
|
+
profileTitle: "你的名字",
|
|
598
|
+
profileLeadBound: "agent 會把你從網頁送出的訊息,認成收到登入驗證碼的那個 Discord 或 Telegram 帳號:用它的 id,名字也用它的,除非你在這裡另外取名。它會跟著你到每一台登入的裝置。這只用來辨認,不代表任何權限。",
|
|
599
|
+
profileLeadUnbound: "這個瀏覽器不是用私訊到你 Discord 或 Telegram 帳號的驗證碼登入的,所以 agent 會把你的網頁訊息看成「web-user」(或你在這裡設定的名字)。用 /web 的驗證碼登入,就能被認成你的帳號。",
|
|
600
|
+
profileName: "名字", profileHelp: "agent 看到:{0} · id {1}", profileSave: "儲存", profileClear: "改回 web-user", profileUseAccount: "改用 {0}",
|
|
601
|
+
profileSaved: "agent 現在會看到你是 {0}", profileFailed: "無法儲存名字",
|
|
588
602
|
syncedPublicLink: "公開連結只能讀取這些設定:在這裡變更只會留在這個瀏覽器。",
|
|
589
603
|
deviceTitle: "只限這台裝置", deviceLead: "導覽、文字大小、視圖寬度、側欄顯示或隱藏、面板寬度與桌面通知,每台裝置各自保留。",
|
|
590
604
|
sec_agents: "Agent",
|
|
@@ -647,7 +661,7 @@ register("settings", {
|
|
|
647
661
|
accessTitle: "存取",
|
|
648
662
|
webChat: "網頁聊天",
|
|
649
663
|
sec_web: "網頁",
|
|
650
|
-
webLead: "網頁儀表板的行為。放寬存取的變更(延長 session、重新開啟或延長瀏覽器終端機、公開連結)需要 fleet admin
|
|
664
|
+
webLead: "網頁儀表板的行為。放寬存取的變更(延長 session、重新開啟或延長瀏覽器終端機、公開連結)需要 fleet admin 在聊天(General topic)或主機上確認;收緊的變更會立即套用。",
|
|
651
665
|
webPublicRefused: "無法透過臨時公開連結變更網頁設定。請從儀表板本身的網址開啟後再變更。",
|
|
652
666
|
webSessionTitle: "登入 session",
|
|
653
667
|
webSessionHint: "適用於從儀表板本身網址登入的 session(本機、區域網路、tailnet)。臨時公開連結固定為 4 小時 / 30 分鐘。縮短會讓已超過新限制的 session 結束;延長會套用到新的登入,以及既有 session 下次使用時的閒置時間。留空 = 預設值。",
|
|
@@ -731,7 +745,7 @@ register("settings", {
|
|
|
731
745
|
help1: "Agent:每個 agent 的設定、啟動/停止、暫停/喚醒,以及新增 agent。",
|
|
732
746
|
help2: "連線:誰可以使用各個 bot、它的綁定、狀態表情、新的 token;以及供應商 API key。",
|
|
733
747
|
help3: "ClassicBot:每個房間的 backend、model 與選項。",
|
|
734
|
-
help4: "一般:所有 agent
|
|
748
|
+
help4: "一般:所有 agent 的預設值與 ClassicBot 預設值。網頁:儀表板的登入 session、瀏覽器終端機、網頁轉發、紀錄與公開連結。「檢查變更」會把它們暫存。",
|
|
735
749
|
help5: "變更會先暫存,再用「套用變更」一起套用。每項都會標示代價:立即生效、重新啟動 agent,或重新啟動 AgEnD。",
|
|
736
750
|
help6: "存取、密鑰或連線的變更,要等 fleet 管理員在聊天中確認後才會套用。",
|
|
737
751
|
},
|
|
@@ -10,6 +10,13 @@ import { api } from "./settings-confirm.js";
|
|
|
10
10
|
|
|
11
11
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
12
12
|
|
|
13
|
+
// UI feedback only: the plan and commit independently enforce this on the server.
|
|
14
|
+
export const adminUserIdPattern = (platform) => platform === "discord" ? "[1-9][0-9]{16,19}" : "[1-9][0-9]{0,19}";
|
|
15
|
+
export function adminUserIdError(platform, value) {
|
|
16
|
+
return typeof value === "string" && new RegExp(`^${adminUserIdPattern(platform)}$`).test(value)
|
|
17
|
+
? null : tn(platform === "discord" ? "wizardNeedDiscordAdmin" : "wizardNeedTelegramAdmin");
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
/** Who this token is: { valid, username?, reason? } — one outbound check, the token sent once and not kept. */
|
|
14
21
|
export async function verifyBotToken(platform, token) {
|
|
15
22
|
const res = await api("/api/settings/quickstart/probe", { method: "POST", body: JSON.stringify({ action: "verify", platform, token }) })
|
|
@@ -10,7 +10,7 @@ import { api } from "./settings-confirm.js";
|
|
|
10
10
|
import { startOperation } from "./settings-apply.js";
|
|
11
11
|
import { BACKENDS, DEFAULT_SCHEMA, impactOf, toYaml } from "./settings-model.js";
|
|
12
12
|
import { Drawer, Select } from "./settings-dialogs.js";
|
|
13
|
-
import { botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-token.js";
|
|
13
|
+
import { adminUserIdError, adminUserIdPattern, botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-token.js";
|
|
14
14
|
import { DiscordServerPicker } from "./settings-discord.js";
|
|
15
15
|
|
|
16
16
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -90,6 +90,8 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
90
90
|
if (w.step === 2) { setW({ ...w, step: 3 }); return; }
|
|
91
91
|
if (w.step === 3) {
|
|
92
92
|
if (!w.identity || !w.identity.valid) { setErr(tn("wizardNeedVerify")); return; }
|
|
93
|
+
const adminError = adminUserIdError(w.platform, w.admin_user_id);
|
|
94
|
+
if (adminError) { setErr(adminError); return; }
|
|
93
95
|
const at = rev.current;
|
|
94
96
|
setBusy(tn("wizardPlanLoading"));
|
|
95
97
|
const res = await post("/api/settings/quickstart/plan", input());
|
|
@@ -146,12 +148,12 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
146
148
|
<${DiscordServerPicker} idPrefix="wz" token=${w.identity && w.identity.valid ? w.token : ""} bot=${w.identity && botHandle(w.identity.username)}
|
|
147
149
|
invite=${w.identity && w.identity.invite} portal=${w.identity && w.identity.portal} guilds=${w.guilds} onGuilds=${(g) => setW((x) => ({ ...x, guilds: g }))}
|
|
148
150
|
guild=${w.guild_id} onGuild=${set("guild_id")} channel=${w.general_channel_id} onChannel=${set("general_channel_id")} />
|
|
149
|
-
<div class="field"><label for="wz-user">${tn("wizardAdminUser")}</label><input id="wz-user" type="text" placeholder=${tn("discordUserPlaceholder")} value=${w.admin_user_id} onInput=${(e) => set("admin_user_id")(e.target.value.trim())} /></div>`
|
|
151
|
+
<div class="field"><label for="wz-user">${tn("wizardAdminUser")}</label><input id="wz-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(w.platform)} placeholder=${tn("discordUserPlaceholder")} value=${w.admin_user_id} onInput=${(e) => set("admin_user_id")(e.target.value.trim())} /></div>`
|
|
150
152
|
: html`
|
|
151
153
|
<div class="field"><label for="wz-group">${tn("groupIdField")}</label><input id="wz-group" type="text" placeholder="-1001234567890" value=${w.group_id} onInput=${(e) => set("group_id")(e.target.value.trim())} />
|
|
152
154
|
<p class="note">${tn("wizardDetectHint")}</p></div>
|
|
153
155
|
<div class="dlg-inline-actions"><button type="button" class="btn btn-sm" disabled=${!!busy} onClick=${detect}>${tn("wizardDetect")}</button></div>
|
|
154
|
-
<div class="field"><label for="wz-user">${tn("wizardAdminUser")}</label><input id="wz-user" type="text" placeholder=${tn("telegramUserPlaceholder")} value=${w.admin_user_id} onInput=${(e) => set("admin_user_id")(e.target.value.trim())} /></div>`}`;
|
|
156
|
+
<div class="field"><label for="wz-user">${tn("wizardAdminUser")}</label><input id="wz-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(w.platform)} placeholder=${tn("telegramUserPlaceholder")} value=${w.admin_user_id} onInput=${(e) => set("admin_user_id")(e.target.value.trim())} /></div>`}`;
|
|
155
157
|
} else {
|
|
156
158
|
body = !w.plan ? html`<p class="note">${tn("wizardPlanLoading")}</p>` : html`
|
|
157
159
|
<p><strong>${tn("wizardWillWrite")}</strong></p>
|
|
@@ -98,7 +98,7 @@ register("app", {
|
|
|
98
98
|
theme: "主題", themeSystem: "跟隨系統", themeLight: "淺色", themeDark: "深色", language: "語言",
|
|
99
99
|
previews: "允許預覽 HTML", tour: "導覽", tourTitle: "再看一次網頁聊天導覽",
|
|
100
100
|
needsYou: "等你回應", approxNote: "從終端畫面判讀,僅供參考", chat: "聊天", details: "詳細資訊", log: "紀錄", instanceViews: "{0} 的檢視",
|
|
101
|
-
textSize: "字級:{0}", textSize_s: "小", textSize_m: "中", textSize_l: "大", textSize_fit: "
|
|
101
|
+
textSize: "字級:{0}", textSize_s: "小", textSize_m: "中", textSize_l: "大", textSize_fit: "填滿", usage: "用量", usageHide: "隱藏用量",
|
|
102
102
|
statusRunning: "執行中", statusStopped: "已停止", statusPaused: "已暫停", statusCrashed: "已當掉", statusWorking: "工作中", statusStuck: "似乎卡住", statusIdle: "閒置",
|
|
103
103
|
connReconnecting: "連不上 AgEnD——可能正在重新啟動。正在重新連線…", connReconnectingIn: "連不上 AgEnD——可能正在重新啟動。{0} 秒後再試。",
|
|
104
104
|
hydrating: "正在載入目前的狀態…", hydrateFailed: "無法載入目前的狀態(提示、送達標記)。",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
// - suspend()/resume(): the page is leaving (pagehide) / came back from the back-forward cache.
|
|
31
31
|
|
|
32
32
|
// #1218: steps_tick — an instance's transcript moved while a Log watches it (SSE only: a poll page asks the Log itself).
|
|
33
|
-
const EVENTS = ["status", "message", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "steps_tick"];
|
|
33
|
+
const EVENTS = ["status", "message", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "steps_tick", "clock"];
|
|
34
34
|
const MAX_HELD = 1000; // live events held during one catch-up read; more → read again afterwards
|
|
35
35
|
const RETRY_DELAYS = [1000, 2000, 4000]; // after the first failed catch-up read; then it waits for Retry
|
|
36
36
|
const POLL_LIMIT_MS = 10000; // a poll that has not answered by then counts as failed
|
|
@@ -114,6 +114,7 @@ export function createStream(opts) {
|
|
|
114
114
|
if (closed || mine !== pollSeq || !r.ok) return false;
|
|
115
115
|
const d = await r.json();
|
|
116
116
|
if (closed || mine !== pollSeq) return false;
|
|
117
|
+
if (typeof d.now === "number") emit("clock", { now: d.now }); // #1823: read just now, handed over at once
|
|
117
118
|
if (d.status) emit("status", d.status);
|
|
118
119
|
for (const m of d.messages || []) emit("message", m);
|
|
119
120
|
if (typeof d.cursor === "string") state.cursor = d.cursor;
|
|
@@ -209,6 +210,8 @@ export function createStream(opts) {
|
|
|
209
210
|
try { data = JSON.parse(e.data); } catch { return; }
|
|
210
211
|
if (!spoke) { spoke = true; alive(); if (again) catchUp(); }
|
|
211
212
|
else alive();
|
|
213
|
+
// #1823: a clock reading is about now, not about order: it is never held behind a catch-up (it would be stale).
|
|
214
|
+
if (name === "clock") { emit(name, data); return; }
|
|
212
215
|
live(name, data, name === "message" ? e.lastEventId : null);
|
|
213
216
|
});
|
|
214
217
|
}
|
package/dist/ui/shared/app.css
CHANGED
|
@@ -69,10 +69,12 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
69
69
|
.sidebar {
|
|
70
70
|
width: var(--sidebar-w); flex-shrink: 0; display: flex; flex-direction: column; background: var(--bg-sidebar);
|
|
71
71
|
position: relative; /* #1722: the resize handle sits on its right edge (the width above: tokens.css, or this device's) */
|
|
72
|
-
padding:
|
|
72
|
+
padding: 0 0 env(safe-area-inset-bottom) env(safe-area-inset-left); /* the top inset is the head's, as the page header's is */
|
|
73
73
|
}
|
|
74
74
|
.sb-collapsed .sidebar { display: none; }
|
|
75
|
-
|
|
75
|
+
/* The brand row is the page header's row: the same height, padding and centre line (--header-h; .panel-head, .panel-title). */
|
|
76
|
+
.side-head { display: flex; align-items: center; gap: var(--s-1); box-sizing: content-box; min-height: calc(var(--header-h) - 2 * var(--s-1));
|
|
77
|
+
padding: max(var(--s-1), env(safe-area-inset-top)) var(--s-2) var(--s-1) var(--s-4); }
|
|
76
78
|
.brand { flex: 1; font-size: var(--fs-lg); font-weight: var(--fw-semibold); letter-spacing: -0.01em; }
|
|
77
79
|
.side-nav { display: flex; flex-direction: column; padding: var(--s-1) var(--s-2); gap: 1px; }
|
|
78
80
|
.side-row {
|
|
@@ -150,6 +152,10 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
150
152
|
.s-device { display: flex; flex-direction: column; gap: var(--s-3); }
|
|
151
153
|
.s-device .sub { margin: 0; color: var(--text-3); font-size: var(--fs-sm); }
|
|
152
154
|
.s-device-row { display: flex; gap: var(--s-2); }
|
|
155
|
+
/* #1827: the person's web name — its field takes the card's width; a refusal reads in the danger colour. */
|
|
156
|
+
.s-profile .field { display: flex; flex-direction: column; gap: var(--s-1); }
|
|
157
|
+
.s-profile .field input { max-width: 24rem; }
|
|
158
|
+
.s-profile .err { color: var(--danger); }
|
|
153
159
|
/* #1620: Settings → Web — the lead, and the read-only list of what is set in fleet.yaml. */
|
|
154
160
|
.s-web .sub { margin: 0; color: var(--text-3); font-size: var(--fs-sm); }
|
|
155
161
|
.s-web-fields:disabled select { opacity: 0.6; } /* over the public link: dimmed like the inputs (.field input:disabled) */
|
|
@@ -195,14 +201,17 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
195
201
|
}
|
|
196
202
|
.panel-head .sb-open { display: none; }
|
|
197
203
|
.sb-collapsed .panel-head .sb-open { display: inline-flex; }
|
|
198
|
-
|
|
204
|
+
/* One row of --header-h on every page (a phone's instance tabs wrap below it): the title, its status and badges on one centre line. */
|
|
205
|
+
.panel-title { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--s-3); min-height: calc(var(--header-h) - 2 * var(--s-1)); }
|
|
199
206
|
.panel-title h1 { font-size: var(--fs-lg); line-height: var(--lh-lg); font-weight: var(--fw-semibold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; outline: none; }
|
|
200
207
|
.panel-sub { color: var(--text-2); font-size: var(--fs-sm); white-space: nowrap; }
|
|
201
208
|
.status { display: inline-flex; align-items: center; gap: 6px; }
|
|
202
209
|
.panel-actions { display: flex; align-items: center; gap: var(--s-1); }
|
|
203
210
|
/* #1523 N2: the instance view switch — beside the title on a wide screen; on a phone, a row of tabs under the header. */
|
|
204
211
|
.inst-switch.seg { padding: 0; margin: 0; width: auto; max-width: none; flex-shrink: 0; }
|
|
205
|
-
.inst-switch .seg-item { min-height: 30px; }
|
|
212
|
+
.inst-switch .seg-item { min-height: 30px; flex-direction: column; justify-content: center; gap: 0; }
|
|
213
|
+
/* The label again in the current tab's weight, taking width but no height and never seen: the bar's size does not change with the tab. */
|
|
214
|
+
.inst-switch .seg-item::after { content: attr(data-label); font-weight: var(--fw-semibold); height: 0; overflow: hidden; visibility: hidden; pointer-events: none; user-select: none; }
|
|
206
215
|
/* #1523 N3 (Q2 = A): the text size, one custom property from the root's data-text-size (header-tools.js). The chat's
|
|
207
216
|
messages, Details and the Fleet tabs scale the type tokens by it; View's terminal scales itself (VIEW_SCALE). */
|
|
208
217
|
:root { --text-scale: 1; }
|
|
@@ -248,6 +257,10 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
248
257
|
.p-log .log-older, .p-log .log-start { text-align: center; margin: var(--s-2) 0; }
|
|
249
258
|
.p-log .log-off { padding: var(--s-6) var(--s-4); }
|
|
250
259
|
.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); }
|
|
260
|
+
/* The header's controls are one height, the icon buttons' (36 px): its small buttons, and the instance tabs when they share
|
|
261
|
+
its row (a phone puts them on a row of their own, under it). */
|
|
262
|
+
.panel-head .btn-sm { min-height: 36px; }
|
|
263
|
+
@media (min-width: 900px) { .panel-head .inst-switch .seg-item { min-height: 36px; } }
|
|
251
264
|
@media (max-width: 899px) {
|
|
252
265
|
.panel-head:has(.inst-switch) { flex-wrap: wrap; }
|
|
253
266
|
.inst-switch.seg { order: 3; flex-basis: 100%; display: grid; grid-template-columns: repeat(4, 1fr); padding: 0 0 var(--s-1); }
|
|
@@ -264,8 +277,7 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
264
277
|
.empty-title { color: var(--text); font-size: var(--fs-lg); font-weight: var(--fw-semibold); }
|
|
265
278
|
.empty-hint { font-size: var(--fs-sm); }
|
|
266
279
|
.empty .btn { margin-top: var(--s-3); }
|
|
267
|
-
/* #1519 P7: the first-run card (a fleet with no chat connection), above
|
|
268
|
-
.first-run-stack { width: 100%; max-width: var(--col); }
|
|
280
|
+
/* #1519 P7: the first-run card (a fleet with no chat connection), above a Settings list (#1854: no longer on Chat). */
|
|
269
281
|
.first-run h3 { display: flex; align-items: center; gap: var(--s-2); }
|
|
270
282
|
.first-run p { color: var(--text-2); font-size: var(--fs-sm); }
|
|
271
283
|
.error-state { display: flex; flex-direction: column; align-items: center; gap: var(--s-3); padding: var(--s-8) var(--s-4); color: var(--danger); text-align: center; }
|
|
@@ -370,6 +382,13 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
370
382
|
such as 「昨天 下午03:58」, ticks) — and a block body would stretch to it; fit-content keeps a short message short, on
|
|
371
383
|
the right under its meta, and a long one still wraps at the message's max-width. */
|
|
372
384
|
.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; }
|
|
385
|
+
/* A person's click on a reply button: the bubble shows 🔘 and the button's label (never the value the agent was given);
|
|
386
|
+
live, the label is a button that shows the reply its buttons were on. */
|
|
387
|
+
.click-chip { display: flex; }
|
|
388
|
+
.click-chip .click-link, .click-chip .click-text { display: inline-flex; align-items: baseline; gap: var(--s-1); padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; overflow-wrap: anywhere; }
|
|
389
|
+
.click-chip .click-link { cursor: pointer; }
|
|
390
|
+
.click-chip .click-link:hover, .click-chip .click-link:focus-visible { text-decoration: underline; }
|
|
391
|
+
.click-mark { flex: none; }
|
|
373
392
|
.msg.agent { align-self: stretch; }
|
|
374
393
|
/* #1672: a status line (another instance's message, a daemon notice): muted plain text, no bubble; a long one folds.
|
|
375
394
|
Its class is `status-line`, never `status`: that is the header's inline-flex badge class, and on a message it turned
|
|
@@ -400,10 +419,13 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
400
419
|
.text-card { margin-top: var(--s-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: var(--s-2) var(--s-3); }
|
|
401
420
|
.text-card .tc-head { display: flex; align-items: center; gap: var(--s-2); }
|
|
402
421
|
.text-card .tc-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-xs); font-family: var(--font-mono); color: var(--text-2); }
|
|
403
|
-
|
|
422
|
+
/* #1812: the scroller spans the card's padding box and insets its own content — 12px on both sides, from the border on
|
|
423
|
+
the left and from the scrollbar (or the border, with overlay scrollbars) on the right. A stable gutter keeps a
|
|
424
|
+
classic scrollbar off the content, and the content from shifting when the scrollbar comes and goes. */
|
|
425
|
+
.text-card .tc-body { margin: var(--s-2) calc(-1 * var(--s-3)) 0; padding: 0 var(--s-3); max-height: 480px; overflow: auto; scrollbar-gutter: stable; }
|
|
404
426
|
.text-card .tc-body[hidden] { display: none; }
|
|
405
427
|
.text-card .tc-pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-size: var(--fs-xs); }
|
|
406
|
-
.text-card .tc-table-wrap { overflow-x: auto; }
|
|
428
|
+
.text-card .tc-table-wrap { overflow-x: auto; padding-bottom: var(--s-3); }
|
|
407
429
|
.text-card .tc-table { border-collapse: collapse; font-size: var(--fs-xs); }
|
|
408
430
|
.text-card .tc-table th, .text-card .tc-table td { border: 1px solid var(--border); padding: 2px 6px; text-align: left; vertical-align: top; }
|
|
409
431
|
.text-card .tc-more { margin-top: var(--s-2); }
|
|
@@ -436,10 +458,17 @@ html.lb-open .scroller { overflow: hidden; }
|
|
|
436
458
|
.msg .md ul { list-style: disc; } .msg .md ol { list-style: decimal; }
|
|
437
459
|
.msg .md li > ul, .msg .md li > ol { margin: 2px 0; }
|
|
438
460
|
.msg .md hr { border: 0; border-top: 1px solid var(--border); margin: var(--s-3) 0; }
|
|
439
|
-
|
|
461
|
+
/* #1812: a table too wide for its message scrolls in this wrapper (see the cell rule below); the bottom padding keeps an
|
|
462
|
+
overlay scrollbar off the last row. */
|
|
463
|
+
.msg .md .tbl { overflow-x: auto; margin: var(--s-2) 0 0; padding-bottom: var(--s-3); }
|
|
440
464
|
.msg .md table { border-collapse: collapse; font-size: var(--fs-sm); }
|
|
441
465
|
.msg .md th, .msg .md td { border: 1px solid var(--border); padding: 5px 9px; text-align: left; vertical-align: top; }
|
|
442
466
|
.msg .md th { background: var(--surface); font-weight: var(--fw-semibold); }
|
|
467
|
+
/* #1812: table cells keep their words whole. The message's `overflow-wrap: anywhere` also shrinks a cell's minimum
|
|
468
|
+
width, so auto table layout squeezed the columns and split words ("directo|r"); `break-word` breaks a word only when
|
|
469
|
+
it alone is wider than its line and leaves the minimum at whole words, so a wide table overflows into its scrolling
|
|
470
|
+
wrapper instead. A column keeps at least 5em: CJK text can break between any two characters. */
|
|
471
|
+
.msg .md th, .msg .md td, .text-card .tc-table th, .text-card .tc-table td { overflow-wrap: break-word; word-break: normal; min-width: 5em; }
|
|
443
472
|
.msg .md .al-c { text-align: center; } .msg .md .al-r { text-align: right; }
|
|
444
473
|
.msg .md h3, .msg .md h4, .msg .md h5, .msg .md h6 { margin: var(--s-3) 0 var(--s-2); font-size: var(--fs-md); font-weight: var(--fw-semibold); }
|
|
445
474
|
.msg .md .tk-k { color: var(--tk-k); } .msg .md .tk-s { color: var(--tk-s); } .msg .md .tk-n { color: var(--tk-n); } .msg .md .tk-c { color: var(--tk-c); font-style: italic; }
|
|
@@ -516,6 +545,10 @@ html.lb-open .scroller { overflow: hidden; }
|
|
|
516
545
|
.rb-btn { max-width: 100%; white-space: normal; text-align: left; }
|
|
517
546
|
.rb-btn.chosen, .rb-btn.chosen:disabled { border-color: var(--ok); color: var(--text); background: color-mix(in srgb, var(--ok) 14%, transparent); font-weight: var(--fw-semibold); opacity: 1; }
|
|
518
547
|
.rb-btn:disabled:not(.chosen) { opacity: 0.55; }
|
|
548
|
+
/* #1823: a poll — its closed buttons show the tally, so they stay readable; the time left keeps its width. */
|
|
549
|
+
.rb-row.poll .rb-btn:disabled { opacity: 0.85; }
|
|
550
|
+
.rb-row.poll .poll-left { font-variant-numeric: tabular-nums; }
|
|
551
|
+
.poll-voters { margin: var(--s-1) 0 0; padding-left: var(--s-4); color: var(--text-2); font-size: var(--fs-sm); }
|
|
519
552
|
.rb-note { font-size: var(--fs-xs); color: var(--text-3); }
|
|
520
553
|
.pv-menu { position: relative; }
|
|
521
554
|
.pv-menu > summary { list-style: none; cursor: pointer; padding: 2px var(--s-2); border-radius: var(--r-sm); color: var(--text-3); }
|
|
@@ -704,7 +737,15 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
704
737
|
.nav-chip { display: flex; align-items: center; gap: var(--s-2); cursor: pointer; color: var(--text); }
|
|
705
738
|
.nav-facet-reset { font-size: var(--fs-xs); }
|
|
706
739
|
|
|
707
|
-
|
|
740
|
+
/* One gap on every page (View's, #1448): the header's controls are the same size on every tab. */
|
|
741
|
+
.panel-actions .btn { gap: var(--s-1); }
|
|
742
|
+
/* Aa's names share one cell, only the current one shown: the button is as wide as its widest name on every page. */
|
|
743
|
+
.hd-text-names { display: inline-grid; }
|
|
744
|
+
.hd-text-names > span { grid-area: 1 / 1; visibility: hidden; }
|
|
745
|
+
.hd-text-names > .on { visibility: visible; }
|
|
746
|
+
.hd-slot { visibility: hidden; pointer-events: none; }
|
|
747
|
+
/* #1844 Fleet overview → Docs: the arrow says "opens a new tab". */
|
|
748
|
+
.hd-docs .hd-ext { color: var(--text-3); }
|
|
708
749
|
.v-term { flex: 1; min-height: 0; overflow: auto; scrollbar-gutter: stable; padding: 5px 6px; background: #000000; position: relative; }
|
|
709
750
|
.v-pre { margin: 0; min-width: 100%; min-height: 100%; font-family: var(--font-mono); line-height: 1.2; white-space: pre; color: #d0d0d0; }
|
|
710
751
|
.v-pre .ansi[data-b] { font-weight: var(--fw-bold); }
|
|
@@ -1041,6 +1082,7 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1041
1082
|
.cmd-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
|
|
1042
1083
|
.cmd-name { font-weight: var(--fw-semibold); }
|
|
1043
1084
|
.cmd-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--font-ui); color: var(--text-2); }
|
|
1085
|
+
.cmd-phase { color: var(--text); font-weight: var(--fw-semibold); }
|
|
1044
1086
|
.cmd-choices { display: flex; flex-wrap: wrap; gap: var(--s-1); max-height: 180px; overflow-y: auto; }
|
|
1045
1087
|
.cmd-choices .btn.current { border-color: var(--accent); color: var(--accent); }
|
|
1046
1088
|
.quick-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); font-size: var(--fs-xs); color: var(--text-2); }
|
|
@@ -13,6 +13,8 @@ import { UsageBody, UsageDialog } from "./usage-dialog.js";
|
|
|
13
13
|
|
|
14
14
|
export const TEXT_SIZE_KEY = "agend_text_size";
|
|
15
15
|
export const VIEW_FIT_KEY = "agend_view_fit";
|
|
16
|
+
/** #1869: set once this device's View Fit was put back (a size chosen on another page used to turn it off). */
|
|
17
|
+
export const VIEW_FIT_RESTORED_KEY = "agend_view_fit_1869";
|
|
16
18
|
const LEGACY_DENSITY_KEY = "agend_view_density";
|
|
17
19
|
export const TEXT_SIZES = ["s", "m", "l"];
|
|
18
20
|
/** View's terminal: its fitted size times this (S / M / L are View's old compact / – / comfortable). */
|
|
@@ -24,6 +26,7 @@ const write = (k, v) => { try { localStorage.setItem(k, v); } catch { /* this pa
|
|
|
24
26
|
/**
|
|
25
27
|
* This device's choice: `agend_text_size` (+ View's Fit), else — once — from View's `agend_view_density`:
|
|
26
28
|
* comfortable → L, compact → S (Fit off); fit, or nothing → M with View on Fit (View's default, as before).
|
|
29
|
+
* #1869: until then any page's S / M / L also turned View's Fit off, so a Fit off from before is put back once.
|
|
27
30
|
*/
|
|
28
31
|
export function loadTextSize() {
|
|
29
32
|
let size = read(TEXT_SIZE_KEY), fit = read(VIEW_FIT_KEY);
|
|
@@ -31,7 +34,11 @@ export function loadTextSize() {
|
|
|
31
34
|
const legacy = read(LEGACY_DENSITY_KEY);
|
|
32
35
|
size = legacy === "comfortable" ? "l" : legacy === "compact" ? "s" : "m";
|
|
33
36
|
fit = legacy === "comfortable" || legacy === "compact" ? "0" : "1";
|
|
34
|
-
write(TEXT_SIZE_KEY, size); write(VIEW_FIT_KEY, fit);
|
|
37
|
+
write(TEXT_SIZE_KEY, size); write(VIEW_FIT_KEY, fit); write(VIEW_FIT_RESTORED_KEY, "1");
|
|
38
|
+
}
|
|
39
|
+
if (read(VIEW_FIT_RESTORED_KEY) !== "1") {
|
|
40
|
+
if (fit === "0") { fit = "1"; write(VIEW_FIT_KEY, fit); }
|
|
41
|
+
write(VIEW_FIT_RESTORED_KEY, "1");
|
|
35
42
|
}
|
|
36
43
|
return { size, viewFit: fit !== "0" };
|
|
37
44
|
}
|
|
@@ -50,15 +57,22 @@ export function initTextSize() {
|
|
|
50
57
|
window.addEventListener("storage", (e) => { if (!e || e.key === TEXT_SIZE_KEY || e.key === VIEW_FIT_KEY || e.key === null) textSizeStore.set(loadTextSize()); });
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
|
-
/**
|
|
54
|
-
|
|
60
|
+
/**
|
|
61
|
+
* A size chosen anywhere is the size everywhere. View's Fit is left only from View's own Aa (`view`): a size chosen on
|
|
62
|
+
* another page keeps View on Fit (#1869).
|
|
63
|
+
*/
|
|
64
|
+
export function setTextSize(size, { view = false } = {}) {
|
|
55
65
|
if (!TEXT_SIZES.includes(size)) return;
|
|
56
|
-
write(TEXT_SIZE_KEY, size);
|
|
57
|
-
|
|
66
|
+
write(TEXT_SIZE_KEY, size);
|
|
67
|
+
if (view) write(VIEW_FIT_KEY, "0");
|
|
68
|
+
textSizeStore.set((s) => ({ size, viewFit: view ? false : s.viewFit }));
|
|
58
69
|
}
|
|
59
70
|
export function setViewFit() { write(VIEW_FIT_KEY, "1"); textSizeStore.set((s) => ({ ...s, viewFit: true })); }
|
|
60
71
|
|
|
61
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Aa: S → M → L → S; on View, Fit → S → M → L → Fit. Every page's button is as wide as its widest name (Fit's
|
|
74
|
+
* included): switching pages or sizes never moves the controls beside it.
|
|
75
|
+
*/
|
|
62
76
|
export function TextSizeButton({ view = false }) {
|
|
63
77
|
const st = useStore(textSizeStore);
|
|
64
78
|
const cur = view && st.viewFit ? "fit" : st.size;
|
|
@@ -66,8 +80,9 @@ export function TextSizeButton({ view = false }) {
|
|
|
66
80
|
const next = order[(order.indexOf(cur) + 1) % order.length];
|
|
67
81
|
const name = t(`app.textSize_${cur}`);
|
|
68
82
|
const label = t("app.textSize", name);
|
|
69
|
-
return html`<button type="button" class="btn btn-ghost btn-sm hd-text" onClick=${() => (next === "fit" ? setViewFit() : setTextSize(next))}
|
|
70
|
-
title=${label} aria-label=${label}><${Icon} name="type" size=${16} /><span class="hide-narrow" aria-hidden="true">${
|
|
83
|
+
return html`<button type="button" class="btn btn-ghost btn-sm hd-text" onClick=${() => (next === "fit" ? setViewFit() : setTextSize(next, { view }))}
|
|
84
|
+
title=${label} aria-label=${label}><${Icon} name="type" size=${16} /><span class="hide-narrow" aria-hidden="true"><span class="hd-text-names">${
|
|
85
|
+
["fit", ...TEXT_SIZES].map(k => html`<span key=${k} class=${k === cur ? "on" : undefined}>${t(`app.textSize_${k}`)}</span>`)}</span></span></button>`;
|
|
71
86
|
}
|
|
72
87
|
|
|
73
88
|
/** Whether the fleet serves the usage panel: the page says so (data-usage-panel); "0" only when turned off. */
|
|
@@ -76,14 +91,15 @@ export function usageOffered() {
|
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
// #1673: the rail. Wide enough for it beside the sidebar and the page: RAIL_MEDIA (followed as it changes). Shown or
|
|
79
|
-
// hidden is this device's choice (agend_usage_rail; "
|
|
94
|
+
// hidden is this device's choice (agend_usage_rail; "1" = shown, "0" = hidden) — a layout choice, not synced (#1665).
|
|
95
|
+
// #1854 (rank 7): a device that never chose starts with it hidden — a quiet first page; ◔ shows it (and remembers).
|
|
80
96
|
export const RAIL_MEDIA = "(min-width: 1200px)";
|
|
81
97
|
export const RAIL_KEY = "agend_usage_rail";
|
|
82
|
-
/** The pages whose header has ◔ — the rail is theirs. */
|
|
83
|
-
export const RAIL_PANELS = ["chat", "view", "details", "fleet"];
|
|
84
|
-
export const usageRailStore = createStore({ open:
|
|
85
|
-
/** A page load: this device's choice. */
|
|
86
|
-
export function initUsageRail() { usageRailStore.set({ open: read(RAIL_KEY)
|
|
98
|
+
/** The pages whose header has ◔ — the rail is theirs (Log's too: one frame on every tab of an instance). */
|
|
99
|
+
export const RAIL_PANELS = ["chat", "view", "log", "details", "fleet"];
|
|
100
|
+
export const usageRailStore = createStore({ open: false });
|
|
101
|
+
/** A page load: this device's choice; none made yet = hidden. */
|
|
102
|
+
export function initUsageRail() { usageRailStore.set({ open: read(RAIL_KEY) === "1" }); }
|
|
87
103
|
export function setUsageRail(open) { write(RAIL_KEY, open ? "1" : "0"); usageRailStore.set({ open: !!open }); }
|
|
88
104
|
const railQuery = () => { try { return typeof matchMedia === "function" ? matchMedia(RAIL_MEDIA) : null; } catch { return null; } };
|
|
89
105
|
/** Whether the screen is wide enough for the rail, now and as it changes. */
|
|
@@ -141,3 +157,7 @@ export function UsageButton({ navKey }) {
|
|
|
141
157
|
export function HeaderTools({ view = false, navKey }) {
|
|
142
158
|
return html`<${TextSizeButton} view=${view} /><${UsageButton} navKey=${navKey} />`;
|
|
143
159
|
}
|
|
160
|
+
/** Where a page with no control of its own after the shared ones (Log) keeps the room Chat's ⋯ takes: the same box on every tab. */
|
|
161
|
+
export function HeaderSlot() {
|
|
162
|
+
return html`<span class="icon-btn hd-slot" aria-hidden="true"></span>`;
|
|
163
|
+
}
|
|
@@ -6,10 +6,13 @@ import { html } from "./app-html.js";
|
|
|
6
6
|
import { t } from "./app-i18n.js";
|
|
7
7
|
import { chatPath, detailsPath, logPath, viewPath } from "./app-route.js";
|
|
8
8
|
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* current: "chat" | "view" | "log" | "details". #1218: Log sits between View and Details. `data-label`: each tab is as
|
|
11
|
+
* wide as its label in the current tab's weight (app.css), so the bar keeps its size whichever tab is current.
|
|
12
|
+
*/
|
|
10
13
|
export function InstanceSwitch({ name, current }) {
|
|
11
14
|
const seg = (key, href, label) => html`<a key=${key} class=${`seg-item${current === key ? " active" : ""}`} href=${href}
|
|
12
|
-
aria-current=${current === key ? "page" : undefined}>${label}</a>`;
|
|
15
|
+
aria-current=${current === key ? "page" : undefined} data-label=${label}>${label}</a>`;
|
|
13
16
|
return html`<nav class="inst-switch seg" aria-label=${t("app.instanceViews", name)}>
|
|
14
17
|
${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
18
|
</nav>`;
|
|
@@ -114,7 +114,9 @@ export { viewStore };
|
|
|
114
114
|
// ── The panel ──
|
|
115
115
|
|
|
116
116
|
// readStream: one stream of reads under a lease (read-stream.js; View's pane and roster use it).
|
|
117
|
-
|
|
117
|
+
// S / M / L: the fitted size kept within 12–22 px, then scaled. Fit (#1869): never larger than what fits — only a 6 px
|
|
118
|
+
// floor for legibility (a grid too wide for the box even at 6 px, e.g. 200 columns on a phone, still scrolls).
|
|
119
|
+
const MIN_PX = 12, MAX_PX = 22, FIT_FLOOR_PX = 6;
|
|
118
120
|
|
|
119
121
|
export function ViewPanel({ route, navKey }) {
|
|
120
122
|
const lease = useLease(navKey);
|
|
@@ -219,10 +221,11 @@ function Terminal({ name, lease, density }) {
|
|
|
219
221
|
const lh = parseFloat(pcs.lineHeight) / parseFloat(pcs.fontSize);
|
|
220
222
|
const lineH = Number.isFinite(lh) && lh > 0 ? lh : 1.2;
|
|
221
223
|
let size = Math.min(innerW / (g.cols * glyphRatio(p)), innerH / (g.rows * lineH)) * 0.995;
|
|
222
|
-
size = Math.max(
|
|
223
|
-
size = Math.max(8, Math.min(size, 32));
|
|
224
|
+
if (density === "fit") size = Math.max(FIT_FLOOR_PX, Math.min(size, MAX_PX));
|
|
225
|
+
else size = Math.max(8, Math.min(Math.max(MIN_PX, Math.min(size, MAX_PX)) * (VIEW_SCALE[density] || 1), 32));
|
|
224
226
|
g.key = key;
|
|
225
|
-
|
|
227
|
+
// A small change is skipped only when growing (the size applied still fits); a smaller box always shrinks it.
|
|
228
|
+
if (size >= g.applied && size - g.applied < 0.75) return;
|
|
226
229
|
g.applied = size;
|
|
227
230
|
p.style.fontSize = `${size.toFixed(2)}px`; // CSSOM, never a style attribute (#1300)
|
|
228
231
|
};
|
|
@@ -314,6 +317,8 @@ function EditProfile({ it, onClose }) {
|
|
|
314
317
|
const [f, setF] = useState({ display: it.display_name || "", role: it.role || "", desc: it.description || "" });
|
|
315
318
|
const [busy, setBusy] = useState(false);
|
|
316
319
|
const [msg, setMsg] = useState(null);
|
|
320
|
+
// #1630 follow-up: the uploaded picture is removed on Save (Cancel keeps it); a newly picked file wins over removing.
|
|
321
|
+
const [removePic, setRemovePic] = useState(false);
|
|
317
322
|
const file = useRef(null);
|
|
318
323
|
async function save() {
|
|
319
324
|
if (busy) return;
|
|
@@ -323,6 +328,7 @@ function EditProfile({ it, onClose }) {
|
|
|
323
328
|
const name = encodeURIComponent(it.instance_name);
|
|
324
329
|
const draft = { display_name: f.display, role: f.role, description: f.desc };
|
|
325
330
|
const picked = file.current && file.current.files && file.current.files[0];
|
|
331
|
+
const removing = removePic;
|
|
326
332
|
try {
|
|
327
333
|
const r = await fetch(`/api/profile/${name}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(draft) });
|
|
328
334
|
if (!lease.current()) return;
|
|
@@ -331,6 +337,9 @@ function EditProfile({ it, onClose }) {
|
|
|
331
337
|
if (picked) {
|
|
332
338
|
const ar = await fetch(`/api/avatar/${name}`, { method: "POST", headers: { "Content-Type": picked.type }, body: picked });
|
|
333
339
|
if (!ar.ok) { const e = await ar.json().catch(() => ({})); throw new Error(tn("avatarFailed", { error: e.error || ar.status })); }
|
|
340
|
+
} else if (removing) { // a newly picked file replaces it instead
|
|
341
|
+
const dr = await fetch(`/api/avatar/${name}`, { method: "DELETE" });
|
|
342
|
+
if (!dr.ok) { const e = await dr.json().catch(() => ({})); throw new Error(tn("avatarRemoveFailed", { error: e.error || dr.status })); }
|
|
334
343
|
}
|
|
335
344
|
if (!lease.current()) return;
|
|
336
345
|
setBusy(false);
|
|
@@ -345,6 +354,8 @@ function EditProfile({ it, onClose }) {
|
|
|
345
354
|
<label class="field"><span>${tn("role")}</span><input value=${f.role} onInput=${(e) => setF({ ...f, role: e.target.value })} /></label>
|
|
346
355
|
<label class="field"><span>${tn("description")}</span><textarea rows="3" value=${f.desc} onInput=${(e) => setF({ ...f, desc: e.target.value })}></textarea></label>
|
|
347
356
|
<label class="field"><span>${tn("avatar")}</span><input ref=${file} type="file" accept="image/png,image/jpeg,image/gif,image/webp" /></label>
|
|
357
|
+
${it.has_avatar ? html`<div class="field-row"><button type="button" class="btn btn-sm" aria-pressed=${removePic ? "true" : "false"}
|
|
358
|
+
onClick=${() => setRemovePic(!removePic)}>${tn(removePic ? "keepPicture" : "removePicture")}</button>${removePic ? html`<span class="muted">${tn("removePictureNote")}</span>` : null}</div>` : null}
|
|
348
359
|
</fieldset></form>
|
|
349
360
|
${msg ? html`<p class="err" role="alert">${msg}${msg === tn("signInToSave") ? html` <a class="link" href=${signInHref()}>${t("app.signIn")}</a>` : null}</p>` : null}
|
|
350
361
|
</${Dialog}>`;
|
package/dist/ui/shared/signin.js
CHANGED
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
const $ = (id) => document.getElementById(id);
|
|
12
12
|
const T = {
|
|
13
13
|
en: {
|
|
14
|
-
checking: "Checking your session…", title: "Sign in to AgEnD", lead: "Enter
|
|
14
|
+
checking: "Checking your session…", title: "Sign in to AgEnD", lead: "Enter your sign-in code.",
|
|
15
15
|
label: "One-time code", submit: "Sign in", working: "Signing in…",
|
|
16
|
-
hint: "Send /web
|
|
17
|
-
|
|
16
|
+
hint: "No code? Send /web in General, then press a sign-in button to get one.",
|
|
17
|
+
nobot: "No chat bot?", nobot_body: "Ask the operator to run agend web --code on the AgEnD computer.",
|
|
18
|
+
refused: "The code is invalid or no longer works. Send /web in General, then press a sign-in button for a new code.",
|
|
19
|
+
unconfirmed: "Sign-in could not be confirmed. Get a new code and try again; if it keeps failing, contact the operator.",
|
|
18
20
|
paused: "Too many wrong codes — sign-in is paused for a few minutes.",
|
|
19
21
|
network: "Could not reach AgEnD. Check the connection and try again.",
|
|
20
22
|
resend: "Send me a new code", resent: "A new code was sent to you privately in chat. Enter it above.",
|
|
@@ -22,10 +24,12 @@
|
|
|
22
24
|
resend_refused: "A new code could not be sent. Ask for one with /web.",
|
|
23
25
|
},
|
|
24
26
|
"zh-TW": {
|
|
25
|
-
checking: "正在確認登入狀態…", title: "登入 AgEnD", lead: "
|
|
27
|
+
checking: "正在確認登入狀態…", title: "登入 AgEnD", lead: "輸入登入碼。",
|
|
26
28
|
label: "一次性登入碼", submit: "登入", working: "登入中…",
|
|
27
|
-
hint: "
|
|
28
|
-
|
|
29
|
+
hint: "沒收到?在 General 傳 /web,再按登入按鈕取碼。",
|
|
30
|
+
nobot: "沒有聊天 bot?", nobot_body: "請管理員在 AgEnD 電腦執行 agend web --code。",
|
|
31
|
+
refused: "登入碼無效或已失效。請在 General 傳 /web,再按登入按鈕取新碼。",
|
|
32
|
+
unconfirmed: "登入暫時無法確認。請重新取碼再試;若仍失敗,請聯絡管理員。",
|
|
29
33
|
paused: "錯誤次數過多,登入暫停幾分鐘。",
|
|
30
34
|
network: "連不上 AgEnD,請檢查連線後再試。",
|
|
31
35
|
resend: "傳一組新的登入碼給我", resent: "新的登入碼已私訊給你,請在上方輸入。",
|
|
@@ -47,8 +51,8 @@
|
|
|
47
51
|
history.replaceState(null, "", here.pathname + here.search + here.hash);
|
|
48
52
|
}
|
|
49
53
|
} catch { /* nothing to clean */ }
|
|
50
|
-
// The command in the
|
|
51
|
-
// and a word, alpha.2 sweep); the text around it is text.
|
|
54
|
+
// The command in the "No chat bot?" help is a <code> that never breaks across lines (a break inside "--code" reads as
|
|
55
|
+
// two dashes and a word, alpha.2 sweep); the text around it is text.
|
|
52
56
|
const CMD = "agend web --code";
|
|
53
57
|
document.querySelectorAll("[data-i18n]").forEach((el) => {
|
|
54
58
|
const text = t(el.getAttribute("data-i18n"));
|
|
@@ -163,7 +167,9 @@
|
|
|
163
167
|
body: JSON.stringify({ code: $("code").value }),
|
|
164
168
|
});
|
|
165
169
|
if (r.ok) { clearBounce(); location.replace(nextTarget()); return; }
|
|
166
|
-
|
|
170
|
+
// #1854: a 5xx is the server failing to confirm the sign-in (503: a public link's notice did not go out), not a
|
|
171
|
+
// wrong code — say so. The code is spent either way: the way on is a new one.
|
|
172
|
+
msg.textContent = r.status === 429 ? t("paused") : r.status >= 500 ? t("unconfirmed") : t("refused");
|
|
167
173
|
} catch {
|
|
168
174
|
msg.textContent = t("network");
|
|
169
175
|
}
|
|
@@ -54,6 +54,7 @@ const P = {
|
|
|
54
54
|
panel: () => html`<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M14 4v16"/>`,
|
|
55
55
|
back: () => html`<path d="M19 12H5M12 19l-7-7 7-7"/>`,
|
|
56
56
|
forward: () => html`<path d="M5 12h14M12 5l7 7-7 7"/>`,
|
|
57
|
+
book: () => html`<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V3H6.5A2.5 2.5 0 0 0 4 5.5z"/><path d="M4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/>`,
|
|
57
58
|
external: () => html`<path d="M14 4h6v6M20 4l-9 9"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>`,
|
|
58
59
|
};
|
|
59
60
|
|