@songsid/agend 2.1.12-beta.9 → 2.2.0-alpha.1
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/auth/one-time-code.d.ts +19 -0
- package/dist/auth/one-time-code.js +49 -0
- package/dist/auth/one-time-code.js.map +1 -0
- package/dist/auth-api.d.ts +52 -0
- package/dist/auth-api.js +346 -0
- package/dist/auth-api.js.map +1 -0
- package/dist/backend/claude-code.d.ts +4 -0
- package/dist/backend/claude-code.js +97 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.js +59 -30
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro-agent.d.ts +66 -0
- package/dist/backend/kiro-agent.js +302 -0
- package/dist/backend/kiro-agent.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +80 -0
- package/dist/backend/kiro-identity.js +537 -0
- package/dist/backend/kiro-identity.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.d.ts +1 -0
- package/dist/backend/kiro-v2-store-worker.js +87 -0
- package/dist/backend/kiro-v2-store-worker.js.map +1 -0
- package/dist/backend/kiro-v2-store.d.ts +54 -0
- package/dist/backend/kiro-v2-store.js +131 -0
- package/dist/backend/kiro-v2-store.js.map +1 -0
- package/dist/backend/kiro-v3-identity.d.ts +5 -0
- package/dist/backend/kiro-v3-identity.js +22 -0
- package/dist/backend/kiro-v3-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +83 -7
- package/dist/backend/kiro.js +418 -91
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +45 -3
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +1 -0
- package/dist/channel/adapters/discord.js +29 -10
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +1 -0
- package/dist/channel/adapters/telegram.js +13 -1
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +9 -1
- package/dist/classic-channel-manager.d.ts +18 -10
- package/dist/classic-channel-manager.js +45 -18
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +225 -127
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +5 -3
- package/dist/command-table.js +29 -27
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +46 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/connection-secrets.d.ts +6 -0
- package/dist/connection-secrets.js +4 -0
- package/dist/connection-secrets.js.map +1 -1
- package/dist/daemon.d.ts +89 -4
- package/dist/daemon.js +661 -77
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.d.ts +6 -1
- package/dist/darwin-memory.js +17 -6
- package/dist/darwin-memory.js.map +1 -1
- package/dist/delivery-consumed-watch.d.ts +85 -0
- package/dist/delivery-consumed-watch.js +244 -0
- package/dist/delivery-consumed-watch.js.map +1 -0
- package/dist/delivery-outbox.d.ts +61 -0
- package/dist/delivery-outbox.js +120 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +14 -5
- package/dist/delivery-reconciliation.js +27 -4
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/delivery-reconciliation.test.js +35 -1
- package/dist/delivery-reconciliation.test.js.map +1 -1
- package/dist/event-loop-watch.d.ts +28 -0
- package/dist/event-loop-watch.js +75 -3
- package/dist/event-loop-watch.js.map +1 -1
- package/dist/fleet-context.d.ts +22 -2
- package/dist/fleet-level-config.d.ts +2 -2
- package/dist/fleet-level-config.js +6 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +293 -12
- package/dist/fleet-manager.js +2364 -449
- package/dist/fleet-manager.js.map +1 -1
- package/dist/host-memory.d.ts +4 -0
- package/dist/host-memory.js.map +1 -1
- package/dist/instance-config-impact.js +5 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -5
- package/dist/instance-lifecycle.js +162 -53
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +150 -16
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.d.ts +2 -0
- package/dist/login-controller.js +17 -5
- package/dist/login-controller.js.map +1 -1
- package/dist/memory-pressure.d.ts +7 -8
- package/dist/memory-pressure.js +76 -15
- package/dist/memory-pressure.js.map +1 -1
- package/dist/monotonic-budget.d.ts +2 -0
- package/dist/monotonic-budget.js +28 -0
- package/dist/monotonic-budget.js.map +1 -0
- package/dist/needs-you-hub.d.ts +114 -0
- package/dist/needs-you-hub.js +370 -0
- package/dist/needs-you-hub.js.map +1 -0
- package/dist/needs-you-live.d.ts +116 -0
- package/dist/needs-you-live.js +228 -0
- package/dist/needs-you-live.js.map +1 -0
- package/dist/needs-you.d.ts +132 -0
- package/dist/needs-you.js +164 -0
- package/dist/needs-you.js.map +1 -0
- package/dist/node-version-guard.d.ts +30 -0
- package/dist/node-version-guard.js +50 -0
- package/dist/node-version-guard.js.map +1 -0
- package/dist/operator-control-paths.d.ts +4 -0
- package/dist/operator-control-paths.js +24 -0
- package/dist/operator-control-paths.js.map +1 -0
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +9 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +5 -5
- package/dist/profile-control.js +4 -20
- package/dist/profile-control.js.map +1 -1
- package/dist/public-web-gateway.d.ts +18 -0
- package/dist/public-web-gateway.js +141 -0
- package/dist/public-web-gateway.js.map +1 -0
- package/dist/public-web-link.d.ts +53 -0
- package/dist/public-web-link.js +175 -0
- package/dist/public-web-link.js.map +1 -0
- package/dist/quickstart-api.d.ts +8 -1
- package/dist/quickstart-api.js +86 -48
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +1 -0
- package/dist/quickstart.js +6 -2
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/db.d.ts +7 -1
- package/dist/scheduler/db.js +38 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/retry-label.d.ts +11 -0
- package/dist/scheduler/retry-label.js +24 -0
- package/dist/scheduler/retry-label.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +55 -2
- package/dist/scheduler/scheduler.js +220 -8
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/types.d.ts +22 -0
- package/dist/scheduler/types.js.map +1 -1
- package/dist/secret-store.d.ts +5 -0
- package/dist/secret-store.js +20 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/service-activation.d.ts +110 -0
- package/dist/service-activation.js +302 -0
- package/dist/service-activation.js.map +1 -0
- package/dist/service-installer.d.ts +7 -0
- package/dist/service-installer.js +7 -4
- package/dist/service-installer.js.map +1 -1
- package/dist/service-restart-selection.d.ts +6 -5
- package/dist/service-restart-selection.js +6 -5
- package/dist/service-restart-selection.js.map +1 -1
- package/dist/settings-api.d.ts +10 -2
- package/dist/settings-api.js +175 -72
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.d.ts +20 -0
- package/dist/settings-baseline.js +104 -0
- package/dist/settings-baseline.js.map +1 -0
- package/dist/settings-change.d.ts +21 -0
- package/dist/settings-change.js +133 -0
- package/dist/settings-change.js.map +1 -0
- package/dist/settings-confirmation.d.ts +105 -0
- package/dist/settings-confirmation.js +232 -0
- package/dist/settings-confirmation.js.map +1 -0
- package/dist/settings-control.d.ts +41 -0
- package/dist/settings-control.js +294 -0
- package/dist/settings-control.js.map +1 -0
- package/dist/settings-effect.d.ts +24 -0
- package/dist/settings-effect.js +169 -0
- package/dist/settings-effect.js.map +1 -0
- package/dist/settings-http-confirmation.d.ts +29 -0
- package/dist/settings-http-confirmation.js +123 -0
- package/dist/settings-http-confirmation.js.map +1 -0
- package/dist/settings-instance-patch.d.ts +4 -0
- package/dist/settings-instance-patch.js +26 -0
- package/dist/settings-instance-patch.js.map +1 -0
- package/dist/settings-request-capability.d.ts +16 -0
- package/dist/settings-request-capability.js +32 -0
- package/dist/settings-request-capability.js.map +1 -0
- package/dist/settings-transaction.d.ts +65 -0
- package/dist/settings-transaction.js +223 -0
- package/dist/settings-transaction.js.map +1 -0
- package/dist/setup-auth.d.ts +10 -18
- package/dist/setup-auth.js +14 -38
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +75 -18
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-guide.d.ts +13 -0
- package/dist/setup-guide.js +25 -0
- package/dist/setup-guide.js.map +1 -0
- package/dist/setup-host.d.ts +16 -0
- package/dist/setup-host.js +197 -32
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +3 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/slash-authz.d.ts +17 -3
- package/dist/slash-authz.js +5 -0
- package/dist/slash-authz.js.map +1 -1
- package/dist/spawn-gate.js +3 -3
- package/dist/spawn-gate.js.map +1 -1
- package/dist/statusline-watcher.d.ts +5 -0
- package/dist/statusline-watcher.js +2 -0
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/steer-capability.d.ts +6 -0
- package/dist/steer-capability.js +8 -0
- package/dist/steer-capability.js.map +1 -1
- package/dist/sync-work-attribution.d.ts +14 -0
- package/dist/sync-work-attribution.js +43 -8
- package/dist/sync-work-attribution.js.map +1 -1
- package/dist/tips.js +10 -5
- package/dist/tips.js.map +1 -1
- package/dist/tmux-control.d.ts +35 -5
- package/dist/tmux-control.js +291 -57
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +20 -6
- package/dist/tmux-manager.js +90 -47
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +59 -0
- package/dist/tmux-read.js +213 -0
- package/dist/tmux-read.js.map +1 -0
- package/dist/topic-commands.d.ts +21 -10
- package/dist/topic-commands.js +191 -58
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.js +2 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/tunnel/cloudflared-install.d.ts +2 -0
- package/dist/tunnel/cloudflared-install.js +1 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +14 -0
- package/dist/tunnel/purpose-lane.js +43 -0
- package/dist/tunnel/purpose-lane.js.map +1 -0
- package/dist/turn-reply-guard.d.ts +2 -0
- package/dist/turn-reply-guard.js +10 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/ui/app.html +18 -0
- package/dist/ui/chat-render.js +478 -0
- package/dist/ui/chat-store.js +289 -0
- package/dist/ui/chat-strings.js +69 -0
- package/dist/ui/chat-thread.js +209 -0
- package/dist/ui/chat-tour.js +116 -0
- package/dist/ui/fleet-org.js +142 -0
- package/dist/ui/panel-chat.js +418 -0
- package/dist/ui/panel-fleet.js +413 -0
- package/dist/ui/panel-needs.js +202 -0
- package/dist/ui/panel-settings.js +750 -0
- package/dist/ui/preview.js +266 -0
- package/dist/ui/settings-apply.js +239 -0
- package/dist/ui/settings-confirm.js +186 -0
- package/dist/ui/settings-dialogs.js +546 -0
- package/dist/ui/settings-model.js +277 -0
- package/dist/ui/settings-strings.js +652 -0
- package/dist/ui/settings-wizard.js +140 -0
- package/dist/ui/shared/agend-auth.js +93 -0
- package/dist/ui/shared/app-ctx.js +80 -0
- package/dist/ui/shared/app-html.js +9 -0
- package/dist/ui/shared/app-i18n.js +95 -0
- package/dist/ui/shared/app-nav.js +73 -0
- package/dist/ui/shared/app-needs.js +114 -0
- package/dist/ui/shared/app-route.js +73 -0
- package/dist/ui/shared/app-session.js +66 -0
- package/dist/ui/shared/app-shell.js +344 -0
- package/dist/ui/shared/app-store.js +51 -0
- package/dist/ui/shared/app-stream.js +201 -0
- package/dist/ui/shared/app.css +676 -0
- package/dist/ui/shared/app.js +88 -0
- package/dist/ui/shared/fonts/OFL.txt +92 -0
- package/dist/ui/shared/fonts/README.md +21 -0
- package/dist/ui/shared/fonts/inter.woff2 +0 -0
- package/dist/ui/shared/htm.module.js +1 -0
- package/dist/ui/shared/panel-view.js +655 -0
- package/dist/ui/shared/preact-hooks.module.js +2 -0
- package/dist/ui/shared/preact.module.js +2 -0
- package/dist/ui/shared/signin.js +130 -0
- package/dist/ui/shared/theme.js +29 -0
- package/dist/ui/shared/tokens.css +153 -0
- package/dist/ui/shared/ui-confirm.js +43 -0
- package/dist/ui/shared/ui-dialog.js +45 -0
- package/dist/ui/shared/ui-icons.js +61 -0
- package/dist/ui/shared/ui-menu.js +39 -0
- package/dist/ui/shared/ui-states.js +20 -0
- package/dist/ui/shared/ui-toast.js +18 -0
- package/dist/ui/shared/vendor/htm.LICENSE +202 -0
- package/dist/ui/shared/vendor/preact.LICENSE +21 -0
- package/dist/ui/shared/vendor/vendor.json +39 -0
- package/dist/ui/shared/view-strings.js +309 -0
- package/dist/ui/signin.html +51 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +12 -3
- package/dist/update-check.js.map +1 -1
- package/dist/update-install.d.ts +71 -0
- package/dist/update-install.js +162 -0
- package/dist/update-install.js.map +1 -0
- package/dist/upgrade-notices.d.ts +17 -0
- package/dist/upgrade-notices.js +93 -0
- package/dist/upgrade-notices.js.map +1 -0
- package/dist/usage/statusline-usage.d.ts +2 -0
- package/dist/usage/statusline-usage.js +14 -10
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.d.ts +6 -1
- package/dist/view-api.js +62 -51
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +71 -14
- package/dist/web-api.js +492 -77
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +71 -27
- package/dist/web-auth.js +169 -69
- package/dist/web-auth.js.map +1 -1
- package/dist/web-body.d.ts +3 -0
- package/dist/web-body.js +55 -0
- package/dist/web-body.js.map +1 -0
- package/dist/web-channel-echo.d.ts +9 -0
- package/dist/web-channel-echo.js +55 -0
- package/dist/web-channel-echo.js.map +1 -0
- package/dist/web-chat-history.d.ts +125 -0
- package/dist/web-chat-history.js +184 -0
- package/dist/web-chat-history.js.map +1 -0
- package/dist/web-continuation.d.ts +7 -0
- package/dist/web-continuation.js +22 -0
- package/dist/web-continuation.js.map +1 -0
- package/dist/web-host-guard.d.ts +34 -0
- package/dist/web-host-guard.js +57 -1
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-login.d.ts +72 -0
- package/dist/web-login.js +132 -0
- package/dist/web-login.js.map +1 -0
- package/dist/web-org.d.ts +55 -0
- package/dist/web-org.js +67 -0
- package/dist/web-org.js.map +1 -0
- package/dist/web-preview.d.ts +77 -0
- package/dist/web-preview.js +240 -0
- package/dist/web-preview.js.map +1 -0
- package/dist/web-request-context.d.ts +10 -0
- package/dist/web-request-context.js +13 -0
- package/dist/web-request-context.js.map +1 -0
- package/dist/web-session.d.ts +167 -0
- package/dist/web-session.js +389 -0
- package/dist/web-session.js.map +1 -0
- package/dist/web-shell-routes.d.ts +59 -0
- package/dist/web-shell-routes.js +96 -0
- package/dist/web-shell-routes.js.map +1 -0
- package/dist/web-upload.d.ts +183 -0
- package/dist/web-upload.js +468 -0
- package/dist/web-upload.js.map +1 -0
- package/dist/website-tips-renderer.d.ts +2 -2
- package/dist/website-tips-renderer.js +3 -3
- package/dist/website-tips-renderer.js.map +1 -1
- package/package.json +8 -5
- package/scripts/preinstall-guard.cjs +36 -0
- package/dist/ui/dashboard.html +0 -774
- package/dist/ui/settings.html +0 -2165
- package/dist/ui/view.html +0 -1139
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
// #1408 step 3: Settings' form pieces and its edit dialogs. One dialog edits one object, and closing it with "Stage
|
|
2
|
+
// change" stages — it never writes (the panel's Apply does, through the app's runner, settings-apply.js). The writes
|
|
3
|
+
// that were direct on the old page stay direct (a connection's new binding, a new connection, removing one, a
|
|
4
|
+
// provider key): each goes through confirmedWrite, so a change an admin must confirm waits for it (#1423).
|
|
5
|
+
import { html, useEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
6
|
+
import { t } from "/assets/app-i18n.js";
|
|
7
|
+
import { useLease } from "/assets/app-ctx.js";
|
|
8
|
+
import { Dialog } from "/assets/ui-dialog.js";
|
|
9
|
+
import { Icon } from "/assets/ui-icons.js";
|
|
10
|
+
import { toast } from "/assets/ui-toast.js";
|
|
11
|
+
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
12
|
+
import { api, confirmedWrite, newKey } from "./settings-confirm.js";
|
|
13
|
+
import {
|
|
14
|
+
ACCESS_MODES, AGENT_FIELDS, AGENT_MODES, BOT_FIELDS, CH_TYPES, CLASSIC_FIELDS, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
|
|
15
|
+
VISIBILITY_MODES, accessConfirmations, agentForm, agentPatch, backendOptions, changedFields, channelId, channelsOf,
|
|
16
|
+
chLabel, hasOwn, impactOf, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
|
|
17
|
+
webEchoIsGroup, worstImpact,
|
|
18
|
+
} from "./settings-model.js";
|
|
19
|
+
|
|
20
|
+
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
21
|
+
export const impactText = (kind) => tn(kind === "now" ? "impactNow" : kind === "fleet" ? "impactFleet" : "impactAgent");
|
|
22
|
+
/** Ask in the app's own dialog (#1408 step 5): resolves true for the confirm button, false otherwise. */
|
|
23
|
+
export const ask = (message, opts = {}) => confirmDialog({ message, ...opts });
|
|
24
|
+
|
|
25
|
+
// ── Form pieces ──
|
|
26
|
+
|
|
27
|
+
export function Select({ value, options, onChange, disabled, id, label }) {
|
|
28
|
+
const opts = options.map((o) => (typeof o === "string" ? { value: o, label: o } : o));
|
|
29
|
+
return html`<select id=${id} value=${value} disabled=${disabled} aria-label=${label} onChange=${(e) => onChange(e.target.value)}>
|
|
30
|
+
${opts.map((o) => html`<option key=${o.value} value=${o.value}>${o.label}</option>`)}</select>`;
|
|
31
|
+
}
|
|
32
|
+
export function BackendSelect({ value, onChange, disabled, id }) {
|
|
33
|
+
return html`<${Select} id=${id} value=${value} disabled=${disabled} onChange=${onChange}
|
|
34
|
+
options=${backendOptions(value).map((o) => (o.unavailable ? { value: o.value, label: tn("backendUnavailable", o.value) } : o))} />`;
|
|
35
|
+
}
|
|
36
|
+
export function Impact({ schema, field }) { return html`<span class="impact">${impactText(impactOf(schema, field))}</span>`; }
|
|
37
|
+
export function Label({ text, schema, field, htmlFor }) {
|
|
38
|
+
return html`<label for=${htmlFor}>${text}${field ? html` <${Impact} schema=${schema} field=${field} />` : null}</label>`;
|
|
39
|
+
}
|
|
40
|
+
export function Feedback({ error, warning }) {
|
|
41
|
+
if (!error && !warning) return null;
|
|
42
|
+
return html`<p class=${`feedback${error ? " error" : " warning"}`} role=${error ? "alert" : undefined}>${error ? tn(error) : tn(warning)}</p>`;
|
|
43
|
+
}
|
|
44
|
+
/** An override of a default: the control, and "Inherit from defaults" beside it (on: the control shows the default). */
|
|
45
|
+
export function Override({ inherit, onInherit, schema, field, children }) {
|
|
46
|
+
return html`<div class="override">${children}
|
|
47
|
+
<label class="check inherit-row"><input type="checkbox" checked=${inherit} onChange=${(e) => onInherit(e.target.checked)} /><span>${tn("inheritDefault")}</span>
|
|
48
|
+
${field ? html`<${Impact} schema=${schema} field=${field} />` : null}</label></div>`;
|
|
49
|
+
}
|
|
50
|
+
export function Drawer({ title, children, open = false }) {
|
|
51
|
+
return html`<details class="drawer" open=${open}><summary>${title}</summary><div class="drawer-body">${children}</div></details>`;
|
|
52
|
+
}
|
|
53
|
+
/** A list of values (user ids, tags): chips with ×, and an input that adds on Enter or Add. Numbers stay numbers. */
|
|
54
|
+
export function ChipList({ items, onChange, placeholder, label }) {
|
|
55
|
+
const [draft, setDraft] = useState("");
|
|
56
|
+
const add = () => {
|
|
57
|
+
const v = draft.trim();
|
|
58
|
+
if (!v) return;
|
|
59
|
+
onChange([...items, /^-?\d+$/.test(v) ? Number(v) : v]);
|
|
60
|
+
setDraft("");
|
|
61
|
+
};
|
|
62
|
+
return html`<div class="chips-box">
|
|
63
|
+
${items.length ? html`<div class="chips">${items.map((v, i) => html`<span key=${`${i}:${v}`} class="chip">${String(v)}
|
|
64
|
+
<button type="button" class="chip-x" aria-label=${tn("removeItem", String(v))} title=${tn("removeItem", String(v))}
|
|
65
|
+
onClick=${() => onChange(items.filter((_, j) => j !== i))}><${Icon} name="close" size=${12} /></button></span>`)}</div>` : null}
|
|
66
|
+
<div class="field-row"><input type="text" value=${draft} placeholder=${placeholder} aria-label=${label || placeholder}
|
|
67
|
+
onInput=${(e) => setDraft(e.target.value)} onKeyDown=${(e) => { if (e.key === "Enter") { e.preventDefault(); add(); } }} />
|
|
68
|
+
<button type="button" class="btn btn-sm" onClick=${add}><${Icon} name="plus" size=${14} />${tn("add")}</button></div>
|
|
69
|
+
</div>`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const replyHint = (backend, mode, kiroUi) => (replyGuardSupported(backend, mode, kiroUi) ? tn("replyGuardSupported") : tn("replyGuardUnsupported", backend, mode));
|
|
73
|
+
|
|
74
|
+
/** The dialog every editor uses: Cancel / Stage change; the footer says what the whole dialog will cost. */
|
|
75
|
+
function EditDialog({ title, subtitle, onClose, onStage, impacts, schema, children, ready = true, wide = true }) {
|
|
76
|
+
const kinds = [...new Set((impacts || []).map((f) => impactOf(schema, f)))];
|
|
77
|
+
return html`<${Dialog} title=${title} label=${subtitle ? `${title} — ${subtitle}` : title} onClose=${onClose} wide=${wide}
|
|
78
|
+
actions=${html`${kinds.length ? html`<span class="note dlg-impact">${kinds.map(impactText).join(" · ")}</span>` : null}
|
|
79
|
+
<button type="button" class="btn" onClick=${onClose}>${tn("cancel")}</button>
|
|
80
|
+
<button type="button" class="btn btn-primary" disabled=${!ready} onClick=${onStage}>${tn("modalDone")}</button>`}>
|
|
81
|
+
${subtitle ? html`<p class="note">${subtitle}</p>` : null}
|
|
82
|
+
<div class="form">${children}</div></${Dialog}>`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Status emojis (#1005): the preview is resolved by AgEnD exactly as the bots react ──
|
|
86
|
+
|
|
87
|
+
let catalog = null;
|
|
88
|
+
async function loadCatalog() {
|
|
89
|
+
if (catalog) return catalog;
|
|
90
|
+
const r = await api("/api/settings/status-emojis").catch(() => null);
|
|
91
|
+
catalog = r && r.ok && r.body ? r.body : { keys: [], builtins: {}, telegram_allowed: [], suggestions: [] };
|
|
92
|
+
return catalog;
|
|
93
|
+
}
|
|
94
|
+
const guildLists = new Map();
|
|
95
|
+
function guildEmojis(channel, refresh) {
|
|
96
|
+
if (refresh || !guildLists.has(channel)) {
|
|
97
|
+
guildLists.set(channel, api(`/api/settings/status-emojis/guild-emojis?channel=${encodeURIComponent(channel)}${refresh ? "&refresh=1" : ""}`)
|
|
98
|
+
.then((r) => (r.ok ? r.body : { error: (r.body && r.body.error) || `HTTP ${r.status}` }), () => ({ error: "network" })));
|
|
99
|
+
}
|
|
100
|
+
return guildLists.get(channel);
|
|
101
|
+
}
|
|
102
|
+
/** Keys in catalog order, blanks dropped — so an unchanged map compares equal. */
|
|
103
|
+
export function orderedStatusMap(map, keys) {
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const key of keys) { const v = map && map[key]; if (typeof v === "string" && v.trim()) out[key] = v.trim(); }
|
|
106
|
+
return Object.keys(out).length ? out : null;
|
|
107
|
+
}
|
|
108
|
+
// no-referrer: nothing here should tell a third-party image host where it was loaded from.
|
|
109
|
+
const EmojiImg = ({ url, alt }) => html`<img src=${url} alt=${alt} referrerpolicy="no-referrer" loading="lazy" />`;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* One status_emojis map. `platform` and `channel` say where it applies; `previewBody(map)` is what the preview resolves.
|
|
113
|
+
* `onChange({ value, baseline })` reports the map (null when empty) and what it was.
|
|
114
|
+
*/
|
|
115
|
+
export function StatusEmojiEditor({ own, platform, channel, previewBody, onChange }) {
|
|
116
|
+
const lease = useLease("status-emojis");
|
|
117
|
+
const [cat, setCat] = useState(catalog);
|
|
118
|
+
const [values, setValues] = useState({ ...(own || {}) });
|
|
119
|
+
const [preview, setPreview] = useState(null);
|
|
120
|
+
const [picker, setPicker] = useState(null);
|
|
121
|
+
const seq = useRef(0), timer = useRef(null);
|
|
122
|
+
useEffect(() => { (async () => { const c = await loadCatalog(); if (lease.current()) setCat(c); })(); }, [lease]);
|
|
123
|
+
const keys = cat ? cat.keys : [];
|
|
124
|
+
useEffect(() => { if (cat) onChange({ value: orderedStatusMap(values, keys), baseline: orderedStatusMap(own, keys) }); }, [cat, values]);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!cat) return;
|
|
127
|
+
lease.clear(timer.current);
|
|
128
|
+
timer.current = lease.timeout(async () => {
|
|
129
|
+
const mine = ++seq.current;
|
|
130
|
+
let r;
|
|
131
|
+
try { r = await api("/api/settings/status-emojis/preview", { method: "POST", body: JSON.stringify(previewBody(orderedStatusMap(values, keys) || {})) }); }
|
|
132
|
+
catch { r = { ok: false, body: { error: "network" } }; }
|
|
133
|
+
if (!lease.current() || mine !== seq.current) return;
|
|
134
|
+
setPreview(r.ok ? { entries: r.body.entries || [], problems: r.body.problems || [] } : { error: (r.body && r.body.error) || "preview failed" });
|
|
135
|
+
}, 150);
|
|
136
|
+
}, [cat, values, platform, channel]);
|
|
137
|
+
if (!cat) return html`<p class="note">…</p>`;
|
|
138
|
+
const builtins = cat.builtins[platform === "telegram" ? "telegram" : "discord"] || {};
|
|
139
|
+
const set = (key, v) => setValues((x) => ({ ...x, [key]: v }));
|
|
140
|
+
return html`<div class="se">
|
|
141
|
+
<div class="se-preview">${preview && preview.entries ? preview.entries.map((e) => html`<span key=${e.key} class="se-item" title=${`${e.key}: ${e.value}`}>
|
|
142
|
+
<span class="se-glyph">${e.image_url ? html`<${EmojiImg} url=${e.image_url} alt=${e.display} />` : e.value}</span>
|
|
143
|
+
<span>${tn(`se_${e.key}`)}</span><span class=${`tag${e.source === "builtin" ? " se-default" : ""}`}>${tn(`se_src_${e.source}`)}</span></span>`) : html`<span class="note">…</span>`}</div>
|
|
144
|
+
${preview && preview.error ? html`<p class="feedback error">${preview.error}</p>` : null}
|
|
145
|
+
${preview && preview.problems && preview.problems.length ? html`<p class="feedback error">${preview.problems.map((p) => `${tn(`se_${p.key}`)}: ${p.problem} — ${tn("se_fallback")}`).join(" ")}</p>` : null}
|
|
146
|
+
<div class="se-rows">${keys.map((key) => html`<div key=${key} class="se-row">
|
|
147
|
+
<label for=${`se-${key}`}>${tn(`se_${key}`)}</label>
|
|
148
|
+
<input id=${`se-${key}`} type="text" autocomplete="off" value=${values[key] || ""} placeholder=${builtins[key] || ""} onInput=${(e) => set(key, e.target.value)} />
|
|
149
|
+
<button type="button" class="btn btn-sm" onClick=${() => setPicker(picker === key ? null : key)} aria-expanded=${picker === key ? "true" : "false"}>${tn("se_pick")}</button>
|
|
150
|
+
<button type="button" class="icon-btn" aria-label=${tn("se_useDefault")} title=${tn("se_useDefault")} onClick=${() => set(key, "")}><${Icon} name="close" size=${14} /></button>
|
|
151
|
+
${picker === key ? html`<${EmojiPicker} cat=${cat} keyName=${key} platform=${platform} channel=${channel}
|
|
152
|
+
onPick=${(v) => { set(key, v); setPicker(null); }} onClose=${() => setPicker(null)} />` : null}
|
|
153
|
+
</div>`)}</div>
|
|
154
|
+
<p class="note">${tn("se_hint")}</p>
|
|
155
|
+
</div>`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function EmojiPicker({ cat, keyName, platform, channel, onPick, onClose }) {
|
|
159
|
+
const lease = useLease(`picker:${keyName}`);
|
|
160
|
+
const [list, setList] = useState(null);
|
|
161
|
+
// progress_prefix is message text, so Telegram's reaction set does not bind it.
|
|
162
|
+
const telegramOnly = platform === "telegram" && keyName !== "progress_prefix";
|
|
163
|
+
const load = async (refresh) => {
|
|
164
|
+
setList(null);
|
|
165
|
+
const l = await guildEmojis(channel, refresh);
|
|
166
|
+
if (lease.current()) setList(l);
|
|
167
|
+
};
|
|
168
|
+
useEffect(() => { if (platform === "discord" && channel) load(false); }, [lease]);
|
|
169
|
+
return html`<div class="se-picker">
|
|
170
|
+
<p class="note">${telegramOnly ? tn("se_telegramOnly") : tn("se_unicode")}</p>
|
|
171
|
+
<div class="se-grid">${(telegramOnly ? cat.telegram_allowed : cat.suggestions).map((e) => html`<button key=${e} type="button" class="btn btn-sm" title=${e} onClick=${() => onPick(e)}>${e}</button>`)}</div>
|
|
172
|
+
${platform === "discord" && channel ? html`<div class="se-guilds">
|
|
173
|
+
<p class="note u-row">${tn("se_serverEmojis")} <button type="button" class="icon-btn" aria-label=${tn("refresh")} title=${tn("refresh")} onClick=${() => load(true)}><${Icon} name="restart" size=${14} /></button></p>
|
|
174
|
+
${!list ? html`<p class="note">…</p>` : list.error ? html`<p class="feedback error">${list.error}</p>` : html`
|
|
175
|
+
${list.guilds.length > 1 ? html`<p class="note">${tn("se_externalEmojiHint")}</p>` : null}
|
|
176
|
+
${list.guilds.map((g) => html`<div key=${g.id}>
|
|
177
|
+
${list.guilds.length > 1 ? html`<p class="note se-guild">${g.name || g.id}${g.primary ? html` <span class="tag">${tn("se_primaryServer")}</span>` : null}</p>` : null}
|
|
178
|
+
${g.error ? html`<p class="feedback error">${g.error}</p>` : !g.emojis.length ? html`<p class="note">${tn("se_noServerEmojis")}</p>`
|
|
179
|
+
// An unavailable emoji (the server lost the Boost tier it needs) cannot be reacted with: shown, not pickable.
|
|
180
|
+
: html`<div class="se-grid">${g.emojis.map((e) => html`<button key=${e.value} type="button" class=${`btn btn-sm${e.available ? "" : " off"}`} disabled=${!e.available}
|
|
181
|
+
title=${`:${e.name}:${e.available ? "" : ` (${tn("se_unavailable")})`}`} onClick=${() => { if (e.available) onPick(e.value); }}><${EmojiImg} url=${e.image_url} alt=${`:${e.name}:`} /></button>`)}</div>`}
|
|
182
|
+
</div>`)}`}
|
|
183
|
+
</div>` : null}
|
|
184
|
+
<div class="dlg-inline-actions"><button type="button" class="btn btn-sm btn-ghost" onClick=${onClose}>${tn("close")}</button></div>
|
|
185
|
+
</div>`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Agent ──
|
|
189
|
+
|
|
190
|
+
export function AgentDialog({ name, inst, ctx, onClose }) {
|
|
191
|
+
const { fleet, schema } = ctx;
|
|
192
|
+
const defaults = fleet.defaults || {};
|
|
193
|
+
const [f, setF] = useState(() => agentForm(inst, defaults));
|
|
194
|
+
const [emojis, setEmojis] = useState({ value: inst.status_emojis || null, baseline: inst.status_emojis || null });
|
|
195
|
+
// #1302: the baseline is what the picker shows before anyone touches it.
|
|
196
|
+
const visibilityBaseline = useMemo(() => (hasOwn(inst, "cross_instance_visibility") ? (VISIBILITY_MODES.includes(inst.cross_instance_visibility) ? inst.cross_instance_visibility : VISIBILITY_MODES[0]) : null), []);
|
|
197
|
+
const set = (k) => (v) => setF((x) => ({ ...x, [k]: v }));
|
|
198
|
+
const setInherit = (k) => (on) => setF((x) => ({ ...x, inherit: { ...x.inherit, [k]: on } }));
|
|
199
|
+
const ids = channelsOf(fleet).map(channelId);
|
|
200
|
+
const kiroUi = inst.kiro_ui ?? defaults.kiro_ui ?? "legacy";
|
|
201
|
+
const errors = {
|
|
202
|
+
auto: f.inherit.auto_pause_after ? "" : nonNegative(f.auto_pause_after),
|
|
203
|
+
hang: f.inherit.hang ? "" : positive(f.hang),
|
|
204
|
+
};
|
|
205
|
+
const failoverWarn = !f.inherit.model_failover && !f.model_failover.trim() ? "failoverEmpty" : "";
|
|
206
|
+
const ready = !errors.auto && !errors.hang;
|
|
207
|
+
const stage = () => {
|
|
208
|
+
if (!ready) return;
|
|
209
|
+
const patch = agentPatch({ ...f, status_emojis: emojis.value }, inst, defaults, visibilityBaseline, emojis.baseline);
|
|
210
|
+
if (Object.keys(patch).length) ctx.stageAgent(name, inst, patch);
|
|
211
|
+
onClose();
|
|
212
|
+
};
|
|
213
|
+
// What an override shows while it inherits: the default.
|
|
214
|
+
const shown = (k, own, dflt) => (f.inherit[k] ? dflt : own);
|
|
215
|
+
const L = (text, field, id) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${id} />`;
|
|
216
|
+
return html`<${EditDialog} title=${ctx.shortName(name, inst)} subtitle=${`ID: ${name}`} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${AGENT_FIELDS} schema=${schema}>
|
|
217
|
+
<div class="grid2">
|
|
218
|
+
<div class="field">${L(tn("displayName"), "instance.display_name", "ag-dn")}
|
|
219
|
+
<${Override} inherit=${f.inherit.display_name} onInherit=${setInherit("display_name")}>
|
|
220
|
+
<input id="ag-dn" type="text" placeholder=${name} disabled=${f.inherit.display_name} value=${shown("display_name", f.display_name, defaults.display_name || "")} onInput=${(e) => set("display_name")(e.target.value)} /></${Override}></div>
|
|
221
|
+
<div class="field">${L(tn("description"), "instance.description", "ag-desc")}<input id="ag-desc" type="text" value=${f.description} onInput=${(e) => set("description")(e.target.value)} /></div>
|
|
222
|
+
<div class="field">${L(tn("backend"), "instance.backend", "ag-be")}<${BackendSelect} id="ag-be" value=${f.backend} onChange=${set("backend")} /></div>
|
|
223
|
+
<div class="field">${L(tn("model"), "instance.model", "ag-model")}<input id="ag-model" type="text" placeholder=${tn("inheritPlaceholder")} value=${f.model} onInput=${(e) => set("model")(e.target.value)} /></div>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="field">${L(tn("workingDir"), "instance.working_directory", "ag-wd")}<input id="ag-wd" type="text" value=${f.working_directory} onInput=${(e) => set("working_directory")(e.target.value)} /></div>
|
|
226
|
+
<div class="field">${L(`${tn("autoPause")} (${tn("minutes")})`, "instance.auto_pause_after", "ag-auto")}
|
|
227
|
+
<${Override} inherit=${f.inherit.auto_pause_after} onInherit=${setInherit("auto_pause_after")}>
|
|
228
|
+
<input id="ag-auto" type="number" min="0" step="1" placeholder=${tn("zeroDisabled")} class=${errors.auto ? "invalid" : ""} disabled=${f.inherit.auto_pause_after}
|
|
229
|
+
value=${shown("auto_pause_after", f.auto_pause_after, String(defaults.auto_pause_after ?? 0))} onInput=${(e) => set("auto_pause_after")(e.target.value)} /></${Override}>
|
|
230
|
+
<${Feedback} error=${errors.auto} /></div>
|
|
231
|
+
<${Drawer} title=${tn("advancedSection")}>
|
|
232
|
+
<div class="grid2">
|
|
233
|
+
<div class="field">${L(tn("channelBinding"), "instance.topic_id", "ag-ch")}<${Select} id="ag-ch" value=${f.channel_id} onChange=${set("channel_id")} options=${["", ...ids]} /></div>
|
|
234
|
+
<div class="field">${L(`${tn("hangTimeout")} (${tn("minutes")})`, "instance.hang_detector", "ag-hang")}
|
|
235
|
+
<${Override} inherit=${f.inherit.hang} onInherit=${setInherit("hang")}>
|
|
236
|
+
<input id="ag-hang" type="number" min="0.1" step="0.5" class=${errors.hang ? "invalid" : ""} disabled=${f.inherit.hang}
|
|
237
|
+
value=${shown("hang", f.hang, String((defaults.hang_detector && defaults.hang_detector.timeout_minutes) ?? 15))} onInput=${(e) => set("hang")(e.target.value)} /></${Override}>
|
|
238
|
+
<${Feedback} error=${errors.hang} /></div>
|
|
239
|
+
${[["agent_mode", "agentMode", AGENT_MODES, defaults.agent_mode || "mcp"], ["tool_set", "toolSet", TOOL_SETS, defaults.tool_set || "full"],
|
|
240
|
+
["tool_progress", "toolProgress", TOOL_PROGRESS, defaults.tool_progress ?? "off"],
|
|
241
|
+
["cross_instance_visibility", "crossInstanceVisibility", VISIBILITY_MODES, visibilityDefault(defaults)],
|
|
242
|
+
["log_level", "logLevel", LOG_LEVELS, defaults.log_level || "info"]].map(([k, label, opts, dflt]) => html`<div key=${k} class="field">
|
|
243
|
+
${L(tn(label), `instance.${k}`, `ag-${k}`)}
|
|
244
|
+
<${Override} inherit=${f.inherit[k]} onInherit=${setInherit(k)}>
|
|
245
|
+
<${Select} id=${`ag-${k}`} value=${shown(k, f[k], dflt)} disabled=${f.inherit[k]} onChange=${set(k)}
|
|
246
|
+
options=${opts.includes(f[k]) || k === "cross_instance_visibility" ? opts : [f[k], ...opts]} /></${Override}>
|
|
247
|
+
${k === "cross_instance_visibility" ? html`<p class="note">${tn("crossInstanceVisibilityHint")}</p>` : null}</div>`)}
|
|
248
|
+
<div class="field">${L(tn("replyCompletionGuard"), "instance.reply_completion_guard")}
|
|
249
|
+
<${Override} inherit=${f.inherit.reply_completion_guard} onInherit=${setInherit("reply_completion_guard")}>
|
|
250
|
+
<label class="check"><input type="checkbox" disabled=${f.inherit.reply_completion_guard} checked=${shown("reply_completion_guard", f.reply_completion_guard, defaults.reply_completion_guard ?? true)}
|
|
251
|
+
onChange=${(e) => set("reply_completion_guard")(e.target.checked)} /><span>${tn("replyCompletionGuard")}</span></label></${Override}>
|
|
252
|
+
<p class="note">${replyHint(f.backend, f.inherit.agent_mode ? defaults.agent_mode || "mcp" : f.agent_mode, kiroUi)}</p></div>
|
|
253
|
+
<div class="field">${L(tn("lightweight"), "instance.lightweight")}
|
|
254
|
+
<${Override} inherit=${f.inherit.lightweight} onInherit=${setInherit("lightweight")}>
|
|
255
|
+
<label class="check"><input type="checkbox" disabled=${f.inherit.lightweight} checked=${shown("lightweight", f.lightweight, defaults.lightweight ?? false)}
|
|
256
|
+
onChange=${(e) => set("lightweight")(e.target.checked)} /><span>${tn("lightweight")}</span></label></${Override}></div>
|
|
257
|
+
<div class="field">${L(tn("modelFailover"), "instance.model_failover", "ag-fo")}
|
|
258
|
+
<${Override} inherit=${f.inherit.model_failover} onInherit=${setInherit("model_failover")}>
|
|
259
|
+
<input id="ag-fo" type="text" placeholder="e.g. sonnet" disabled=${f.inherit.model_failover}
|
|
260
|
+
value=${shown("model_failover", f.model_failover, (defaults.model_failover && defaults.model_failover[0]) || "")} onInput=${(e) => set("model_failover")(e.target.value)} /></${Override}>
|
|
261
|
+
<${Feedback} warning=${failoverWarn} /></div>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="field">${L(tn("systemPrompt"), "instance.systemPrompt", "ag-sys")}<input id="ag-sys" type="text" placeholder="e.g. file:prompts/dev.md" value=${f.systemPrompt} onInput=${(e) => set("systemPrompt")(e.target.value)} /></div>
|
|
264
|
+
<div class="field">${L(tn("tags"), "instance.tags")}<${ChipList} items=${f.tags} onChange=${set("tags")} placeholder=${tn("tagPlaceholder")} label=${tn("tags")} /></div>
|
|
265
|
+
<label class="check"><input type="checkbox" checked=${f.general_topic} onChange=${(e) => set("general_topic")(e.target.checked)} /><span>${tn("generalTopicField")}</span>
|
|
266
|
+
<${Impact} schema=${schema} field="instance.general_topic" /></label>
|
|
267
|
+
</${Drawer}>
|
|
268
|
+
<${Drawer} title=${tn("statusEmojis")}>
|
|
269
|
+
<p class="note">${tn("statusEmojisInstance")} <${Impact} schema=${schema} field="instance.status_emojis" /></p>
|
|
270
|
+
<${StatusEmojiEditor} own=${inst.status_emojis} platform=${ctx.channelType(f.channel_id || ids[0] || null)} channel=${f.channel_id || ids[0] || null}
|
|
271
|
+
previewBody=${(map) => ({ channel_id: f.channel_id || ids[0] || null, instance_config: map })} onChange=${setEmojis} />
|
|
272
|
+
</${Drawer}>
|
|
273
|
+
</${EditDialog}>`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── Connection (bot) ──
|
|
277
|
+
|
|
278
|
+
/** Rebind a connection: the provider proves the target first, the person confirms it, then the binding job runs. */
|
|
279
|
+
/**
|
|
280
|
+
* `alive()` is the dialog's lease: once the dialog is gone, a verification that answers late asks nothing and writes
|
|
281
|
+
* nothing (#1453 review). A write already sent is still followed to its end.
|
|
282
|
+
*/
|
|
283
|
+
async function rebind(connection, groupId, generalChannelId, say, alive) {
|
|
284
|
+
const key = newKey("binding");
|
|
285
|
+
const binding = { group_id: String(groupId).trim(), general_channel_id: String(generalChannelId || "").trim() || null };
|
|
286
|
+
const base = `/api/settings/connections/${encodeURIComponent(connection)}/binding`;
|
|
287
|
+
say({ text: tn("bindingVerifying") });
|
|
288
|
+
let verified;
|
|
289
|
+
try { verified = await api(`${base}/verify`, { method: "POST", headers: { "Idempotency-Key": key }, body: JSON.stringify({ ...binding, idempotency_key: key }) }); }
|
|
290
|
+
catch { say({ error: tn("failed") }); return false; }
|
|
291
|
+
if (!alive()) return false;
|
|
292
|
+
if (!verified.ok) { say({ error: (verified.body && verified.body.error) || tn("bindingVerifyFailed") }); return false; }
|
|
293
|
+
const probe = (verified.body && verified.body.probe) || {};
|
|
294
|
+
const target = [probe.group_name || binding.group_id, probe.channel_name || probe.channel_id].filter(Boolean).join(" / ");
|
|
295
|
+
const perms = [probe.can_view ? "view" : null, probe.can_send ? "send" : null, probe.can_manage_topics ? "topics" : null].filter(Boolean).join(", ");
|
|
296
|
+
say({ text: tn("bindingVerified", `${target}${perms ? ` (${perms})` : ""}`) });
|
|
297
|
+
if (!(await ask(tn("bindingConfirm", target || binding.group_id), { confirmLabel: tn("bindingApplyButton") }))) { if (alive()) say({ text: tn("bindingNotApplied") }); return false; }
|
|
298
|
+
if (!alive()) return false;
|
|
299
|
+
say({ text: tn("bindingApplying") });
|
|
300
|
+
const applied = await confirmedWrite(`${base}/apply`, { method: "POST", key, label: tn("bindingLabel", connection),
|
|
301
|
+
body: { verification_id: verified.body && verified.body.verification_id, idempotency_key: key } }).catch(() => ({ ok: false, body: { error: tn("failed") } }));
|
|
302
|
+
if (!applied.ok && applied.status !== 409) { say({ error: (applied.body && applied.body.error) || tn("bindingApplyFailed") }); return false; }
|
|
303
|
+
let result = applied.body || {};
|
|
304
|
+
for (let n = 0; result.job_id && result.result === "applying" && n < 60; n++) {
|
|
305
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
306
|
+
const status = await api(`${base}/apply/${encodeURIComponent(result.job_id)}`).catch(() => null);
|
|
307
|
+
if (status && status.ok) result = status.body || result;
|
|
308
|
+
}
|
|
309
|
+
const labels = { applied: "bindingApplied", rolled_back: "bindingRolledBack", rollback_failed: "bindingRollbackFailed", applying: "stillApplying" };
|
|
310
|
+
const ok = result.result === "applied";
|
|
311
|
+
say(ok ? { text: tn(labels.applied) } : { error: tn(labels[result.result] || "bindingUnknown") });
|
|
312
|
+
return ok;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* One connection, named by its id — never by its place in the list: a reload after another connection was added or
|
|
317
|
+
* removed must not point this dialog (and a token typed into it) at a different connection (#1453 review). If the
|
|
318
|
+
* connection is gone, the dialog closes and its draft goes with it.
|
|
319
|
+
*/
|
|
320
|
+
export function BotDialog({ id, ctx, onClose }) {
|
|
321
|
+
const { fleet, schema } = ctx;
|
|
322
|
+
const chs = channelsOf(fleet);
|
|
323
|
+
const index = chs.findIndex((c, i) => channelId(c, i) === id);
|
|
324
|
+
const ch = index >= 0 ? chs[index] : null;
|
|
325
|
+
const type = (ch && ch.type) || "telegram";
|
|
326
|
+
// The moment its connection is gone (this render), nothing started here may ask or write any more — not one frame
|
|
327
|
+
// later, when the close below has run (#1453 review). Revocation is for good: every disappearance starts a new epoch,
|
|
328
|
+
// and a flow belongs to the epoch it began in, so the same id coming back does not revive it.
|
|
329
|
+
const epoch = useRef(0), present = useRef(!!ch);
|
|
330
|
+
const latest = useRef(ctx);
|
|
331
|
+
latest.current = ctx;
|
|
332
|
+
if (present.current && !ch) epoch.current++;
|
|
333
|
+
present.current = !!ch;
|
|
334
|
+
useEffect(() => { if (!ch) onClose(); }, [!ch]);
|
|
335
|
+
const previousAccess = useMemo(() => structuredClone((ch && ch.access) || { mode: "locked", allowed_users: [] }), []);
|
|
336
|
+
const [mode, setMode] = useState(previousAccess.mode || "locked");
|
|
337
|
+
const [users, setUsers] = useState([...(previousAccess.allowed_users || [])]);
|
|
338
|
+
const [group, setGroup] = useState(ch && ch.group_id != null ? String(ch.group_id) : "");
|
|
339
|
+
const [general, setGeneral] = useState(ch && ch.options && ch.options.general_channel_id != null ? String(ch.options.general_channel_id) : "");
|
|
340
|
+
const [binding, setBinding] = useState(null);
|
|
341
|
+
const [bindBusy, setBindBusy] = useState(false);
|
|
342
|
+
const [token, setToken] = useState("");
|
|
343
|
+
const [tokenNote, setTokenNote] = useState(null);
|
|
344
|
+
const [emojis, setEmojis] = useState({ value: null, baseline: null });
|
|
345
|
+
const lease = useLease("bot-dialog");
|
|
346
|
+
const accessWarn = mode === "locked" && users.length === 0 ? "accessLockedEmpty" : "";
|
|
347
|
+
const stageToken = () => {
|
|
348
|
+
if (!ch) return;
|
|
349
|
+
if (!token) { setTokenNote({ error: tn("tokenRequired") }); return; }
|
|
350
|
+
// The token goes into the staged change and nowhere else: not a label, not the URL, not storage.
|
|
351
|
+
ctx.stage(`secret:${id}`, { label: tn("rotateToken", type), impact: schema.order[0], connectionSecret: { id, secret: token } });
|
|
352
|
+
setToken("");
|
|
353
|
+
setTokenNote({ text: tn("tokenQueued") });
|
|
354
|
+
};
|
|
355
|
+
const stage = () => {
|
|
356
|
+
if (!ch) return;
|
|
357
|
+
const original = structuredClone(chs);
|
|
358
|
+
const next = structuredClone(original);
|
|
359
|
+
next[index] = { ...next[index], access: { ...(next[index].access || {}), mode, allowed_users: [...users] } };
|
|
360
|
+
const accessChanged = !sameValue(next, original);
|
|
361
|
+
const emojisChanged = !sameValue(emojis.value, emojis.baseline);
|
|
362
|
+
if (emojisChanged) {
|
|
363
|
+
const options = { ...(next[index].options || {}) };
|
|
364
|
+
if (emojis.value) options.status_emojis = emojis.value; else delete options.status_emojis;
|
|
365
|
+
next[index] = { ...next[index], options };
|
|
366
|
+
}
|
|
367
|
+
if (accessChanged || emojisChanged) {
|
|
368
|
+
ctx.stageChannels(`connections:${ch.id || chLabel(index)}`, next, {
|
|
369
|
+
label: tn(accessChanged && emojisChanged ? "updateAccessEmojis" : accessChanged ? "updateAccess" : "updateEmojis", ch.id || chLabel(index)),
|
|
370
|
+
impact: impactOf(schema, accessChanged ? "fleet.channels" : "fleet.channel.options.status_emojis"),
|
|
371
|
+
// Confirmed at Apply, against this snapshot — not the live form, which may change again before then.
|
|
372
|
+
confirms: accessChanged ? accessConfirmations(previousAccess, mode, users) : [],
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
onClose();
|
|
376
|
+
};
|
|
377
|
+
const remove = async () => {
|
|
378
|
+
if (!ch) return;
|
|
379
|
+
const asked = epoch.current;
|
|
380
|
+
if (!(await ask(tn("removeBot", `${type} (${chLabel(index)})`), { confirmLabel: tn("deleteBot"), danger: true }))) return;
|
|
381
|
+
if (!lease.current() || !present.current || epoch.current !== asked) return;
|
|
382
|
+
// The list as it is now (a reload may have changed it while the question was open): only this connection goes.
|
|
383
|
+
const now = channelsOf(latest.current.fleet);
|
|
384
|
+
if (!now.some((c, j) => channelId(c, j) === id)) return;
|
|
385
|
+
const next = now.filter((c, j) => channelId(c, j) !== id);
|
|
386
|
+
const res = await confirmedWrite("/api/settings/fleet/channels", { method: "PUT", body: next, label: tn("removeBotLabel", id) }).catch(() => ({ ok: false, body: {} }));
|
|
387
|
+
if (!res.ok) { toast((res.body && res.body.error) || tn("failed"), false); return; }
|
|
388
|
+
toast(tn("saved"));
|
|
389
|
+
ctx.reload();
|
|
390
|
+
if (lease.current()) onClose();
|
|
391
|
+
};
|
|
392
|
+
const L = (text, field, htmlFor) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${htmlFor} />`;
|
|
393
|
+
if (!ch) return null;
|
|
394
|
+
return html`<${EditDialog} title=${type === "telegram" ? "Telegram" : "Discord"} subtitle=${id} onClose=${onClose} onStage=${stage} impacts=${BOT_FIELDS} schema=${schema}>
|
|
395
|
+
<div class="grid2">
|
|
396
|
+
<div class="field">${L(tn("accessMode"), "fleet.channel.access.mode", "bot-mode")}<${Select} id="bot-mode" value=${mode} onChange=${setMode} options=${ACCESS_MODES} />
|
|
397
|
+
<${Feedback} warning=${accessWarn} /></div>
|
|
398
|
+
<div class="field">${L(tn("allowedUsers"), "fleet.channel.access.allowed_users")}<${ChipList} items=${users} onChange=${setUsers} placeholder=${tn("userIdPlaceholder")} label=${tn("allowedUsers")} /></div>
|
|
399
|
+
</div>
|
|
400
|
+
<div class="field">${L(type === "discord" ? tn("guildIdField") : tn("groupIdField"), "fleet.channels", "bot-group")}
|
|
401
|
+
<input id="bot-group" type="text" autocomplete="off" value=${group} onInput=${(e) => setGroup(e.target.value)} />
|
|
402
|
+
<label for="bot-general">${type === "discord" ? tn("generalChannelId") : tn("forumTopicId")}</label>
|
|
403
|
+
<input id="bot-general" type="text" autocomplete="off" value=${general} placeholder=${tn("optional")} onInput=${(e) => setGeneral(e.target.value)} />
|
|
404
|
+
<p class="note">${tn("bindingHint")}</p>
|
|
405
|
+
<div class="dlg-inline-actions"><button type="button" class="btn btn-sm" disabled=${bindBusy} onClick=${async () => {
|
|
406
|
+
if (!group.trim()) { setBinding({ error: tn("groupRequired") }); return; }
|
|
407
|
+
setBindBusy(true);
|
|
408
|
+
const mine = epoch.current;
|
|
409
|
+
const alive = () => lease.current() && present.current && epoch.current === mine;
|
|
410
|
+
const ok = await rebind(id, group, general, (s) => { if (alive()) setBinding(s); }, alive);
|
|
411
|
+
if (lease.current()) setBindBusy(false);
|
|
412
|
+
if (ok) ctx.reload();
|
|
413
|
+
}}>${tn("bindingButton")}</button>
|
|
414
|
+
${binding ? html`<span class=${`feedback${binding.error ? " error" : ""}`} role="status">${binding.error || binding.text}</span>` : null}</div>
|
|
415
|
+
</div>
|
|
416
|
+
<${Drawer} title=${tn("statusEmojis")}>
|
|
417
|
+
<p class="note">${tn("statusEmojisChannel")} <${Impact} schema=${schema} field="fleet.channel.options.status_emojis" /></p>
|
|
418
|
+
<${StatusEmojiEditor} own=${ch.options && ch.options.status_emojis} platform=${type} channel=${id}
|
|
419
|
+
previewBody=${(map) => ({ channel_id: id, platform: type, channel_config: map })} onChange=${setEmojis} />
|
|
420
|
+
</${Drawer}>
|
|
421
|
+
<${Drawer} title=${tn("advancedSection")}>
|
|
422
|
+
<div class="field"><span>${tn("tokenEnv")}</span><code>${ch.bot_token_env || tn("noTokenEnv")}</code></div>
|
|
423
|
+
<div class="field"><label for="bot-token">${tn("tokenField")}</label>
|
|
424
|
+
<input id="bot-token" type="password" autocomplete="new-password" value=${token} placeholder=${tn("tokenPlaceholder")} onInput=${(e) => setToken(e.target.value)} />
|
|
425
|
+
<p class="note">${tn("tokenHint")}</p>
|
|
426
|
+
<div class="dlg-inline-actions"><button type="button" class="btn btn-sm" onClick=${stageToken}>${tn("tokenButton")}</button>
|
|
427
|
+
${tokenNote ? html`<span class=${`feedback${tokenNote.error ? " error" : ""}`} role="status">${tokenNote.error || tokenNote.text}</span>` : null}</div></div>
|
|
428
|
+
<div class="dlg-inline-actions"><button type="button" class="btn btn-sm danger" onClick=${remove}><${Icon} name="trash" size=${14} />${tn("deleteBot")}</button></div>
|
|
429
|
+
</${Drawer}>
|
|
430
|
+
</${EditDialog}>`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** A new connection: written at once (as before), through the confirmation an admin may have to give. */
|
|
434
|
+
export function NewBotDialog({ ctx, onClose }) {
|
|
435
|
+
const lease = useLease("new-bot");
|
|
436
|
+
const [f, setF] = useState({ type: "discord", id: "", env: "", group: "" });
|
|
437
|
+
const [busy, setBusy] = useState(false);
|
|
438
|
+
const [err, setErr] = useState("");
|
|
439
|
+
const save = async () => {
|
|
440
|
+
if (!f.env.trim()) { setErr(tn("tokenEnvRequired")); return; }
|
|
441
|
+
const next = [...channelsOf(ctx.fleet), { id: f.id.trim() || undefined, type: f.type, bot_token_env: f.env.trim(), group_id: f.group.trim() || undefined, access: { mode: "locked", allowed_users: [] } }];
|
|
442
|
+
setBusy(true);
|
|
443
|
+
const res = await confirmedWrite("/api/settings/fleet/channels", { method: "PUT", body: next, label: tn("addBotLabel", f.id.trim() || f.type) }).catch(() => ({ ok: false, body: {} }));
|
|
444
|
+
if (!res.ok) { toast((res.body && res.body.error) || tn("failed"), false); if (lease.current()) setBusy(false); return; }
|
|
445
|
+
toast(tn("saved"));
|
|
446
|
+
ctx.reload();
|
|
447
|
+
if (lease.current()) onClose();
|
|
448
|
+
};
|
|
449
|
+
const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
|
|
450
|
+
return html`<${Dialog} title=${tn("newBot")} onClose=${onClose} busy=${busy}
|
|
451
|
+
actions=${html`<button type="button" class="btn" disabled=${busy} onClick=${onClose}>${tn("cancel")}</button>
|
|
452
|
+
<button type="button" class="btn btn-primary" disabled=${busy} onClick=${save}>${tn("save")}</button>`}>
|
|
453
|
+
<div class="form">
|
|
454
|
+
<div class="grid2">
|
|
455
|
+
<div class="field"><label for="nb-type">${tn("type")}</label><${Select} id="nb-type" value=${f.type} onChange=${(v) => setF({ ...f, type: v })} options=${CH_TYPES} /></div>
|
|
456
|
+
<div class="field"><label for="nb-id">${tn("adapterId")}</label><input id="nb-id" type="text" placeholder="persona1" value=${f.id} onInput=${set("id")} /></div>
|
|
457
|
+
<div class="field"><label for="nb-env">${tn("tokenEnv")} *</label><input id="nb-env" type="text" placeholder="BOT_TOKEN_ENV" value=${f.env} onInput=${set("env")} /></div>
|
|
458
|
+
<div class="field"><label for="nb-group">${tn("groupId")}</label><input id="nb-group" type="text" value=${f.group} onInput=${set("group")} /></div>
|
|
459
|
+
</div>
|
|
460
|
+
<p class="note">${tn("envHint")}</p>
|
|
461
|
+
${err ? html`<p class="feedback error" role="alert">${err}</p>` : null}
|
|
462
|
+
</div></${Dialog}>`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ── ClassicBot room ──
|
|
466
|
+
|
|
467
|
+
export function ClassicDialog({ room, ctx, onClose }) {
|
|
468
|
+
const { fleet, classic, schema } = ctx;
|
|
469
|
+
const cd = (classic && classic.defaults) || {}, fd = fleet.defaults || {};
|
|
470
|
+
const inherited = {
|
|
471
|
+
backend: cd.backend || fd.backend || "claude-code", model: cd.model || fd.model || "",
|
|
472
|
+
auto_pause_after: cd.auto_pause_after ?? fd.auto_pause_after ?? 0, tool_progress: cd.tool_progress ?? fd.tool_progress ?? "off",
|
|
473
|
+
reply_completion_guard: cd.reply_completion_guard ?? fd.reply_completion_guard ?? true,
|
|
474
|
+
};
|
|
475
|
+
const c = room;
|
|
476
|
+
const [f, setF] = useState(() => ({
|
|
477
|
+
backend: c.backend || inherited.backend, model: c.model || "",
|
|
478
|
+
auto_pause_after: String(c.auto_pause_after ?? inherited.auto_pause_after), tool_progress: c.tool_progress ?? inherited.tool_progress,
|
|
479
|
+
reply_completion_guard: c.reply_completion_guard ?? inherited.reply_completion_guard,
|
|
480
|
+
collab: !!c.collab, web_echo: !!c.web_echo, context_lines: String(c.context_lines ?? cd.context_lines ?? 5),
|
|
481
|
+
inherit: { auto_pause_after: !hasOwn(c, "auto_pause_after"), tool_progress: !hasOwn(c, "tool_progress"), reply_completion_guard: !hasOwn(c, "reply_completion_guard") },
|
|
482
|
+
}));
|
|
483
|
+
const set = (k) => (v) => setF((x) => ({ ...x, [k]: v }));
|
|
484
|
+
const setInherit = (k) => (on) => setF((x) => ({ ...x, inherit: { ...x.inherit, [k]: on } }));
|
|
485
|
+
const errors = { auto: f.inherit.auto_pause_after ? "" : nonNegative(f.auto_pause_after), context: nonNegativeInteger(f.context_lines) };
|
|
486
|
+
const ready = !errors.auto && !errors.context;
|
|
487
|
+
const runtime = ctx.classicRuntime(c.instanceName);
|
|
488
|
+
const classicMode = fd.agent_mode ?? "mcp";
|
|
489
|
+
const lease = useLease("classic-dialog");
|
|
490
|
+
const stage = async () => {
|
|
491
|
+
if (!ready) return;
|
|
492
|
+
const patch = changedFields({
|
|
493
|
+
backend: f.backend, model: f.model.trim() || null,
|
|
494
|
+
auto_pause_after: f.inherit.auto_pause_after ? null : Number(f.auto_pause_after),
|
|
495
|
+
tool_progress: f.inherit.tool_progress ? null : f.tool_progress,
|
|
496
|
+
reply_completion_guard: f.inherit.reply_completion_guard ? null : f.reply_completion_guard,
|
|
497
|
+
collab: f.collab, context_lines: Number(f.context_lines), web_echo: f.web_echo,
|
|
498
|
+
}, {
|
|
499
|
+
backend: c.backend || inherited.backend, model: (c.model && c.model.trim()) || null,
|
|
500
|
+
auto_pause_after: hasOwn(c, "auto_pause_after") ? c.auto_pause_after : null,
|
|
501
|
+
tool_progress: hasOwn(c, "tool_progress") ? c.tool_progress : null,
|
|
502
|
+
reply_completion_guard: hasOwn(c, "reply_completion_guard") ? c.reply_completion_guard : null,
|
|
503
|
+
collab: !!c.collab, context_lines: c.context_lines ?? cd.context_lines ?? 5, web_echo: !!c.web_echo,
|
|
504
|
+
});
|
|
505
|
+
if (!Object.keys(patch).length) { onClose(); return; }
|
|
506
|
+
// #1320: turning web echo on for a group posts every web message where all its members can read it.
|
|
507
|
+
if (patch.web_echo === true && !c.web_echo && webEchoIsGroup(c.channelId)
|
|
508
|
+
&& !(await ask(tn("webEchoGroupConfirm"), { confirmLabel: tn("webEchoEnable") }))) return;
|
|
509
|
+
if (!lease.current()) return;
|
|
510
|
+
ctx.stageClassic(c, patch);
|
|
511
|
+
onClose();
|
|
512
|
+
};
|
|
513
|
+
const L = (text, field, id) => html`<${Label} text=${text} schema=${schema} field=${field} htmlFor=${id} />`;
|
|
514
|
+
return html`<${EditDialog} title=${c.name || c.instanceName} subtitle=${`ID: ${c.instanceName} · ${tn(`exec_${runtime.execution}`)}`} onClose=${onClose} onStage=${stage} ready=${ready} impacts=${CLASSIC_FIELDS} schema=${schema}>
|
|
515
|
+
<div class="grid2">
|
|
516
|
+
<div class="field">${L(tn("backend"), "classic.backend", "cl-be")}<${BackendSelect} id="cl-be" value=${f.backend} onChange=${set("backend")} /></div>
|
|
517
|
+
<div class="field">${L(tn("model"), "classic.model", "cl-model")}<input id="cl-model" type="text" placeholder=${inherited.model || tn("inheritPlaceholder")} value=${f.model} onInput=${(e) => set("model")(e.target.value)} /></div>
|
|
518
|
+
<div class="field">${L(`${tn("autoPause")} (${tn("minutes")})`, "classic.auto_pause_after", "cl-auto")}
|
|
519
|
+
<${Override} inherit=${f.inherit.auto_pause_after} onInherit=${setInherit("auto_pause_after")}>
|
|
520
|
+
<input id="cl-auto" type="number" min="0" step="1" class=${errors.auto ? "invalid" : ""} disabled=${f.inherit.auto_pause_after}
|
|
521
|
+
value=${f.inherit.auto_pause_after ? String(inherited.auto_pause_after) : f.auto_pause_after} onInput=${(e) => set("auto_pause_after")(e.target.value)} /></${Override}>
|
|
522
|
+
<${Feedback} error=${errors.auto} /></div>
|
|
523
|
+
<div class="field">${L(tn("toolProgress"), "classic.tool_progress", "cl-tp")}
|
|
524
|
+
<${Override} inherit=${f.inherit.tool_progress} onInherit=${setInherit("tool_progress")}>
|
|
525
|
+
<${Select} id="cl-tp" disabled=${f.inherit.tool_progress} value=${f.inherit.tool_progress ? inherited.tool_progress : f.tool_progress} onChange=${set("tool_progress")} options=${TOOL_PROGRESS} /></${Override}></div>
|
|
526
|
+
<div class="field">${L(tn("replyCompletionGuard"), "classic.reply_completion_guard")}
|
|
527
|
+
<${Override} inherit=${f.inherit.reply_completion_guard} onInherit=${setInherit("reply_completion_guard")}>
|
|
528
|
+
<label class="check"><input type="checkbox" disabled=${f.inherit.reply_completion_guard} checked=${f.inherit.reply_completion_guard ? inherited.reply_completion_guard : f.reply_completion_guard}
|
|
529
|
+
onChange=${(e) => set("reply_completion_guard")(e.target.checked)} /><span>${tn("replyCompletionGuard")}</span></label></${Override}>
|
|
530
|
+
<p class="note">${replyHint(f.backend, classicMode, fd.kiro_ui)}</p></div>
|
|
531
|
+
<label class="check"><input type="checkbox" checked=${f.collab} onChange=${(e) => set("collab")(e.target.checked)} /><span>${tn("collabMode")}</span><${Impact} schema=${schema} field="classic.collab" /></label>
|
|
532
|
+
<label class="check"><input type="checkbox" checked=${f.web_echo} onChange=${(e) => set("web_echo")(e.target.checked)} /><span>${tn("webEcho")}</span><${Impact} schema=${schema} field="classic.web_echo" /></label>
|
|
533
|
+
</div>
|
|
534
|
+
<${Drawer} title=${tn("advancedSection")}>
|
|
535
|
+
<div class="field">${L(tn("contextLines"), "classic.context_lines", "cl-ctx")}
|
|
536
|
+
<input id="cl-ctx" type="number" min="0" step="1" class=${errors.context ? "invalid" : ""} value=${f.context_lines} onInput=${(e) => set("context_lines")(e.target.value)} />
|
|
537
|
+
<${Feedback} error=${errors.context} /></div>
|
|
538
|
+
<div class="dlg-inline-actions">
|
|
539
|
+
${runtime.status === "running" ? html`<button type="button" class="btn btn-sm" onClick=${() => { onClose(); ctx.pauseWake(c.instanceName, "pause"); }}>${tn("pause")}</button>` : null}
|
|
540
|
+
${runtime.status === "paused" ? html`<button type="button" class="btn btn-sm" onClick=${() => { onClose(); ctx.pauseWake(c.instanceName, "wake"); }}>${tn("wake")}</button>` : null}
|
|
541
|
+
</div>
|
|
542
|
+
</${Drawer}>
|
|
543
|
+
</${EditDialog}>`;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export { worstImpact };
|