@songsid/agend 2.3.0-beta.22 → 2.3.0-beta.24
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 +2 -2
- package/README.zh-TW.md +2 -2
- package/dist/agent-cli.js +4 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +1 -0
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +5 -0
- package/dist/agent-whats-new.js +16 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +7 -0
- package/dist/auth-api.js +54 -21
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex-package.d.ts +30 -0
- package/dist/backend/codex-package.js +133 -0
- package/dist/backend/codex-package.js.map +1 -0
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +9 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +3 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +9 -1
- package/dist/backend/kiro.js +102 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode-permission.js +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js +2 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/binary-probe.js +1 -1
- package/dist/bug-report-cli.js +11 -10
- package/dist/bug-report-cli.js.map +1 -1
- package/dist/bug-report.js +11 -10
- package/dist/bug-report.js.map +1 -1
- package/dist/bundled-skills.d.ts +7 -3
- package/dist/bundled-skills.js +16 -7
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +3 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +12 -4
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +3 -1
- package/dist/channel/mcp-tools.js +36 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/child-process.d.ts +21 -0
- package/dist/child-process.js +54 -0
- package/dist/child-process.js.map +1 -0
- package/dist/cli.js +188 -204
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +20 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.js +31 -8
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.js +1 -1
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -1
- package/dist/export-import.js +4 -3
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +45 -0
- package/dist/fleet-manager.js +324 -36
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.d.ts +5 -5
- package/dist/full-restart.js +5 -5
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +48 -10
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +7 -8
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +17 -4
- package/dist/general-knowledge/steering/core-rules.md +1 -1
- package/dist/instance-config-impact.js +10 -3
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +4 -4
- package/dist/locale.js +296 -4
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.js +1 -1
- package/dist/login-shell.js +3 -1
- package/dist/login-shell.js.map +1 -1
- package/dist/ls-rows.js +1 -1
- package/dist/needs-you-hub.d.ts +98 -1
- package/dist/needs-you-hub.js +313 -44
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +5 -2
- package/dist/needs-you-live.js +6 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +58 -3
- package/dist/needs-you.js +36 -3
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-schemas.d.ts +3 -3
- package/dist/public-link-progress.d.ts +12 -1
- package/dist/public-link-progress.js +5 -10
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +5 -0
- package/dist/public-tunnel-config.js +45 -1
- package/dist/public-tunnel-config.js.map +1 -1
- package/dist/public-web-link.js +6 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart.js +15 -9
- package/dist/quickstart.js.map +1 -1
- package/dist/resource-report.js +1 -1
- package/dist/scheduler/task-board.d.ts +41 -1
- package/dist/scheduler/task-board.js +152 -5
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.js +2 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-installer.d.ts +69 -0
- package/dist/service-installer.js +182 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +44 -0
- package/dist/service-owner.js +48 -0
- package/dist/service-owner.js.map +1 -0
- package/dist/service-plan.d.ts +8 -0
- package/dist/service-plan.js +13 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/settings-api.d.ts +4 -0
- package/dist/settings-api.js +21 -3
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +88 -4
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +2 -0
- package/dist/settings-confirmation.js +14 -8
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-control.js +2 -1
- package/dist/settings-control.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +2 -0
- package/dist/settings-http-confirmation.js +3 -2
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-request-capability.d.ts +4 -1
- package/dist/settings-request-capability.js +11 -3
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/setup-auth.d.ts +1 -0
- package/dist/setup-auth.js +2 -1
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-host.d.ts +1 -0
- package/dist/setup-host.js +7 -3
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +1 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-summary.d.ts +101 -0
- package/dist/task-board-summary.js +177 -0
- package/dist/task-board-summary.js.map +1 -0
- package/dist/task-board-waker.d.ts +8 -1
- package/dist/task-board-waker.js +15 -6
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.js +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +13 -3
- package/dist/tmux-exec-helper.js +34 -29
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.js +1 -1
- package/dist/tmux-read.js +1 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/topic-commands.d.ts +3 -0
- package/dist/topic-commands.js +29 -18
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +5 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +2 -0
- package/dist/transcript-sources.js +4 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js +3 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +1 -1
- package/dist/tunnel/cloudflared.js +51 -10
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +13 -0
- package/dist/tunnel/named-diagnostics.js +52 -0
- package/dist/tunnel/named-diagnostics.js.map +1 -0
- package/dist/tunnel/types.d.ts +2 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/ui/app-effects.js +10 -4
- package/dist/ui/chat-strings.js +3 -0
- package/dist/ui/chat-thread.js +14 -7
- package/dist/ui/effect-trial-offer.js +2 -31
- package/dist/ui/effects-waker.js +2 -70
- package/dist/ui/fleet-task-card.js +5 -5
- package/dist/ui/fleet-task-common.js +43 -9
- package/dist/ui/fleet-task-work.js +107 -0
- package/dist/ui/fleet-tasks.js +46 -12
- package/dist/ui/image-lightbox.js +39 -1
- package/dist/ui/image-zoom.js +242 -0
- package/dist/ui/panel-chat.js +24 -13
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +6 -2
- package/dist/ui/panel-needs.js +21 -6
- package/dist/ui/panel-settings.js +35 -13
- package/dist/ui/preview-panel.js +2 -1
- package/dist/ui/search-dialog.js +2 -1
- package/dist/ui/settings-appearance.js +44 -10
- package/dist/ui/settings-dialogs.js +33 -17
- package/dist/ui/settings-discord.js +6 -2
- package/dist/ui/settings-guests.js +2 -1
- package/dist/ui/settings-model.js +2 -11
- package/dist/ui/settings-mods.js +8 -3
- package/dist/ui/settings-strings.js +32 -0
- package/dist/ui/settings-token.js +3 -1
- package/dist/ui/settings-web-authority.js +6 -3
- package/dist/ui/settings-wizard.js +13 -7
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +2 -2
- package/dist/ui/shared/app-store.js +2 -2
- package/dist/ui/shared/app.css +93 -32
- package/dist/ui/shared/app.js +5 -1
- package/dist/ui/shared/effect-app-runtime.js +7 -5
- package/dist/ui/shared/effect-chat-runtime.js +2 -7
- package/dist/ui/shared/effect-cursor.js +3 -2
- package/dist/ui/shared/effect-display-metadata.js +39 -0
- package/dist/ui/shared/effect-registry.js +4 -2
- package/dist/ui/shared/effect-runtime-loader.js +3 -1
- package/dist/ui/shared/effect-stock-trust.js +5 -5
- package/dist/ui/shared/feature-flags.js +127 -0
- package/dist/ui/shared/identity-color.js +6 -3
- package/dist/ui/shared/instance-nav.js +3 -2
- package/dist/ui/shared/panel-view.js +14 -29
- package/dist/ui/shared/text-undo.js +210 -0
- package/dist/ui/shared/ui-dialog.js +4 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/usage-dialog.js +29 -8
- package/dist/ui/shared/view-strings.js +2 -4
- package/dist/ui/web-terminal/terminal.html +10 -10
- package/dist/ui/web-terminal/terminal.js +58 -12
- package/dist/update-dispatch.js +1 -1
- package/dist/update-launch.d.ts +1 -1
- package/dist/update-launch.js +1 -2
- package/dist/update-launch.js.map +1 -1
- package/dist/update-preflight.js +1 -1
- package/dist/usage/providers.js +1 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +1 -1
- package/dist/web-api.d.ts +6 -0
- package/dist/web-api.js +163 -21
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon-worker.js +2 -1
- package/dist/web-app-icon-worker.js.map +1 -1
- package/dist/web-app-icon.d.ts +10 -8
- package/dist/web-app-icon.js +14 -13
- package/dist/web-app-icon.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +1 -0
- package/dist/web-appearance-tools.js +7 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance.d.ts +4 -10
- package/dist/web-appearance.js +4 -3
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-auth.d.ts +8 -4
- package/dist/web-auth.js +28 -17
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +14 -0
- package/dist/web-chat-history.js +26 -1
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-cookie-names.d.ts +5 -0
- package/dist/web-cookie-names.js +12 -0
- package/dist/web-cookie-names.js.map +1 -0
- package/dist/web-experimental-http.d.ts +2 -0
- package/dist/web-experimental-http.js +12 -0
- package/dist/web-experimental-http.js.map +1 -0
- package/dist/web-experimental.d.ts +11 -0
- package/dist/web-experimental.js +49 -0
- package/dist/web-experimental.js.map +1 -0
- package/dist/web-guest-access.d.ts +1 -0
- package/dist/web-guest-access.js +4 -2
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -0
- package/dist/web-mod-store.js +10 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-png.d.ts +4 -0
- package/dist/web-png.js +10 -2
- package/dist/web-png.js.map +1 -1
- package/dist/web-preview.d.ts +2 -0
- package/dist/web-preview.js +2 -0
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +4 -2
- package/dist/web-returning-device.js +12 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +5 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +1 -0
- package/dist/web-settings-tools.js +14 -10
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal.js +1 -1
- package/dist/web-theme-store.d.ts +2 -2
- package/dist/web-theme-store.js +6 -3
- package/dist/web-theme-store.js.map +1 -1
- package/package.json +11 -11
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// provider key): each goes through confirmedWrite, so a change an admin must confirm waits for it (#1423).
|
|
5
5
|
import { html, useEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
6
6
|
import { t } from "/assets/app-i18n.js";
|
|
7
|
+
import { recordEdit } from "/assets/text-undo.js";
|
|
7
8
|
import { useLease } from "/assets/app-ctx.js";
|
|
8
9
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
9
10
|
import { Icon } from "/assets/ui-icons.js";
|
|
@@ -16,11 +17,13 @@ import { startOperation } from "./settings-apply.js";
|
|
|
16
17
|
import "./settings-strings.js"; // its words, wherever a picker of this file is used (#2072: Fleet's create dialog too)
|
|
17
18
|
import { backendChoices, modelChoices, OTHER_MODEL, useBackendRows, useModelCatalog } from "./settings-catalog.js";
|
|
18
19
|
import {
|
|
19
|
-
ACCESS_MODES,
|
|
20
|
+
ACCESS_MODES, AGENT_MODES, CH_TYPES, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
|
|
20
21
|
VISIBILITY_MODES, accessConfirmations, agentForm, agentPatch, backendOptions, changedFields, channelId, channelsOf,
|
|
21
|
-
chLabel, hasOwn, impactOf, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
|
|
22
|
+
chLabel, hasOwn, impactOf, newConnectionImpact, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
|
|
22
23
|
classicWebEchoEffective, worstImpact,
|
|
23
24
|
} from "./settings-model.js";
|
|
25
|
+
/** #2342: a field by id, for recordEdit before a pick or a reset writes it. */
|
|
26
|
+
const byId = (id) => (typeof document !== "undefined" ? document.getElementById(id) : null);
|
|
24
27
|
|
|
25
28
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
26
29
|
export const impactText = (kind) => tn(kind === "now" ? "impactNow" : kind === "fleet" ? "impactFleet" : "impactAgent");
|
|
@@ -103,7 +106,7 @@ export function ChipList({ items, onChange, placeholder, label }) {
|
|
|
103
106
|
|
|
104
107
|
const replyHint = (backend, mode, kiroUi) => (replyGuardSupported(backend, mode, kiroUi) ? tn("replyGuardSupported") : tn("replyGuardUnsupported", backend, mode));
|
|
105
108
|
|
|
106
|
-
/** The dialog every editor uses: Cancel / Stage change; the footer says what the
|
|
109
|
+
/** The dialog every editor uses: Cancel / Stage change; the footer says what the changes made so far will cost (#2346: only the fields that changed). */
|
|
107
110
|
function EditDialog({ title, subtitle, onClose, onStage, impacts, schema, children, ready = true, wide = true }) {
|
|
108
111
|
const kinds = [...new Set((impacts || []).map((f) => impactOf(schema, f)))];
|
|
109
112
|
return html`<${Dialog} title=${title} label=${subtitle ? `${title} — ${subtitle}` : title} onClose=${onClose} wide=${wide}
|
|
@@ -184,9 +187,9 @@ export function StatusEmojiEditor({ own, platform, channel, previewBody, onChang
|
|
|
184
187
|
<label for=${`se-${key}`}>${tn(`se_${key}`)}</label>
|
|
185
188
|
<input id=${`se-${key}`} type="text" autocomplete="off" value=${values[key] || ""} placeholder=${builtins[key] || ""} onInput=${(e) => set(key, e.target.value)} />
|
|
186
189
|
<button type="button" class="btn btn-sm" onClick=${() => setPicker(picker === key ? null : key)} aria-expanded=${picker === key ? "true" : "false"}>${tn("se_pick")}</button>
|
|
187
|
-
<button type="button" class="icon-btn" aria-label=${tn("se_useDefault")} title=${tn("se_useDefault")} onClick=${() => set(key, "")}><${Icon} name="close" size=${14} /></button>
|
|
190
|
+
<button type="button" class="icon-btn" aria-label=${tn("se_useDefault")} title=${tn("se_useDefault")} onClick=${() => { recordEdit(byId(`se-${key}`)); set(key, ""); }}><${Icon} name="close" size=${14} /></button>
|
|
188
191
|
${picker === key ? html`<${EmojiPicker} cat=${cat} keyName=${key} platform=${platform} channel=${channel}
|
|
189
|
-
onPick=${(v) => { set(key, v); setPicker(null); }} onClose=${() => setPicker(null)} />` : null}
|
|
192
|
+
onPick=${(v) => { recordEdit(byId(`se-${key}`)); set(key, v); setPicker(null); }} onClose=${() => setPicker(null)} />` : null}
|
|
190
193
|
</div>`)}</div>
|
|
191
194
|
<p class="note">${tn("se_hint")}</p>
|
|
192
195
|
</div>`;
|
|
@@ -241,16 +244,17 @@ export function AgentDialog({ name, inst, ctx, onClose }) {
|
|
|
241
244
|
};
|
|
242
245
|
const failoverWarn = !f.inherit.model_failover && !f.model_failover.trim() ? "failoverEmpty" : "";
|
|
243
246
|
const ready = !errors.auto && !errors.hang;
|
|
247
|
+
const pending = () => agentPatch({ ...f, status_emojis: emojis.value }, inst, defaults, visibilityBaseline, emojis.baseline);
|
|
244
248
|
const stage = () => {
|
|
245
249
|
if (!ready) return;
|
|
246
|
-
const patch =
|
|
250
|
+
const patch = pending();
|
|
247
251
|
if (Object.keys(patch).length) ctx.stageAgent(name, inst, patch);
|
|
248
252
|
onClose();
|
|
249
253
|
};
|
|
250
254
|
// What an override shows while it inherits: the default.
|
|
251
255
|
const shown = (k, own, dflt) => (f.inherit[k] ? dflt : own);
|
|
252
256
|
const L = (text, field, id) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${id} />`;
|
|
253
|
-
return html`<${EditDialog} title=${ctx.shortName(name, inst)} subtitle=${
|
|
257
|
+
return html`<${EditDialog} title=${ctx.shortName(name, inst)} subtitle=${tn("idLine", name)} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${Object.keys(pending()).map((k) => `instance.${k}`)} schema=${schema}>
|
|
254
258
|
<div class="grid2">
|
|
255
259
|
<div class="field">${L(tn("displayName"), "instance.display_name", "ag-dn")}
|
|
256
260
|
<${Override} inherit=${f.inherit.display_name} onInherit=${setInherit("display_name")}>
|
|
@@ -406,8 +410,8 @@ export function BotDialog({ id, ctx, onClose }) {
|
|
|
406
410
|
setToken(""); setTokenId(null); setReplacing(false);
|
|
407
411
|
setTokenNote({ text: tn("tokenQueued") });
|
|
408
412
|
};
|
|
409
|
-
|
|
410
|
-
|
|
413
|
+
/** The connections with this dialog's edits, and which parts changed. */
|
|
414
|
+
const pending = () => {
|
|
411
415
|
const original = structuredClone(chs);
|
|
412
416
|
const next = structuredClone(original);
|
|
413
417
|
next[index] = { ...next[index], access: { ...(next[index].access || {}), mode, allowed_users: [...users] } };
|
|
@@ -418,6 +422,16 @@ export function BotDialog({ id, ctx, onClose }) {
|
|
|
418
422
|
if (emojis.value) options.status_emojis = emojis.value; else delete options.status_emojis;
|
|
419
423
|
next[index] = { ...next[index], options };
|
|
420
424
|
}
|
|
425
|
+
return { next, accessChanged, emojisChanged };
|
|
426
|
+
};
|
|
427
|
+
const pendingImpacts = () => {
|
|
428
|
+
if (!ch) return [];
|
|
429
|
+
const p = pending();
|
|
430
|
+
return [...(p.accessChanged ? ["fleet.channels"] : []), ...(p.emojisChanged ? ["fleet.channel.options.status_emojis"] : [])];
|
|
431
|
+
};
|
|
432
|
+
const stage = () => {
|
|
433
|
+
if (!ch) return;
|
|
434
|
+
const { next, accessChanged, emojisChanged } = pending();
|
|
421
435
|
if (accessChanged || emojisChanged) {
|
|
422
436
|
ctx.stageChannels(`connections:${ch.id || chLabel(index)}`, next, {
|
|
423
437
|
label: tn(accessChanged && emojisChanged ? "updateAccessEmojis" : accessChanged ? "updateAccess" : "updateEmojis", ch.id || chLabel(index)),
|
|
@@ -445,7 +459,7 @@ export function BotDialog({ id, ctx, onClose }) {
|
|
|
445
459
|
};
|
|
446
460
|
const L = (text, field, htmlFor) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${htmlFor} />`;
|
|
447
461
|
if (!ch) return null;
|
|
448
|
-
return html`<${EditDialog} title=${type === "telegram" ? "Telegram" : "Discord"} subtitle=${id} onClose=${onClose} onStage=${stage} impacts=${
|
|
462
|
+
return html`<${EditDialog} title=${type === "telegram" ? "Telegram" : "Discord"} subtitle=${id} onClose=${onClose} onStage=${stage} impacts=${pendingImpacts()} schema=${schema}>
|
|
449
463
|
<div class="grid2">
|
|
450
464
|
<div class="field">${L(tn("accessMode"), "fleet.channel.access.mode", "bot-mode")}<${Select} id="bot-mode" value=${mode} onChange=${setMode} options=${ACCESS_MODES} />
|
|
451
465
|
<${Feedback} warning=${accessWarn} /></div>
|
|
@@ -503,6 +517,7 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
503
517
|
// The form's revision (#1529 review): any change to the token, platform, id or group moves it; a Verify or a plan
|
|
504
518
|
// that began before is dropped when it lands — never restoring an old token, naming it, or planning for it.
|
|
505
519
|
const rev = useRef(0);
|
|
520
|
+
// The fields keep what was typed (#2342: trimming the box would rewrite it, losing its undo); trimmed here.
|
|
506
521
|
const body = (x) => ({ platform: x.type, connection_only: true, admin_user_id: x.admin.trim(), ...(x.id.trim() ? { channel_id: x.id.trim() } : {}),
|
|
507
522
|
...(x.group.trim() ? (x.type === "discord" ? { guild_id: x.group.trim() } : { group_id: x.group.trim() }) : {}),
|
|
508
523
|
...(x.type === "discord" && x.general.trim() ? { general_channel_id: x.general.trim() } : {}) });
|
|
@@ -538,7 +553,7 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
538
553
|
setBusy(true);
|
|
539
554
|
const p = await planFor(x);
|
|
540
555
|
if (!p) { if (lease.current()) setBusy(false); return; }
|
|
541
|
-
const handed = startOperation([{ label: tn("addBotLabel", p.channel_id), impact:
|
|
556
|
+
const handed = startOperation([{ label: tn("addBotLabel", p.channel_id), impact: newConnectionImpact(ctx.schema),
|
|
542
557
|
request: { method: "POST", url: "/api/settings/quickstart/commit",
|
|
543
558
|
body: { ...body(x), channel_id: p.channel_id, token_env: p.token_env, token_env_generated: true, token: x.token }, sensitive: true } }]);
|
|
544
559
|
if (!handed) { setErr(tn("applyBusyLocal")); setBusy(false); return; }
|
|
@@ -560,9 +575,9 @@ export function NewBotDialog({ ctx, onClose }) {
|
|
|
560
575
|
invite=${f.identity && f.identity.invite} portal=${f.identity && f.identity.portal} guilds=${f.guilds} onGuilds=${(g) => setF((x) => ({ ...x, guilds: g }))}
|
|
561
576
|
guild=${f.group} onGuild=${set("group")} channel=${f.general} onChannel=${set("general")} />`
|
|
562
577
|
: html`<div class="field"><label for="nb-group">${tn("groupIdField")}</label>
|
|
563
|
-
<input id="nb-group" type="text" value=${f.group} onInput=${(e) => set("group")(e.target.value
|
|
578
|
+
<input id="nb-group" type="text" value=${f.group} onInput=${(e) => set("group")(e.target.value)} /></div>`}
|
|
564
579
|
<div class="field"><label for="nb-user">${tn("wizardAdminUser")}</label>
|
|
565
|
-
<input id="nb-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(f.type)} value=${f.admin} onInput=${(e) => set("admin")(e.target.value
|
|
580
|
+
<input id="nb-user" type="text" required=${true} inputmode="numeric" pattern=${adminUserIdPattern(f.type)} value=${f.admin} onInput=${(e) => set("admin")(e.target.value)} /></div>
|
|
566
581
|
<${Drawer} title=${tn("advancedSection")}>
|
|
567
582
|
<div class="field"><label for="nb-id">${tn("connectionIdOptional")}</label><input id="nb-id" type="text" placeholder=${plan ? plan.channel_id : f.type}
|
|
568
583
|
value=${f.id} onInput=${(e) => set("id")(e.target.value)} onBlur=${() => { if (f.identity && f.identity.valid) planFor(f); }} /></div>
|
|
@@ -597,9 +612,7 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
597
612
|
const runtime = ctx.classicRuntime(c.instanceName);
|
|
598
613
|
const classicMode = fd.agent_mode ?? "mcp";
|
|
599
614
|
const lease = useLease("classic-dialog");
|
|
600
|
-
const
|
|
601
|
-
if (!ready) return;
|
|
602
|
-
const patch = changedFields({
|
|
615
|
+
const pending = () => changedFields({
|
|
603
616
|
backend: f.backend, model: f.model.trim() || null,
|
|
604
617
|
auto_pause_after: f.inherit.auto_pause_after ? null : Number(f.auto_pause_after),
|
|
605
618
|
tool_progress: f.inherit.tool_progress ? null : f.tool_progress,
|
|
@@ -612,13 +625,16 @@ export function ClassicDialog({ room, ctx, onClose }) {
|
|
|
612
625
|
reply_completion_guard: hasOwn(c, "reply_completion_guard") ? c.reply_completion_guard : null,
|
|
613
626
|
collab: !!c.collab, context_lines: c.context_lines ?? cd.context_lines ?? 5, web_echo: classicWebEchoEffective(c, fleet),
|
|
614
627
|
});
|
|
628
|
+
const stage = async () => {
|
|
629
|
+
if (!ready) return;
|
|
630
|
+
const patch = pending();
|
|
615
631
|
if (!Object.keys(patch).length) { onClose(); return; }
|
|
616
632
|
if (!lease.current()) return;
|
|
617
633
|
ctx.stageClassic(c, patch);
|
|
618
634
|
onClose();
|
|
619
635
|
};
|
|
620
636
|
const L = (text, field, id) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${id} />`;
|
|
621
|
-
return html`<${EditDialog} title=${c.name || c.instanceName} subtitle=${
|
|
637
|
+
return html`<${EditDialog} title=${c.name || c.instanceName} subtitle=${`${tn("idLine", c.instanceName)} · ${tn(`exec_${runtime.execution}`)}`} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${Object.keys(pending()).map((k) => `classic.${k}`)} schema=${schema}>
|
|
622
638
|
<div class="grid2">
|
|
623
639
|
<div class="field">${L(tn("backend"), "classic.backend", "cl-be")}<${BackendSelect} id="cl-be" value=${f.backend} onChange=${set("backend")} /></div>
|
|
624
640
|
<div class="field">${L(tn("model"), "classic.model", "cl-model")}<${ModelSelect} id="cl-model" value=${f.model} onChange=${set("model")}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// B, nor after A again (an older A), nor once the page is gone.
|
|
10
10
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
11
11
|
import { t } from "/assets/app-i18n.js";
|
|
12
|
+
import { useTypedText } from "/assets/text-undo.js";
|
|
12
13
|
import { api } from "./settings-confirm.js";
|
|
13
14
|
import { Select } from "./settings-dialogs.js";
|
|
14
15
|
|
|
@@ -23,6 +24,9 @@ const probe = (body) => api("/api/settings/quickstart/probe", { method: "POST",
|
|
|
23
24
|
*/
|
|
24
25
|
export function DiscordServerPicker({ idPrefix, token, bot, invite, portal, guilds, onGuilds, guild, onGuild, channel, onChannel }) {
|
|
25
26
|
const [polling, setPolling] = useState(null); // { timedOut } while waiting for the bot to join
|
|
27
|
+
// #2342: the typed IDs keep what was typed (a space, the undo history); the picks get them trimmed.
|
|
28
|
+
const [guildText, onGuildText] = useTypedText(guild);
|
|
29
|
+
const [channelText, onChannelText] = useTypedText(channel);
|
|
26
30
|
const [channels, setChannels] = useState(null); // null: not asked; { list } | { error }
|
|
27
31
|
const live = useRef(true);
|
|
28
32
|
const now = useRef({ token, guild, channel });
|
|
@@ -120,11 +124,11 @@ export function DiscordServerPicker({ idPrefix, token, bot, invite, portal, guil
|
|
|
120
124
|
<div class="field"><label for=${`${idPrefix}-guild`}>${tn("guildIdField")}</label>
|
|
121
125
|
${guilds && guilds.length
|
|
122
126
|
? html`<${Select} id=${`${idPrefix}-guild`} value=${guild} onChange=${(v) => pickGuild(v, true)} options=${["", ...guilds.map((g) => ({ value: g.id, label: `${g.name} (${g.id})` }))]} />`
|
|
123
|
-
: html`<input id=${`${idPrefix}-guild`} type="text" value=${
|
|
127
|
+
: html`<input id=${`${idPrefix}-guild`} type="text" value=${guildText} placeholder=${tn("discordNoServerYet")} onInput=${(e) => pickGuild(onGuildText(e.target.value), true)} />`}</div>
|
|
124
128
|
<div class="field"><label for=${`${idPrefix}-gen`}>${tn("wizardGeneralChannel")}</label>
|
|
125
129
|
${channels && channels.list && channels.list.length
|
|
126
130
|
? html`<${Select} id=${`${idPrefix}-gen`} value=${channel} onChange=${pickChannel} options=${["", ...channels.list.map((c) => ({ value: c.id, label: `#${c.name}` }))]} />`
|
|
127
|
-
: html`<input id=${`${idPrefix}-gen`} type="text" value=${
|
|
131
|
+
: html`<input id=${`${idPrefix}-gen`} type="text" value=${channelText} onInput=${(e) => pickChannel(onChannelText(e.target.value))} />`}
|
|
128
132
|
${channels && channels.error ? html`<p class="feedback warning">${channels.error}</p>` : null}</div>
|
|
129
133
|
</div>`;
|
|
130
134
|
}
|
|
@@ -8,6 +8,7 @@ import { t } from "/assets/app-i18n.js";
|
|
|
8
8
|
import { toast } from "/assets/ui-toast.js";
|
|
9
9
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
10
10
|
import { api } from "./settings-confirm.js";
|
|
11
|
+
import { DEFAULT_SCHEMA, impactOf } from "./settings-model.js";
|
|
11
12
|
import "./settings-strings.js";
|
|
12
13
|
|
|
13
14
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -59,7 +60,7 @@ export function GuestsCard({ ctx, staged, disabled = false }) {
|
|
|
59
60
|
const setList = (key, k, list) => setF((x) => { const m = { ...x[key] }; if (list === undefined) delete m[k]; else m[k] = list; return { ...x, [key]: m }; });
|
|
60
61
|
const review = () => {
|
|
61
62
|
const body = guestsBody(original, f);
|
|
62
|
-
if (body) ctx.stage("guests", { label: tn("guestsTitle"), impact: "
|
|
63
|
+
if (body) ctx.stage("guests", { label: tn("guestsTitle"), impact: impactOf(ctx.schema || DEFAULT_SCHEMA, "web.guests"), request: { method: "PUT", url: "/api/settings/fleet/web", body: { guests: body } } });
|
|
63
64
|
else if (staged.has("guests")) ctx.unstage("guests");
|
|
64
65
|
setMsg(tn("readyApply"));
|
|
65
66
|
};
|
|
@@ -56,6 +56,8 @@ export const chLabel = (i) => (i === 0 ? "primary" : "persona");
|
|
|
56
56
|
// ── Impacts: the server's (/api/settings/schema, derived from HOT_INSTANCE_CONFIG_KEYS); this page never decides one ──
|
|
57
57
|
export const DEFAULT_SCHEMA = { impacts: {}, order: ["now", "instance", "fleet"] };
|
|
58
58
|
export const impactOf = (schema, field) => (schema.impacts && schema.impacts[field]) || "instance";
|
|
59
|
+
/** #2346: a new connection starts at once while another one runs (the schema says so); the first one needs AgEnD restarted. */
|
|
60
|
+
export const newConnectionImpact = (schema) => (schema && schema.connections_start_live === true ? "now" : impactOf(schema || DEFAULT_SCHEMA, "fleet.channels"));
|
|
59
61
|
/** A batch costs as much as its worst member, in the order the server ships. */
|
|
60
62
|
export function batchImpact(schema, keys, impactFor) {
|
|
61
63
|
const order = schema.order;
|
|
@@ -197,17 +199,6 @@ export function agentForm(inst, defaults) {
|
|
|
197
199
|
};
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
/** Every field the agent dialog shows, for its footer's impact summary. */
|
|
201
|
-
export const AGENT_FIELDS = [
|
|
202
|
-
"instance.display_name", "instance.description", "instance.backend", "instance.model", "instance.working_directory",
|
|
203
|
-
"instance.auto_pause_after", "instance.topic_id", "instance.hang_detector", "instance.agent_mode", "instance.tool_set",
|
|
204
|
-
"instance.tool_progress", "instance.cross_instance_visibility", "instance.reply_completion_guard", "instance.log_level",
|
|
205
|
-
"instance.lightweight", "instance.model_failover", "instance.systemPrompt", "instance.tags", "instance.general_topic",
|
|
206
|
-
"instance.status_emojis",
|
|
207
|
-
];
|
|
208
|
-
export const CLASSIC_FIELDS = ["classic.backend", "classic.model", "classic.auto_pause_after", "classic.tool_progress",
|
|
209
|
-
"classic.reply_completion_guard", "classic.collab", "classic.context_lines", "classic.web_echo"];
|
|
210
|
-
export const BOT_FIELDS = ["fleet.channel.access.mode", "fleet.channel.access.allowed_users", "fleet.channels", "fleet.channel.options.status_emojis"];
|
|
211
202
|
|
|
212
203
|
/** Validation messages (keys of the settings namespace), or "" when the value is fine. */
|
|
213
204
|
export const nonNegative = (v) => (!Number.isFinite(Number(v)) || String(v).trim() === "" || Number(v) < 0 ? "mustNonNegative" : "");
|
package/dist/ui/settings-mods.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// parts the person ticks are applied, all or nothing. Custom CSS from a file is never ticked by default. Installed
|
|
5
5
|
// mods can be downloaded again or uninstalled (what they applied stays: Reset and the CSS controls work on it).
|
|
6
6
|
// Export builds a file from what is in force now.
|
|
7
|
+
import { canvasWorkerEnabled, useCanvasWorker } from "/assets/feature-flags.js";
|
|
7
8
|
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
8
9
|
import { t } from "/assets/app-i18n.js";
|
|
9
10
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
@@ -36,6 +37,8 @@ function download(text, name) {
|
|
|
36
37
|
function why(body) {
|
|
37
38
|
if (!body) return tn("modFailed", "?");
|
|
38
39
|
if (Array.isArray(body.problems) && body.problems.length) return body.problems.slice(0, 5).map((p) => `${p.path || "file"}: ${p.problem}`).join("\n");
|
|
40
|
+
// #2390: a refusal the page has words for, in its language (the host's detail is English).
|
|
41
|
+
if (body.error === "canvas_worker_experimental") return tn("modCanvasWorkerOff");
|
|
39
42
|
return tn("modFailed", body.detail ? `${body.error}: ${body.detail}` : body.error || "?");
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -45,7 +48,7 @@ export function defaultChoice(plan) {
|
|
|
45
48
|
for (const it of plan.items) {
|
|
46
49
|
if (it.part === "theme" || it.part === "identity" || it.part === "ambient") c[it.part] = true;
|
|
47
50
|
else if (it.part === "widget") c.widgets[it.slot] = true;
|
|
48
|
-
else if (it.part === "effect") c.effects = true;
|
|
51
|
+
else if (it.part === "effect" && canvasWorkerEnabled()) c.effects = true;
|
|
49
52
|
// ownerCss stays false: "read it before turning it on" (r4 §4).
|
|
50
53
|
}
|
|
51
54
|
return c;
|
|
@@ -80,7 +83,7 @@ export function ThemeDetail({ theme, pictures, effects }) {
|
|
|
80
83
|
${th.background ? pic(th.background.image, tn("modBackground")) : null}
|
|
81
84
|
${th.background ? row(tn("modBackgroundFit"), `${th.background.fit} · ${Math.round(th.background.opacity * 100)}%`) : null}
|
|
82
85
|
${th.avatar ? pic(th.avatar, tn("modAvatar")) : null}
|
|
83
|
-
${(effects || []).map((e, i) => html`<div key=${i}>${row(tn("modPlacesEffect"), html`<span><code>${e.effect}</code>${e.id ? html` <span class="muted">${e.id.slice(0, 8)}</span>` : null}${Object.keys(e.params || {}).length ? html` · <code>${JSON.stringify(e.params)}</code>` : null}</span>`)}</div>`)}
|
|
86
|
+
${(canvasWorkerEnabled() ? effects || [] : []).map((e, i) => html`<div key=${i}>${row(tn("modPlacesEffect"), html`<span><code>${e.effect}</code>${e.id ? html` <span class="muted">${e.id.slice(0, 8)}</span>` : null}${Object.keys(e.params || {}).length ? html` · <code>${JSON.stringify(e.params)}</code>` : null}</span>`)}</div>`)}
|
|
84
87
|
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify(th, null, 2)}</pre></details>
|
|
85
88
|
</div>`;
|
|
86
89
|
}
|
|
@@ -114,6 +117,7 @@ function PlanItem({ it, choice, setChoice, binds, fleet }) {
|
|
|
114
117
|
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify({ loop: it.loop, click: it.click }, null, 2)}</pre></details></div></li>`;
|
|
115
118
|
}
|
|
116
119
|
if (it.part === "effect") {
|
|
120
|
+
if (!canvasWorkerEnabled()) return null;
|
|
117
121
|
return html`<li class="s-mod-item">${tick(tn("modPartEffect", it.title), choice.effects, (v) => setChoice({ ...choice, effects: v }))}
|
|
118
122
|
<div class="s-mod-detail"><span>${kib(it.bytes)} · ${tn("modRegions", it.regions.join(", "))}</span>${it.wants.length ? html`<span>${tn("modWants", it.wants.join(", "))}</span>` : null}</div></li>`;
|
|
119
123
|
}
|
|
@@ -137,7 +141,7 @@ function Plan({ staged, onDone }) {
|
|
|
137
141
|
const names = (app.instances || []).map((i) => i.name);
|
|
138
142
|
const install = async () => {
|
|
139
143
|
setBusy(true); setError("");
|
|
140
|
-
const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: choice, ...(toInstance ? { target } : {}) });
|
|
144
|
+
const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: { ...choice, effects: canvasWorkerEnabled() && choice.effects }, ...(toInstance ? { target } : {}) });
|
|
141
145
|
setBusy(false);
|
|
142
146
|
// The stage this plan shows is gone or no longer this plan (replaced on another device, expired): read again —
|
|
143
147
|
// the page then shows whatever is staged now, with its own, fresh choices (keyed by stage).
|
|
@@ -205,6 +209,7 @@ function ExportForm() {
|
|
|
205
209
|
}
|
|
206
210
|
|
|
207
211
|
export function ModsCard() {
|
|
212
|
+
useCanvasWorker();
|
|
208
213
|
const [state, setState] = useState(null);
|
|
209
214
|
const [busy, setBusy] = useState(false);
|
|
210
215
|
const [error, setError] = useState("");
|
|
@@ -21,6 +21,7 @@ register("settings", {
|
|
|
21
21
|
namedTunnelSetup: "How to set this up",
|
|
22
22
|
namedToken: "Cloudflare connector token",
|
|
23
23
|
namedTokenHint: "Stored privately, never shown again. Format checked locally; Cloudflare credentials and routing are checked when opening. Saving or clearing closes the current public link.",
|
|
24
|
+
namedTokenInvalid: "Paste only the long string that starts with eyJ.",
|
|
24
25
|
namedTokenSet: "Set token",
|
|
25
26
|
namedTokenClear: "Clear token",
|
|
26
27
|
awaitingConfirmation: "Waiting for confirmation.",
|
|
@@ -375,6 +376,8 @@ register("settings", {
|
|
|
375
376
|
modCancel: "Cancel",
|
|
376
377
|
modInstalled: "Installed {0}.",
|
|
377
378
|
modFailed: "Not done: {0}",
|
|
379
|
+
idLine: "ID: {0}",
|
|
380
|
+
modCanvasWorkerOff: "Not done: this mod has Canvas Worker effects, an experimental feature. Turn it on in Settings → Web appearance first.",
|
|
378
381
|
modDownload: "Download",
|
|
379
382
|
modUninstall: "Uninstall",
|
|
380
383
|
modUninstallConfirm: "Uninstall {0}? Its effects leave your library unless something still uses them. What it applied stays.",
|
|
@@ -418,6 +421,19 @@ register("settings", {
|
|
|
418
421
|
appIconError_app_icon_superseded: "The icon was reset meanwhile; choose the picture again.",
|
|
419
422
|
appIconError_app_icon_not_reset: "The default could not be restored ({0}).",
|
|
420
423
|
appIconError_other: "The app icon was not changed ({0}).",
|
|
424
|
+
appIconError_app_icon_not_kept: "This fleet does not keep an app icon.",
|
|
425
|
+
appIconError_app_icon_failed: "The app icon request failed; try again.",
|
|
426
|
+
appIconDetail_max_size: "at most {0} MiB",
|
|
427
|
+
appIconDetail_png_only: "a PNG image; the page converts JPG and WebP",
|
|
428
|
+
appIconDetail_busy: "another icon is being made",
|
|
429
|
+
appIconDetail_reset_meanwhile: "the icon was reset meanwhile",
|
|
430
|
+
appIconDetail_too_slow: "it took too long to read",
|
|
431
|
+
appIconDetail_unreadable: "the image could not be read",
|
|
432
|
+
appIconDetail_corrupt: "the PNG file could not be read ({0})",
|
|
433
|
+
appIconDetail_not_square: "it is not square ({0}×{1})",
|
|
434
|
+
appIconDetail_too_small: "it is smaller than {0}×{0}",
|
|
435
|
+
appIconDetail_too_big: "it is larger than {0}×{0}",
|
|
436
|
+
appIconDetail_interlaced: "interlaced PNGs are not supported",
|
|
421
437
|
cssTitle: "Custom CSS (yours only)",
|
|
422
438
|
cssLead: "Applies to your screens only.",
|
|
423
439
|
cssLoadFailed: "Couldn't load your custom CSS. Reload the page to try again.",
|
|
@@ -621,6 +637,7 @@ register("settings", {
|
|
|
621
637
|
namedTunnelSetup: "設定教學",
|
|
622
638
|
namedToken: "Cloudflare 連接器 token",
|
|
623
639
|
namedTokenHint: "私密儲存,不會讀回。儲存時只檢查格式,開啟時才確認憑證與路由。設定或清除會關閉目前的公開連結。",
|
|
640
|
+
namedTokenInvalid: "請只貼上以 eyJ 開頭的長字串。",
|
|
624
641
|
namedTokenSet: "設定 token",
|
|
625
642
|
namedTokenClear: "清除 token",
|
|
626
643
|
awaitingConfirmation: "等待確認中。",
|
|
@@ -949,6 +966,8 @@ register("settings", {
|
|
|
949
966
|
modCancel: "取消",
|
|
950
967
|
modInstalled: "已安裝 {0}。",
|
|
951
968
|
modFailed: "未完成:{0}",
|
|
969
|
+
idLine: "ID:{0}",
|
|
970
|
+
modCanvasWorkerOff: "未完成:這個 mod 含有 Canvas Worker 效果,這是實驗性功能。請先到「設定 → 網頁外觀」開啟。",
|
|
952
971
|
modDownload: "下載",
|
|
953
972
|
modUninstall: "解除安裝",
|
|
954
973
|
modUninstallConfirm: "要解除安裝 {0} 嗎?它的效果會離開你的效果庫,除非還有東西在使用。它已套用的外觀會保留。",
|
|
@@ -992,6 +1011,19 @@ register("settings", {
|
|
|
992
1011
|
appIconError_app_icon_superseded: "圖示剛被恢復成預設,請重新選擇圖片。",
|
|
993
1012
|
appIconError_app_icon_not_reset: "無法恢復預設({0})。",
|
|
994
1013
|
appIconError_other: "App 圖示沒有更換({0})。",
|
|
1014
|
+
appIconError_app_icon_not_kept: "這個 fleet 沒有保存 App 圖示。",
|
|
1015
|
+
appIconError_app_icon_failed: "App 圖示的請求失敗,請再試一次。",
|
|
1016
|
+
appIconDetail_max_size: "最多 {0} MiB",
|
|
1017
|
+
appIconDetail_png_only: "需要 PNG 圖片;頁面會自動轉換 JPG 與 WebP",
|
|
1018
|
+
appIconDetail_busy: "另一張圖示正在處理中",
|
|
1019
|
+
appIconDetail_reset_meanwhile: "圖示剛被恢復成預設",
|
|
1020
|
+
appIconDetail_too_slow: "讀取時間過長",
|
|
1021
|
+
appIconDetail_unreadable: "無法讀取這張圖片",
|
|
1022
|
+
appIconDetail_corrupt: "無法解讀這個 PNG 檔案({0})",
|
|
1023
|
+
appIconDetail_not_square: "不是正方形({0}×{1})",
|
|
1024
|
+
appIconDetail_too_small: "小於 {0}×{0}",
|
|
1025
|
+
appIconDetail_too_big: "大於 {0}×{0}",
|
|
1026
|
+
appIconDetail_interlaced: "不支援交錯式(interlaced)PNG",
|
|
995
1027
|
cssTitle: "自訂 CSS(只套用在你自己)",
|
|
996
1028
|
cssLead: "只套用在你的畫面。",
|
|
997
1029
|
cssLoadFailed: "無法載入你的自訂 CSS。請重新整理頁面再試一次。",
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// hands it back — afterwards a connection shows "Token set · @bot".
|
|
6
6
|
import { html, useState } from "/assets/app-html.js";
|
|
7
7
|
import { t } from "/assets/app-i18n.js";
|
|
8
|
+
import { useTypedText } from "/assets/text-undo.js";
|
|
8
9
|
import { Icon } from "/assets/ui-icons.js";
|
|
9
10
|
import { api } from "./settings-confirm.js";
|
|
10
11
|
|
|
@@ -36,10 +37,11 @@ export const botHandle = (username) => (username ? `@${String(username).replace(
|
|
|
36
37
|
*/
|
|
37
38
|
export function TokenField({ id, platform, value, onInput, identity, busy, onVerify, hint }) {
|
|
38
39
|
const [shown, setShown] = useState(false);
|
|
40
|
+
const [text, onText] = useTypedText(value); // #2342: the box keeps what was typed; the parent gets it trimmed
|
|
39
41
|
return html`<div class="field token-field"><label for=${id}>${tn("wizardToken")}</label>
|
|
40
42
|
<div class="token-row">
|
|
41
43
|
<input id=${id} type=${shown ? "text" : "password"} autocomplete="off" autocapitalize="off" spellcheck=${false} disabled=${!!busy}
|
|
42
|
-
placeholder=${platform === "discord" ? "MTA…" : "123456:ABC-DEF…"} value=${
|
|
44
|
+
placeholder=${platform === "discord" ? "MTA…" : "123456:ABC-DEF…"} value=${text} onInput=${(e) => onInput(onText(e.target.value))} />
|
|
43
45
|
<button type="button" class="icon-btn" aria-pressed=${shown ? "true" : "false"} title=${shown ? tn("tokenHide") : tn("tokenShow")}
|
|
44
46
|
aria-label=${shown ? tn("tokenHide") : tn("tokenShow")} onClick=${() => setShown(!shown)}><${Icon} name="eye" size=${16} /></button>
|
|
45
47
|
</div>
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
// them grants anything by itself — a session still needs its account's Confirm in Discord or Telegram).
|
|
6
6
|
import { html, useEffect, useState } from "/assets/app-html.js";
|
|
7
7
|
import { t } from "/assets/app-i18n.js";
|
|
8
|
+
import { useTypedText } from "/assets/text-undo.js";
|
|
8
9
|
import { toast } from "/assets/ui-toast.js";
|
|
9
10
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
10
11
|
import { api } from "./settings-confirm.js";
|
|
11
12
|
import { Feedback } from "./settings-dialogs.js";
|
|
13
|
+
import { DEFAULT_SCHEMA, impactOf } from "./settings-model.js";
|
|
12
14
|
import { resetWebAuthorization } from "/assets/web-authorize.js";
|
|
13
15
|
import "./settings-strings.js";
|
|
14
16
|
|
|
@@ -26,6 +28,8 @@ export function verifiedAuthorityBody(original, f) {
|
|
|
26
28
|
export function WebAuthorityCard({ ctx, staged, disabled = false }) {
|
|
27
29
|
const [overview, setOverview] = useState(null), [failed, setFailed] = useState(false);
|
|
28
30
|
const [original, setOriginal] = useState(null), [f, setF] = useState(null), [msg, setMsg] = useState(null);
|
|
31
|
+
// #2342: the hours box keeps what was typed ("01", "1.50"; its undo history); the form gets the number.
|
|
32
|
+
const [hoursText, onHoursText] = useTypedText(f ? f.hours : null, (v) => (v.trim() === "" ? null : Number(v)));
|
|
29
33
|
const load = async () => {
|
|
30
34
|
// What changes here (a revoke, the switch) changes this browser's own line: the chat reads it again when it shows.
|
|
31
35
|
resetWebAuthorization();
|
|
@@ -46,7 +50,6 @@ export function WebAuthorityCard({ ctx, staged, disabled = false }) {
|
|
|
46
50
|
if (!overview || !f) return card(html`<p class="note">${t("app.loading")}</p>`);
|
|
47
51
|
|
|
48
52
|
const set = (patch) => setF((x) => ({ ...x, ...patch }));
|
|
49
|
-
const hoursText = f.hours === null ? "" : String(f.hours);
|
|
50
53
|
const hoursError = f.hours !== null && (!Number.isFinite(f.hours) || f.hours < overview.hours.min || f.hours > overview.hours.max) ? "vaHoursRange" : "";
|
|
51
54
|
const stuckOff = f.enabled && original.enabled && !overview.state.enabled;
|
|
52
55
|
const warnHosts = !f.loopbackOnly && overview.lanHosts.length > 0;
|
|
@@ -55,7 +58,7 @@ export function WebAuthorityCard({ ctx, staged, disabled = false }) {
|
|
|
55
58
|
const review = () => {
|
|
56
59
|
if (hoursError) return;
|
|
57
60
|
const body = verifiedAuthorityBody(original, f);
|
|
58
|
-
if (body) ctx.stage("verified_authority", { label: tn("vaTitle"), impact: "
|
|
61
|
+
if (body) ctx.stage("verified_authority", { label: tn("vaTitle"), impact: impactOf(ctx.schema || DEFAULT_SCHEMA, "web.verified_authority"), request: { method: "PUT", url: "/api/settings/fleet/web", body: { verified_authority: body } } });
|
|
59
62
|
else if (staged.has("verified_authority")) ctx.unstage("verified_authority");
|
|
60
63
|
setMsg(tn("readyApply"));
|
|
61
64
|
};
|
|
@@ -77,7 +80,7 @@ export function WebAuthorityCard({ ctx, staged, disabled = false }) {
|
|
|
77
80
|
${warnHosts ? html`<p class="note s-va-warn" role="status" id="vaLanWarning">${tn("vaLanWarning", overview.lanHosts.join(", "))}</p>` : null}
|
|
78
81
|
<div class="field"><label for="vaHours">${tn("vaHours", overview.hours.min, overview.hours.max)}</label>
|
|
79
82
|
<input id="vaHours" type="number" min=${overview.hours.min} max=${overview.hours.max} step="any" placeholder=${tn("defaultHours", overview.hours.default)}
|
|
80
|
-
class=${hoursError ? "invalid" : ""} value=${hoursText} onInput=${(e) => set({ hours:
|
|
83
|
+
class=${hoursError ? "invalid" : ""} value=${hoursText} onInput=${(e) => set({ hours: onHoursText(e.target.value) })} />
|
|
81
84
|
<${Feedback} error=${hoursError} /></div>
|
|
82
85
|
<h4>${tn("vaSessionsTitle")}</h4>
|
|
83
86
|
${overview.authorized.length
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
// progress, deadline and restart recovery as every other change.
|
|
5
5
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
6
6
|
import { t } from "/assets/app-i18n.js";
|
|
7
|
+
import { useTypedText } from "/assets/text-undo.js";
|
|
7
8
|
import { useLease } from "/assets/app-ctx.js";
|
|
8
9
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
9
10
|
import { api } from "./settings-confirm.js";
|
|
10
11
|
import { startOperation } from "./settings-apply.js";
|
|
11
|
-
import { BACKENDS,
|
|
12
|
+
import { BACKENDS, newConnectionImpact, toYaml } from "./settings-model.js";
|
|
12
13
|
import { Drawer, Select } from "./settings-dialogs.js";
|
|
13
14
|
import { adminUserIdError, adminUserIdPattern, botHandle, TokenEnvNote, TokenField, verifyBotToken } from "./settings-token.js";
|
|
14
15
|
import { DiscordServerPicker } from "./settings-discord.js";
|
|
@@ -26,6 +27,11 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
26
27
|
const bump = () => { rev.current++; };
|
|
27
28
|
const [busy, setBusy] = useState("");
|
|
28
29
|
const [err, setErr] = useState("");
|
|
30
|
+
// #2342: the typed fields keep what was typed (a space in a path, the undo history); the form gets them trimmed.
|
|
31
|
+
const [dirText, onDirText] = useTypedText(w ? w.working_directory : "");
|
|
32
|
+
const [nameText, onNameText] = useTypedText(w ? w.instance_name : "");
|
|
33
|
+
const [userText, onUserText] = useTypedText(w ? w.admin_user_id : "");
|
|
34
|
+
const [groupText, onGroupText] = useTypedText(w ? w.group_id : "");
|
|
29
35
|
useEffect(() => {
|
|
30
36
|
(async () => {
|
|
31
37
|
const env = await api("/api/settings/quickstart/environment").catch(() => null);
|
|
@@ -115,7 +121,7 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
115
121
|
// either if it was taken since, instead of quietly picking another.
|
|
116
122
|
const body = { ...input(), channel_id: w.plan.channel_id, token_env: w.plan.token_env, token_env_generated: true, token: w.token };
|
|
117
123
|
// It writes the connection and the first agent: it costs what a connection change does (the server's schema).
|
|
118
|
-
const handed = startOperation([{ label: tn("wizardTitle"), impact:
|
|
124
|
+
const handed = startOperation([{ label: tn("wizardTitle"), impact: newConnectionImpact(ctx && ctx.schema),
|
|
119
125
|
request: { method: "POST", url: "/api/settings/quickstart/commit", body, sensitive: true } }]);
|
|
120
126
|
if (!handed) { setErr(tn("applyBusyLocal")); return; }
|
|
121
127
|
setW((x) => ({ ...x, token: "" }));
|
|
@@ -134,8 +140,8 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
134
140
|
<p class="note">${tn("wizardAgentExistingNote")}</p></div>` : html`${which}<div class="field"><label for="wz-be">${tn("wizardBackend")}</label>
|
|
135
141
|
<${Select} id="wz-be" value=${w.backend} onChange=${set("backend")} options=${w.env.backends && w.env.backends.length ? w.env.backends : BACKENDS} />
|
|
136
142
|
<p class="note">${w.env.backends && w.env.backends.length ? tn("wizardBackendFound", w.env.backends.join(", ")) : tn("wizardBackendNone")}</p></div>
|
|
137
|
-
<div class="field"><label for="wz-wd">${tn("workingDir")}</label><input id="wz-wd" type="text" placeholder="/home/you/projects/app" value=${
|
|
138
|
-
<div class="field"><label for="wz-name">${agents.length ? tn("wizardAgentNewName") : tn("wizardAgentName")}</label><input id="wz-name" type="text" value=${
|
|
143
|
+
<div class="field"><label for="wz-wd">${tn("workingDir")}</label><input id="wz-wd" type="text" placeholder="/home/you/projects/app" value=${dirText} onInput=${(e) => set("working_directory")(onDirText(e.target.value))} /></div>
|
|
144
|
+
<div class="field"><label for="wz-name">${agents.length ? tn("wizardAgentNewName") : tn("wizardAgentName")}</label><input id="wz-name" type="text" value=${nameText} onInput=${(e) => set("instance_name")(onNameText(e.target.value))} /></div>`;
|
|
139
145
|
} else if (w.step === 2) {
|
|
140
146
|
body = html`<div class="seg-inline" role="group" aria-label=${tn("wizardPlatform")}>
|
|
141
147
|
${["telegram", "discord"].map((p) => html`<button key=${p} type="button" class=${`btn${w.platform === p ? " btn-primary" : ""}`} aria-pressed=${w.platform === p ? "true" : "false"}
|
|
@@ -148,12 +154,12 @@ export function SetupWizard({ ctx, onClose }) {
|
|
|
148
154
|
<${DiscordServerPicker} idPrefix="wz" token=${w.identity && w.identity.valid ? w.token : ""} bot=${w.identity && botHandle(w.identity.username)}
|
|
149
155
|
invite=${w.identity && w.identity.invite} portal=${w.identity && w.identity.portal} guilds=${w.guilds} onGuilds=${(g) => setW((x) => ({ ...x, guilds: g }))}
|
|
150
156
|
guild=${w.guild_id} onGuild=${set("guild_id")} channel=${w.general_channel_id} onChannel=${set("general_channel_id")} />
|
|
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=${
|
|
157
|
+
<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=${userText} onInput=${(e) => set("admin_user_id")(onUserText(e.target.value))} /></div>`
|
|
152
158
|
: html`
|
|
153
|
-
<div class="field"><label for="wz-group">${tn("groupIdField")}</label><input id="wz-group" type="text" placeholder="-1001234567890" value=${
|
|
159
|
+
<div class="field"><label for="wz-group">${tn("groupIdField")}</label><input id="wz-group" type="text" placeholder="-1001234567890" value=${groupText} onInput=${(e) => set("group_id")(onGroupText(e.target.value))} />
|
|
154
160
|
<p class="note">${tn("wizardDetectHint")}</p></div>
|
|
155
161
|
<div class="dlg-inline-actions"><button type="button" class="btn btn-sm" disabled=${!!busy} onClick=${detect}>${tn("wizardDetect")}</button></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=${
|
|
162
|
+
<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=${userText} onInput=${(e) => set("admin_user_id")(onUserText(e.target.value))} /></div>`}`;
|
|
157
163
|
} else {
|
|
158
164
|
body = !w.plan ? html`<p class="note">${tn("wizardPlanLoading")}</p>` : html`
|
|
159
165
|
<p><strong>${tn("wizardWillWrite")}</strong></p>
|
|
@@ -94,7 +94,7 @@ register("app", {
|
|
|
94
94
|
search: "Search", searchTitle: "Search chats, messages between agents and tasks ({0})",
|
|
95
95
|
confirmTitle: "Please confirm", confirmOk: "Continue", cancel: "Cancel",
|
|
96
96
|
// The server's own wording (src/locale.ts needs.reason.*), so the web says what Discord says.
|
|
97
|
-
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",
|
|
97
|
+
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", needs_reason_task_review: "Waiting for your confirmation", needs_reason_task_blocked: "Blocked: asks you",
|
|
98
98
|
},
|
|
99
99
|
"zh-TW": {
|
|
100
100
|
brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance", newAgent: "新增 Agent",
|
|
@@ -129,6 +129,6 @@ register("app", {
|
|
|
129
129
|
needsNav: "等你處理", needsTab: "待處理", needsCount: "{0} 件待處理",
|
|
130
130
|
search: "搜尋", searchTitle: "搜尋聊天、agent 之間的訊息與任務({0})",
|
|
131
131
|
confirmTitle: "請確認", confirmOk: "繼續", cancel: "取消",
|
|
132
|
-
needs_reason_hang: "沒有回應", needs_reason_exited: "已結束", needs_reason_assist: "在終端機上等待", needs_reason_permission: "需要權限", needs_reason_dangerous_command: "危險指令待確認", needs_reason_login: "需要登入", needs_reason_dialog: "停在對話框", needs_reason_terminal_input: "可能在等輸入", needs_reason_auth_paused: "已暫停:需要登入", needs_reason_crashed: "已崩潰", needs_reason_delivery_uncertain: "無法確認是否送達", needs_reason_delivery_failed: "傳送失敗",
|
|
132
|
+
needs_reason_hang: "沒有回應", needs_reason_exited: "已結束", needs_reason_assist: "在終端機上等待", needs_reason_permission: "需要權限", needs_reason_dangerous_command: "危險指令待確認", needs_reason_login: "需要登入", needs_reason_dialog: "停在對話框", needs_reason_terminal_input: "可能在等輸入", needs_reason_auth_paused: "已暫停:需要登入", needs_reason_crashed: "已崩潰", needs_reason_delivery_uncertain: "無法確認是否送達", needs_reason_delivery_failed: "傳送失敗", needs_reason_task_review: "等你確認", needs_reason_task_blocked: "卡住,在問你",
|
|
133
133
|
},
|
|
134
134
|
});
|
|
@@ -77,8 +77,8 @@ export function startNeedsNotifier({ open, env = globalThis } = {}) {
|
|
|
77
77
|
seen.add(item.id);
|
|
78
78
|
if (!notifyOn(env)) return;
|
|
79
79
|
try {
|
|
80
|
-
const n = new env.Notification(item.instance, { body: item.detail ? `${reasonText(item)}: ${item.detail}` : reasonText(item), tag: item.id });
|
|
81
|
-
n.onclick = () => { try { env.focus && env.focus(); } catch { /* ignore */ } if (open) open(item.instance); n.close(); };
|
|
80
|
+
const n = new env.Notification(item.instance || reasonText(item), { body: item.detail ? `${reasonText(item)}: ${item.detail}` : reasonText(item), tag: item.id });
|
|
81
|
+
n.onclick = () => { try { env.focus && env.focus(); } catch { /* ignore */ } if (open && item.instance) open(item.instance); n.close(); };
|
|
82
82
|
} catch { /* a browser that refuses at the last moment: the list still shows it */ }
|
|
83
83
|
};
|
|
84
84
|
const consider = (items) => {
|
|
@@ -24,7 +24,7 @@ export function createStore(initial) {
|
|
|
24
24
|
* (app-stream.js catchUp) — "catching" / "retrying" / "failed" / "ok"; `retryHydration` asks for it again.
|
|
25
25
|
* `connections`: how many chat connections the fleet has (#1519 P7: 0 is web-only — the first-run card); null until known. */
|
|
26
26
|
export const appStore = createStore({ ready: false, instances: [], uptime: 0, exec: {}, awaiting: {}, connection: "connecting", needs: [], connections: null,
|
|
27
|
-
hydration: "none", retryHydration: null });
|
|
27
|
+
hydration: "none", retryHydration: null, canvasWorker: typeof document !== "undefined" && document.body?.dataset.canvasWorker === "1" });
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* #1920 phase 3: this page is a guest's — the server says so on <body> (data-tier="guest"). A guest sees only its own
|
|
@@ -49,7 +49,7 @@ export function applyStatus(d) {
|
|
|
49
49
|
exec[i.name] = (i.execution_state !== undefined ? i.execution_state : i.state) ?? null;
|
|
50
50
|
if (i.state === "awaiting_input") awaiting[i.name] = typeof i.interaction_summary === "string" ? i.interaction_summary : "";
|
|
51
51
|
}
|
|
52
|
-
appStore.set({ ready: true, instances: d.instances, uptime: d.uptime ?? 0, exec, awaiting,
|
|
52
|
+
appStore.set({ canvasWorker: d.canvas_worker === true, ready: true, instances: d.instances, uptime: d.uptime ?? 0, exec, awaiting,
|
|
53
53
|
connections: typeof d.connections === "number" ? d.connections : null,
|
|
54
54
|
// #1665: the host preferences' revision (app-prefs.js re-reads them when it moves).
|
|
55
55
|
prefsRev: typeof d.prefs_rev === "number" ? d.prefs_rev : null,
|