@songsid/agend 2.2.0-alpha.1 → 2.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/README.zh-TW.md +6 -4
- package/dist/agent-cli.js +6 -1
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +4 -1
- package/dist/agent-endpoint.js +82 -14
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-token-header.d.ts +13 -0
- package/dist/agent-token-header.js +16 -0
- package/dist/agent-token-header.js.map +1 -0
- package/dist/atomic-write.d.ts +21 -0
- package/dist/atomic-write.js +65 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.js +4 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/codex.d.ts +7 -0
- package/dist/backend/codex.js +7 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +1 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +7 -0
- package/dist/backend/kiro.js +82 -33
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.d.ts +7 -0
- package/dist/backend/muse.js +8 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +12 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/binary-probe.d.ts +80 -0
- package/dist/binary-probe.js +182 -0
- package/dist/binary-probe.js.map +1 -0
- package/dist/cache-analysis.d.ts +92 -0
- package/dist/cache-analysis.js +147 -0
- package/dist/cache-analysis.js.map +1 -0
- package/dist/cache-ledger.d.ts +108 -0
- package/dist/cache-ledger.js +230 -0
- package/dist/cache-ledger.js.map +1 -0
- package/dist/cache-prices.d.ts +32 -0
- package/dist/cache-prices.js +73 -0
- package/dist/cache-prices.js.map +1 -0
- package/dist/cache-scan.d.ts +39 -0
- package/dist/cache-scan.js +115 -0
- package/dist/cache-scan.js.map +1 -0
- package/dist/cache-service.d.ts +131 -0
- package/dist/cache-service.js +452 -0
- package/dist/cache-service.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +5 -1
- package/dist/channel/adapters/discord.js +56 -11
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +38 -1
- package/dist/channel/adapters/telegram.js +82 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +8 -1
- package/dist/channel/tool-router.js +4 -2
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +21 -0
- package/dist/cli-entry.d.ts +19 -0
- package/dist/cli-entry.js +36 -0
- package/dist/cli-entry.js.map +1 -0
- package/dist/cli.js +376 -54
- package/dist/cli.js.map +1 -1
- package/dist/connection-secrets.d.ts +5 -0
- package/dist/connection-secrets.js.map +1 -1
- package/dist/cpu-profile.js +50 -0
- package/dist/cpu-profile.js.map +1 -1
- package/dist/daemon.d.ts +18 -0
- package/dist/daemon.js +256 -63
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +1 -0
- package/dist/delivery-outbox.js +12 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/discord-permissions.d.ts +33 -0
- package/dist/discord-permissions.js +42 -0
- package/dist/discord-permissions.js.map +1 -0
- package/dist/env-file.d.ts +11 -0
- package/dist/env-file.js +27 -0
- package/dist/env-file.js.map +1 -0
- package/dist/event-log.d.ts +4 -1
- package/dist/event-log.js +6 -3
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-context.d.ts +5 -0
- package/dist/fleet-context.js.map +1 -1
- package/dist/fleet-control-audit.d.ts +5 -0
- package/dist/fleet-control-audit.js +7 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-level-config.d.ts +8 -1
- package/dist/fleet-level-config.js +53 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +122 -7
- package/dist/fleet-manager.js +871 -152
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-stop-budget.d.ts +22 -0
- package/dist/fleet-stop-budget.js +95 -0
- package/dist/fleet-stop-budget.js.map +1 -0
- package/dist/full-restart.js +2 -3
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
- package/dist/install-lock.d.ts +49 -0
- package/dist/install-lock.js +200 -0
- package/dist/install-lock.js.map +1 -0
- package/dist/instance-config-impact.js +3 -2
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/kiro-transcript-lane.d.ts +8 -4
- package/dist/kiro-transcript-lane.js +66 -44
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/locale.js +96 -0
- package/dist/locale.js.map +1 -1
- package/dist/next-steps.d.ts +5 -0
- package/dist/next-steps.js +26 -0
- package/dist/next-steps.js.map +1 -0
- package/dist/outbound-handlers.js +8 -0
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +4 -0
- package/dist/outbound-schemas.js +7 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/outbox-open-error.d.ts +7 -0
- package/dist/outbox-open-error.js +15 -0
- package/dist/outbox-open-error.js.map +1 -0
- package/dist/package-preimage.d.ts +26 -0
- package/dist/package-preimage.js +96 -0
- package/dist/package-preimage.js.map +1 -0
- package/dist/pane-context-cache.d.ts +25 -0
- package/dist/pane-context-cache.js +73 -0
- package/dist/pane-context-cache.js.map +1 -0
- package/dist/paths.d.ts +0 -7
- package/dist/paths.js +8 -2
- package/dist/paths.js.map +1 -1
- package/dist/provider-probe.d.ts +15 -0
- package/dist/provider-probe.js +20 -0
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-link-progress.d.ts +73 -0
- package/dist/public-link-progress.js +190 -0
- package/dist/public-link-progress.js.map +1 -0
- package/dist/public-web-gateway.js +2 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +6 -1
- package/dist/public-web-link.js +87 -15
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +93 -20
- package/dist/quickstart-api.js +194 -62
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +15 -0
- package/dist/quickstart.js +110 -33
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +98 -0
- package/dist/reply-buttons-controller.js +174 -0
- package/dist/reply-buttons-controller.js.map +1 -0
- package/dist/reply-buttons.d.ts +113 -0
- package/dist/reply-buttons.js +208 -0
- package/dist/reply-buttons.js.map +1 -0
- package/dist/reply-dedup.js +3 -1
- package/dist/reply-dedup.js.map +1 -1
- package/dist/restart-guard.d.ts +53 -0
- package/dist/restart-guard.js +114 -0
- package/dist/restart-guard.js.map +1 -0
- package/dist/scheduler/db.js +6 -2
- package/dist/scheduler/db.js.map +1 -1
- package/dist/service-activation.d.ts +62 -7
- package/dist/service-activation.js +237 -55
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +56 -1
- package/dist/service-installer.js +152 -12
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +65 -0
- package/dist/service-plan.js +108 -0
- package/dist/service-plan.js.map +1 -0
- package/dist/settings-api.js +92 -10
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-authority.d.ts +11 -0
- package/dist/settings-authority.js +128 -0
- package/dist/settings-authority.js.map +1 -0
- package/dist/settings-change.d.ts +5 -0
- package/dist/settings-change.js +37 -8
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +3 -0
- package/dist/settings-confirmation.js +7 -1
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +19 -7
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +1 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-redaction.d.ts +18 -0
- package/dist/settings-redaction.js +58 -0
- package/dist/settings-redaction.js.map +1 -0
- package/dist/setup-form.d.ts +9 -1
- package/dist/setup-form.js +25 -1
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.js +15 -6
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +27 -19
- package/dist/setup-wizard.js.map +1 -1
- package/dist/sqlite-open-errors.d.ts +12 -0
- package/dist/sqlite-open-errors.js +13 -0
- package/dist/sqlite-open-errors.js.map +1 -0
- package/dist/systemd-runtime.d.ts +15 -0
- package/dist/systemd-runtime.js +41 -0
- package/dist/systemd-runtime.js.map +1 -0
- package/dist/tmux-control.d.ts +44 -0
- package/dist/tmux-control.js +170 -21
- package/dist/tmux-control.js.map +1 -1
- package/dist/token-env-name.d.ts +37 -0
- package/dist/token-env-name.js +93 -0
- package/dist/token-env-name.js.map +1 -0
- package/dist/tool-permissions.js +5 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +7 -16
- package/dist/topic-commands.js +56 -85
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +3 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +68 -0
- package/dist/transcript-sources.js +336 -29
- package/dist/transcript-sources.js.map +1 -1
- package/dist/transcript-turns.d.ts +39 -0
- package/dist/transcript-turns.js +58 -0
- package/dist/transcript-turns.js.map +1 -0
- package/dist/tunnel/cloudflared-install.d.ts +18 -0
- package/dist/tunnel/cloudflared-install.js +15 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +3 -0
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/ui/chat-commands.js +43 -0
- package/dist/ui/chat-render.js +34 -8
- package/dist/ui/chat-store.js +51 -2
- package/dist/ui/chat-strings.js +30 -2
- package/dist/ui/chat-thread.js +141 -47
- package/dist/ui/first-run.js +64 -0
- package/dist/ui/fleet-cache.js +168 -0
- package/dist/ui/html-attachment.js +26 -0
- package/dist/ui/panel-chat.js +117 -23
- package/dist/ui/panel-fleet.js +31 -17
- package/dist/ui/panel-settings.js +29 -15
- package/dist/ui/preview-panel.js +237 -0
- package/dist/ui/preview.js +7 -3
- package/dist/ui/settings-apply.js +3 -1
- package/dist/ui/settings-dialogs.js +95 -25
- package/dist/ui/settings-discord.js +130 -0
- package/dist/ui/settings-model.js +21 -0
- package/dist/ui/settings-strings.js +50 -8
- package/dist/ui/settings-token.js +54 -0
- package/dist/ui/settings-wizard.js +63 -29
- package/dist/ui/shared/app-route.js +1 -1
- package/dist/ui/shared/app-shell.js +25 -37
- package/dist/ui/shared/app-store.js +5 -3
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +181 -5
- package/dist/ui/shared/app.js +2 -1
- package/dist/ui/shared/instance-nav.js +310 -0
- package/dist/ui/shared/panel-view.js +12 -190
- package/dist/ui/shared/signin.js +13 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/view-roster-store.js +5 -0
- package/dist/ui/shared/view-strings.js +12 -0
- package/dist/ui/signin.html +2 -1
- package/dist/update-dispatch.d.ts +28 -0
- package/dist/update-dispatch.js +73 -0
- package/dist/update-dispatch.js.map +1 -0
- package/dist/update-install.d.ts +78 -8
- package/dist/update-install.js +158 -30
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +22 -0
- package/dist/update-launch.js +106 -0
- package/dist/update-launch.js.map +1 -0
- package/dist/update-marker.js +3 -8
- package/dist/update-marker.js.map +1 -1
- package/dist/usage/format-rich.js +7 -1
- package/dist/usage/format-rich.js.map +1 -1
- 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 +4 -0
- package/dist/usage/providers.js +2 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.js +48 -2
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +40 -0
- package/dist/view-api.js +28 -10
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +22 -0
- package/dist/web-api.js +115 -16
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +8 -0
- package/dist/web-auth.js +34 -8
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +21 -0
- package/dist/web-chat-history.js +44 -0
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +77 -0
- package/dist/web-commands.js +81 -0
- package/dist/web-commands.js.map +1 -0
- package/dist/web-host-guard.d.ts +18 -2
- package/dist/web-host-guard.js +30 -5
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-session.d.ts +7 -1
- package/dist/web-session.js +23 -2
- package/dist/web-session.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/launcher/agend +110 -0
- package/launcher/agend-agent +110 -0
- package/launcher/agend-agent.cjs +4 -0
- package/launcher/agend.cjs +4 -0
- package/launcher/install-admission.cjs +105 -0
- package/launcher/launch.cjs +64 -0
- package/launcher/postinstall.cjs +118 -0
- package/launcher/run-in-process.cjs +13 -0
- package/launcher/runtime-platform.cjs +86 -0
- package/launcher/runtime-select.cjs +217 -0
- package/package.json +12 -4
- package/scripts/preinstall-guard.cjs +40 -21
- package/templates/launchd.plist.ejs +2 -2
- package/templates/systemd.service.ejs +4 -4
package/dist/ui/panel-chat.js
CHANGED
|
@@ -10,7 +10,7 @@ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-
|
|
|
10
10
|
import { t } from "/assets/app-i18n.js";
|
|
11
11
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
12
12
|
import { useLease } from "/assets/app-ctx.js";
|
|
13
|
-
import { PanelHeader, onPanelKey, setTitle, addFooterItem, statusClass, statusLabel, openDrawer } from "/assets/app-shell.js";
|
|
13
|
+
import { PanelHeader, onPanelKey, setTitle, addFooterItem, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
|
|
14
14
|
import { navigate } from "/assets/app-nav.js";
|
|
15
15
|
import { Menu } from "/assets/ui-menu.js";
|
|
16
16
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
@@ -21,8 +21,11 @@ import { confirmDialog } from "/assets/ui-confirm.js";
|
|
|
21
21
|
import "./chat-strings.js";
|
|
22
22
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
23
23
|
import { createChatStore } from "./chat-store.js";
|
|
24
|
-
import { createThread } from "./chat-thread.js";
|
|
24
|
+
import { createThread, msgKey } from "./chat-thread.js";
|
|
25
|
+
import { PreviewPanel, openPanel, panelStore, shownKey } from "./preview-panel.js";
|
|
26
|
+
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
25
27
|
import { installTour, refreshTourSpot, startTour } from "./chat-tour.js";
|
|
28
|
+
import { FirstRunCard } from "./first-run.js";
|
|
26
29
|
|
|
27
30
|
const R = () => globalThis.AgendChatRender;
|
|
28
31
|
const P = () => globalThis.AgendPreview;
|
|
@@ -56,7 +59,7 @@ export async function setPreviewOptIn(on) {
|
|
|
56
59
|
export function boot({ stream, boot: bootData, deps = {} }) {
|
|
57
60
|
if (store) return store;
|
|
58
61
|
store = createChatStore({
|
|
59
|
-
fetch: (...a) => fetch(...a), toast, announce, t, setTimeout: (fn, ms) => setTimeout(fn, ms), ...deps,
|
|
62
|
+
fetch: (...a) => fetch(...a), toast, announce, t, setTimeout: (fn, ms) => setTimeout(fn, ms), confirm: confirmDialog, ...deps,
|
|
60
63
|
});
|
|
61
64
|
store.attach(stream);
|
|
62
65
|
// #1306: what the server said about previews for this load. The page-wide message and storage listeners this adds
|
|
@@ -97,9 +100,9 @@ async function copyText(text) {
|
|
|
97
100
|
return ok;
|
|
98
101
|
}
|
|
99
102
|
/** The HTML as a file: a blob of the source as application/octet-stream, saved as reply.html — never opened. */
|
|
100
|
-
function downloadHtml(code) {
|
|
103
|
+
function downloadHtml(code, name) {
|
|
101
104
|
const url = URL.createObjectURL(new Blob([code], { type: "application/octet-stream" }));
|
|
102
|
-
const a = document.createElement("a"); a.href = url; a.download = "reply.html"; a.className = "offscreen";
|
|
105
|
+
const a = document.createElement("a"); a.href = url; a.download = typeof name === "string" && name.trim() ? name.trim() : "reply.html"; a.className = "offscreen";
|
|
103
106
|
document.body.appendChild(a); a.click(); a.remove();
|
|
104
107
|
URL.revokeObjectURL(url);
|
|
105
108
|
}
|
|
@@ -125,9 +128,14 @@ export function ChatPanel({ route, navKey }) {
|
|
|
125
128
|
}, [name, !!inst]);
|
|
126
129
|
useEffect(() => { setTitle(name || t("app.chat")); }, [name, navKey]);
|
|
127
130
|
if (!name) {
|
|
131
|
+
// #1519 P7: a web-only fleet starts here with the first-run card; with no agent at all, the way to make one.
|
|
132
|
+
const none = app.ready && !app.instances.length;
|
|
128
133
|
return html`<div class="panel p-chat"><${PanelHeader} title=${t("app.chat")} />
|
|
129
|
-
<div class="panel-body center"
|
|
130
|
-
|
|
134
|
+
<div class="panel-body center"><div class="first-run-stack"><${FirstRunCard} />
|
|
135
|
+
${none ? html`<${Empty} icon="bot" title=${t("app.noInstances")} hint=${t("app.noInstancesHint")}
|
|
136
|
+
action=${html`<button type="button" class="btn btn-primary" onClick=${requestNewInstance}><${Icon} name="plus" size=${16} />${t("app.newInstance")}</button>`} />`
|
|
137
|
+
: html`<${Empty} icon="chat" title=${t("chat.pick")} hint=${t("chat.pickHint")}
|
|
138
|
+
action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} />`}</div></div></div>`;
|
|
131
139
|
}
|
|
132
140
|
if (!app.ready) return html`<div class="panel p-chat"><${PanelHeader} title=${name} /><div class="panel-body"><${Skeleton} lines=${5} /></div></div>`;
|
|
133
141
|
if (!inst) return html`<${NotFound} name=${name} />`;
|
|
@@ -146,7 +154,7 @@ function NotFound({ name }) {
|
|
|
146
154
|
function ChatView({ name, inst, lease, exec, awaiting }) {
|
|
147
155
|
const [dialog, setDialog] = useState(null); // "details" | "delete" | null
|
|
148
156
|
const [wrap, setWrap] = useState(codeWrap);
|
|
149
|
-
const view = useRef(null);
|
|
157
|
+
const view = useRef(null), split = useRef(null), thread = useRef(null);
|
|
150
158
|
useEffect(() => { wrapListeners.add(setWrap); return () => wrapListeners.delete(setWrap); }, []);
|
|
151
159
|
// First visit to this instance's chat on this page: its history (a navigation — it counts as use). Back again: nothing.
|
|
152
160
|
useEffect(() => { store.openHistory(name, lease); }, [name]);
|
|
@@ -189,11 +197,21 @@ function ChatView({ name, inst, lease, exec, awaiting }) {
|
|
|
189
197
|
{ key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
|
|
190
198
|
];
|
|
191
199
|
const cls = statusClass(inst, exec, awaiting);
|
|
192
|
-
|
|
200
|
+
// #1269 quick actions: the model and effort it runs, each a way to /model or /effort (the same pick list).
|
|
201
|
+
const pick = (command) => store.runCommand(name, command, "", { alive: () => lease.current() });
|
|
202
|
+
const sub = html`<span class="status"><span class=${`dot ${cls}`} aria-hidden="true"></span>${statusLabel(inst, exec, awaiting)}</span>
|
|
203
|
+
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", inst.model)} aria-label=${t("chat.chipModel", inst.model)} onClick=${() => pick("model")}>${inst.model}</button>` : null}
|
|
204
|
+
${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
|
|
193
205
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
|
|
194
206
|
<${PanelHeader} title=${name} sub=${sub}><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
195
|
-
|
|
196
|
-
|
|
207
|
+
<div class="chat-split" ref=${split}>
|
|
208
|
+
<div class="chat-main">
|
|
209
|
+
<${Thread} name=${name} th=${thread} />
|
|
210
|
+
<${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} />
|
|
211
|
+
</div>
|
|
212
|
+
<${PreviewPanel} name=${name} split=${split} msgs=${() => store.state.msgs[name] || []} subscribe=${(fn) => store.subscribe(fn)}
|
|
213
|
+
keyOf=${msgKey} reveal=${(k) => thread.current && thread.current.reveal(k)} download=${downloadHtml} />
|
|
214
|
+
</div>
|
|
197
215
|
<div class="drop-overlay" aria-hidden="true"><div class="drop-card"><${Icon} name="attach" size=${32} /><span>${t("chat.dropHere")}</span></div></div>
|
|
198
216
|
${dialog === "details" ? html`<${DetailsDialog} name=${name} onClose=${() => setDialog(null)} />` : null}
|
|
199
217
|
${dialog === "delete" ? html`<${DeleteDialog} name=${name} onClose=${() => setDialog(null)} />` : null}
|
|
@@ -201,14 +219,17 @@ function ChatView({ name, inst, lease, exec, awaiting }) {
|
|
|
201
219
|
}
|
|
202
220
|
|
|
203
221
|
/** The thread: Preact owns the elements, the keyed renderer owns what is inside the list (chat-thread.js). */
|
|
204
|
-
function Thread({ name }) {
|
|
205
|
-
const scroller = useRef(null), list = useRef(null)
|
|
222
|
+
function Thread({ name, th }) {
|
|
223
|
+
const scroller = useRef(null), list = useRef(null);
|
|
206
224
|
const [jump, setJump] = useState({ show: false, unseen: 0 });
|
|
207
225
|
const [empty, setEmpty] = useState(false);
|
|
208
226
|
useLayoutEffect(() => {
|
|
209
227
|
const thread = createThread(list.current, scroller.current, {
|
|
210
228
|
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: setJump, onEmpty: setEmpty,
|
|
211
229
|
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
230
|
+
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
|
231
|
+
replyButtonBusy: (id) => store.state.rbBusy.has(id),
|
|
232
|
+
panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o) }, // #1481
|
|
212
233
|
});
|
|
213
234
|
th.current = thread;
|
|
214
235
|
thread.render(store.state.msgs[name] || [], { restore: store.state.scrollMemo[name] ?? null });
|
|
@@ -218,12 +239,13 @@ function Thread({ name }) {
|
|
|
218
239
|
else if (kind === "sent") thread.jumpLatest();
|
|
219
240
|
});
|
|
220
241
|
const offPv = P() ? P().onChange(() => thread.refreshCards()) : () => {};
|
|
242
|
+
const offPanel = panelStore.subscribe(() => thread.refreshCards()); // a card shows whether the panel has its block
|
|
221
243
|
const onScroll = () => { store.state.scrollMemo[name] = thread.onScroll(); };
|
|
222
244
|
const sc = scroller.current, ls = list.current;
|
|
223
245
|
sc.addEventListener("scroll", onScroll, { passive: true });
|
|
224
246
|
ls.addEventListener("click", thread.onClick);
|
|
225
247
|
return () => {
|
|
226
|
-
off(); offPv();
|
|
248
|
+
off(); offPv(); offPanel();
|
|
227
249
|
sc.removeEventListener("scroll", onScroll);
|
|
228
250
|
ls.removeEventListener("click", thread.onClick);
|
|
229
251
|
thread.dispose(); // the whole thread goes: every preview in it stops first
|
|
@@ -239,20 +261,51 @@ function Thread({ name }) {
|
|
|
239
261
|
}
|
|
240
262
|
|
|
241
263
|
/** Everything under the thread: prompts, the working line, a failed send, files waiting, and the composer. */
|
|
242
|
-
function Dock({ name, lease, exec, awaiting }) {
|
|
264
|
+
function Dock({ name, inst, lease, exec, awaiting }) {
|
|
243
265
|
useChatTick(name, ["state"]);
|
|
244
266
|
const s = store.state;
|
|
245
267
|
return html`<div class="dock"><div class="dock-col">
|
|
246
268
|
<${Prompts} name=${name} />
|
|
247
269
|
<${WorkBar} name=${name} lease=${lease} exec=${exec} awaiting=${awaiting} />
|
|
270
|
+
<${CommandCard} name=${name} lease=${lease} />
|
|
271
|
+
<${QuickActions} name=${name} inst=${inst} lease=${lease} />
|
|
248
272
|
${s.failedSends[name] ? html`<div class="failed-send" role="alert"><span>${t("chat.notSent")}</span><span class="txt">${s.failedSends[name]}</span>
|
|
249
273
|
<button type="button" class="btn btn-primary btn-sm" onClick=${() => store.putBack(name)}>${t("chat.putBack")}</button>
|
|
250
274
|
<button type="button" class="btn btn-sm" onClick=${() => store.discardFailed(name)}>${t("chat.discard")}</button></div>` : null}
|
|
251
|
-
<${Composer} name=${name} busy=${R().isBusy(exec)} />
|
|
275
|
+
<${Composer} name=${name} lease=${lease} busy=${R().isBusy(exec)} />
|
|
252
276
|
<p class="hint">${t("chat.composerHint")}</p>
|
|
253
277
|
</div></div>`;
|
|
254
278
|
}
|
|
255
279
|
|
|
280
|
+
/** #1269: what the last chat command answered — its text, why it was refused, or a list to choose from. */
|
|
281
|
+
function CommandCard({ name, lease }) {
|
|
282
|
+
const c = store.state.commands[name];
|
|
283
|
+
if (!c) return null;
|
|
284
|
+
const run = (args) => store.runCommand(name, c.command, args, { alive: () => lease.current() });
|
|
285
|
+
return html`<div class=${`cmd-card${c.error ? " bad" : ""}`} role=${c.error ? "alert" : "status"}>
|
|
286
|
+
<div class="cmd-head"><span class="cmd-name mono">/${c.command}</span>
|
|
287
|
+
${c.busy ? html`<span class="wait">${t("chat.cmdRunning", c.command)}</span>` : html`<button type="button" class="icon-btn" title=${t("chat.cmdDismiss")}
|
|
288
|
+
aria-label=${t("chat.cmdDismiss")} onClick=${() => store.dismissCommand(name)}><${Icon} name="close" size=${14} /></button>`}</div>
|
|
289
|
+
${c.error ? html`<p class="cmd-text">${c.error}</p>` : null}
|
|
290
|
+
${c.text ? html`<pre class="cmd-text">${c.text}</pre>` : null}
|
|
291
|
+
${c.choices ? html`<div class="cmd-choices" role="group" aria-label=${t("chat.cmdChoose")}>${c.choices.options.map((o) => html`<button key=${o.id} type="button"
|
|
292
|
+
class=${`btn btn-sm${o.id === c.choices.current ? " current" : ""}`} aria-current=${o.id === c.choices.current ? "true" : undefined} onClick=${() => run(o.id)}>${o.label}</button>`)}</div>` : null}
|
|
293
|
+
</div>`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** #1269 quick actions: Compact and Clear… once the instance's context is 70% used (from the status it already has). */
|
|
297
|
+
function QuickActions({ name, inst, lease }) {
|
|
298
|
+
const pct = inst && typeof inst.context_pct === "number" ? Math.round(inst.context_pct) : null;
|
|
299
|
+
if (pct == null || pct < 70) return null;
|
|
300
|
+
const busy = !!(store.state.commands[name] && store.state.commands[name].busy);
|
|
301
|
+
const run = (command) => store.runCommand(name, command, "", { alive: () => lease.current() });
|
|
302
|
+
return html`<div class="quick-actions" role="group" aria-label=${t("chat.chipContext", pct)}>
|
|
303
|
+
<span class="qa-ctx">${t("chat.chipContext", pct)}</span>
|
|
304
|
+
<button type="button" class="btn btn-sm" disabled=${busy} onClick=${() => run("compact")}>${t("chat.chipCompact")}</button>
|
|
305
|
+
<button type="button" class="btn btn-sm" disabled=${busy} onClick=${() => run("clear")}>${t("chat.chipClear")}</button>
|
|
306
|
+
</div>`;
|
|
307
|
+
}
|
|
308
|
+
|
|
256
309
|
function Prompts({ name }) {
|
|
257
310
|
const list = store.promptsFor(name);
|
|
258
311
|
if (!list.length) return null;
|
|
@@ -281,20 +334,53 @@ function WorkBar({ name, lease, exec, awaiting }) {
|
|
|
281
334
|
: html`<span class="elapsed" title=${t("chat.elapsedTitle")} aria-hidden="true">${since == null ? "" : R().formatElapsed(performance.now() - since)}</span>`}</div>`;
|
|
282
335
|
}
|
|
283
336
|
|
|
284
|
-
function Composer({ name, busy }) {
|
|
337
|
+
function Composer({ name, lease, busy }) {
|
|
285
338
|
const s = store.state;
|
|
286
339
|
const input = useRef(null), fileIn = useRef(null);
|
|
287
340
|
const [, tick] = useState(0);
|
|
341
|
+
const [sel, setSel] = useState(0);
|
|
342
|
+
const [closedFor, setClosedFor] = useState(null); // the draft the palette was closed on (Esc); it opens again on change
|
|
288
343
|
const value = s.drafts[name] || "";
|
|
289
344
|
const files = s.pendingFiles[name] || [];
|
|
290
345
|
const content = !!value.trim() || files.length > 0;
|
|
346
|
+
// A file waiting: what goes is a message with its file, never a command (no palette, no command line).
|
|
347
|
+
const palette = files.length || closedFor === value ? null : paletteFor(value);
|
|
348
|
+
const pick = palette && palette.length ? palette[Math.min(sel, palette.length - 1)] : null;
|
|
291
349
|
// Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300).
|
|
292
350
|
useLayoutEffect(() => { const el = input.current; if (!el) return; el.style.height = "auto"; el.style.height = `${Math.min(el.scrollHeight, 240)}px`; }, [value]);
|
|
293
351
|
useEffect(() => { if (input.current) input.current.focus(); }, [name]);
|
|
294
352
|
useEffect(() => { refreshTourSpot(); });
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
353
|
+
const setValue = (v) => { store.setDraft(name, v); setSel(0); tick(n => n + 1); };
|
|
354
|
+
// #1269: a command line runs the command (POST /ui/command); anything else — "/" included — is sent as before.
|
|
355
|
+
// The draft is consumed only by a command that starts: one still out keeps the new line in the composer (#1476 review).
|
|
356
|
+
const runLine = (cmd) => {
|
|
357
|
+
if (!store.commandFree(name)) return;
|
|
358
|
+
setValue("");
|
|
359
|
+
store.runCommand(name, cmd.command, cmd.args, { alive: () => lease.current() });
|
|
360
|
+
};
|
|
361
|
+
const complete = (c) => setValue(`/${c.name}${c.arg ? " " : ""}`);
|
|
362
|
+
const submit = () => {
|
|
363
|
+
if (pick) {
|
|
364
|
+
if (value === `/${pick.name}` && !needsArgument(pick.name)) runLine({ command: pick.name, args: "" });
|
|
365
|
+
else complete(pick);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const cmd = files.length ? null : parseCommandLine(value);
|
|
369
|
+
if (cmd && needsArgument(cmd.command) && !cmd.args) { setValue(`/${cmd.command} `); return; }
|
|
370
|
+
if (cmd) { runLine(cmd); return; }
|
|
371
|
+
store.send(name);
|
|
372
|
+
};
|
|
373
|
+
const onKeyDown = (e) => {
|
|
374
|
+
if (palette && palette.length && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
setSel((n) => (Math.min(n, palette.length - 1) + (e.key === "ArrowDown" ? 1 : palette.length - 1)) % palette.length);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (pick && e.key === "Tab" && !e.shiftKey) { e.preventDefault(); complete(pick); return; }
|
|
380
|
+
if (palette && e.key === "Escape" && !e.isComposing) { e.preventDefault(); setClosedFor(value); return; }
|
|
381
|
+
if (R().composerKey(e) === "send") { e.preventDefault(); submit(); }
|
|
382
|
+
};
|
|
383
|
+
const onInput = (e) => { setValue(e.target.value); };
|
|
298
384
|
// Files in a paste are attached; a very long text paste goes as a text file — only once it is really attached.
|
|
299
385
|
const onPaste = (e) => {
|
|
300
386
|
const list = e.clipboardData && e.clipboardData.files;
|
|
@@ -302,18 +388,26 @@ function Composer({ name, busy }) {
|
|
|
302
388
|
if (list && list.length) { store.addFiles(name, list); if (!text) e.preventDefault(); return; }
|
|
303
389
|
if (R().isLongPaste(text) && store.attachPastedText(name, text)) e.preventDefault();
|
|
304
390
|
};
|
|
391
|
+
const optId = (c) => `cmd-${c.name}`;
|
|
305
392
|
return html`<div class="composer-box">
|
|
393
|
+
${palette ? html`<ul class="cmd-palette" id="cmdPalette" role="listbox" aria-label=${t("chat.cmdCommands")}>
|
|
394
|
+
${palette.length ? palette.map((c) => html`<li key=${c.name} id=${optId(c)} role="option" aria-selected=${c === pick ? "true" : "false"}
|
|
395
|
+
class=${c === pick ? "on" : ""} onMouseDown=${(e) => { e.preventDefault(); complete(c); }}>
|
|
396
|
+
<span class="cmd-name mono">/${c.name}${c.arg ? html` <span class="cmd-arg">${c.arg}</span>` : null}</span><span class="cmd-desc">${t(`chat.cmd_${c.name}`)}</span></li>`)
|
|
397
|
+
: html`<li class="cmd-none" role="option" aria-selected="false">${t("chat.cmdNotCommand")}</li>`}
|
|
398
|
+
</ul>` : null}
|
|
306
399
|
${files.length ? html`<div class="pending-files">${files.map((f, i) => html`<${FileChip} key=${`${f.name}-${i}`} f=${f} i=${i} name=${name} />`)}</div>` : null}
|
|
307
400
|
<div class="composer">
|
|
308
401
|
<button id="attachBtn" type="button" class="icon-btn" title=${t("chat.attachTitle")} aria-label=${t("chat.attachTitle")} onClick=${() => fileIn.current && fileIn.current.click()}><${Icon} name="attach" /></button>
|
|
309
402
|
<input ref=${fileIn} type="file" multiple hidden accept="image/png,image/jpeg,image/gif,image/webp,application/pdf,text/*,.md,.csv,.json,.log,.yaml,.yml"
|
|
310
403
|
onChange=${(e) => { store.addFiles(name, e.target.files); e.target.value = ""; }} />
|
|
311
404
|
<textarea id="msgIn" ref=${input} rows="1" value=${value} placeholder=${t("chat.composerPlaceholder", name)} aria-label=${t("chat.composerPlaceholder", name)}
|
|
312
|
-
autocomplete="off"
|
|
405
|
+
autocomplete="off" aria-controls=${palette ? "cmdPalette" : undefined} aria-expanded=${palette ? "true" : "false"}
|
|
406
|
+
aria-activedescendant=${pick ? optId(pick) : undefined} onInput=${onInput} onKeyDown=${onKeyDown} onPaste=${onPaste}></textarea>
|
|
313
407
|
<button id="stopBtn" type="button" class="btn btn-stop" hidden=${!busy} title=${t("chat.stopReplyTitle")}
|
|
314
|
-
disabled=${!!s.cancelling[name]} onClick=${() => store.cancelReply(name)}><${Icon} name="stop" size=${14}
|
|
408
|
+
disabled=${!!s.cancelling[name]} onClick=${() => store.cancelReply(name)}><${Icon} name="stop" size=${14} /><span class="stop-label">${t("chat.stopReply")}</span></button>
|
|
315
409
|
<button id="sendBtn" type="button" class="btn btn-primary btn-send" hidden=${busy && !content} disabled=${!content || !!s.sending[name]}
|
|
316
|
-
aria-label=${t("chat.send")} title=${t("chat.send")} onClick=${
|
|
410
|
+
aria-label=${t("chat.send")} title=${t("chat.send")} onClick=${submit}><${Icon} name="send" size=${16} /><span class="send-label">${t("chat.send")}</span></button>
|
|
317
411
|
</div></div>`;
|
|
318
412
|
}
|
|
319
413
|
|
package/dist/ui/panel-fleet.js
CHANGED
|
@@ -18,13 +18,14 @@ import { toast } from "/assets/ui-toast.js";
|
|
|
18
18
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
19
19
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
20
20
|
import { OrgChart } from "./fleet-org.js";
|
|
21
|
+
import { CacheReport } from "./fleet-cache.js";
|
|
21
22
|
|
|
22
23
|
register("fleet", {
|
|
23
24
|
en: {
|
|
24
|
-
title: "Fleet", tasks: "Tasks", schedules: "Schedules", teams: "Teams", org: "Org chart", config: "Config",
|
|
25
|
+
title: "Fleet", tasks: "Tasks", schedules: "Schedules", teams: "Teams", org: "Org chart", cache: "Cache", config: "Config",
|
|
25
26
|
newTask: "New task", newSchedule: "New schedule", newTeam: "New team",
|
|
26
27
|
noTasks: "No tasks yet", noTasksHint: "Create one to coordinate fleet work.", noSchedules: "No schedules yet", noSchedulesHint: "Automate recurring work with cron.",
|
|
27
|
-
noTeams: "No teams yet", noTeamsHint: "Group instances for coordinated work.", countTasks: "{0} tasks", countSchedules: "{0} schedules", countTeams: "{0} teams",
|
|
28
|
+
noTeams: "No teams yet", noTeamsHint: "Group instances for coordinated work.", countTasks: "{0} tasks", countSchedules: "{0} schedules", countTeams: "{0} teams", countTask: "1 task", countSchedule: "1 schedule", countTeam: "1 team",
|
|
28
29
|
claim: "Claim", done: "Done", delete: "Delete", on: "on", off: "off", cancel: "Cancel", create: "Create", creating: "Creating…", save: "Save changes",
|
|
29
30
|
taskTitle: "Title", taskDesc: "Description", priority: "Priority", assignee: "Assignee", unassigned: "Unassigned",
|
|
30
31
|
pNormal: "Normal", pLow: "Low", pHigh: "High", pUrgent: "Urgent",
|
|
@@ -35,16 +36,16 @@ register("fleet", {
|
|
|
35
36
|
channel: "Channel", type: "Type", tokenEnv: "Bot token env", groupId: "Group ID", show: "Show", hide: "Hide", access: "Access control", mode: "Mode", locked: "Locked", open: "Open",
|
|
36
37
|
allowedUsers: "Allowed users", allowedUsersNote: "Comma-separated user IDs", defaults: "Defaults", backend: "Backend", roots: "Project roots", addRoot: "Add root", removeRoot: "Remove",
|
|
37
38
|
saveNote: "Saving rewrites fleet.yaml (its comments are lost).",
|
|
38
|
-
newInstance: "New instance", directory: "Directory", directoryHint: "Optional — a workspace is created if empty", topic: "
|
|
39
|
-
topicRequired: "
|
|
39
|
+
newInstance: "New instance", directory: "Directory", directoryHint: "Optional — a workspace is created if empty", topic: "Name", topicHint: "From the directory name",
|
|
40
|
+
topicRequired: "Name is required when Directory is empty", description: "Description", descriptionHint: "What this instance does", model: "Model", modelHint: "e.g. sonnet, opus, gpt-5",
|
|
40
41
|
branch: "Branch (git worktree)", branchHint: "e.g. feature-x", tags: "Tags", tagsHint: "comma-separated, e.g. dev, review", systemPrompt: "System prompt", systemPromptHint: "Custom instructions for this instance",
|
|
41
42
|
fleetDefault: "Use the fleet default", notInstalled: "(not installed)", deprecated: "(deprecated)", instanceCreated: "Instance created",
|
|
42
43
|
},
|
|
43
44
|
"zh-TW": {
|
|
44
|
-
title: "Fleet", tasks: "Tasks", schedules: "排程", teams: "Teams", org: "組織圖", config: "設定",
|
|
45
|
+
title: "Fleet", tasks: "Tasks", schedules: "排程", teams: "Teams", org: "組織圖", cache: "快取", config: "設定",
|
|
45
46
|
newTask: "新增 task", newSchedule: "新增排程", newTeam: "新增 team",
|
|
46
47
|
noTasks: "還沒有 task", noTasksHint: "建立一個來協調 fleet 的工作。", noSchedules: "還沒有排程", noSchedulesHint: "用 cron 自動執行例行工作。",
|
|
47
|
-
noTeams: "還沒有 team", noTeamsHint: "把 instance 分組一起工作。", countTasks: "{0} 個 task", countSchedules: "{0} 個排程", countTeams: "{0} 個 team",
|
|
48
|
+
noTeams: "還沒有 team", noTeamsHint: "把 instance 分組一起工作。", countTasks: "{0} 個 task", countSchedules: "{0} 個排程", countTeams: "{0} 個 team", countTask: "1 個 task", countSchedule: "1 個排程", countTeam: "1 個 team",
|
|
48
49
|
claim: "認領", done: "完成", delete: "刪除", on: "開", off: "關", cancel: "取消", create: "建立", creating: "建立中…", save: "儲存變更",
|
|
49
50
|
taskTitle: "標題", taskDesc: "說明", priority: "優先順序", assignee: "負責人", unassigned: "未指派",
|
|
50
51
|
pNormal: "一般", pLow: "低", pHigh: "高", pUrgent: "緊急",
|
|
@@ -55,14 +56,14 @@ register("fleet", {
|
|
|
55
56
|
channel: "頻道", type: "類型", tokenEnv: "Bot token 環境變數", groupId: "Group ID", show: "顯示", hide: "隱藏", access: "存取控制", mode: "模式", locked: "鎖定", open: "開放",
|
|
56
57
|
allowedUsers: "允許的使用者", allowedUsersNote: "以逗號分隔的使用者 ID", defaults: "預設值", backend: "Backend", roots: "專案根目錄", addRoot: "新增根目錄", removeRoot: "移除",
|
|
57
58
|
saveNote: "儲存會改寫 fleet.yaml(其中的註解會遺失)。",
|
|
58
|
-
newInstance: "新增 instance", directory: "目錄", directoryHint: "可留空——留空會自動建立工作區", topic: "
|
|
59
|
-
topicRequired: "
|
|
59
|
+
newInstance: "新增 instance", directory: "目錄", directoryHint: "可留空——留空會自動建立工作區", topic: "名稱", topicHint: "預設取自目錄名稱",
|
|
60
|
+
topicRequired: "目錄留空時必須填寫名稱", description: "說明", descriptionHint: "這個 instance 做什麼", model: "模型", modelHint: "例如 sonnet、opus、gpt-5",
|
|
60
61
|
branch: "分支(git worktree)", branchHint: "例如 feature-x", tags: "標籤", tagsHint: "以逗號分隔,例如 dev, review", systemPrompt: "System prompt", systemPromptHint: "這個 instance 的自訂指示",
|
|
61
62
|
fleetDefault: "使用 fleet 預設", notInstalled: "(未安裝)", deprecated: "(已淘汰)", instanceCreated: "Instance 已建立",
|
|
62
63
|
},
|
|
63
64
|
});
|
|
64
65
|
|
|
65
|
-
const ICONS = { tasks: "tasks", schedules: "clock", teams: "team", org: "org", config: "sliders" };
|
|
66
|
+
const ICONS = { tasks: "tasks", schedules: "clock", teams: "team", org: "org", cache: "chart", config: "sliders" };
|
|
66
67
|
const BACKENDS = ["claude-code", "codex", "opencode", "kiro-cli", "antigravity", "grok", "muse"];
|
|
67
68
|
|
|
68
69
|
async function api(method, path, body, lease) {
|
|
@@ -73,7 +74,7 @@ async function api(method, path, body, lease) {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
|
-
* Load `path` under the lease: `null` while the first load runs, `{ error }` on failure. A refresh (a new `version`,
|
|
77
|
+
* Load `path` under the lease: `null` while the first load runs, `{ error }` on failure (no answer, or an HTTP error). A refresh (a new `version`,
|
|
77
78
|
* after Claim or Create) keeps showing what is there until the new list arrives — nothing below it is torn down, so
|
|
78
79
|
* an open dialog and its draft survive (#1425 review). Only the latest request's answer is kept.
|
|
79
80
|
*/
|
|
@@ -86,7 +87,10 @@ function useLoad(lease, path, version) {
|
|
|
86
87
|
if (shownFor.current !== lease) { shownFor.current = lease; setState(null); }
|
|
87
88
|
(async () => {
|
|
88
89
|
try {
|
|
89
|
-
|
|
90
|
+
// A read the server refused (a 4xx/5xx with an { error } body) is a failure, not an empty list (alpha.2 sweep).
|
|
91
|
+
const r = await lease.fetch(path, { method: "GET", headers: { "Content-Type": "application/json" } });
|
|
92
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
93
|
+
const d = await r.json();
|
|
90
94
|
if (lease.current() && mine === seq.current) setState({ data: d });
|
|
91
95
|
} catch { if (lease.current() && mine === seq.current) setState({ error: true }); }
|
|
92
96
|
})();
|
|
@@ -98,12 +102,12 @@ export function FleetPanel({ route, navKey }) {
|
|
|
98
102
|
const lease = useLease(navKey);
|
|
99
103
|
const tab = route.tab;
|
|
100
104
|
useEffect(() => { setTitle(`${t("fleet.title")} · ${t(`fleet.${tab}`)}`); }, [tab, navKey]);
|
|
101
|
-
const Body = { tasks: Tasks, schedules: Schedules, teams: Teams, org: Org, config: Config }[tab] || Tasks;
|
|
105
|
+
const Body = { tasks: Tasks, schedules: Schedules, teams: Teams, org: Org, cache: Cache, config: Config }[tab] || Tasks;
|
|
102
106
|
return html`<div class="panel p-fleet">
|
|
103
107
|
<${PanelHeader} title=${t("fleet.title")} />
|
|
104
108
|
<nav class="seg" aria-label=${t("fleet.title")}>${FLEET_TABS.map(k => html`<a key=${k} href=${fleetPath(k)} class=${`seg-item${k === tab ? " active" : ""}`}
|
|
105
109
|
aria-current=${k === tab ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${t(`fleet.${k}`)}</span></a>`)}</nav>
|
|
106
|
-
<div class="panel-body"><div class=${`col${tab === "org" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
|
|
110
|
+
<div class="panel-body"><div class=${`col${tab === "org" || tab === "cache" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
|
|
107
111
|
</div>`;
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -126,7 +130,7 @@ function Tasks({ lease }) {
|
|
|
126
130
|
}
|
|
127
131
|
return html`<${Loaded} state=${state}>${(d) => {
|
|
128
132
|
const tasks = d.tasks || [];
|
|
129
|
-
return html`<${ListHead} count=${t("fleet.countTasks", tasks.length)} label=${t("fleet.newTask")} action=${{ onClick: () => setCreating(true) }} />
|
|
133
|
+
return html`<${ListHead} count=${tasks.length === 1 ? t("fleet.countTask") : t("fleet.countTasks", tasks.length)} label=${t("fleet.newTask")} action=${{ onClick: () => setCreating(true) }} />
|
|
130
134
|
${tasks.length ? html`<ul class="rows">${tasks.map(x => html`<li key=${x.id} class="row-item">
|
|
131
135
|
<span class=${`pill ${x.status}`}>${x.status}</span><span class="grow">${x.title}</span>
|
|
132
136
|
${x.assignee ? html`<a class="link" href=${chatPath(x.assignee)}>${x.assignee}</a>` : null}
|
|
@@ -150,7 +154,7 @@ function Schedules({ lease }) {
|
|
|
150
154
|
}
|
|
151
155
|
return html`<${Loaded} state=${state}>${(d) => {
|
|
152
156
|
const list = d.schedules || [];
|
|
153
|
-
return html`<${ListHead} count=${t("fleet.countSchedules", list.length)} label=${t("fleet.newSchedule")} action=${{ onClick: () => setCreating(true) }} />
|
|
157
|
+
return html`<${ListHead} count=${list.length === 1 ? t("fleet.countSchedule") : t("fleet.countSchedules", list.length)} label=${t("fleet.newSchedule")} action=${{ onClick: () => setCreating(true) }} />
|
|
154
158
|
${list.length ? html`<ul class="rows">${list.map(x => html`<li key=${x.id} class="row-item">
|
|
155
159
|
<span class=${`pill ${x.enabled ? "open" : "done"}`}>${x.enabled ? t("fleet.on") : t("fleet.off")}</span>
|
|
156
160
|
<span class="grow">${x.label || cronDesc(x.cron)}</span><span class="mono muted" title=${cronDesc(x.cron)}>${x.cron}</span>
|
|
@@ -174,7 +178,7 @@ function Teams({ lease }) {
|
|
|
174
178
|
}
|
|
175
179
|
return html`<${Loaded} state=${state}>${(d) => {
|
|
176
180
|
const entries = Object.entries(d.teams || {});
|
|
177
|
-
return html`<${ListHead} count=${t("fleet.countTeams", entries.length)} label=${t("fleet.newTeam")} action=${{ onClick: () => setCreating(true) }} />
|
|
181
|
+
return html`<${ListHead} count=${entries.length === 1 ? t("fleet.countTeam") : t("fleet.countTeams", entries.length)} label=${t("fleet.newTeam")} action=${{ onClick: () => setCreating(true) }} />
|
|
178
182
|
${entries.length ? html`<ul class="rows">${entries.map(([name, team]) => html`<li key=${name} class="row-item">
|
|
179
183
|
<span class="grow strong">${name}</span><span class="muted">${(team.members || []).join(", ")}</span>
|
|
180
184
|
<button type="button" class="btn btn-ghost btn-sm danger" onClick=${() => del(name)}>${t("fleet.delete")}</button></li>`)}</ul>`
|
|
@@ -191,6 +195,16 @@ function Org({ lease }) {
|
|
|
191
195
|
return html`<${Loaded} state=${state}>${(org) => html`<${OrgChart} org=${org} app=${app} />`}</${Loaded}>`;
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
/** #1468: the prompt-cache analysis — one read per opening and per window chosen; Refresh reads again (fleet-cache.js). */
|
|
199
|
+
function Cache({ lease }) {
|
|
200
|
+
const [win, setWin] = useState("7d");
|
|
201
|
+
const [version, setVersion] = useState(0);
|
|
202
|
+
const state = useLoad(lease, `/ui/cache?window=${win}`, version);
|
|
203
|
+
return html`<${Loaded} state=${state}>${(report) => (report && Array.isArray(report.instances)
|
|
204
|
+
? html`<${CacheReport} report=${report} win=${win} setWin=${setWin} refresh=${() => setVersion((v) => v + 1)} />`
|
|
205
|
+
: html`<${ErrorState} onRetry=${renavigate} />`)}</${Loaded}>`;
|
|
206
|
+
}
|
|
207
|
+
|
|
194
208
|
function Secret({ id, value, onInput, label }) {
|
|
195
209
|
const [shown, setShown] = useState(false);
|
|
196
210
|
return html`<div class="field-row"><input id=${id} type=${shown ? "text" : "password"} value=${value} onInput=${onInput} autocomplete="off" />
|
|
@@ -345,7 +359,7 @@ function CreateTeam({ onClose, onDone }) {
|
|
|
345
359
|
</${FormDialog}>`;
|
|
346
360
|
}
|
|
347
361
|
|
|
348
|
-
/** "New instance": the sidebar's
|
|
362
|
+
/** "New instance": the sidebar's + opens it from anywhere (#1408 step 3 merges it with Settings' create flow). */
|
|
349
363
|
export function CreateInstanceDialog({ onClose }) {
|
|
350
364
|
const lease = useLease("create-instance");
|
|
351
365
|
const [backends, setBackends] = useState(null);
|
|
@@ -12,7 +12,7 @@ import { html, useEffect, useMemo, useRef, useState } from "/assets/app-html.js"
|
|
|
12
12
|
import { t } from "/assets/app-i18n.js";
|
|
13
13
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
14
14
|
import { useLease } from "/assets/app-ctx.js";
|
|
15
|
-
import { PanelHeader, setTitle } from "/assets/app-shell.js";
|
|
15
|
+
import { PanelHeader, PendingChanges, setTitle } from "/assets/app-shell.js";
|
|
16
16
|
import { navigate, setLeaveGuard } from "/assets/app-nav.js";
|
|
17
17
|
import { settingsPath, SETTINGS_SECTIONS } from "/assets/app-route.js";
|
|
18
18
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
@@ -25,7 +25,7 @@ import { api, attach, confirmedWrite, newKey } from "./settings-confirm.js";
|
|
|
25
25
|
import { dismissOperation, operationActive, restartFleet, startOperation, takeLeftover } from "./settings-apply.js";
|
|
26
26
|
import {
|
|
27
27
|
ACCESS_MODES, AGENT_MODES, DEFAULT_SCHEMA, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
|
|
28
|
-
changedFields, channelId, channelsOf, chLabel, effectiveSummary, fleetModel, fromYaml, fullModelRequests, groupAgents,
|
|
28
|
+
changedFields, channelId, channelsOf, chLabel, connectionState, effectiveSummary, fleetModel, fromYaml, fullModelRequests, groupAgents,
|
|
29
29
|
hasOwn, impactOf, localeOptions, defaultsImpact, requestImpact, nonNegative, positive, replyGuardSupported, shortName, STATUS_ORDER, toYaml,
|
|
30
30
|
visibilityDefault, worstImpact,
|
|
31
31
|
} from "./settings-model.js";
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
} from "./settings-dialogs.js";
|
|
36
36
|
import { SetupWizard } from "./settings-wizard.js";
|
|
37
37
|
import { CreateInstanceDialog } from "./panel-fleet.js";
|
|
38
|
+
import { FirstRunCard, takeWizardRequest } from "./first-run.js";
|
|
38
39
|
|
|
39
40
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
40
41
|
const ICONS = { agents: "bot", bots: "plug", classic: "room", general: "sliders", advanced: "code" };
|
|
@@ -66,7 +67,8 @@ async function loadAll(lease) {
|
|
|
66
67
|
const connections = await read(lease, "/api/settings/connections");
|
|
67
68
|
if (!lease.current()) return null;
|
|
68
69
|
out.connections = connections.ok && Array.isArray(connections.body) ? connections.body : [];
|
|
69
|
-
|
|
70
|
+
// Only where the fleet offers them (the schema says so): otherwise the route is a 404 and there is nothing to show.
|
|
71
|
+
const secrets = out.schema && out.schema.provider_secrets === false ? { ok: false } : await read(lease, "/api/settings/provider-secrets");
|
|
70
72
|
if (!lease.current()) return null;
|
|
71
73
|
out.providerOk = secrets.ok;
|
|
72
74
|
out.providerSecrets = secrets.ok && Array.isArray(secrets.body) ? secrets.body : [];
|
|
@@ -196,6 +198,8 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
196
198
|
};
|
|
197
199
|
|
|
198
200
|
const ctx = data && !data.error ? makeCtx(data, setData, stage, unstage, reload, reloadLive, () => mount.current()) : null;
|
|
201
|
+
// #1519 P7: "Connect a chat app" on the first-run card elsewhere came here for the wizard: it opens once the data is in.
|
|
202
|
+
useEffect(() => { if (ctx && takeWizardRequest()) setDialog({ kind: "wizard", key: navKey }); }, [!!ctx, navKey]);
|
|
199
203
|
const sectionBody = !data ? html`<${Skeleton} lines=${6} />`
|
|
200
204
|
: data.error ? html`<${ErrorState} message=${tn("configLoadFailed")} onRetry=${reload} />`
|
|
201
205
|
: section === "agents" ? html`<${Agents} ctx=${ctx} search=${search} openDialog=${(d) => setDialog({ ...d, key: navKey })} />`
|
|
@@ -218,6 +222,8 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
218
222
|
${searchable ? html`<div class="s-toolbar"><label class="s-search"><${Icon} name="search" size=${14} /><span class="sr-only">${tn("searchAll")}</span>
|
|
219
223
|
<input type="search" value=${search} placeholder=${tn("searchAll")} onInput=${(e) => setSearch(e.target.value)} /></label></div>` : null}
|
|
220
224
|
${op ? html`<${OperationCard} op=${op} schema=${data && data.schema} />` : null}
|
|
225
|
+
<${PendingChanges} inline=${true} />
|
|
226
|
+
${ctx && (section === "agents" || section === "bots") ? html`<${FirstRunCard} onConnect=${() => setDialog({ kind: "wizard", key: navKey })} />` : null}
|
|
221
227
|
${sectionBody}
|
|
222
228
|
</div></div>
|
|
223
229
|
${staged.size ? html`<${PendingBar} staged=${staged} busy=${opBusy(op)} onApply=${apply} onDiscard=${discard} />` : null}
|
|
@@ -329,7 +335,7 @@ function Agents({ ctx, search, openDialog }) {
|
|
|
329
335
|
setBusy((b) => new Map(b).set(name, action));
|
|
330
336
|
try { await work(); } finally { setBusy((b) => { const n = new Map(b); n.delete(name); return n; }); }
|
|
331
337
|
};
|
|
332
|
-
const head = html`<div class="list-head"><span>${tn("countAgents", Object.keys(insts).length)}</span>
|
|
338
|
+
const head = html`<div class="list-head"><span>${Object.keys(insts).length === 1 ? tn("countAgent") : tn("countAgents", Object.keys(insts).length)}</span>
|
|
333
339
|
<button type="button" class="btn btn-primary" onClick=${() => openDialog({ kind: "create" })}><${Icon} name="plus" size=${16} />${tn("newAgent")}</button></div>`;
|
|
334
340
|
if (!Object.keys(insts).length) return html`${head}<${Empty} icon="bot" title=${tn("noAgents")} />`;
|
|
335
341
|
if (!groups.length) return html`${head}<${Empty} icon="search" title=${tn("noAgentMatch", search.trim())} />`;
|
|
@@ -350,6 +356,7 @@ function Agents({ ctx, search, openDialog }) {
|
|
|
350
356
|
<span class=${dotClass(st)} title=${tn(STATUS_KEY[st] || "stStopped")} aria-hidden="true"></span>
|
|
351
357
|
<span class="s-name" title=${name}>${shortName(name, inst)}</span>
|
|
352
358
|
<span class="s-meta" title=${sum.backend}>${sum.model}</span>
|
|
359
|
+
<span class="s-br" aria-hidden="true"></span>
|
|
353
360
|
<span class="tag">${tn("effortTag", sum.effort)}</span>
|
|
354
361
|
${ch ? html`<span class=${`tag${ch.includes("persona") ? " persona" : ""}`}>${ch}</span>` : null}
|
|
355
362
|
<span class="sr-only">${tn(STATUS_KEY[st] || "stStopped")}</span>
|
|
@@ -370,12 +377,17 @@ function Bots({ ctx, search, openDialog }) {
|
|
|
370
377
|
const all = channelsOf(ctx.fleet);
|
|
371
378
|
const needle = search.trim().toLowerCase();
|
|
372
379
|
const rows = all.map((ch, i) => ({ ch, i })).filter(({ ch }) => !needle || [ch.id, ch.type, ch.group_id, ch.bot_token_env].some((v) => String(v ?? "").toLowerCase().includes(needle)));
|
|
373
|
-
const head = html`<div class="list-head"><span>${tn("countBots", all.length)}</span>
|
|
380
|
+
const head = html`<div class="list-head"><span>${all.length === 1 ? tn("countBot") : tn("countBots", all.length)}</span>
|
|
374
381
|
<button type="button" class="btn btn-primary" onClick=${() => openDialog({ kind: "newBot" })}><${Icon} name="plus" size=${16} />${tn("newBot")}</button></div>`;
|
|
375
382
|
return html`${head}
|
|
376
383
|
${!rows.length ? html`<${Empty} icon="plug" title=${needle ? tn("noAgentMatch", search.trim()) : tn("noBots")} />` : html`<div class="s-list">${rows.map(({ ch, i }) => {
|
|
377
384
|
const users = (ch.access && ch.access.allowed_users) || [];
|
|
378
|
-
const
|
|
385
|
+
const meta = ctx.connections.find((c, j) => channelId(c, j) === channelId(ch, i)) || {};
|
|
386
|
+
const token = meta.token_present;
|
|
387
|
+
// #1519 P6: this connection's own state (not the fleet's "Connected") — a stopped one says so; a known problem says
|
|
388
|
+
// what to do (P3: Message Content intent; a rejected token).
|
|
389
|
+
const state = connectionState(meta, ctx.fleetUp);
|
|
390
|
+
const stateText = tn(state.key, state.bot);
|
|
379
391
|
// #1408 step 5: one line on a desktop — who it is, its facts in one clipped line (all of them in its tooltip),
|
|
380
392
|
// then the cluster; on a phone the facts go under it. It no longer wraps fact by fact.
|
|
381
393
|
const facts = [ch.bot_token_env || tn("noTokenEnv"),
|
|
@@ -383,13 +395,13 @@ function Bots({ ctx, search, openDialog }) {
|
|
|
383
395
|
tn("accessTag", (ch.access && ch.access.mode) || "locked"),
|
|
384
396
|
users.length ? `${users.slice(0, 3).join(", ")}${users.length > 3 ? ` +${users.length - 3}` : ""}` : tn("noAllowedUsers")].filter(Boolean).join(" · ");
|
|
385
397
|
return html`<div key=${channelId(ch, i)} class="s-row s-conn">
|
|
386
|
-
<span class="s-ident"><span class=${`dot ${
|
|
398
|
+
<span class="s-ident"><span class=${`dot ${state.cls}`} title=${stateText} aria-hidden="true"></span>
|
|
387
399
|
<span class="s-name">${ch.type === "telegram" ? "Telegram" : "Discord"}</span>
|
|
388
400
|
<span class=${`tag${i === 0 ? "" : " persona"}`}>${ch.id || chLabel(i)}</span></span>
|
|
389
401
|
<span class="s-facts" title=${facts}>${facts}</span>
|
|
390
402
|
<span class="s-actions">
|
|
391
403
|
<span class=${`tag${token ? "" : " warn"}`}>${token ? tn("tokenConfigured") : tn("tokenMissing")}</span>
|
|
392
|
-
<span class=${`s-state ${
|
|
404
|
+
<span class=${`s-state ${state.cls}`} title=${stateText}>${stateText}</span>
|
|
393
405
|
<button type="button" class="btn btn-sm" onClick=${() => openDialog({ kind: "bot", id: channelId(ch, i) })}>${tn("settingsButton")}</button></span>
|
|
394
406
|
</div>`;
|
|
395
407
|
})}</div>`}
|
|
@@ -464,7 +476,7 @@ function Classic({ ctx, search, openDialog }) {
|
|
|
464
476
|
.sort((a, b) => ((STATUS_ORDER[ctx.classicRuntime(a.instanceName).status] ?? 9) - (STATUS_ORDER[ctx.classicRuntime(b.instanceName).status] ?? 9))
|
|
465
477
|
|| String(a.name || "").localeCompare(String(b.name || "")));
|
|
466
478
|
if (!list.length) return html`<${Empty} icon="search" title=${tn("noAgentMatch", search.trim())} />`;
|
|
467
|
-
return html`<div class="list-head"><span>${tn("countRooms", rooms.length)}</span></div><div class="s-list">${list.map((c) => {
|
|
479
|
+
return html`<div class="list-head"><span>${rooms.length === 1 ? tn("countRoom") : tn("countRooms", rooms.length)}</span></div><div class="s-list">${list.map((c) => {
|
|
468
480
|
const rt = ctx.classicRuntime(c.instanceName);
|
|
469
481
|
const sum = effectiveSummary(ctx.live[c.instanceName] || {}, c, ctx.fleet.defaults || {}, (ctx.classic && ctx.classic.defaults) || {});
|
|
470
482
|
const adapter = c.adapterId || "discord";
|
|
@@ -472,6 +484,7 @@ function Classic({ ctx, search, openDialog }) {
|
|
|
472
484
|
<span class=${dotClass(rt.execution)} title=${tn(`exec_${rt.execution}`)} aria-hidden="true"></span>
|
|
473
485
|
<span class="s-name" title=${c.instanceName || ""}>${shortName(c.name || c.instanceName || "?", { display_name: c.display_name })}</span>
|
|
474
486
|
<span class="s-meta" title=${sum.backend}>${sum.model}</span>
|
|
487
|
+
<span class="s-br" aria-hidden="true"></span>
|
|
475
488
|
<span class="tag">${tn("effortTag", sum.effort)}</span>
|
|
476
489
|
<span class=${`tag${adapter !== "discord" ? " persona" : ""}`}>${adapter}</span>
|
|
477
490
|
${c.collab ? html`<span class="tag">collab</span>` : null}
|
|
@@ -666,10 +679,10 @@ function Developer({ ctx }) {
|
|
|
666
679
|
<div class="seg-inline" role="group" aria-label=${tn("format")}>
|
|
667
680
|
${["yaml", "json"].map((k) => html`<button key=${k} type="button" class=${`btn btn-sm${fmt === k ? " on" : ""}`} aria-pressed=${fmt === k ? "true" : "false"} onClick=${() => setFmt(k)}>${k.toUpperCase()}</button>`)}</div>
|
|
668
681
|
<button type="button" class="btn btn-sm" onClick=${() => setEditing(!editing)}>${editing ? tn("viewMode") : tn("editMode")}</button>
|
|
669
|
-
<button type="button" class="btn btn-sm" onClick=${copy}><${Icon} name="copy" size=${14}
|
|
670
|
-
<button type="button" class="btn btn-sm" onClick=${download}><${Icon} name="download" size=${14}
|
|
682
|
+
<button type="button" class="btn btn-sm" title=${tn("copy")} onClick=${copy}><${Icon} name="copy" size=${14} /><span class="lbl">${tn("copy")}</span></button>
|
|
683
|
+
<button type="button" class="btn btn-sm" title=${tn("devDownload")} onClick=${download}><${Icon} name="download" size=${14} /><span class="lbl">${tn("devDownload")}</span></button>
|
|
671
684
|
</div>
|
|
672
|
-
${editing ? html`<textarea class="s-yaml" aria-label=${tn("devEditor")} spellcheck="false" value=${draft} onInput=${(e) => setDraft(e.target.value)}></textarea>
|
|
685
|
+
${editing ? html`<textarea class="s-yaml" aria-label=${tn("devEditor")} spellcheck="false" wrap="off" value=${draft} onInput=${(e) => setDraft(e.target.value)}></textarea>
|
|
673
686
|
<div class="save-row"><button type="button" class="btn btn-primary" onClick=${save}>${tn("devApply")}</button><span class="note">${tn("devApplyHint")}</span></div>`
|
|
674
687
|
: html`<pre class="s-yaml">${text}</pre>`}
|
|
675
688
|
${msg ? html`<p class=${`feedback${msg.error ? " error" : ""}`} role="status">${msg.error || msg.text}</p>` : null}
|
|
@@ -695,7 +708,8 @@ function OperationCard({ op, schema }) {
|
|
|
695
708
|
const phaseBusy = ["writing", "posting", "watching"].includes(op.phase);
|
|
696
709
|
const job = op.job;
|
|
697
710
|
const rows = (job && job.targets) || [];
|
|
698
|
-
|
|
711
|
+
// A fleet row of kind "hot" is connections starting without a restart (#1519 P6), not AgEnD restarting.
|
|
712
|
+
const fleetRestarting = job && job.status === "running" && rows.some((r) => r.target === "fleet" && r.status === "running" && r.kind !== "hot");
|
|
699
713
|
const needsRestart = rows.some((r) => r.status === "restart-required");
|
|
700
714
|
const failedRows = rows.some((r) => r.status === "failed");
|
|
701
715
|
const mismatch = schema && schema.fleet_signature_mismatch;
|
|
@@ -728,11 +742,11 @@ function OperationCard({ op, schema }) {
|
|
|
728
742
|
${op.steps.length ? html`<ul class="s-op-steps">${op.steps.map((s, i) => html`<li key=${i} class=${`s-step ${s.status}`}>
|
|
729
743
|
<span class="s-step-icon" aria-hidden="true">${icon(s.status) ? html`<${Icon} name=${icon(s.status)} size=${14} />` : "·"}</span>
|
|
730
744
|
<span class="grow">${s.label}</span>
|
|
731
|
-
<span class="note">${s.status === "waiting" ? tn("stepWaiting") : s.status === "skipped" ? tn("stepSkipped") : s.status === "failed" ? (s.error || tn("applyFailed")) : impactText(s.impact)}</span></li>`)}</ul>` : null}
|
|
745
|
+
<span class="note">${s.status === "waiting" ? tn("stepWaiting") : s.status === "skipped" ? tn("stepSkipped") : s.status === "failed" ? (s.error || tn("applyFailed")) : s.note || impactText(s.impact)}</span></li>`)}</ul>` : null}
|
|
732
746
|
${rows.length ? html`<ul class="s-op-steps">${rows.map((r) => html`<li key=${r.target} class=${`s-step ${r.status}`}>
|
|
733
747
|
<span class="s-step-icon" aria-hidden="true">${r.status === "done" ? html`<${Icon} name="check" size=${14} />` : r.status === "failed" ? html`<${Icon} name="alert" size=${14} />` : r.status === "restart-required" ? html`<${Icon} name="restart" size=${14} />` : "·"}</span>
|
|
734
748
|
<span class="grow">${r.target === "fleet" ? "AgEnD" : shortName(r.target, {})}</span>
|
|
735
|
-
<span class="note">${r.target === "fleet" && r.status === "running" ? tn("restartFleetTitle") : r.status === "restart-required" ? tn("applyRestartNeeded")
|
|
749
|
+
<span class="note">${r.target === "fleet" && r.status === "running" ? tn(r.kind === "hot" ? "connStartingNow" : "restartFleetTitle") : r.status === "restart-required" ? tn("applyRestartNeeded")
|
|
736
750
|
: r.settled_by === "no-change" ? tn("applyNoChange") : r.settled_by === "fleet-restart" ? tn("viaRestart") : r.kind === "hot" ? tn("impactNow") : tn("impactAgent")}</span>
|
|
737
751
|
${r.error ? html`<span class="feedback error">${r.error}</span>` : null}</li>`)}</ul>` : null}
|
|
738
752
|
${job && job.error ? html`<p class="feedback error">${job.error}</p>` : null}
|