@songsid/agend 2.2.0-alpha.9 → 2.2.0-beta.10
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 +16 -9
- package/dist/agent-cli.js +15 -7
- package/dist/agent-cli.js.map +1 -1
- 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 +147 -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/antigravity.d.ts +3 -0
- package/dist/backend/antigravity.js +9 -0
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-carry.d.ts +22 -0
- package/dist/backend/claude-carry.js +170 -0
- package/dist/backend/claude-carry.js.map +1 -0
- package/dist/backend/claude-code.d.ts +65 -2
- package/dist/backend/claude-code.js +226 -33
- 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/credential-profile.d.ts +40 -3
- package/dist/backend/credential-profile.js +113 -5
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +1 -1
- package/dist/backend/effort-metadata.js +3 -2
- 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-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 +39 -3
- package/dist/backend/kiro.js +143 -19
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +33 -0
- package/dist/backend/launch-effort.js +123 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +84 -0
- package/dist/backend/muse.js +449 -53
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +33 -4
- package/dist/backend/types.js +2 -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/background-work-ended.d.ts +18 -0
- package/dist/background-work-ended.js +30 -0
- package/dist/background-work-ended.js.map +1 -0
- package/dist/cache-service.d.ts +2 -0
- package/dist/cache-service.js +2 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +26 -1
- package/dist/channel/adapters/discord.js +98 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +26 -2
- package/dist/channel/adapters/telegram.js +123 -19
- 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 +24 -13
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-edit-errors.d.ts +5 -0
- package/dist/channel/message-edit-errors.js +24 -0
- package/dist/channel/message-edit-errors.js.map +1 -0
- 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 +41 -3
- package/dist/cli.js +133 -28
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +6 -3
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +59 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +15 -2
- package/dist/cross-instance-envelope.js +29 -5
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +156 -1
- package/dist/daemon.js +764 -89
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +62 -7
- package/dist/delivery-outbox.js +309 -22
- 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-context.d.ts +10 -1
- package/dist/fleet-manager.d.ts +352 -10
- package/dist/fleet-manager.js +1925 -175
- 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 +92 -21
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +27 -3
- 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 +8 -3
- package/dist/general-knowledge/steering/core-rules.md +8 -7
- package/dist/host-memory.d.ts +10 -0
- package/dist/host-memory.js +33 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/human-delivery-receipts.d.ts +83 -0
- package/dist/human-delivery-receipts.js +194 -0
- package/dist/human-delivery-receipts.js.map +1 -0
- 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 +6 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +29 -6
- package/dist/instance-lifecycle.js +79 -28
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +3 -1
- package/dist/instructions.js +11 -8
- package/dist/instructions.js.map +1 -1
- 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/locale.js +204 -44
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +99 -0
- package/dist/log-service.js +243 -0
- package/dist/log-service.js.map +1 -0
- package/dist/log-steps.d.ts +169 -0
- package/dist/log-steps.js +858 -0
- package/dist/log-steps.js.map +1 -0
- package/dist/login-controller.d.ts +20 -3
- package/dist/login-controller.js +71 -19
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +5 -2
- package/dist/login-flows.js +5 -4
- package/dist/login-flows.js.map +1 -1
- package/dist/memory-pressure.d.ts +13 -1
- package/dist/memory-pressure.js +50 -10
- package/dist/memory-pressure.js.map +1 -1
- package/dist/needs-you-hub.js +2 -0
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +6 -0
- package/dist/needs-you-live.js +23 -3
- package/dist/needs-you-live.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 +100 -0
- package/dist/outbound-handlers.js +263 -96
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +26 -15
- package/dist/outbound-schemas.js +24 -7
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-link-progress.d.ts +1 -1
- package/dist/public-link-progress.js +8 -2
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.d.ts +101 -0
- package/dist/public-preview.js +236 -0
- package/dist/public-preview.js.map +1 -0
- package/dist/public-web-gateway.js +11 -4
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +24 -1
- package/dist/public-web-link.js +63 -9
- package/dist/public-web-link.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +3 -0
- package/dist/reply-buttons-controller.js +3 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- 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 +33 -0
- package/dist/restart-progress.js +30 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +46 -29
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +31 -0
- package/dist/service-installer.js +41 -2
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +85 -25
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +72 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +28 -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/spawn-gate.js +5 -1
- package/dist/spawn-gate.js.map +1 -1
- package/dist/spawn-hold-reply.d.ts +12 -0
- package/dist/spawn-hold-reply.js +39 -0
- package/dist/spawn-hold-reply.js.map +1 -0
- 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/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/tips.js +4 -4
- package/dist/tips.js.map +1 -1
- package/dist/tool-progress.js +3 -2
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.d.ts +42 -2
- package/dist/topic-commands.js +92 -7
- package/dist/topic-commands.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-jsonl.d.ts +80 -3
- package/dist/transcript-jsonl.js +147 -10
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.d.ts +19 -0
- package/dist/transcript-monitor.js +87 -15
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +78 -0
- package/dist/transcript-sources.js +375 -64
- 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/tunnel/cloudflared.d.ts +7 -15
- package/dist/tunnel/cloudflared.js +36 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +5 -1
- package/dist/tunnel/purpose-lane.js +14 -7
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.d.ts +5 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/turn-resume.d.ts +2 -0
- package/dist/turn-resume.js +1 -1
- package/dist/turn-resume.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-emojis.js +33 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +42 -2
- package/dist/ui/chat-store.js +2 -2
- package/dist/ui/chat-strings.js +38 -20
- package/dist/ui/chat-thread.js +69 -12
- package/dist/ui/chat-time.js +80 -0
- package/dist/ui/panel-chat.js +39 -10
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +199 -57
- package/dist/ui/panel-log.js +428 -0
- package/dist/ui/panel-needs.js +6 -2
- package/dist/ui/panel-settings.js +137 -32
- package/dist/ui/preview-panel.js +55 -15
- package/dist/ui/preview-status.js +28 -0
- package/dist/ui/preview.js +44 -3
- package/dist/ui/settings-dialogs.js +7 -2
- package/dist/ui/settings-model.js +2 -1
- package/dist/ui/settings-strings.js +46 -4
- package/dist/ui/settings-token.js +1 -1
- package/dist/ui/shared/app-i18n.js +43 -17
- package/dist/ui/shared/app-prefs.js +250 -0
- package/dist/ui/shared/app-route.js +7 -2
- package/dist/ui/shared/app-shell.js +165 -8
- package/dist/ui/shared/app-store.js +5 -1
- package/dist/ui/shared/app-stream.js +23 -5
- package/dist/ui/shared/app.css +159 -23
- package/dist/ui/shared/app.js +11 -1
- package/dist/ui/shared/fonts/README.md +1 -1
- package/dist/ui/shared/header-tools.js +55 -5
- package/dist/ui/shared/instance-nav.js +241 -45
- package/dist/ui/shared/instance-switch.js +4 -4
- 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/tokens.css +10 -1
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/usage-dialog.js +23 -5
- package/dist/ui/shared/view-strings.js +32 -4
- package/dist/ui/signin.html +1 -1
- package/dist/unrecognised-prompt.d.ts +23 -0
- package/dist/unrecognised-prompt.js +74 -0
- package/dist/unrecognised-prompt.js.map +1 -0
- package/dist/update-install.d.ts +28 -2
- package/dist/update-install.js +22 -4
- package/dist/update-install.js.map +1 -1
- package/dist/update-marker.d.ts +51 -0
- package/dist/update-marker.js +61 -0
- package/dist/update-marker.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/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +34 -5
- package/dist/usage/providers.js +160 -27
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.d.ts +6 -2
- package/dist/usage/statusline-usage.js +10 -4
- package/dist/usage/statusline-usage.js.map +1 -1
- 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 +49 -2
- package/dist/web-api.js +421 -31
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +2 -2
- package/dist/web-auth.js +9 -4
- package/dist/web-auth.js.map +1 -1
- package/dist/web-commands.d.ts +27 -4
- package/dist/web-commands.js +19 -4
- package/dist/web-commands.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-mentions.d.ts +44 -0
- package/dist/web-mentions.js +75 -0
- package/dist/web-mentions.js.map +1 -0
- package/dist/web-prefs.d.ts +63 -0
- package/dist/web-prefs.js +265 -0
- package/dist/web-prefs.js.map +1 -0
- package/dist/web-preview.d.ts +14 -1
- package/dist/web-preview.js +35 -13
- package/dist/web-preview.js.map +1 -1
- package/dist/web-reply-files.d.ts +45 -0
- package/dist/web-reply-files.js +223 -0
- package/dist/web-reply-files.js.map +1 -0
- package/dist/web-schedules.d.ts +12 -0
- package/dist/web-schedules.js +34 -0
- package/dist/web-schedules.js.map +1 -0
- package/dist/web-server-drain.d.ts +10 -0
- package/dist/web-server-drain.js +50 -0
- package/dist/web-server-drain.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 +23 -0
- package/dist/web-settings-patch.js +77 -0
- package/dist/web-settings-patch.js.map +1 -0
- package/dist/web-shell-routes.d.ts +5 -1
- package/dist/web-shell-routes.js +10 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-upload.d.ts +17 -4
- package/dist/web-upload.js +64 -13
- package/dist/web-upload.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/launchd.plist.ejs +3 -1
- package/templates/systemd.service.ejs +2 -1
|
@@ -15,6 +15,7 @@ register("settings", {
|
|
|
15
15
|
publicProtocol: "Tunnel protocol",
|
|
16
16
|
webEcho: "Web echo",
|
|
17
17
|
webEchoHint: "Copies web messages to the bound Telegram or Discord topic. ClassicBot rooms are excluded.",
|
|
18
|
+
webLogTab: "Log tab: each agent's steps", webLogTabHint: "Shows each instance's prompts, replies, tool calls and results, and fleet events in its Log tab, read from its own transcript only while the tab is open. Secret values are masked. Also over the public link. Turning it on waits for a fleet admin's confirmation.",
|
|
18
19
|
general: "General Settings",
|
|
19
20
|
advanced: "Advanced",
|
|
20
21
|
new: "New",
|
|
@@ -75,7 +76,7 @@ register("settings", {
|
|
|
75
76
|
crossInstanceVisibility: "Bot-to-bot messages in topics",
|
|
76
77
|
crossInstanceVisibilityHint: "full: the whole message · summary: one line · hidden: nothing. Messages are always delivered; the Mirror Topic still shows them.",
|
|
77
78
|
replyCompletionGuard: "Require a delivered reply",
|
|
78
|
-
replyGuardSupported: "Available for Claude Code and Kiro legacy/TUI in MCP mode.",
|
|
79
|
+
replyGuardSupported: "Available for Claude Code, Codex, Muse and Kiro legacy/TUI in MCP mode.",
|
|
79
80
|
replyGuardUnsupported: "Stored but inactive: {0} in {1} mode does not support reply-drop recovery.",
|
|
80
81
|
logLevel: "Log level",
|
|
81
82
|
concurrency: "Startup concurrency",
|
|
@@ -217,7 +218,9 @@ register("settings", {
|
|
|
217
218
|
concurrencyWarning: "May exceed CPU capacity on smaller hosts",
|
|
218
219
|
staggerRange: "Must be between 0 and 30000",
|
|
219
220
|
staggerWarning: "Very low values can cause startup spikes",
|
|
220
|
-
sec_device: "
|
|
221
|
+
sec_device: "Preferences", syncedTitle: "Synced across your devices", syncedLead: "Changes here apply on every device where you are signed in.",
|
|
222
|
+
syncedPublicLink: "On the public link these are read-only: a change here stays in this browser.",
|
|
223
|
+
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
224
|
sec_agents: "Agents",
|
|
222
225
|
sec_bots: "Connections",
|
|
223
226
|
sec_classic: "ClassicBot",
|
|
@@ -277,6 +280,24 @@ register("settings", {
|
|
|
277
280
|
keyRollbackFailed: "Rollback failed",
|
|
278
281
|
accessTitle: "Access",
|
|
279
282
|
webChat: "Web chat",
|
|
283
|
+
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.",
|
|
285
|
+
webPublicRefused: "Web settings can't be changed over a temporary public link. Open the dashboard at its own address to change them.",
|
|
286
|
+
webSessionTitle: "Sign-in sessions",
|
|
287
|
+
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.",
|
|
288
|
+
sessionAbsolute: "Session length, hours (1–720)",
|
|
289
|
+
sessionIdle: "Sign out after unused for, hours (1–720)",
|
|
290
|
+
sessionRange: "Must be a number of hours from 1 to 720",
|
|
291
|
+
sessionIdleOver: "Must not exceed the session length",
|
|
292
|
+
defaultHours: "{0} (default)",
|
|
293
|
+
webTerminalTitle: "Browser terminal",
|
|
294
|
+
webTerminalHint: "The token-gated terminal /login opens to sign a CLI in. Off: /login refuses. Applies to the next /login.",
|
|
295
|
+
webTerminalEnabled: "Allow the browser terminal",
|
|
296
|
+
webTerminalTtl: "Terminal session minutes (1–20)",
|
|
297
|
+
webTerminalTtlRange: "Must be a whole number from 1 to 20",
|
|
298
|
+
webReadOnlyTitle: "Set in fleet.yaml",
|
|
299
|
+
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).",
|
|
300
|
+
notSet: "not set (default)",
|
|
280
301
|
defaultsTitle: "Defaults",
|
|
281
302
|
classicBackend: "ClassicBot default backend",
|
|
282
303
|
whoUsesClassic: "Who can use ClassicBot",
|
|
@@ -385,6 +406,7 @@ register("settings", {
|
|
|
385
406
|
publicProtocol: "Tunnel 協定",
|
|
386
407
|
webEcho: "Web 同步",
|
|
387
408
|
webEchoHint: "將網頁訊息複製到綁定的 Telegram 或 Discord 主題,不包含 ClassicBot 群組。",
|
|
409
|
+
webLogTab: "紀錄分頁:每個 agent 的步驟", webLogTabHint: "在每個 instance 的「紀錄」分頁顯示提示、回覆、工具呼叫與結果,以及 fleet 事件;只在分頁開著時從它自己的對話紀錄讀取。機密值會遮蔽。公開連結也看得到。開啟需 fleet admin 確認。",
|
|
388
410
|
general: "一般設定",
|
|
389
411
|
advanced: "進階",
|
|
390
412
|
new: "新增",
|
|
@@ -420,7 +442,7 @@ register("settings", {
|
|
|
420
442
|
crossInstanceVisibility: "Agent 之間的訊息在主題中",
|
|
421
443
|
crossInstanceVisibilityHint: "full:完整訊息 · summary:一行摘要 · hidden:不顯示。訊息一律照常送達,Mirror Topic 也照常顯示。",
|
|
422
444
|
replyCompletionGuard: "要求送出回覆",
|
|
423
|
-
replyGuardSupported: "Claude Code 與 Kiro legacy/TUI 的 MCP 模式可用。",
|
|
445
|
+
replyGuardSupported: "Claude Code、Codex、Muse 與 Kiro legacy/TUI 的 MCP 模式可用。",
|
|
424
446
|
replyGuardUnsupported: "設定會保留,但不會生效:{0} 的 {1} 模式不支援回覆遺失補救。",
|
|
425
447
|
logLevel: "Log 等級",
|
|
426
448
|
concurrency: "啟動併發數",
|
|
@@ -562,7 +584,9 @@ register("settings", {
|
|
|
562
584
|
concurrencyWarning: "在較小的主機上可能超出 CPU 負荷",
|
|
563
585
|
staggerRange: "必須介於 0 到 30000",
|
|
564
586
|
staggerWarning: "數值太低可能造成啟動尖峰",
|
|
565
|
-
sec_device: "
|
|
587
|
+
sec_device: "偏好設定", syncedTitle: "在你所有的裝置同步", syncedLead: "這裡的變更會套用到你登入的每一台裝置。",
|
|
588
|
+
syncedPublicLink: "公開連結只能讀取這些設定:在這裡變更只會留在這個瀏覽器。",
|
|
589
|
+
deviceTitle: "只限這台裝置", deviceLead: "導覽、文字大小、視圖寬度、側欄顯示或隱藏、面板寬度與桌面通知,每台裝置各自保留。",
|
|
566
590
|
sec_agents: "Agent",
|
|
567
591
|
sec_bots: "連線",
|
|
568
592
|
sec_classic: "ClassicBot",
|
|
@@ -622,6 +646,24 @@ register("settings", {
|
|
|
622
646
|
keyRollbackFailed: "還原失敗",
|
|
623
647
|
accessTitle: "存取",
|
|
624
648
|
webChat: "網頁聊天",
|
|
649
|
+
sec_web: "網頁",
|
|
650
|
+
webLead: "網頁儀表板的行為。放寬存取的變更(延長 session、重新開啟或延長瀏覽器終端機、公開連結)需要 fleet admin 在 General 確認;收緊的變更會立即套用。",
|
|
651
|
+
webPublicRefused: "無法透過臨時公開連結變更網頁設定。請從儀表板本身的網址開啟後再變更。",
|
|
652
|
+
webSessionTitle: "登入 session",
|
|
653
|
+
webSessionHint: "適用於從儀表板本身網址登入的 session(本機、區域網路、tailnet)。臨時公開連結固定為 4 小時 / 30 分鐘。縮短會讓已超過新限制的 session 結束;延長會套用到新的登入,以及既有 session 下次使用時的閒置時間。留空 = 預設值。",
|
|
654
|
+
sessionAbsolute: "Session 時長,小時(1–720)",
|
|
655
|
+
sessionIdle: "閒置多久後登出,小時(1–720)",
|
|
656
|
+
sessionRange: "必須是 1 到 720 之間的小時數",
|
|
657
|
+
sessionIdleOver: "不能超過 session 時長",
|
|
658
|
+
defaultHours: "{0}(預設)",
|
|
659
|
+
webTerminalTitle: "瀏覽器終端機",
|
|
660
|
+
webTerminalHint: "/login 用來登入 CLI 的 token 保護終端機。關閉時 /login 會拒絕。下一次 /login 起生效。",
|
|
661
|
+
webTerminalEnabled: "允許瀏覽器終端機",
|
|
662
|
+
webTerminalTtl: "終端機 session 分鐘數(1–20)",
|
|
663
|
+
webTerminalTtlRange: "必須是 1 到 20 之間的整數",
|
|
664
|
+
webReadOnlyTitle: "在 fleet.yaml 設定",
|
|
665
|
+
webReadOnlyLead: "這裡僅供檢視:這些設定決定儀表板監聽和公開的內容。請編輯 fleet.yaml 後執行 agend reload(web.preview_port 與 web.preview_origin 需要重新啟動 AgEnD)。",
|
|
666
|
+
notSet: "未設定(預設)",
|
|
625
667
|
defaultsTitle: "預設值",
|
|
626
668
|
classicBackend: "ClassicBot 預設 backend",
|
|
627
669
|
whoUsesClassic: "誰可以使用 ClassicBot",
|
|
@@ -31,7 +31,7 @@ export function TokenField({ id, platform, value, onInput, identity, busy, onVer
|
|
|
31
31
|
const [shown, setShown] = useState(false);
|
|
32
32
|
return html`<div class="field token-field"><label for=${id}>${tn("wizardToken")}</label>
|
|
33
33
|
<div class="token-row">
|
|
34
|
-
<input id=${id} type=${shown ? "text" : "password"} autocomplete="off" autocapitalize="off" spellcheck
|
|
34
|
+
<input id=${id} type=${shown ? "text" : "password"} autocomplete="off" autocapitalize="off" spellcheck=${false} disabled=${!!busy}
|
|
35
35
|
placeholder=${platform === "discord" ? "MTA…" : "123456:ABC-DEF…"} value=${value} onInput=${(e) => onInput(e.target.value.trim())} />
|
|
36
36
|
<button type="button" class="icon-btn" aria-pressed=${shown ? "true" : "false"} title=${shown ? tn("tokenHide") : tn("tokenShow")}
|
|
37
37
|
aria-label=${shown ? tn("tokenHide") : tn("tokenShow")} onClick=${() => setShown(!shown)}><${Icon} name="eye" size=${16} /></button>
|
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
// #1408 §4: one dictionary for the app, namespaced (app.*, chat.*, fleet.*, …). A panel registers its own namespace
|
|
2
2
|
// when it loads; t("chat.send") resolves it in the chosen language and falls back to English, then to the key.
|
|
3
|
-
// The language is
|
|
3
|
+
// The language is the person's choice — kept on the host since #1665 and cached in this browser as agend_lang, the key
|
|
4
|
+
// every panel has used — else the device's.
|
|
4
5
|
|
|
5
6
|
const tables = { en: {}, "zh-TW": {} };
|
|
6
7
|
const listeners = new Set();
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
/** The device's own language: what applies when none is chosen. */
|
|
10
|
+
export function deviceLanguage() { return typeof navigator !== "undefined" && /^zh/i.test(navigator.language || "") ? "zh-TW" : "en"; }
|
|
11
|
+
function cacheLang(v) { try { if (v) localStorage.setItem("agend_lang", v); else localStorage.removeItem("agend_lang"); } catch { /* this page only */ } }
|
|
12
|
+
/**
|
|
13
|
+
* #1665: the signed-in page carries the host's values (<html data-prefs>) — the same rule as theme.js: the host's
|
|
14
|
+
* language is this page's (the browser's copy is refreshed, best effort); none chosen on the host and this device in
|
|
15
|
+
* step with it: the device's language; not in step yet, or no data-prefs: this browser's copy, else the device's.
|
|
16
|
+
*/
|
|
17
|
+
function initialLang() {
|
|
18
|
+
let d = null;
|
|
19
|
+
try { d = JSON.parse(document.documentElement.getAttribute("data-prefs") || "null"); } catch { d = null; }
|
|
20
|
+
if (d && typeof d === "object" && d.values && typeof d.values === "object") {
|
|
21
|
+
const host = d.values.lang;
|
|
22
|
+
if (host === "en" || host === "zh-TW") { cacheLang(host); return host; }
|
|
23
|
+
let inStep = false;
|
|
24
|
+
try { inStep = localStorage.getItem("agend_prefs_synced") === "1"; } catch { inStep = false; }
|
|
25
|
+
if (inStep) { cacheLang(null); return deviceLanguage(); }
|
|
26
|
+
}
|
|
27
|
+
try { const v = localStorage.getItem("agend_lang"); if (v === "en" || v === "zh-TW") return v; } catch { /* none */ }
|
|
28
|
+
return deviceLanguage();
|
|
10
29
|
}
|
|
11
|
-
let current =
|
|
30
|
+
let current = initialLang();
|
|
12
31
|
|
|
13
32
|
/** Add a namespace: `register("chat", { en: {...}, "zh-TW": {...} })`. */
|
|
14
33
|
export function register(ns, byLang) {
|
|
@@ -19,11 +38,14 @@ export function register(ns, byLang) {
|
|
|
19
38
|
|
|
20
39
|
export function lang() { return current; }
|
|
21
40
|
|
|
22
|
-
/**
|
|
23
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Choose a language; every subscriber re-renders (a new navigation lease, §4). `{ store: false }`: this page only —
|
|
43
|
+
* the host's "none chosen" arriving from another device (app-prefs.js), which leaves no copy in this browser.
|
|
44
|
+
*/
|
|
45
|
+
export function setLang(next, opts = {}) {
|
|
24
46
|
if (next !== "en" && next !== "zh-TW") return;
|
|
25
47
|
current = next;
|
|
26
|
-
|
|
48
|
+
if (opts.store !== false) cacheLang(next);
|
|
27
49
|
if (typeof document !== "undefined" && document.documentElement) document.documentElement.lang = next === "zh-TW" ? "zh-Hant" : "en";
|
|
28
50
|
for (const fn of listeners) fn(next);
|
|
29
51
|
}
|
|
@@ -38,13 +60,15 @@ export function t(key, ...values) {
|
|
|
38
60
|
|
|
39
61
|
register("app", {
|
|
40
62
|
en: {
|
|
41
|
-
brand: "AgEnD", fleet: "Fleet", view: "View", settings: "Settings", instances: "Instances", newInstance: "New instance",
|
|
63
|
+
brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet overview", view: "View", settings: "Settings", instances: "Instances", newInstance: "New instance",
|
|
42
64
|
collapse: "Hide the sidebar", expand: "Show the sidebar", openMenu: "Open the sidebar", closeMenu: "Close the sidebar",
|
|
65
|
+
resizeSidebar: "Sidebar width", resizeSidebarHint: "Drag to resize · double-click to reset",
|
|
66
|
+
names: "Names", namesNickname: "Nickname first", namesInstance: "Real name first",
|
|
43
67
|
theme: "Theme", themeSystem: "System", themeLight: "Light", themeDark: "Dark", language: "Language",
|
|
44
|
-
previews: "Allow HTML previews
|
|
45
|
-
needsYou: "needs you", approxNote: "Read from the terminal, so approximate", chat: "Chat", details: "Details", instanceViews: "Views of {0}",
|
|
46
|
-
textSize: "Text size: {0}", textSize_s: "S", textSize_m: "M", textSize_l: "L", textSize_fit: "Fit", usage: "Usage",
|
|
47
|
-
statusRunning: "running", statusStopped: "stopped", statusCrashed: "crashed", statusWorking: "working", statusStuck: "looks stuck", statusIdle: "idle",
|
|
68
|
+
previews: "Allow HTML previews", tour: "Tour", tourTitle: "Show the web chat tour again",
|
|
69
|
+
needsYou: "needs you", approxNote: "Read from the terminal, so approximate", chat: "Chat", details: "Details", log: "Log", instanceViews: "Views of {0}",
|
|
70
|
+
textSize: "Text size: {0}", textSize_s: "S", textSize_m: "M", textSize_l: "L", textSize_fit: "Fit", usage: "Usage", usageHide: "Hide usage",
|
|
71
|
+
statusRunning: "running", statusStopped: "stopped", statusPaused: "paused", statusCrashed: "crashed", statusWorking: "working", statusStuck: "looks stuck", statusIdle: "idle",
|
|
48
72
|
connReconnecting: "Can't reach AgEnD — it may be restarting. Reconnecting…", connReconnectingIn: "Can't reach AgEnD — it may be restarting. Trying again in {0} s.",
|
|
49
73
|
hydrating: "Loading what is open now…", hydrateFailed: "Could not load what is open now (prompts, ticks).",
|
|
50
74
|
skip: "Skip to the conversation", loadFailed: "This could not be loaded.", retry: "Try again", loading: "Loading…",
|
|
@@ -67,13 +91,15 @@ register("app", {
|
|
|
67
91
|
needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed",
|
|
68
92
|
},
|
|
69
93
|
"zh-TW": {
|
|
70
|
-
brand: "AgEnD", fleet: "Fleet", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance",
|
|
94
|
+
brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance",
|
|
71
95
|
collapse: "隱藏側邊欄", expand: "顯示側邊欄", openMenu: "打開側邊欄", closeMenu: "關閉側邊欄",
|
|
96
|
+
resizeSidebar: "側邊欄寬度", resizeSidebarHint: "拖曳調整寬度 · 按兩下恢復預設",
|
|
97
|
+
names: "名稱", namesNickname: "暱稱優先", namesInstance: "實際名稱優先",
|
|
72
98
|
theme: "主題", themeSystem: "跟隨系統", themeLight: "淺色", themeDark: "深色", language: "語言",
|
|
73
|
-
previews: "
|
|
74
|
-
needsYou: "等你回應", approxNote: "從終端畫面判讀,僅供參考", chat: "聊天", details: "詳細資訊", instanceViews: "{0} 的檢視",
|
|
75
|
-
textSize: "字級:{0}", textSize_s: "小", textSize_m: "中", textSize_l: "大", textSize_fit: "剛好填滿", usage: "用量",
|
|
76
|
-
statusRunning: "執行中", statusStopped: "已停止", statusCrashed: "已當掉", statusWorking: "工作中", statusStuck: "似乎卡住", statusIdle: "閒置",
|
|
99
|
+
previews: "允許預覽 HTML", tour: "導覽", tourTitle: "再看一次網頁聊天導覽",
|
|
100
|
+
needsYou: "等你回應", approxNote: "從終端畫面判讀,僅供參考", chat: "聊天", details: "詳細資訊", log: "紀錄", instanceViews: "{0} 的檢視",
|
|
101
|
+
textSize: "字級:{0}", textSize_s: "小", textSize_m: "中", textSize_l: "大", textSize_fit: "剛好填滿", usage: "用量", usageHide: "隱藏用量",
|
|
102
|
+
statusRunning: "執行中", statusStopped: "已停止", statusPaused: "已暫停", statusCrashed: "已當掉", statusWorking: "工作中", statusStuck: "似乎卡住", statusIdle: "閒置",
|
|
77
103
|
connReconnecting: "連不上 AgEnD——可能正在重新啟動。正在重新連線…", connReconnectingIn: "連不上 AgEnD——可能正在重新啟動。{0} 秒後再試。",
|
|
78
104
|
hydrating: "正在載入目前的狀態…", hydrateFailed: "無法載入目前的狀態(提示、送達標記)。",
|
|
79
105
|
skip: "跳到對話", loadFailed: "無法載入。", retry: "再試一次", loading: "載入中…",
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// #1665: the web preferences that follow the person between devices — kept on the host (/ui/prefs), with this browser's
|
|
2
|
+
// localStorage, under the keys every panel already reads, as the cache. Design: the comment on #1665.
|
|
3
|
+
//
|
|
4
|
+
// - Synced: theme, language, the sidebar (order, folds, filter, favorites — #1664) and the HTML-preview opt-in. The tour and the first-run
|
|
5
|
+
// card stay on each device, as every layout choice does.
|
|
6
|
+
// - The signed-in page is served with the host's values (<html data-prefs>). This module, imported before any panel
|
|
7
|
+
// reads its key, copies them into the cache: the host wins once it has a value — and, on a device already in step
|
|
8
|
+
// with it, a key the host does not have is the default (its stale copy here is cleared). theme.js and the
|
|
9
|
+
// dictionary did the same for the theme and language, before paint.
|
|
10
|
+
// - A change made here is cached at once and sent to the host (a short debounce gathers a burst, such as typing in the
|
|
11
|
+
// filter). Writes go one at a time; a key stays this page's own from the change until its write has ended, so no
|
|
12
|
+
// answer — a read or another write — that started before can put an older value back. Every answer carries the
|
|
13
|
+
// host's revision, and one older than what this page already has is ignored.
|
|
14
|
+
// - The public link, and the anonymous View reader, have no host write: theirs stays local.
|
|
15
|
+
// - The first load on a device (no agend_prefs_synced yet) uploads what it had locally for the keys the host does not
|
|
16
|
+
// have yet, each with onlyIfUnset — the first device to arrive wins — then reads the host's values back. Only once
|
|
17
|
+
// every upload got an answer and that read succeeded is the device marked: anything else tries again next load.
|
|
18
|
+
// - The status frame's prefs_rev: another device changed something; the host's values are read again and applied. A
|
|
19
|
+
// revision counts as seen only once its read succeeded (a failed one is tried again, at most every few seconds).
|
|
20
|
+
import { deviceLanguage, lang, setLang } from "./app-i18n.js";
|
|
21
|
+
import { appStore } from "./app-store.js";
|
|
22
|
+
|
|
23
|
+
const SYNCED_FLAG = "agend_prefs_synced";
|
|
24
|
+
const RETRY_MS = 5000;
|
|
25
|
+
const json = { toLocal: (v) => JSON.stringify(v), fromLocal: (s) => { try { return s == null ? undefined : JSON.parse(s); } catch { return undefined; } } };
|
|
26
|
+
/** Each synced preference: its cache key, and how a host value is cached (null: no entry) and read back. */
|
|
27
|
+
export const SYNCED = {
|
|
28
|
+
theme: { ls: "agend_theme", toLocal: (v) => (v === "light" || v === "dark" ? v : null), fromLocal: (s) => (s === "light" || s === "dark" ? s : undefined) },
|
|
29
|
+
lang: { ls: "agend_lang", toLocal: (v) => (v === "en" || v === "zh-TW" ? v : null), fromLocal: (s) => (s === "en" || s === "zh-TW" ? s : undefined) },
|
|
30
|
+
"nav.order": { ls: "agend_view_sidebar_order", ...json },
|
|
31
|
+
"nav.collapsed": { ls: "agend_instance_collapsed", ...json },
|
|
32
|
+
"nav.filter": { ls: "agend_instance_filter", ...json },
|
|
33
|
+
"nav.favorites": { ls: "agend_favorites", ...json }, // #1664
|
|
34
|
+
"preview.optIn": { ls: "agend_html_preview", toLocal: (v) => (v ? "on" : null), fromLocal: (s) => (s === "on" ? true : undefined) },
|
|
35
|
+
"names.primary": { ls: "agend_names_primary", toLocal: (v) => (v === "instance" ? v : null), fromLocal: (s) => (s === "instance" ? s : undefined) }, // #1722
|
|
36
|
+
};
|
|
37
|
+
const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
|
|
38
|
+
const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
39
|
+
|
|
40
|
+
function readLocal(key) { try { return localStorage.getItem(key); } catch { return null; } }
|
|
41
|
+
function writeLocal(key, s) { try { if (s == null) localStorage.removeItem(key); else localStorage.setItem(key, s); } catch { /* this page only */ } }
|
|
42
|
+
function cache(key, value) { const k = SYNCED[key]; writeLocal(k.ls, value == null ? null : k.toLocal(value)); }
|
|
43
|
+
const inStep = () => readLocal(SYNCED_FLAG) === "1";
|
|
44
|
+
|
|
45
|
+
function pageHost() {
|
|
46
|
+
try {
|
|
47
|
+
const d = JSON.parse(document.documentElement.getAttribute("data-prefs") || "null");
|
|
48
|
+
return d && typeof d === "object" && typeof d.rev === "number" && d.values && typeof d.values === "object" ? d : null;
|
|
49
|
+
} catch { return null; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const initial = typeof document !== "undefined" ? pageHost() : null;
|
|
53
|
+
let rev = initial ? initial.rev : null;
|
|
54
|
+
let host = initial ? initial.values : null;
|
|
55
|
+
const pending = new Map(); // key → value chosen here, not sent yet
|
|
56
|
+
const mine = new Map(); // key → value chosen here whose write is not over (pending or out)
|
|
57
|
+
const inflight = new Map(); // key → writes sent and not answered yet
|
|
58
|
+
let flushTimer = null;
|
|
59
|
+
let writes = Promise.resolve(); // one write at a time, in the order they were made
|
|
60
|
+
const listeners = new Set();
|
|
61
|
+
|
|
62
|
+
// The host's values into the cache, now — before the sidebar, the tour or the first-run card read their keys. A key
|
|
63
|
+
// the host does not have: the default, on a device in step with the host; this device's own (to upload) otherwise.
|
|
64
|
+
if (host) {
|
|
65
|
+
const step = inStep();
|
|
66
|
+
for (const key of Object.keys(SYNCED)) {
|
|
67
|
+
if (own(host, key)) cache(key, host[key]);
|
|
68
|
+
else if (step) cache(key, null);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** This page may change the host's preferences: the signed-in app, not over the public link. */
|
|
73
|
+
export function prefsWritable() {
|
|
74
|
+
return !!initial && !(typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The value in force for a key, in memory — never only in this browser's storage (a full storage must not bring back
|
|
78
|
+
* an old value): this page's own choice while its write is not over; else the host's; else, on a device in step with
|
|
79
|
+
* the host, the default ({ value: undefined }). null: not known here (no host values on this page, or a device not yet
|
|
80
|
+
* in step) — the caller reads this browser's copy.
|
|
81
|
+
*/
|
|
82
|
+
export function prefInForce(key) {
|
|
83
|
+
if (!own(SYNCED, key)) return null;
|
|
84
|
+
if (mine.has(key)) return { value: mine.get(key) };
|
|
85
|
+
if (host && own(host, key)) return { value: host[key] };
|
|
86
|
+
if (initial && inStep()) return { value: undefined };
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/** The host's value for a key as this page last saw it (undefined: not set there, or not known). */
|
|
90
|
+
export function hostPref(key) { return host && own(host, key) ? host[key] : undefined; }
|
|
91
|
+
/** Called with the keys another device changed, after they are cached and applied here. Returns the unsubscribe. */
|
|
92
|
+
export function onPrefsChanged(fn) { listeners.add(fn); return () => listeners.delete(fn); }
|
|
93
|
+
|
|
94
|
+
/** A host answer ({ values, rev }): a real view of the host (not an unreadable store's defaults)? */
|
|
95
|
+
const isView = (body) => !!body && typeof body.rev === "number" && !!body.values && typeof body.values === "object" && !body.unreadable;
|
|
96
|
+
/** Take a view as this page's — unless it is older than the one it has. True: taken. */
|
|
97
|
+
function take(body) {
|
|
98
|
+
if (!isView(body) || (rev !== null && body.rev < rev)) return false;
|
|
99
|
+
rev = body.rev;
|
|
100
|
+
host = body.values;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A preference chosen on this page: cached now, and kept on the host (a burst within 400 ms is one write). Not writable
|
|
106
|
+
* (the public link, the anonymous reader): this browser only, as before.
|
|
107
|
+
*/
|
|
108
|
+
export function savePref(key, value) {
|
|
109
|
+
if (!own(SYNCED, key)) return;
|
|
110
|
+
cache(key, value);
|
|
111
|
+
if (!prefsWritable()) return;
|
|
112
|
+
pending.set(key, value);
|
|
113
|
+
mine.set(key, value);
|
|
114
|
+
clearTimeout(flushTimer);
|
|
115
|
+
flushTimer = setTimeout(flush, 400);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** One write. ok: the host took it (its answer is a view; an older one than this page has is not taken). */
|
|
119
|
+
async function post(set, onlyIfUnset) {
|
|
120
|
+
const res = await fetch("/ui/prefs", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" },
|
|
121
|
+
body: JSON.stringify(onlyIfUnset ? { set, onlyIfUnset: true } : { set }) });
|
|
122
|
+
const body = await res.json().catch(() => null);
|
|
123
|
+
const ok = res.ok && isView(body);
|
|
124
|
+
if (ok) take(body);
|
|
125
|
+
return { ok, status: res.status };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function flush() {
|
|
129
|
+
flushTimer = null;
|
|
130
|
+
if (!pending.size) return writes;
|
|
131
|
+
const set = Object.fromEntries(pending);
|
|
132
|
+
const keys = [...pending.keys()];
|
|
133
|
+
pending.clear();
|
|
134
|
+
for (const k of keys) inflight.set(k, (inflight.get(k) || 0) + 1);
|
|
135
|
+
writes = writes.then(async () => {
|
|
136
|
+
let ok = false;
|
|
137
|
+
try { ok = (await post(set, false)).ok; } catch { ok = false; }
|
|
138
|
+
const released = new Set();
|
|
139
|
+
for (const k of keys) {
|
|
140
|
+
const n = (inflight.get(k) || 1) - 1;
|
|
141
|
+
if (n) inflight.set(k, n); else inflight.delete(k);
|
|
142
|
+
if (!n && !pending.has(k)) { mine.delete(k); released.add(k); }
|
|
143
|
+
}
|
|
144
|
+
// Each key whose last write this was, and not changed here again since, now shows the newest host view this page
|
|
145
|
+
// has — usually its own value; another device's newer one if that arrived while the write was out (held back then,
|
|
146
|
+
// and an older answer to this write cannot overrule it); refused or not sent, what the host has (read back first).
|
|
147
|
+
// Always applied: what the page shows came from this page's own choice, not from the cache, so the cache matching
|
|
148
|
+
// the host (a storage that refused the choice) says nothing about the screen.
|
|
149
|
+
if (!ok) await refresh();
|
|
150
|
+
align(released, { force: true });
|
|
151
|
+
});
|
|
152
|
+
return writes;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Read the host's values again and apply the ones that differ from this page's — not a key still being changed here
|
|
157
|
+
* (unsent, or its write not answered yet), and nothing from an answer older than the view this page has. True: the read
|
|
158
|
+
* succeeded (taken, or older than what this page already has — nothing to do).
|
|
159
|
+
*/
|
|
160
|
+
export async function refresh() {
|
|
161
|
+
if (!initial) return false;
|
|
162
|
+
let body = null;
|
|
163
|
+
try {
|
|
164
|
+
const res = await fetch("/ui/prefs", { credentials: "same-origin" });
|
|
165
|
+
body = res.ok ? await res.json() : null;
|
|
166
|
+
} catch { body = null; }
|
|
167
|
+
if (!isView(body)) return false;
|
|
168
|
+
const before = new Map(Object.keys(SYNCED).map((key) => [key, own(host, key) ? SYNCED[key].toLocal(host[key]) : null]));
|
|
169
|
+
if (take(body)) align(Object.keys(SYNCED), { before });
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Show the host view this page has for these keys — except a key still being changed here (unsent, or out). A key is
|
|
175
|
+
* applied when its host value moved in memory (`before`: the host view before this read), when this browser's copy
|
|
176
|
+
* differs, or always (`force`: this page's own write just ended). The copy alone never decides: a storage that refused
|
|
177
|
+
* a write keeps an old copy that may equal the host while the screen shows something else.
|
|
178
|
+
*/
|
|
179
|
+
function align(keys, opts = {}) {
|
|
180
|
+
const changed = new Set();
|
|
181
|
+
for (const key of keys) {
|
|
182
|
+
if (pending.has(key) || inflight.has(key)) continue;
|
|
183
|
+
const k = SYNCED[key];
|
|
184
|
+
const want = own(host, key) ? k.toLocal(host[key]) : null;
|
|
185
|
+
const copyDiffers = readLocal(k.ls) !== want;
|
|
186
|
+
const moved = !!opts.before && opts.before.get(key) !== want;
|
|
187
|
+
if (!copyDiffers && !moved && !opts.force) continue;
|
|
188
|
+
if (copyDiffers) writeLocal(k.ls, want);
|
|
189
|
+
changed.add(key);
|
|
190
|
+
}
|
|
191
|
+
if (changed.size) apply(changed);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function apply(changed) {
|
|
195
|
+
// Only what differs from what the page shows (a key re-applied after its own write may already be in force).
|
|
196
|
+
const T = globalThis.AgendTheme;
|
|
197
|
+
if (changed.has("theme") && typeof T !== "undefined") { const v = hostPref("theme") || "system"; if (typeof T.get !== "function" || T.get() !== v) T.set(v); }
|
|
198
|
+
if (changed.has("lang")) {
|
|
199
|
+
const l = hostPref("lang");
|
|
200
|
+
// None chosen any more: the device's own language, on this page only (no copy is kept for it).
|
|
201
|
+
if (l) { if (l !== lang()) setLang(l); } else if (deviceLanguage() !== lang()) setLang(deviceLanguage(), { store: false });
|
|
202
|
+
}
|
|
203
|
+
const P = globalThis.AgendPreview;
|
|
204
|
+
if (changed.has("preview.optIn") && P && typeof P.setOptIn === "function") {
|
|
205
|
+
const on = hostPref("preview.optIn") === true;
|
|
206
|
+
if (typeof P.optedIn !== "function" || P.optedIn() !== on) P.setOptIn(on);
|
|
207
|
+
}
|
|
208
|
+
for (const fn of [...listeners]) { try { fn(changed); } catch { /* one listener's error stops no other */ } }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Once per device: upload what this browser had for the keys the host has not got yet (each on its own, onlyIfUnset — a
|
|
213
|
+
* value the host refuses as invalid is simply not taken), then adopt the host's values. Marked only when every upload
|
|
214
|
+
* was answered (taken, or refused as invalid) and the read back succeeded; a request with no answer, a server error, a
|
|
215
|
+
* refused session or an unreadable store leaves the device unmarked, so the next load tries again.
|
|
216
|
+
*/
|
|
217
|
+
export async function migratePrefs() {
|
|
218
|
+
if (!prefsWritable() || inStep()) return false;
|
|
219
|
+
for (const key of Object.keys(SYNCED)) {
|
|
220
|
+
if (own(host, key)) continue;
|
|
221
|
+
const local = SYNCED[key].fromLocal(readLocal(SYNCED[key].ls));
|
|
222
|
+
if (local === undefined) continue;
|
|
223
|
+
let r;
|
|
224
|
+
try { r = await post({ [key]: local }, true); } catch { return false; }
|
|
225
|
+
if (!r.ok && r.status !== 400) return false;
|
|
226
|
+
}
|
|
227
|
+
if (!(await refresh())) return false;
|
|
228
|
+
writeLocal(SYNCED_FLAG, "1");
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** The app's boot ("full" mode): the first-load migration, then another device's changes at each status frame. */
|
|
233
|
+
export function bootPrefs() {
|
|
234
|
+
if (!initial) return;
|
|
235
|
+
// Each new revision the frames announce, read once (the store changes many times a second); one announced while a read
|
|
236
|
+
// is out is read as soon as it ends. Seen only once its read succeeded: after a failed read the next try waits for a
|
|
237
|
+
// later frame, at least RETRY_MS on.
|
|
238
|
+
let seen = rev, busy = false, failedAt = -Infinity;
|
|
239
|
+
const check = () => {
|
|
240
|
+
const r = appStore.get().prefsRev;
|
|
241
|
+
if (typeof r !== "number" || r === seen || busy) return;
|
|
242
|
+
if (r === rev) { seen = r; return; }
|
|
243
|
+
if (now() - failedAt < RETRY_MS) return;
|
|
244
|
+
busy = true;
|
|
245
|
+
refresh().then((ok) => { if (ok) seen = r; else failedAt = now(); }, () => { failedAt = now(); })
|
|
246
|
+
.finally(() => { busy = false; check(); });
|
|
247
|
+
};
|
|
248
|
+
appStore.subscribe(check);
|
|
249
|
+
migratePrefs();
|
|
250
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// a test runs both over one list of cases). Pure: no DOM, no history.
|
|
3
3
|
|
|
4
4
|
export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "config"];
|
|
5
|
-
export const SETTINGS_SECTIONS = ["device", "agents", "bots", "classic", "general", "advanced"]; // #1604: device first
|
|
5
|
+
export const SETTINGS_SECTIONS = ["device", "agents", "bots", "classic", "general", "web", "advanced"]; // #1604: device first
|
|
6
6
|
const MAX_INSTANCE_NAME = 128;
|
|
7
7
|
const PATH_SEPARATOR_OR_CONTROL = /[/\\\u0000-\u001f\u007f]/;
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ export function isSafeInstanceName(name) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* A path → the route it names: { panel: "chat" | "view" | "details", instance } | { panel: "fleet", tab } |
|
|
16
|
+
* A path → the route it names: { panel: "chat" | "view" | "details" | "log", instance } | { panel: "fleet", tab } |
|
|
17
17
|
* { panel: "settings", section } | null (not a page of the app: a full load). A malformed name is null too; the
|
|
18
18
|
* server answers it 400.
|
|
19
19
|
*/
|
|
@@ -28,6 +28,9 @@ export function parseRoute(pathname) {
|
|
|
28
28
|
if (m) return FLEET_TABS.includes(m[1]) ? { panel: "fleet", tab: m[1] } : null;
|
|
29
29
|
m = /^\/ui\/chat\/([^/]+)$/.exec(pathname);
|
|
30
30
|
if (m) return named("chat", m[1]);
|
|
31
|
+
// #1218: one instance's Log.
|
|
32
|
+
m = /^\/ui\/log\/([^/]+)$/.exec(pathname);
|
|
33
|
+
if (m) return named("log", m[1]);
|
|
31
34
|
if (pathname === "/view") return { panel: "view", instance: null };
|
|
32
35
|
m = /^\/view\/([^/]+)$/.exec(pathname);
|
|
33
36
|
if (m) return named("view", m[1]);
|
|
@@ -45,6 +48,7 @@ function named(panel, segment) {
|
|
|
45
48
|
export function chatPath(instance) { return `/ui/chat/${encodeURIComponent(instance)}`; }
|
|
46
49
|
export function viewPath(instance) { return instance ? `/view/${encodeURIComponent(instance)}` : "/view"; }
|
|
47
50
|
export function detailsPath(instance) { return `/ui/fleet/agent/${encodeURIComponent(instance)}`; }
|
|
51
|
+
export function logPath(instance) { return `/ui/log/${encodeURIComponent(instance)}`; }
|
|
48
52
|
export function fleetPath(tab) { return tab && tab !== "tasks" ? `/ui/fleet/${tab}` : "/ui/fleet"; }
|
|
49
53
|
export const NEEDS_PATH = "/ui/needs";
|
|
50
54
|
export function settingsPath(section) { return section && section !== "agents" ? `/settings/${section}` : "/settings"; }
|
|
@@ -55,6 +59,7 @@ export function routePath(route) {
|
|
|
55
59
|
if (route.panel === "needs") return NEEDS_PATH;
|
|
56
60
|
if (route.panel === "view") return viewPath(route.instance);
|
|
57
61
|
if (route.panel === "details") return detailsPath(route.instance);
|
|
62
|
+
if (route.panel === "log") return logPath(route.instance);
|
|
58
63
|
return route.instance ? chatPath(route.instance) : "/ui";
|
|
59
64
|
}
|
|
60
65
|
/** One string per route: a change of it is a new navigation (a new lease for the panel, §4). */
|