From 31dd56e7866543d123cf0772dabfd2a2eb121deb Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+catomean@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:08:33 +0200 Subject: [PATCH 1/4] style: format with oxfmt (11 files) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo already had oxfmt wired as format/format:check, but nothing in scripts/check.mjs ever called it — verify passing was never evidence the tree was formatted. These 11 files had drifted; fixed mechanically with 'oxfmt --write'. --- .github/pull_request_template.md | 14 ---- .github/workflows/full-release-validation.yml | 11 +++- extensions/canvas/scripts/copy-a2ui.d.mts | 5 +- extensions/diffs/src/viewer-client.test.ts | 7 +- extensions/oc-path/src/oc-path/edit.ts | 64 ++++++++++++++----- extensions/oc-path/src/oc-path/jsonc/emit.ts | 4 +- .../oc-path/src/oc-path/jsonc/resolve.ts | 12 +++- extensions/oc-path/src/oc-path/parse.ts | 17 ++--- extensions/oc-path/src/oc-path/resolve.ts | 44 +++++++++---- scripts/lib/local-build-metadata.d.mts | 5 +- scripts/package-openclaw-for-docker.mjs | 17 ++--- 11 files changed, 120 insertions(+), 80 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d325fb5e84ee8..06c78d2d180ba 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,19 +2,14 @@ What problem does this PR solve? - Why does this matter now? - What is the intended outcome? - What is intentionally out of scope? - What does success look like? - What should reviewers focus on?
@@ -75,13 +70,10 @@ Be mindful of private information like IP addresses, API keys, phone numbers, no Which commands did you run? - What regression coverage was added or updated? - What failed before this fix, if known? - If no test was added, why not?
@@ -95,16 +87,12 @@ List focused commands, not every incidental check. CI is useful support, but ext Did user-visible behavior change? (`Yes/No`) - Did config, environment, or migration behavior change? (`Yes/No`) - Did security, auth, secrets, network, or tool execution behavior change? (`Yes/No`) - What is the highest-risk area? - How is that risk mitigated?
@@ -118,10 +106,8 @@ Use this for author judgment that is not obvious from the diff. ClawSweeper can What is the next action? - What is still waiting on author, maintainer, CI, or external proof? - Which bot or reviewer comments were addressed?
diff --git a/.github/workflows/full-release-validation.yml b/.github/workflows/full-release-validation.yml index 2f3a283a4c8a8..24999295fbc5c 100644 --- a/.github/workflows/full-release-validation.yml +++ b/.github/workflows/full-release-validation.yml @@ -1139,7 +1139,16 @@ jobs: summary: name: Verify full validation - needs: [resolve_target, docker_runtime_assets_preflight, normal_ci, plugin_prerelease, release_checks, npm_telegram, performance] + needs: + [ + resolve_target, + docker_runtime_assets_preflight, + normal_ci, + plugin_prerelease, + release_checks, + npm_telegram, + performance, + ] if: always() runs-on: ubuntu-24.04 timeout-minutes: 5 diff --git a/extensions/canvas/scripts/copy-a2ui.d.mts b/extensions/canvas/scripts/copy-a2ui.d.mts index 76f35c91c1532..6e9d240c0d4fc 100644 --- a/extensions/canvas/scripts/copy-a2ui.d.mts +++ b/extensions/canvas/scripts/copy-a2ui.d.mts @@ -1,4 +1 @@ -export declare function copyA2uiAssets(params: { - srcDir: string; - outDir: string; -}): Promise; +export declare function copyA2uiAssets(params: { srcDir: string; outDir: string }): Promise; diff --git a/extensions/diffs/src/viewer-client.test.ts b/extensions/diffs/src/viewer-client.test.ts index 01191811bd39b..c7883cdfdf1e0 100644 --- a/extensions/diffs/src/viewer-client.test.ts +++ b/extensions/diffs/src/viewer-client.test.ts @@ -99,10 +99,9 @@ describe("createToolbarButton icon safety", () => { it("SVG strings in toolbarIconSvg contain no XSS patterns", () => { for (const pattern of XSS_PATTERNS) { - expect( - VIEWER_CLIENT_SRC.includes(pattern), - `source must not contain "${pattern}"`, - ).toBe(false); + expect(VIEWER_CLIENT_SRC.includes(pattern), `source must not contain "${pattern}"`).toBe( + false, + ); } }); diff --git a/extensions/oc-path/src/oc-path/edit.ts b/extensions/oc-path/src/oc-path/edit.ts index c1cd01db5875c..7217eca5e8b65 100644 --- a/extensions/oc-path/src/oc-path/edit.ts +++ b/extensions/oc-path/src/oc-path/edit.ts @@ -26,11 +26,17 @@ export function setMdOcPath(ast: MdAst, path: OcPath, newValue: string): MdEditR guardSentinel(newValue, formatOcPath(path)); if (path.section === "[frontmatter]") { const key = path.item ?? path.field; - if (key === undefined) {return { ok: false, reason: "unresolved" };} + if (key === undefined) { + return { ok: false, reason: "unresolved" }; + } const idx = ast.frontmatter.findIndex((e) => e.key === key); - if (idx === -1) {return { ok: false, reason: "unresolved" };} + if (idx === -1) { + return { ok: false, reason: "unresolved" }; + } const existing = ast.frontmatter[idx]; - if (existing === undefined) {return { ok: false, reason: "unresolved" };} + if (existing === undefined) { + return { ok: false, reason: "unresolved" }; + } const newEntry: FrontmatterEntry = { ...existing, value: newValue }; const newFm = ast.frontmatter.slice(); newFm[idx] = newEntry; @@ -43,16 +49,26 @@ export function setMdOcPath(ast: MdAst, path: OcPath, newValue: string): MdEditR const sectionSlug = path.section.toLowerCase(); const blockIdx = ast.blocks.findIndex((b) => b.slug === sectionSlug); - if (blockIdx === -1) {return { ok: false, reason: "unresolved" };} + if (blockIdx === -1) { + return { ok: false, reason: "unresolved" }; + } const block = ast.blocks[blockIdx]; - if (block === undefined) {return { ok: false, reason: "unresolved" };} + if (block === undefined) { + return { ok: false, reason: "unresolved" }; + } const itemSlug = path.item.toLowerCase(); const itemIdx = block.items.findIndex((i) => i.slug === itemSlug); - if (itemIdx === -1) {return { ok: false, reason: "unresolved" };} + if (itemIdx === -1) { + return { ok: false, reason: "unresolved" }; + } const item = block.items[itemIdx]; - if (item === undefined) {return { ok: false, reason: "unresolved" };} - if (item.kv === undefined) {return { ok: false, reason: "no-item-kv" };} + if (item === undefined) { + return { ok: false, reason: "unresolved" }; + } + if (item.kv === undefined) { + return { ok: false, reason: "no-item-kv" }; + } if (item.kv.key.toLowerCase() !== path.field.toLowerCase()) { return { ok: false, reason: "unresolved" }; } @@ -78,9 +94,15 @@ function rebuildBlockBody(block: AstBlock, newItems: readonly AstItem[]): string for (let i = 0; i < newItems.length; i++) { const newItem = newItems[i]; const oldItem = block.items[i]; - if (newItem === undefined || oldItem === undefined) {continue;} - if (newItem.kv === undefined || oldItem.kv === undefined) {continue;} - if (newItem.kv.value === oldItem.kv.value) {continue;} + if (newItem === undefined || oldItem === undefined) { + continue; + } + if (newItem.kv === undefined || oldItem.kv === undefined) { + continue; + } + if (newItem.kv.value === oldItem.kv.value) { + continue; + } const re = new RegExp(`^(\\s*-\\s*${escapeRegex(oldItem.kv.key)}\\s*:\\s*).*$`, "m"); body = body.replace(re, `$1${newItem.kv.value}`); } @@ -101,19 +123,29 @@ function finalize(ast: MdAst): MdEditResult { parts.push("---"); } if (ast.preamble.length > 0) { - if (parts.length > 0) {parts.push("");} + if (parts.length > 0) { + parts.push(""); + } parts.push(ast.preamble); } for (const block of ast.blocks) { - if (parts.length > 0) {parts.push("");} + if (parts.length > 0) { + parts.push(""); + } parts.push(`## ${block.heading}`); - if (block.bodyText.length > 0) {parts.push(block.bodyText);} + if (block.bodyText.length > 0) { + parts.push(block.bodyText); + } } return { ok: true, ast: { ...ast, raw: parts.join("\n") } }; } function formatFrontmatterValue(value: string): string { - if (value.length === 0) {return '""';} - if (/[:#&*?|<>=!%@`,[\]{}\r\n]/.test(value)) {return JSON.stringify(value);} + if (value.length === 0) { + return '""'; + } + if (/[:#&*?|<>=!%@`,[\]{}\r\n]/.test(value)) { + return JSON.stringify(value); + } return value; } diff --git a/extensions/oc-path/src/oc-path/jsonc/emit.ts b/extensions/oc-path/src/oc-path/jsonc/emit.ts index 6afe3c038e555..8df49c8d3f3da 100644 --- a/extensions/oc-path/src/oc-path/jsonc/emit.ts +++ b/extensions/oc-path/src/oc-path/jsonc/emit.ts @@ -33,7 +33,9 @@ export function emitJsonc(ast: JsoncAst, opts: JsoncEmitOptions = {}): string { } // Render mode loses comments; walks leaves for caller-injected sentinel. - if (ast.root === null) {return "";} + if (ast.root === null) { + return ""; + } return renderValue(ast.root, guardPath, []); } diff --git a/extensions/oc-path/src/oc-path/jsonc/resolve.ts b/extensions/oc-path/src/oc-path/jsonc/resolve.ts index a261777a1169d..2d96be2f69a9f 100644 --- a/extensions/oc-path/src/oc-path/jsonc/resolve.ts +++ b/extensions/oc-path/src/oc-path/jsonc/resolve.ts @@ -21,11 +21,15 @@ export type JsoncOcPathMatch = }; export function resolveJsoncOcPath(ast: JsoncAst, path: OcPath): JsoncOcPathMatch | null { - if (ast.root === null) {return null;} + if (ast.root === null) { + return null; + } const segments: string[] = []; const collect = (slot: string | undefined): void => { - if (slot === undefined) {return;} + if (slot === undefined) { + return; + } for (const s of splitRespectingBrackets(slot, ".")) { segments.push(isQuotedSeg(s) ? unquoteSeg(s) : s); } @@ -34,7 +38,9 @@ export function resolveJsoncOcPath(ast: JsoncAst, path: OcPath): JsoncOcPathMatc collect(path.item); collect(path.field); - if (segments.length === 0) {return { kind: "root", node: ast };} + if (segments.length === 0) { + return { kind: "root", node: ast }; + } return resolveJsoncValueOcPath(ast.root, segments); } diff --git a/extensions/oc-path/src/oc-path/parse.ts b/extensions/oc-path/src/oc-path/parse.ts index 3fa0b725308e3..91210f4b43309 100644 --- a/extensions/oc-path/src/oc-path/parse.ts +++ b/extensions/oc-path/src/oc-path/parse.ts @@ -12,14 +12,7 @@ */ import MarkdownIt from "markdown-it"; - -import type { - AstBlock, - AstItem, - Diagnostic, - FrontmatterEntry, - ParseResult, -} from "./ast.js"; +import type { AstBlock, AstItem, Diagnostic, FrontmatterEntry, ParseResult } from "./ast.js"; import { slugify } from "./slug.js"; type Token = ReturnType[number]; @@ -153,7 +146,9 @@ function extractItems(tokens: readonly Token[], bodyFileLine: number): AstItem[] const items: AstItem[] = []; for (let i = 0; i < tokens.length; i++) { const t = tokens[i]; - if (t.type !== "list_item_open" || t.map === null) {continue;} + if (t.type !== "list_item_open" || t.map === null) { + continue; + } // First inline at the item's own depth is the item text. let nestedDepth = 0; let text = ""; @@ -175,9 +170,7 @@ function extractItems(tokens: readonly Token[], bodyFileLine: number): AstItem[] text, slug: kvMatch ? slugify(kvMatch[1]) : slugify(text), line: bodyFileLine + t.map[0], - ...(kvMatch !== null - ? { kv: { key: kvMatch[1].trim(), value: kvMatch[2].trim() } } - : {}), + ...(kvMatch !== null ? { kv: { key: kvMatch[1].trim(), value: kvMatch[2].trim() } } : {}), }); } return items; diff --git a/extensions/oc-path/src/oc-path/resolve.ts b/extensions/oc-path/src/oc-path/resolve.ts index 25ae98fdbf468..423e03448b6c2 100644 --- a/extensions/oc-path/src/oc-path/resolve.ts +++ b/extensions/oc-path/src/oc-path/resolve.ts @@ -35,39 +35,61 @@ export type OcPathMatch = export function resolveMdOcPath(ast: MdAst, path: OcPath): OcPathMatch | null { if (path.section === "[frontmatter]") { const key = path.item ?? path.field; - if (key === undefined) {return null;} + if (key === undefined) { + return null; + } const entry = ast.frontmatter.find((e) => e.key === key); - if (entry === undefined) {return null;} + if (entry === undefined) { + return null; + } return { kind: "frontmatter", node: entry }; } - if (path.section === undefined) {return { kind: "root", node: ast };} + if (path.section === undefined) { + return { kind: "root", node: ast }; + } const block = ast.blocks.find((b) => b.slug === path.section!.toLowerCase()); - if (block === undefined) {return null;} - if (path.item === undefined) {return { kind: "block", node: block };} + if (block === undefined) { + return null; + } + if (path.item === undefined) { + return { kind: "block", node: block }; + } // Item dispatch: ordinal (#N) > positional ($last) > slug. // Ordinal uses document order so duplicate-slug items stay distinct. let item: AstItem | undefined; if (isOrdinalSeg(path.item)) { const n = parseOrdinalSeg(path.item); - if (n === null || n < 0 || n >= block.items.length) {return null;} + if (n === null || n < 0 || n >= block.items.length) { + return null; + } item = block.items[n]; } else if (isPositionalSeg(path.item)) { const concrete = resolvePositionalSeg(path.item, { indexable: true, size: block.items.length, }); - if (concrete === null) {return null;} + if (concrete === null) { + return null; + } item = block.items[Number(concrete)]; } else { item = block.items.find((i) => i.slug === path.item!.toLowerCase()); } - if (item === undefined) {return null;} - if (path.field === undefined) {return { kind: "item", node: item, block };} + if (item === undefined) { + return null; + } + if (path.field === undefined) { + return { kind: "item", node: item, block }; + } - if (item.kv === undefined) {return null;} - if (item.kv.key.toLowerCase() !== path.field.toLowerCase()) {return null;} + if (item.kv === undefined) { + return null; + } + if (item.kv.key.toLowerCase() !== path.field.toLowerCase()) { + return null; + } return { kind: "item-field", node: item, block, value: item.kv.value }; } diff --git a/scripts/lib/local-build-metadata.d.mts b/scripts/lib/local-build-metadata.d.mts index 0e367d588f0f1..b4eba18835873 100644 --- a/scripts/lib/local-build-metadata.d.mts +++ b/scripts/lib/local-build-metadata.d.mts @@ -1,7 +1,4 @@ -export { - BUILD_STAMP_FILE, - RUNTIME_POSTBUILD_STAMP_FILE, -} from "./local-build-metadata-paths.mjs"; +export { BUILD_STAMP_FILE, RUNTIME_POSTBUILD_STAMP_FILE } from "./local-build-metadata-paths.mjs"; export function resolveGitHead(params?: { cwd?: string; diff --git a/scripts/package-openclaw-for-docker.mjs b/scripts/package-openclaw-for-docker.mjs index c403a4539bcad..155802d69478b 100644 --- a/scripts/package-openclaw-for-docker.mjs +++ b/scripts/package-openclaw-for-docker.mjs @@ -155,16 +155,13 @@ function run(command, args, cwd, options = {}) { }; const terminateChild = () => { killChild("SIGTERM"); - forceKillTimeout = setTimeout( - () => { - forceKillTimeout = undefined; - if (settled && !processGroupAlive()) { - return; - } - killChild("SIGKILL"); - }, - options.killAfterMs ?? DEFAULT_TIMEOUT_KILL_AFTER_MS, - ); + forceKillTimeout = setTimeout(() => { + forceKillTimeout = undefined; + if (settled && !processGroupAlive()) { + return; + } + killChild("SIGKILL"); + }, options.killAfterMs ?? DEFAULT_TIMEOUT_KILL_AFTER_MS); forceKillTimeout.unref?.(); }; ACTIVE_CHILD_KILLERS.add(killChild); From 61faa4e996e98a812e451e8ebf23076fd0512976 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+catomean@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:08:35 +0200 Subject: [PATCH 2/4] fix(ci): wire the existing oxfmt gate into check.mjs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit format/format:check have existed since oxfmt was adopted, but scripts/check.mjs never called format:check — a passing 'pnpm check' was never evidence the tree was formatted, only that it built and typechecked. Prettier was never the right formatter here for a 17.6k-file repo; this repo already made the right call with a faster Rust-based one, it just never got wired in. Added as the last entry in the existing 'preflight guards' stage, alongside the other cheap npm-script guards it already runs in parallel with — one line, same shape as every other entry there. Proved by mutation: appended deliberately mis-formatted code to a tracked file and ran 'node scripts/check.mjs' end to end. It failed at the format step, naming exactly that file, and nothing else changed. Reverted before committing. Unrelated pre-existing finding while running the full check, noted for whoever owns it: 'npm shrinkwrap guard' also failed — 'npm-shrinkwrap.json is stale. Run pnpm deps:shrinkwrap:generate.' — with a clean git status on both npm-shrinkwrap.json and pnpm-lock.yaml beforehand, so it is not something this change caused. --- scripts/check.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/check.mjs b/scripts/check.mjs index 96ca9170abcce..f82cc2533f51e 100644 --- a/scripts/check.mjs +++ b/scripts/check.mjs @@ -108,6 +108,7 @@ export async function main(argv = process.argv.slice(2)) { { name: "duplicate scan target coverage", args: ["dup:check:coverage"] }, { name: "npm shrinkwrap guard", args: ["deps:shrinkwrap:check"] }, { name: "package patch guard", args: ["deps:patches:check"] }, + { name: "format", args: ["format:check"] }, ], }, { From f180f3228ddfb2f04ff2f3e7118c4683bd3fefd9 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+catomean@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:33:38 +0200 Subject: [PATCH 3/4] fix(ci): also add format:check to the CI guards shard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scripts/check.mjs wiring in the previous commit is what 'pnpm check' and any local dev running the aggregate check picks up. It turns out CI itself does not call check.mjs at all for this task: .github/workflows/ci.yml's check-shard job hand-lists the same commands a second time, independently, in a 'case "$TASK" in guards) ... esac' block — and that list had drifted from check.mjs's own array even before this PR (it's missing several entries check.mjs has, e.g. media-download-helpers, runtime-sidecar-loaders, opengrep-rule-metadata). Confirmed by reading check-guards' actual CI log: the workflow runs each 'pnpm check:*'/'lint:*' line directly, never invoking node scripts/check.mjs. So the previous commit alone, however correct, would never have been enforced by CI — only by a human or agent remembering to run 'pnpm check' locally, which is the exact kind of unenforced-by-default gap this PR exists to close. Added as one more line in the 'guards)' case, matching every other entry's shape. The two lists (check.mjs's array, and this case block) are still two independently-maintained copies of the same guard list after this change — that duplication is pre-existing and out of scope here, but is worth someone eventually collapsing to one source of truth. --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1675b8822cd4c..c044860ddb946 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1302,6 +1302,7 @@ jobs: pnpm lint:auth:no-pairing-store-group pnpm lint:auth:pairing-account-scope pnpm check:import-cycles + pnpm format:check # build-artifacts already runs the tsdown/runtime build for the same Node-relevant changes. NODE_OPTIONS=--max-old-space-size=8192 pnpm build:plugin-sdk:strict-smoke ;; From 317cf66962e8e2f7de8e4ee3915bce88bd840543 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Tue, 1 Sep 2026 03:37:05 +0200 Subject: [PATCH 4/4] fix(deps): bump oxfmt 0.52.0 -> 0.65.0, oxfmt is unusable in CI at 0.52.0 oxfmt@0.52.0's native binding (@oxfmt/binding-linux-x64-gnu) cannot be resolved by npm/pnpm's optional-dependency install on GitHub's hosted runner (confirmed via a diagnostic workflow_dispatch run, cache-cleared to rule out stale-cache: same failure with zero cache present). This makes the format:check gate wired in the previous commits unable to run at all on this repo's actual CI, not just locally. 0.65.0 resolves the binding cleanly and was confirmed green via a diagnostic PR/dispatch before being folded in here. The 13-version gap also changed a few internal formatting rules (mainly how ternaries inside lit-html template expressions wrap), so this commit includes the resulting --write diff across 138 files alongside the version bump and lockfile update. No source-level renames, directive relocations, or logic changes -- verified the diff is whitespace/wrap only and that no eslint-disable/@ts-expect-error comment lost its target line. --- .github/workflows/docs-agent.yml | 3 +- .github/workflows/test-performance-agent.yml | 3 +- docs/channels/feishu.md | 2 +- docs/channels/line.md | 4 +- docs/concepts/message-lifecycle-refactor.md | 8 +- docs/nodes/media-understanding.md | 12 +- docs/plugins/message-presentation.md | 3 +- docs/plugins/sdk-entrypoints.md | 8 +- extensions/acpx/src/runtime.test.ts | 46 +- extensions/browser/src/browser-tool.test.ts | 26 +- .../server-context.chrome-test-harness.ts | 9 +- .../canvas/src/host/a2ui-app/bootstrap.js | 26 +- .../native-subagent-monitor.test.ts | 50 +- .../discord/src/actions/runtime.test.ts | 7 +- extensions/discord/src/chunk.ts | 2 +- .../native-command.think-autocomplete.test.ts | 18 +- extensions/discord/src/subagent-hooks.test.ts | 6 +- .../src/test-support/component-runtime.ts | 26 +- .../discord/src/voice/manager.e2e.test.ts | 12 +- extensions/feishu/src/bot.test.ts | 44 +- .../feishu/src/lifecycle.test-support.ts | 40 +- extensions/matrix/src/env-vars.ts | 2 +- .../memory-core/src/short-term-promotion.ts | 2 +- extensions/open-prose/skills/prose/SKILL.md | 19 +- extensions/qa-lab/src/runtime-parity.ts | 8 +- .../inbound-pipeline.self-echo.test.ts | 102 +- extensions/qqbot/src/engine/group/history.ts | 16 +- extensions/synology-chat/src/client.ts | 12 +- .../telegram/src/bot-native-command-menu.ts | 2 +- .../src/webhook/realtime-handler.test.ts | 364 +- ...o-reply.connection-and-logging.e2e.test.ts | 5 +- .../realtime-transcription-provider.test.ts | 6 +- extensions/xai/speech-provider.test.ts | 6 +- .../monitor-mocks-test-support.ts | 28 +- extensions/zalouser/src/zalo-js.test-mocks.ts | 70 +- package.json | 2 +- .../agent-core/src/harness/utils/truncate.ts | 2 +- packages/memory-host-sdk/src/host/internal.ts | 2 +- packages/speech-core/src/tts.test.ts | 14 +- packages/terminal-core/src/table.ts | 2 +- pnpm-lock.yaml | 171 +- qa/copilot-capabilities.md | 72 +- .../dev/discord-acp-plain-language-smoke.ts | 14 +- scripts/pre-commit/pnpm-audit-prod.mjs | 2 +- scripts/test-docker-all.mjs | 2 +- .../bash-tools.exec-host-gateway.test.ts | 16 +- src/agents/bash-tools.exec-host-node.test.ts | 38 +- src/agents/command/delivery.test.ts | 12 +- .../run.overflow-compaction.harness.ts | 14 +- src/agents/model-compat.test.ts | 58 +- src/agents/sandbox/fs-bridge.test-helpers.ts | 10 +- src/agents/subagent-announce.test.ts | 10 +- src/auto-reply/reply/commands-acp/shared.ts | 4 +- .../reply/commands-core.send-policy.test.ts | 4 +- src/auto-reply/reply/current-turn-images.ts | 12 +- .../message/inbound-reply-dispatch.ts | 4 +- .../plugins/setup-wizard-binary.test.ts | 58 +- .../plugins/setup-wizard-proxy.test.ts | 76 +- src/cli/daemon-cli/status.test.ts | 28 +- src/cli/windows-argv.ts | 4 +- src/commands/auth-choice.test.ts | 52 +- .../shared/plugin-dependency-cleanup.ts | 20 +- src/commands/gateway-status.test.ts | 12 +- src/commands/models/list.auth-index.test.ts | 12 +- src/commands/onboard-auth.test.ts | 20 +- .../auth-choice.plugin-providers.test.ts | 24 +- src/config/io.write-config.test.ts | 10 +- src/config/mutate.test.ts | 12 +- .../validation.channel-metadata.test.ts | 10 +- src/cron/service/timer.regression.test.ts | 10 +- src/flows/doctor-core-checks.ts | 72 +- src/flows/search-setup.ts | 5 +- src/gateway/chat-attachments.test.ts | 15 +- src/gateway/config-reload-plan.ts | 52 +- src/gateway/mcp-http.test.ts | 10 +- .../server-methods/channels.start.test.ts | 20 +- .../server-methods/models-auth-status.test.ts | 16 +- src/gateway/server-reload-handlers.test.ts | 110 +- src/gateway/server-restart-sentinel.test.ts | 24 +- src/gateway/server-startup-plugins.test.ts | 108 +- src/infra/shell-inline-command.ts | 6 +- src/plugins/hooks.before-install.test.ts | 52 +- src/plugins/providers.test.ts | 12 +- .../text/reasoning-tag-text-partitioner.ts | 2 +- src/talk/activation-name.ts | 10 +- ui/src/ui/app-render.helpers.ts | 46 +- ui/src/ui/app-render.ts | 2921 +++++++++-------- ui/src/ui/chat/chat-queue.ts | 90 +- ui/src/ui/chat/chat-welcome.ts | 28 +- ui/src/ui/chat/context-notice.ts | 100 +- ui/src/ui/chat/grouped-render.ts | 494 +-- ui/src/ui/chat/run-controls.ts | 154 +- ui/src/ui/chat/session-controls.ts | 256 +- ui/src/ui/chat/tool-cards.ts | 260 +- ui/src/ui/components/dashboard-header.ts | 20 +- ui/src/ui/components/file-preview-modal.ts | 34 +- ui/src/ui/components/modal-dialog.ts | 16 +- ui/src/ui/gateway.node.test.ts | 12 +- ui/src/ui/views/activity.ts | 50 +- ui/src/ui/views/agents-panels-overview.ts | 32 +- ui/src/ui/views/agents-panels-status-files.ts | 633 ++-- ui/src/ui/views/agents-panels-tools-skills.ts | 350 +- ui/src/ui/views/agents.ts | 381 ++- ui/src/ui/views/channels.config.ts | 22 +- .../ui/views/channels.nostr-profile-form.ts | 140 +- ui/src/ui/views/channels.nostr.ts | 215 +- ui/src/ui/views/channels.shared.ts | 8 +- ui/src/ui/views/channels.telegram.ts | 40 +- ui/src/ui/views/channels.ts | 111 +- ui/src/ui/views/channels.whatsapp.ts | 72 +- ui/src/ui/views/chat.ts | 654 ++-- ui/src/ui/views/command-palette.ts | 78 +- ui/src/ui/views/config-form.node.ts | 432 +-- ui/src/ui/views/config-form.render.ts | 106 +- ui/src/ui/views/config-quick.ts | 311 +- ui/src/ui/views/config.ts | 1226 +++---- ui/src/ui/views/cron-quick-create.ts | 32 +- ui/src/ui/views/cron.ts | 1704 +++++----- ui/src/ui/views/debug.ts | 92 +- .../ui/views/dreaming-restart-confirmation.ts | 18 +- ui/src/ui/views/dreaming.ts | 728 ++-- ui/src/ui/views/exec-approval.ts | 31 +- ui/src/ui/views/instances.ts | 32 +- ui/src/ui/views/login-gate.ts | 6 +- ui/src/ui/views/logs.ts | 58 +- ui/src/ui/views/markdown-sidebar.ts | 162 +- ui/src/ui/views/mcp.ts | 19 +- ui/src/ui/views/nodes-exec-approvals.ts | 179 +- ui/src/ui/views/nodes.ts | 228 +- ui/src/ui/views/overview-attention.ts | 20 +- ui/src/ui/views/overview-cards.ts | 46 +- ui/src/ui/views/overview-event-log.ts | 12 +- ui/src/ui/views/overview.ts | 247 +- ui/src/ui/views/sessions.ts | 648 ++-- ui/src/ui/views/skill-workshop.ts | 395 +-- ui/src/ui/views/skills.ts | 448 +-- ui/src/ui/views/usage-render-details.ts | 528 +-- ui/src/ui/views/usage-render-overview.ts | 372 ++- ui/src/ui/views/usage.ts | 386 ++- ui/src/ui/views/workboard.ts | 826 ++--- 140 files changed, 9928 insertions(+), 8905 deletions(-) diff --git a/.github/workflows/docs-agent.yml b/.github/workflows/docs-agent.yml index 6df42c682a67b..c9e9bf8a954c1 100644 --- a/.github/workflows/docs-agent.yml +++ b/.github/workflows/docs-agent.yml @@ -197,8 +197,7 @@ jobs: - name: Restore Node 24 path if: steps.gate.outputs.run_agent == 'true' - run: - | # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job + run: | # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job set -euo pipefail export PATH="${NODE_BIN}:${PATH}" echo "${NODE_BIN}" >> "$GITHUB_PATH" diff --git a/.github/workflows/test-performance-agent.yml b/.github/workflows/test-performance-agent.yml index 0ec19bd43995d..3f387f25c54ee 100644 --- a/.github/workflows/test-performance-agent.yml +++ b/.github/workflows/test-performance-agent.yml @@ -181,8 +181,7 @@ jobs: - name: Restore Node 24 path if: steps.gate.outputs.run_agent == 'true' && steps.patch.outputs.has_changes == 'true' - run: - | # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job + run: | # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job set -euo pipefail export PATH="${NODE_BIN}:${PATH}" echo "${NODE_BIN}" >> "$GITHUB_PATH" diff --git a/docs/channels/feishu.md b/docs/channels/feishu.md index 716a47545dc85..e39a489904092 100644 --- a/docs/channels/feishu.md +++ b/docs/channels/feishu.md @@ -25,7 +25,7 @@ Requires OpenClaw 2026.5.29 or above. Run `openclaw --version` to check. Upgrade ``` Choose manual setup to paste an App ID and App Secret from Feishu Open Platform, or choose QR setup to create a bot automatically. If the domestic Feishu mobile app does not react to the QR code, rerun setup and choose manual setup. - + ```bash openclaw gateway restart diff --git a/docs/channels/line.md b/docs/channels/line.md index 7785e8e3a2705..37eec1bfe585d 100644 --- a/docs/channels/line.md +++ b/docs/channels/line.md @@ -181,9 +181,7 @@ messages. }, flexMessage: { altText: "Status card", - contents: { - /* Flex payload */ - }, + contents: {/* Flex payload */}, }, templateMessage: { type: "confirm", diff --git a/docs/concepts/message-lifecycle-refactor.md b/docs/concepts/message-lifecycle-refactor.md index cb3330041e7f5..8ffc3b9fe3ad8 100644 --- a/docs/concepts/message-lifecycle-refactor.md +++ b/docs/concepts/message-lifecycle-refactor.md @@ -787,13 +787,7 @@ type DurableSendIntent = { batch?: RenderedMessageBatch; liveState?: LiveMessageState; status: - | "pending" - | "sending" - | "committing" - | "unknown_after_send" - | "sent" - | "failed" - | "cancelled"; + "pending" | "sending" | "committing" | "unknown_after_send" | "sent" | "failed" | "cancelled"; attempt: number; nextAttemptAt?: number; receipt?: MessageReceipt; diff --git a/docs/nodes/media-understanding.md b/docs/nodes/media-understanding.md index e478c76869ee2..4591f96300e76 100644 --- a/docs/nodes/media-understanding.md +++ b/docs/nodes/media-understanding.md @@ -69,20 +69,14 @@ If understanding fails or is disabled, **the reply flow continues** with the ori { tools: { media: { - models: [ - /* shared list */ - ], - image: { - /* optional overrides */ - }, + models: [/* shared list */], + image: {/* optional overrides */}, audio: { /* optional overrides */ echoTranscript: true, echoFormat: '📝 "{transcript}"', }, - video: { - /* optional overrides */ - }, + video: {/* optional overrides */}, }, }, } diff --git a/docs/plugins/message-presentation.md b/docs/plugins/message-presentation.md index cea93067383c6..8af637c91fa40 100644 --- a/docs/plugins/message-presentation.md +++ b/docs/plugins/message-presentation.md @@ -53,8 +53,7 @@ type MessagePresentationBlock = | { type: "select"; placeholder?: string; options: MessagePresentationOption[] }; type MessagePresentationAction = - | { type: "command"; command: string } - | { type: "callback"; value: string }; + { type: "command"; command: string } | { type: "callback"; value: string }; type MessagePresentationButton = { label: string; diff --git a/docs/plugins/sdk-entrypoints.md b/docs/plugins/sdk-entrypoints.md index 2be835bf45992..f7954a38939e4 100644 --- a/docs/plugins/sdk-entrypoints.md +++ b/docs/plugins/sdk-entrypoints.md @@ -105,12 +105,8 @@ export default definePluginEntry({ name: "My Plugin", description: "Short summary", register(api) { - api.registerProvider({ - /* ... */ - }); - api.registerTool({ - /* ... */ - }); + api.registerProvider({/* ... */}); + api.registerTool({/* ... */}); }, }); ``` diff --git a/extensions/acpx/src/runtime.test.ts b/extensions/acpx/src/runtime.test.ts index acffec327b39e..020b71e65b4ca 100644 --- a/extensions/acpx/src/runtime.test.ts +++ b/extensions/acpx/src/runtime.test.ts @@ -442,27 +442,25 @@ describe("AcpxRuntime fresh reset wrapper", () => { list: () => ["codex"], }, }); - vi.spyOn(delegate, "startTurn").mockImplementation( - (input): AcpRuntimeTurn => ({ - requestId: input.requestId, - events: (async function* () { - yield { - type: "text_delta" as const, - stream: "output" as const, - text: "Vou mapear o fluxo real primeiro...", - }; - })(), - result: Promise.resolve({ - status: "failed" as const, - error: { - message: "Internal error", - retryable: false, - }, - }), - cancel: vi.fn(async () => {}), - closeStream: vi.fn(async () => {}), + vi.spyOn(delegate, "startTurn").mockImplementation((input): AcpRuntimeTurn => ({ + requestId: input.requestId, + events: (async function* () { + yield { + type: "text_delta" as const, + stream: "output" as const, + text: "Vou mapear o fluxo real primeiro...", + }; + })(), + result: Promise.resolve({ + status: "failed" as const, + error: { + message: "Internal error", + retryable: false, + }, }), - ); + cancel: vi.fn(async () => {}), + closeStream: vi.fn(async () => {}), + })); const turn = runtime.startTurn({ handle: { @@ -561,8 +559,9 @@ describe("AcpxRuntime fresh reset wrapper", () => { const runTurn = vi.spyOn(delegate, "runTurn").mockImplementation(async function* () { yield { type: "done" }; }); - const startTurn = vi.spyOn(delegate, "startTurn").mockImplementation( - (input): AcpRuntimeTurn => ({ + const startTurn = vi + .spyOn(delegate, "startTurn") + .mockImplementation((input): AcpRuntimeTurn => ({ requestId: input.requestId, events: (async function* () { yield { type: "done" as const, stopReason: "end_turn" }; @@ -573,8 +572,7 @@ describe("AcpxRuntime fresh reset wrapper", () => { }), cancel: vi.fn(async () => {}), closeStream: vi.fn(async () => {}), - }), - ); + })); for await (const ignoredEventValue of runtime.runTurn({ handle: { diff --git a/extensions/browser/src/browser-tool.test.ts b/extensions/browser/src/browser-tool.test.ts index 599ef00dcb2b1..d5d40b29a601e 100644 --- a/extensions/browser/src/browser-tool.test.ts +++ b/extensions/browser/src/browser-tool.test.ts @@ -21,15 +21,13 @@ const browserClientMocks = vi.hoisted(() => ({ browserProfiles: vi.fn( async (..._args: unknown[]): Promise>> => [], ), - browserSnapshot: vi.fn( - async (..._args: unknown[]): Promise> => ({ - ok: true, - format: "ai", - targetId: "t1", - url: "https://example.com", - snapshot: "ok", - }), - ), + browserSnapshot: vi.fn(async (..._args: unknown[]): Promise> => ({ + ok: true, + format: "ai", + targetId: "t1", + url: "https://example.com", + snapshot: "ok", + })), browserStart: vi.fn(async (..._args: unknown[]) => ({})), browserStatus: vi.fn(async (..._args: unknown[]) => ({ ok: true, @@ -111,12 +109,10 @@ const nodesUtilsMocks = vi.hoisted(() => ({ })); const gatewayMocks = vi.hoisted(() => ({ - callGatewayTool: vi.fn( - async (): Promise> => ({ - ok: true, - payload: { result: { ok: true, running: true } }, - }), - ), + callGatewayTool: vi.fn(async (): Promise> => ({ + ok: true, + payload: { result: { ok: true, running: true } }, + })), })); const configMocks = vi.hoisted(() => ({ diff --git a/extensions/browser/src/browser/server-context.chrome-test-harness.ts b/extensions/browser/src/browser/server-context.chrome-test-harness.ts index 7c68c0f044da8..cb986dfb9d73f 100644 --- a/extensions/browser/src/browser/server-context.chrome-test-harness.ts +++ b/extensions/browser/src/browser/server-context.chrome-test-harness.ts @@ -15,10 +15,11 @@ vi.mock("./chrome.js", () => ({ message: "mock CDP diagnostic", elapsedMs: 1, })), - formatChromeCdpDiagnostic: vi.fn((diagnostic: { ok: boolean; code?: string; message?: string }) => - diagnostic.ok - ? "CDP diagnostic: ready." - : `CDP diagnostic: ${diagnostic.code}; ${diagnostic.message}.`, + formatChromeCdpDiagnostic: vi.fn( + (diagnostic: { ok: boolean; code?: string; message?: string }) => + diagnostic.ok + ? "CDP diagnostic: ready." + : `CDP diagnostic: ${diagnostic.code}; ${diagnostic.message}.`, ), isChromeCdpReady: vi.fn(async () => true), isChromeReachable: vi.fn(async () => true), diff --git a/extensions/canvas/src/host/a2ui-app/bootstrap.js b/extensions/canvas/src/host/a2ui-app/bootstrap.js index a8c2df8b5ffc8..b8fdb6102af7e 100644 --- a/extensions/canvas/src/host/a2ui-app/bootstrap.js +++ b/extensions/canvas/src/host/a2ui-app/bootstrap.js @@ -566,17 +566,21 @@ class OpenClawA2UIHost extends LitElement { ? `Failed: ${this.pendingAction.name}` : ""; - return html` ${this.pendingAction && this.pendingAction.phase !== "error" - ? html`
-
-
${statusText}
-
` - : ""} - ${this.toast - ? html`
- ${this.toast.text} -
` - : ""} + return html` ${ + this.pendingAction && this.pendingAction.phase !== "error" + ? html`
+
+
${statusText}
+
` + : "" + } + ${ + this.toast + ? html`
+ ${this.toast.text} +
` + : "" + }
${repeat( this.surfaces, diff --git a/extensions/codex/src/app-server/native-subagent-monitor.test.ts b/extensions/codex/src/app-server/native-subagent-monitor.test.ts index e9d74c70ee5b3..08b94bfabe542 100644 --- a/extensions/codex/src/app-server/native-subagent-monitor.test.ts +++ b/extensions/codex/src/app-server/native-subagent-monitor.test.ts @@ -53,27 +53,25 @@ function createRuntime() { error?: string; }>; }; - const createRunningTaskRun = vi.fn( - (params): AgentHarnessTaskRecord => ({ - taskId: params.sourceId ?? params.runId, - runtime: "subagent", - sourceId: params.sourceId, - requesterSessionKey: "agent:main:main", - ownerKey: "agent:main:main", - scopeKind: "session", - agentId: params.agentId, - runId: params.runId, - label: params.label, - task: params.task, - status: "running", - deliveryStatus: params.deliveryStatus ?? "not_applicable", - notifyPolicy: params.notifyPolicy ?? "silent", - createdAt: params.startedAt ?? Date.now(), - startedAt: params.startedAt, - lastEventAt: params.lastEventAt, - progressSummary: params.progressSummary, - }), - ); + const createRunningTaskRun = vi.fn((params): AgentHarnessTaskRecord => ({ + taskId: params.sourceId ?? params.runId, + runtime: "subagent", + sourceId: params.sourceId, + requesterSessionKey: "agent:main:main", + ownerKey: "agent:main:main", + scopeKind: "session", + agentId: params.agentId, + runId: params.runId, + label: params.label, + task: params.task, + status: "running", + deliveryStatus: params.deliveryStatus ?? "not_applicable", + notifyPolicy: params.notifyPolicy ?? "silent", + createdAt: params.startedAt ?? Date.now(), + startedAt: params.startedAt, + lastEventAt: params.lastEventAt, + progressSummary: params.progressSummary, + })); const taskRuntime = { createRunningTaskRun, tryCreateRunningTaskRun: vi.fn((params) => createRunningTaskRun(params)), @@ -85,12 +83,10 @@ function createRuntime() { return { ...taskRuntime, createAgentHarnessTaskRuntime: vi.fn(() => taskRuntime), - deliverAgentHarnessTaskCompletion: vi.fn( - async (): Promise => ({ - delivered: true, - path: "direct" as const, - }), - ), + deliverAgentHarnessTaskCompletion: vi.fn(async (): Promise => ({ + delivered: true, + path: "direct" as const, + })), }; } diff --git a/extensions/discord/src/actions/runtime.test.ts b/extensions/discord/src/actions/runtime.test.ts index 6ebf8119bcf2c..4e30e6d3eb678 100644 --- a/extensions/discord/src/actions/runtime.test.ts +++ b/extensions/discord/src/actions/runtime.test.ts @@ -47,9 +47,10 @@ const discordSendMocks = { name: "edited", })), editMessageDiscord: vi.fn(async () => ({})), - fetchChannelInfoDiscord: vi.fn( - async (channelId: string): Promise => ({ id: channelId, type: 0 }), - ), + fetchChannelInfoDiscord: vi.fn(async (channelId: string): Promise => ({ + id: channelId, + type: 0, + })), fetchChannelPermissionsDiscord: vi.fn(async () => ({})), fetchGuildInfoDiscord: vi.fn(async (guildId: string) => ({ id: guildId, diff --git a/extensions/discord/src/chunk.ts b/extensions/discord/src/chunk.ts index d780506052182..a6de02b636151 100644 --- a/extensions/discord/src/chunk.ts +++ b/extensions/discord/src/chunk.ts @@ -102,7 +102,7 @@ function findWhitespaceBreak(window: string) { } function findCjkPunctuationBreak(window: string) { - for (let end = window.length; end > 0; ) { + for (let end = window.length; end > 0;) { const code = window.charCodeAt(end - 1); const start = isLowSurrogate(code) && end > 1 ? end - 2 : end - 1; const char = window.slice(start, end); diff --git a/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts b/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts index 603df7f3cfaeb..f40f8a0b25cd0 100644 --- a/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts +++ b/extensions/discord/src/monitor/native-command.think-autocomplete.test.ts @@ -181,10 +181,11 @@ describe("discord native /think autocomplete", () => { providerThinkingMocks.resolveProviderBinaryThinking.mockReturnValue(undefined); providerThinkingMocks.resolveProviderDefaultThinkingLevel.mockReturnValue(undefined); providerThinkingMocks.resolveProviderThinkingProfile.mockReturnValue(undefined); - providerThinkingMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) => - provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId) - ? true - : undefined, + providerThinkingMocks.resolveProviderXHighThinking.mockImplementation( + ({ provider, context }) => + provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId) + ? true + : undefined, ); buildModelsProviderDataMock.mockResolvedValue({ byProvider: new Map>(), @@ -212,10 +213,11 @@ describe("discord native /think autocomplete", () => { providerThinkingMocks.resolveProviderThinkingProfile.mockReset(); providerThinkingMocks.resolveProviderThinkingProfile.mockReturnValue(undefined); providerThinkingMocks.resolveProviderXHighThinking.mockReset(); - providerThinkingMocks.resolveProviderXHighThinking.mockImplementation(({ provider, context }) => - provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId) - ? true - : undefined, + providerThinkingMocks.resolveProviderXHighThinking.mockImplementation( + ({ provider, context }) => + provider === "openai" && ["gpt-5.4", "gpt-5.4-pro"].includes(context.modelId) + ? true + : undefined, ); installProviderThinkingRegistryForTest(); fs.mkdirSync(path.dirname(STORE_PATH), { recursive: true }); diff --git a/extensions/discord/src/subagent-hooks.test.ts b/extensions/discord/src/subagent-hooks.test.ts index fb7af43a00e06..ed27cc35695f7 100644 --- a/extensions/discord/src/subagent-hooks.test.ts +++ b/extensions/discord/src/subagent-hooks.test.ts @@ -55,9 +55,9 @@ const hookMocks = vi.hoisted(() => { return { resolveDiscordAccountImpl, resolveDiscordAccount: vi.fn(resolveDiscordAccountImpl), - autoBindSpawnedDiscordSubagent: vi.fn( - async (): Promise<{ threadId: string } | null> => ({ threadId: "thread-1" }), - ), + autoBindSpawnedDiscordSubagent: vi.fn(async (): Promise<{ threadId: string } | null> => ({ + threadId: "thread-1", + })), listThreadBindingsBySessionKey: vi.fn((_params?: unknown): ThreadBindingRecord[] => []), unbindThreadBindingsBySessionKey: vi.fn(() => []), }; diff --git a/extensions/discord/src/test-support/component-runtime.ts b/extensions/discord/src/test-support/component-runtime.ts index 0cff458a50748..e4b03b7d5adcf 100644 --- a/extensions/discord/src/test-support/component-runtime.ts +++ b/extensions/discord/src/test-support/component-runtime.ts @@ -25,20 +25,18 @@ type DiscordComponentRuntimeMocks = { upsertPairingRequestMock: AsyncUnknownMock; }; -const runtimeMocks = vi.hoisted( - (): DiscordComponentRuntimeMocks => ({ - buildPluginBindingResolvedTextMock: vi.fn(), - dispatchPluginInteractiveHandlerMock: vi.fn(), - dispatchReplyMock: vi.fn(), - enqueueSystemEventMock: vi.fn(), - readAllowFromStoreMock: vi.fn(), - readSessionUpdatedAtMock: vi.fn(), - recordInboundSessionMock: vi.fn(), - resolveStorePathMock: vi.fn(), - resolvePluginConversationBindingApprovalMock: vi.fn(), - upsertPairingRequestMock: vi.fn(), - }), -); +const runtimeMocks = vi.hoisted((): DiscordComponentRuntimeMocks => ({ + buildPluginBindingResolvedTextMock: vi.fn(), + dispatchPluginInteractiveHandlerMock: vi.fn(), + dispatchReplyMock: vi.fn(), + enqueueSystemEventMock: vi.fn(), + readAllowFromStoreMock: vi.fn(), + readSessionUpdatedAtMock: vi.fn(), + recordInboundSessionMock: vi.fn(), + resolveStorePathMock: vi.fn(), + resolvePluginConversationBindingApprovalMock: vi.fn(), + upsertPairingRequestMock: vi.fn(), +})); export const readAllowFromStoreMock: AsyncUnknownMock = runtimeMocks.readAllowFromStoreMock; export const dispatchPluginInteractiveHandlerMock: AsyncUnknownMock = diff --git a/extensions/discord/src/voice/manager.e2e.test.ts b/extensions/discord/src/voice/manager.e2e.test.ts index 585ada5dda5e5..6c9708fbb41e7 100644 --- a/extensions/discord/src/voice/manager.e2e.test.ts +++ b/extensions/discord/src/voice/manager.e2e.test.ts @@ -140,9 +140,10 @@ const { (...args: unknown[]) => Promise >(async () => undefined), transcribeAudioFileMock: vi.fn(async () => ({ text: "hello from voice" })), - textToSpeechStreamMock: vi.fn( - async (): Promise => ({ success: false, error: "stream unavailable" }), - ), + textToSpeechStreamMock: vi.fn(async (): Promise => ({ + success: false, + error: "stream unavailable", + })), textToSpeechMock: vi.fn(async () => ({ success: true, audioPath: "/tmp/voice.mp3" })), logVerboseMock: vi.fn(), resolveConfiguredRealtimeVoiceProviderMock: vi.fn(() => ({ @@ -306,9 +307,8 @@ function createClient() { rest: { get: vi.fn(), }, - fetchChannel: vi.fn( - async (channelId: string): Promise => - createVoiceChannelInfo(channelId), + fetchChannel: vi.fn(async (channelId: string): Promise => + createVoiceChannelInfo(channelId), ), fetchGuild: vi.fn(async (guildId: string) => ({ id: guildId, diff --git a/extensions/feishu/src/bot.test.ts b/extensions/feishu/src/bot.test.ts index a28d12ba60d03..dc903d2ae5bfd 100644 --- a/extensions/feishu/src/bot.test.ts +++ b/extensions/feishu/src/bot.test.ts @@ -440,16 +440,18 @@ async function dispatchMessage(params: { cfg: ClawdbotConfig; event: FeishuMessa describe("handleFeishuMessage ACP routing", () => { beforeEach(() => { vi.clearAllMocks(); - mockResolveConfiguredBindingRoute.mockReset().mockImplementation( - ({ - route, - }: { - route: NonNullable["route"]; - }): ConfiguredBindingRoute => ({ - bindingResolution: null, - route, - }), - ); + mockResolveConfiguredBindingRoute + .mockReset() + .mockImplementation( + ({ + route, + }: { + route: NonNullable["route"]; + }): ConfiguredBindingRoute => ({ + bindingResolution: null, + route, + }), + ); mockEnsureConfiguredBindingRouteReady.mockReset().mockResolvedValue({ ok: true }); mockResolveBoundConversation.mockReset().mockReturnValue(null); mockTouchBinding.mockReset(); @@ -961,16 +963,18 @@ describe("handleFeishuMessage command authorization", () => { mockListFeishuThreadMessages.mockReset().mockResolvedValue([]); mockReadSessionUpdatedAt.mockReturnValue(undefined); mockResolveStorePath.mockReturnValue("/tmp/feishu-sessions.json"); - mockResolveConfiguredBindingRoute.mockReset().mockImplementation( - ({ - route, - }: { - route: NonNullable["route"]; - }): ConfiguredBindingRoute => ({ - bindingResolution: null, - route, - }), - ); + mockResolveConfiguredBindingRoute + .mockReset() + .mockImplementation( + ({ + route, + }: { + route: NonNullable["route"]; + }): ConfiguredBindingRoute => ({ + bindingResolution: null, + route, + }), + ); mockEnsureConfiguredBindingRouteReady.mockReset().mockResolvedValue({ ok: true }); mockResolveBoundConversation.mockReset().mockReturnValue(null); mockTouchBinding.mockReset(); diff --git a/extensions/feishu/src/lifecycle.test-support.ts b/extensions/feishu/src/lifecycle.test-support.ts index a48a9634ccdc2..3c2cacabd0c5d 100644 --- a/extensions/feishu/src/lifecycle.test-support.ts +++ b/extensions/feishu/src/lifecycle.test-support.ts @@ -62,27 +62,25 @@ type FeishuLifecycleTestMocks = { sendCardFeishuMock: AsyncUnknownMock; }; -const feishuLifecycleTestMocks = vi.hoisted( - (): FeishuLifecycleTestMocks => ({ - createEventDispatcherMock: vi.fn(), - monitorWebSocketMock: vi.fn(async () => {}), - monitorWebhookMock: vi.fn(async () => {}), - createFeishuThreadBindingManagerMock: vi.fn(() => ({ stop: vi.fn() })), - createFeishuReplyDispatcherMock: vi.fn(), - resolveBoundConversationMock: vi.fn<(ref?: unknown) => BoundConversation | null>(() => null), - touchBindingMock: vi.fn(), - resolveAgentRouteMock: vi.fn(), - resolveConfiguredBindingRouteMock: vi.fn(), - ensureConfiguredBindingRouteReadyMock: vi.fn(), - dispatchReplyFromConfigMock: vi.fn(), - withReplyDispatcherMock: vi.fn(), - finalizeInboundContextMock: vi.fn((ctx) => ctx), - getMessageFeishuMock: vi.fn(async () => null), - listFeishuThreadMessagesMock: vi.fn(async () => []), - sendMessageFeishuMock: vi.fn(async () => ({ messageId: "om_sent", chatId: "chat_default" })), - sendCardFeishuMock: vi.fn(async () => ({ messageId: "om_card", chatId: "chat_default" })), - }), -); +const feishuLifecycleTestMocks = vi.hoisted((): FeishuLifecycleTestMocks => ({ + createEventDispatcherMock: vi.fn(), + monitorWebSocketMock: vi.fn(async () => {}), + monitorWebhookMock: vi.fn(async () => {}), + createFeishuThreadBindingManagerMock: vi.fn(() => ({ stop: vi.fn() })), + createFeishuReplyDispatcherMock: vi.fn(), + resolveBoundConversationMock: vi.fn<(ref?: unknown) => BoundConversation | null>(() => null), + touchBindingMock: vi.fn(), + resolveAgentRouteMock: vi.fn(), + resolveConfiguredBindingRouteMock: vi.fn(), + ensureConfiguredBindingRouteReadyMock: vi.fn(), + dispatchReplyFromConfigMock: vi.fn(), + withReplyDispatcherMock: vi.fn(), + finalizeInboundContextMock: vi.fn((ctx) => ctx), + getMessageFeishuMock: vi.fn(async () => null), + listFeishuThreadMessagesMock: vi.fn(async () => []), + sendMessageFeishuMock: vi.fn(async () => ({ messageId: "om_sent", chatId: "chat_default" })), + sendCardFeishuMock: vi.fn(async () => ({ messageId: "om_card", chatId: "chat_default" })), +})); export function getFeishuLifecycleTestMocks(): FeishuLifecycleTestMocks { return feishuLifecycleTestMocks; diff --git a/extensions/matrix/src/env-vars.ts b/extensions/matrix/src/env-vars.ts index 0639d97776515..bb7552e96cbf2 100644 --- a/extensions/matrix/src/env-vars.ts +++ b/extensions/matrix/src/env-vars.ts @@ -44,7 +44,7 @@ export function getMatrixScopedEnvVarNames(accountId: string): { function decodeMatrixEnvAccountToken(token: string): string | undefined { let decoded = ""; - for (let index = 0; index < token.length; ) { + for (let index = 0; index < token.length;) { const hexEscape = /^_X([0-9A-F]+)_/.exec(token.slice(index)); if (hexEscape) { const hex = hexEscape[1]; diff --git a/extensions/memory-core/src/short-term-promotion.ts b/extensions/memory-core/src/short-term-promotion.ts index f4e0091d2e689..541c0621bc3b7 100644 --- a/extensions/memory-core/src/short-term-promotion.ts +++ b/extensions/memory-core/src/short-term-promotion.ts @@ -1697,7 +1697,7 @@ function extractTargetHeadingBodySnippet( return null; } const normalizedBody = normalizeSnippet(bodySnippet); - for (let separatorIndex = targetSnippet.indexOf(": "); separatorIndex > 0; ) { + for (let separatorIndex = targetSnippet.indexOf(": "); separatorIndex > 0;) { const targetBody = normalizeSnippet(targetSnippet.slice(separatorIndex + 2)); if (targetBody && normalizedBody.startsWith(targetBody)) { return targetBody; diff --git a/extensions/open-prose/skills/prose/SKILL.md b/extensions/open-prose/skills/prose/SKILL.md index c6c2ed06d0979..462905b592f95 100644 --- a/extensions/open-prose/skills/prose/SKILL.md +++ b/extensions/open-prose/skills/prose/SKILL.md @@ -52,15 +52,16 @@ There is only ONE skill: `open-prose`. There are NO separate skills like `prose- 3. Run with: `prose run examples/28-gas-town.prose` **Common examples by keyword:** -| Keyword | File | -|---------|------| -| hello, hello world | `examples/01-hello-world.prose` | -| gas town, gastown | `examples/28-gas-town.prose` | -| captain, chair | `examples/29-captains-chair.prose` | -| forge, browser | `examples/37-the-forge.prose` | -| parallel | `examples/16-parallel-reviews.prose` | -| pipeline | `examples/21-pipeline-operations.prose` | -| error, retry | `examples/22-error-handling.prose` | + +| Keyword | File | +| ------------------ | --------------------------------------- | +| hello, hello world | `examples/01-hello-world.prose` | +| gas town, gastown | `examples/28-gas-town.prose` | +| captain, chair | `examples/29-captains-chair.prose` | +| forge, browser | `examples/37-the-forge.prose` | +| parallel | `examples/16-parallel-reviews.prose` | +| pipeline | `examples/21-pipeline-operations.prose` | +| error, retry | `examples/22-error-handling.prose` | ### Remote Programs diff --git a/extensions/qa-lab/src/runtime-parity.ts b/extensions/qa-lab/src/runtime-parity.ts index 7a519ea87a02f..1b7cb912c212a 100644 --- a/extensions/qa-lab/src/runtime-parity.ts +++ b/extensions/qa-lab/src/runtime-parity.ts @@ -958,13 +958,13 @@ async function loadRuntimeParityMockToolCalls( if (!Array.isArray(payload)) { return null; } - const requests = payload.filter(isMessageRecord).map( - (entry): RuntimeParityMockRequestSnapshot => ({ + const requests = payload + .filter(isMessageRecord) + .map((entry): RuntimeParityMockRequestSnapshot => ({ plannedToolName: readNonEmptyString(entry.plannedToolName), plannedToolArgs: entry.plannedToolArgs ?? null, toolOutput: readNonEmptyString(entry.toolOutput) ?? "", - }), - ); + })); return resolveToolCallOrderFromMockRequests(requests); } catch { return null; diff --git a/extensions/qqbot/src/engine/gateway/inbound-pipeline.self-echo.test.ts b/extensions/qqbot/src/engine/gateway/inbound-pipeline.self-echo.test.ts index ac6e8969f49fb..b78ca8ceaecb5 100644 --- a/extensions/qqbot/src/engine/gateway/inbound-pipeline.self-echo.test.ts +++ b/extensions/qqbot/src/engine/gateway/inbound-pipeline.self-echo.test.ts @@ -156,60 +156,58 @@ function makeDeps(overrides: Partial = {}): InboundPipeline })), }, access: { - resolveInboundAccess: vi.fn( - (input): QQBotInboundAccess => ({ - state: { - channelId: "qqbot", - accountId: "qq-main", - conversationKind: input.isGroup ? "group" : "direct", - event: { - kind: "message", - authMode: "inbound", - mayPair: true, - hasOriginSubject: false, - originSubjectMatched: false, - }, - routeFacts: [], - allowlists: { - dm: emptyAllowlist, - pairingStore: emptyAllowlist, - group: emptyAllowlist, - commandOwner: emptyAllowlist, - commandGroup: emptyAllowlist, - }, - }, - ingress: { - admission: "dispatch", - decision: "allow", - decisiveGateId: "activation", - reasonCode: "activation_allowed", - graph: { gates: [] }, - }, - senderAccess: { - allowed: true, - decision: "allow", - reasonCode: input.isGroup ? "group_policy_allowed" : "dm_policy_open", - effectiveAllowFrom: [], - effectiveGroupAllowFrom: [], - providerMissingFallbackApplied: false, - }, - commandAccess: { - requested: true, - authorized: true, - shouldBlockControlCommand: false, - reasonCode: "command_authorized", - }, - routeAccess: { - allowed: true, + resolveInboundAccess: vi.fn((input): QQBotInboundAccess => ({ + state: { + channelId: "qqbot", + accountId: "qq-main", + conversationKind: input.isGroup ? "group" : "direct", + event: { + kind: "message", + authMode: "inbound", + mayPair: true, + hasOriginSubject: false, + originSubjectMatched: false, }, - activationAccess: { - ran: false, - allowed: true, - shouldSkip: false, - reasonCode: "activation_allowed", + routeFacts: [], + allowlists: { + dm: emptyAllowlist, + pairingStore: emptyAllowlist, + group: emptyAllowlist, + commandOwner: emptyAllowlist, + commandGroup: emptyAllowlist, }, - }), - ), + }, + ingress: { + admission: "dispatch", + decision: "allow", + decisiveGateId: "activation", + reasonCode: "activation_allowed", + graph: { gates: [] }, + }, + senderAccess: { + allowed: true, + decision: "allow", + reasonCode: input.isGroup ? "group_policy_allowed" : "dm_policy_open", + effectiveAllowFrom: [], + effectiveGroupAllowFrom: [], + providerMissingFallbackApplied: false, + }, + commandAccess: { + requested: true, + authorized: true, + shouldBlockControlCommand: false, + reasonCode: "command_authorized", + }, + routeAccess: { + allowed: true, + }, + activationAccess: { + ran: false, + allowed: true, + shouldSkip: false, + reasonCode: "activation_allowed", + }, + })), resolveSlashCommandAuthorization: vi.fn(() => true), }, audioConvert: { diff --git a/extensions/qqbot/src/engine/group/history.ts b/extensions/qqbot/src/engine/group/history.ts index e1779fd02a74e..1531b8cc83e52 100644 --- a/extensions/qqbot/src/engine/group/history.ts +++ b/extensions/qqbot/src/engine/group/history.ts @@ -128,15 +128,13 @@ export function toAttachmentSummaries( if (!attachments?.length) { return undefined; } - return attachments.map( - (att, i): AttachmentSummary => ({ - type: inferAttachmentType(att.content_type), - filename: att.filename, - transcript: att.asr_refer_text || undefined, - localPath: localPaths?.[i] || undefined, - url: att.url || undefined, - }), - ); + return attachments.map((att, i): AttachmentSummary => ({ + type: inferAttachmentType(att.content_type), + filename: att.filename, + transcript: att.asr_refer_text || undefined, + localPath: localPaths?.[i] || undefined, + url: att.url || undefined, + })); } /** diff --git a/extensions/synology-chat/src/client.ts b/extensions/synology-chat/src/client.ts index cbb662e48f040..8395457998c4b 100644 --- a/extensions/synology-chat/src/client.ts +++ b/extensions/synology-chat/src/client.ts @@ -49,13 +49,11 @@ const ChatUserSchema = z username: z.string().optional(), nickname: z.string().optional(), }) - .transform( - (user): ChatUser => ({ - user_id: user.user_id, - username: user.username ?? "", - nickname: user.nickname ?? "", - }), - ); + .transform((user): ChatUser => ({ + user_id: user.user_id, + username: user.username ?? "", + nickname: user.nickname ?? "", + })); const ChatUserListResponseSchema = z.object({ success: z.boolean(), diff --git a/extensions/telegram/src/bot-native-command-menu.ts b/extensions/telegram/src/bot-native-command-menu.ts index 52a8267b2731e..18b2f77634240 100644 --- a/extensions/telegram/src/bot-native-command-menu.ts +++ b/extensions/telegram/src/bot-native-command-menu.ts @@ -47,7 +47,7 @@ const cappedTelegramMenuCache = new Map< function countTelegramCommandText(value: string): number { let count = 0; - for (let index = 0; index < value.length; ) { + for (let index = 0; index < value.length;) { const codePoint = value.codePointAt(index); index += codePoint && codePoint > 0xffff ? 2 : 1; count += 1; diff --git a/extensions/voice-call/src/webhook/realtime-handler.test.ts b/extensions/voice-call/src/webhook/realtime-handler.test.ts index 3ff9f6d78b80c..9476beb237150 100644 --- a/extensions/voice-call/src/webhook/realtime-handler.test.ts +++ b/extensions/voice-call/src/webhook/realtime-handler.test.ts @@ -194,21 +194,19 @@ describe("RealtimeCallHandler path routing", () => { }, ); const processEvent = vi.fn(); - const getCallByProviderCallId = vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-outbound", - provider: "twilio", - direction: "outbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ); + const getCallByProviderCallId = vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-outbound", + provider: "twilio", + direction: "outbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })); const handler = makeHandler(undefined, { manager: { processEvent, @@ -270,21 +268,19 @@ describe("RealtimeCallHandler path routing", () => { it("joins Telnyx realtime streams to the token-bound call", async () => { const processEvent = vi.fn(); - const getCall = vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "v3:call-1", - provider: "telnyx", - direction: "inbound", - state: "answered", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: { initialMessage: "hello" }, - }), - ); + const getCall = vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "v3:call-1", + provider: "telnyx", + direction: "inbound", + state: "answered", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: { initialMessage: "hello" }, + })); const createBridge = vi.fn(() => makeBridge()); const handler = makeHandler(undefined, { manager: { @@ -370,21 +366,19 @@ describe("RealtimeCallHandler path routing", () => { it("rejects Telnyx stream starts that do not match the token-bound call", async () => { const processEvent = vi.fn(); - const getCall = vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "v3:call-1", - provider: "telnyx", - direction: "inbound", - state: "answered", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ); + const getCall = vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "v3:call-1", + provider: "telnyx", + direction: "inbound", + state: "answered", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })); const createBridge = vi.fn(() => makeBridge()); const handler = makeHandler(undefined, { manager: { @@ -436,21 +430,19 @@ describe("RealtimeCallHandler path routing", () => { return makeBridge({ triggerGreeting }); }, ); - const getCallByProviderCallId = vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-silent", - provider: "twilio", - direction: "outbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ); + const getCallByProviderCallId = vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-silent", + provider: "twilio", + direction: "outbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })); const handler = makeHandler(undefined, { manager: { getCallByProviderCallId, @@ -488,21 +480,19 @@ describe("RealtimeCallHandler path routing", () => { it("speaks through the active outbound realtime bridge by call id", async () => { const triggerGreeting = vi.fn(); const createBridge = vi.fn(() => makeBridge({ triggerGreeting })); - const getCallByProviderCallId = vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-speak", - provider: "twilio", - direction: "outbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ); + const getCallByProviderCallId = vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-speak", + provider: "twilio", + direction: "outbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })); const handler = makeHandler(undefined, { manager: { getCallByProviderCallId, @@ -555,21 +545,19 @@ describe("RealtimeCallHandler path routing", () => { }); }, ); - const getCallByProviderCallId = vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-complete", - provider: "twilio", - direction: "inbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ); + const getCallByProviderCallId = vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-complete", + provider: "twilio", + direction: "inbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })); const handler = makeHandler(undefined, { manager: { processEvent, @@ -824,21 +812,19 @@ describe("RealtimeCallHandler path routing", () => { return bridge; }, ); - const getCallByProviderCallId = vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-tool", - provider: "twilio", - direction: "inbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ); + const getCallByProviderCallId = vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-tool", + provider: "twilio", + direction: "inbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })); const handler = makeHandler(undefined, { manager: { getCallByProviderCallId, @@ -952,21 +938,19 @@ describe("RealtimeCallHandler path routing", () => { { consultPolicy: "always" }, { manager: { - getCallByProviderCallId: vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-force", - provider: "twilio", - direction: "inbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ), + getCallByProviderCallId: vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-force", + provider: "twilio", + direction: "inbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })), }, realtimeProvider: makeRealtimeProvider(createBridge), }, @@ -1038,21 +1022,19 @@ describe("RealtimeCallHandler path routing", () => { const handler = makeHandler(undefined, { manager: { processEvent, - getCallByProviderCallId: vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-direct-turns", - provider: "twilio", - direction: "inbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ), + getCallByProviderCallId: vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-direct-turns", + provider: "twilio", + direction: "inbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })), }, realtimeProvider: makeRealtimeProvider(createBridge), }); @@ -1114,21 +1096,19 @@ describe("RealtimeCallHandler path routing", () => { ); const handler = makeHandler(undefined, { manager: { - getCallByProviderCallId: vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-settle", - provider: "twilio", - direction: "inbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ), + getCallByProviderCallId: vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-settle", + provider: "twilio", + direction: "inbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })), }, realtimeProvider: makeRealtimeProvider(createBridge), }); @@ -1217,21 +1197,19 @@ describe("RealtimeCallHandler path routing", () => { { consultPolicy: "always" }, { manager: { - getCallByProviderCallId: vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-native", - provider: "twilio", - direction: "inbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ), + getCallByProviderCallId: vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-native", + provider: "twilio", + direction: "inbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })), }, realtimeProvider: makeRealtimeProvider(createBridge), }, @@ -1311,21 +1289,19 @@ describe("RealtimeCallHandler path routing", () => { }, { manager: { - getCallByProviderCallId: vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-fast", - provider: "twilio", - direction: "inbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ), + getCallByProviderCallId: vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-fast", + provider: "twilio", + direction: "inbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })), }, realtimeProvider: makeRealtimeProvider(createBridge), }, @@ -1383,21 +1359,19 @@ describe("RealtimeCallHandler websocket hardening", () => { ); const handler = makeHandler(undefined, { manager: { - getCallByProviderCallId: vi.fn( - (): CallRecord => ({ - callId: "call-1", - providerCallId: "CA-backpressure", - provider: "twilio", - direction: "inbound", - state: "ringing", - from: "+15550001234", - to: "+15550009999", - startedAt: Date.now(), - transcript: [], - processedEventIds: [], - metadata: {}, - }), - ), + getCallByProviderCallId: vi.fn((): CallRecord => ({ + callId: "call-1", + providerCallId: "CA-backpressure", + provider: "twilio", + direction: "inbound", + state: "ringing", + from: "+15550001234", + to: "+15550009999", + startedAt: Date.now(), + transcript: [], + processedEventIds: [], + metadata: {}, + })), }, realtimeProvider: makeRealtimeProvider(createBridge), }); diff --git a/extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts b/extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts index fa35ac85c326b..a7e1e3500a3e8 100644 --- a/extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts +++ b/extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts @@ -102,8 +102,9 @@ async function startWatchdogScenario(params: { } function expectErrorContaining(errorFn: unknown, text: string): void { - const messages = ((errorFn as { mock?: { calls?: unknown[][] } }).mock?.calls ?? []).map((call) => - typeof call[0] === "string" ? call[0] : call[0] instanceof Error ? call[0].message : "", + const messages = ((errorFn as { mock?: { calls?: unknown[][] } }).mock?.calls ?? []).map( + (call) => + typeof call[0] === "string" ? call[0] : call[0] instanceof Error ? call[0].message : "", ); expect(messages.join("\n")).toContain(text); } diff --git a/extensions/xai/realtime-transcription-provider.test.ts b/extensions/xai/realtime-transcription-provider.test.ts index 99f70a2232e90..350aa51f154b7 100644 --- a/extensions/xai/realtime-transcription-provider.test.ts +++ b/extensions/xai/realtime-transcription-provider.test.ts @@ -8,9 +8,9 @@ import { buildXaiRealtimeTranscriptionProvider } from "./realtime-transcription- const { isProviderAuthProfileConfiguredMock, resolveApiKeyForProviderMock } = vi.hoisted(() => ({ isProviderAuthProfileConfiguredMock: vi.fn(() => false), - resolveApiKeyForProviderMock: vi.fn( - async (): Promise<{ apiKey: string | undefined }> => ({ apiKey: undefined }), - ), + resolveApiKeyForProviderMock: vi.fn(async (): Promise<{ apiKey: string | undefined }> => ({ + apiKey: undefined, + })), })); vi.mock("openclaw/plugin-sdk/provider-auth", () => ({ diff --git a/extensions/xai/speech-provider.test.ts b/extensions/xai/speech-provider.test.ts index a00128b87d484..29fcbf9a03f17 100644 --- a/extensions/xai/speech-provider.test.ts +++ b/extensions/xai/speech-provider.test.ts @@ -6,9 +6,9 @@ const { xaiTTSMock, isProviderAuthProfileConfiguredMock, resolveApiKeyForProvide vi.hoisted(() => ({ xaiTTSMock: vi.fn(async () => Buffer.from("audio-bytes")), isProviderAuthProfileConfiguredMock: vi.fn(() => false), - resolveApiKeyForProviderMock: vi.fn( - async (): Promise<{ apiKey: string | undefined }> => ({ apiKey: undefined }), - ), + resolveApiKeyForProviderMock: vi.fn(async (): Promise<{ apiKey: string | undefined }> => ({ + apiKey: undefined, + })), })); vi.mock("./tts.js", () => ({ diff --git a/extensions/zalo/src/test-support/monitor-mocks-test-support.ts b/extensions/zalo/src/test-support/monitor-mocks-test-support.ts index 5fb0aeaaca378..cc6d263730a08 100644 --- a/extensions/zalo/src/test-support/monitor-mocks-test-support.ts +++ b/extensions/zalo/src/test-support/monitor-mocks-test-support.ts @@ -36,21 +36,19 @@ type ZaloLifecycleMocks = { getZaloRuntimeMock: UnknownMock; }; -const lifecycleMocks = vi.hoisted( - (): ZaloLifecycleMocks => ({ - setWebhookMock: vi.fn(async () => ({ ok: true, result: { url: "" } })), - deleteWebhookMock: vi.fn(async () => ({ ok: true, result: { url: "" } })), - getWebhookInfoMock: vi.fn(async () => ({ ok: true, result: { url: "" } })), - getUpdatesMock: vi.fn(() => new Promise(() => {})), - sendChatActionMock: vi.fn(async () => ({ ok: true })), - sendMessageMock: vi.fn(async () => ({ - ok: true, - result: { message_id: "zalo-test-reply-1" }, - })), - sendPhotoMock: vi.fn(async () => ({ ok: true })), - getZaloRuntimeMock: vi.fn(), - }), -); +const lifecycleMocks = vi.hoisted((): ZaloLifecycleMocks => ({ + setWebhookMock: vi.fn(async () => ({ ok: true, result: { url: "" } })), + deleteWebhookMock: vi.fn(async () => ({ ok: true, result: { url: "" } })), + getWebhookInfoMock: vi.fn(async () => ({ ok: true, result: { url: "" } })), + getUpdatesMock: vi.fn(() => new Promise(() => {})), + sendChatActionMock: vi.fn(async () => ({ ok: true })), + sendMessageMock: vi.fn(async () => ({ + ok: true, + result: { message_id: "zalo-test-reply-1" }, + })), + sendPhotoMock: vi.fn(async () => ({ ok: true })), + getZaloRuntimeMock: vi.fn(), +})); const setWebhookMock = lifecycleMocks.setWebhookMock; export const getUpdatesMock = lifecycleMocks.getUpdatesMock; diff --git a/extensions/zalouser/src/zalo-js.test-mocks.ts b/extensions/zalouser/src/zalo-js.test-mocks.ts index e9dbedb935063..394810637a89b 100644 --- a/extensions/zalouser/src/zalo-js.test-mocks.ts +++ b/extensions/zalouser/src/zalo-js.test-mocks.ts @@ -19,42 +19,40 @@ type ZaloJsMocks = { waitForZaloQrLoginMock: Mock; }; -const zaloJsMocks = vi.hoisted( - (): ZaloJsMocks => ({ - checkZaloAuthenticatedMock: vi.fn(async () => false), - getZaloUserInfoMock: vi.fn(async () => null), - listZaloFriendsMock: vi.fn(async () => []), - listZaloFriendsMatchingMock: vi.fn(async () => []), - listZaloGroupMembersMock: vi.fn(async () => []), - listZaloGroupsMock: vi.fn(async () => []), - listZaloGroupsMatchingMock: vi.fn(async () => []), - logoutZaloProfileMock: vi.fn(async () => ({ - cleared: true, - loggedOut: true, - message: "Logged out and cleared local session.", - })), - resolveZaloAllowFromEntriesMock: vi.fn(async ({ entries }: { entries: string[] }) => - entries.map((entry) => ({ input: entry, resolved: true, id: entry, note: undefined })), - ), - resolveZaloGroupContextMock: vi.fn(async (_profile, groupId) => ({ - groupId, - name: undefined, - members: [], - })), - resolveZaloGroupsByEntriesMock: vi.fn(async ({ entries }: { entries: string[] }) => - entries.map((entry) => ({ input: entry, resolved: true, id: entry, note: undefined })), - ), - startZaloListenerMock: vi.fn(async () => ({ stop: vi.fn() })), - startZaloQrLoginMock: vi.fn(async () => ({ - message: "qr pending", - qrDataUrl: undefined, - })), - waitForZaloQrLoginMock: vi.fn(async () => ({ - connected: false, - message: "login pending", - })), - }), -); +const zaloJsMocks = vi.hoisted((): ZaloJsMocks => ({ + checkZaloAuthenticatedMock: vi.fn(async () => false), + getZaloUserInfoMock: vi.fn(async () => null), + listZaloFriendsMock: vi.fn(async () => []), + listZaloFriendsMatchingMock: vi.fn(async () => []), + listZaloGroupMembersMock: vi.fn(async () => []), + listZaloGroupsMock: vi.fn(async () => []), + listZaloGroupsMatchingMock: vi.fn(async () => []), + logoutZaloProfileMock: vi.fn(async () => ({ + cleared: true, + loggedOut: true, + message: "Logged out and cleared local session.", + })), + resolveZaloAllowFromEntriesMock: vi.fn(async ({ entries }: { entries: string[] }) => + entries.map((entry) => ({ input: entry, resolved: true, id: entry, note: undefined })), + ), + resolveZaloGroupContextMock: vi.fn(async (_profile, groupId) => ({ + groupId, + name: undefined, + members: [], + })), + resolveZaloGroupsByEntriesMock: vi.fn(async ({ entries }: { entries: string[] }) => + entries.map((entry) => ({ input: entry, resolved: true, id: entry, note: undefined })), + ), + startZaloListenerMock: vi.fn(async () => ({ stop: vi.fn() })), + startZaloQrLoginMock: vi.fn(async () => ({ + message: "qr pending", + qrDataUrl: undefined, + })), + waitForZaloQrLoginMock: vi.fn(async () => ({ + connected: false, + message: "login pending", + })), +})); export const checkZaloAuthenticatedMock = zaloJsMocks.checkZaloAuthenticatedMock; export const getZaloUserInfoMock = zaloJsMocks.getZaloUserInfoMock; diff --git a/package.json b/package.json index 917ac3c150649..296bd9dd90c8e 100644 --- a/package.json +++ b/package.json @@ -1939,7 +1939,7 @@ "jscpd": "4.2.4", "jsdom": "29.1.1", "lit": "3.3.3", - "oxfmt": "0.52.0", + "oxfmt": "0.65.0", "oxlint": "1.67.0", "oxlint-tsgolint": "0.23.0", "shiki": "4.1.0", diff --git a/packages/agent-core/src/harness/utils/truncate.ts b/packages/agent-core/src/harness/utils/truncate.ts index f92fb63785e94..e81406684787f 100644 --- a/packages/agent-core/src/harness/utils/truncate.ts +++ b/packages/agent-core/src/harness/utils/truncate.ts @@ -324,7 +324,7 @@ function truncateStringToBytesFromEnd(str: string, maxBytes: number): string { let outputBytes = 0; let start = str.length; let needsReplacement = false; - for (let i = str.length; i > 0; ) { + for (let i = str.length; i > 0;) { let characterStart = i - 1; const code = str.charCodeAt(characterStart); let characterBytes: number; diff --git a/packages/memory-host-sdk/src/host/internal.ts b/packages/memory-host-sdk/src/host/internal.ts index 7ad391ed2b64a..6f42475bcb577 100644 --- a/packages/memory-host-sdk/src/host/internal.ts +++ b/packages/memory-host-sdk/src/host/internal.ts @@ -458,7 +458,7 @@ export function chunkMarkdown( const coarse = line.slice(start, start + maxChars); if (estimateStringChars(coarse) > maxChars) { const fineStep = Math.max(1, chunking.tokens); - for (let j = 0; j < coarse.length; ) { + for (let j = 0; j < coarse.length;) { let end = Math.min(j + fineStep, coarse.length); // Avoid splitting inside a UTF-16 surrogate pair (CJK Extension B+). if (end < coarse.length) { diff --git a/packages/speech-core/src/tts.test.ts b/packages/speech-core/src/tts.test.ts index fb286f231d0d8..a7aa5360fc990 100644 --- a/packages/speech-core/src/tts.test.ts +++ b/packages/speech-core/src/tts.test.ts @@ -19,14 +19,12 @@ import { afterEach, describe, expect, it, vi } from "vitest"; type MockSpeechSynthesisResult = Awaited>; const synthesizeMock = vi.hoisted(() => - vi.fn( - async (request: SpeechSynthesisRequest): Promise => ({ - audioBuffer: Buffer.from("voice"), - fileExtension: ".ogg", - outputFormat: "ogg", - voiceCompatible: request.target === "voice-note", - }), - ), + vi.fn(async (request: SpeechSynthesisRequest): Promise => ({ + audioBuffer: Buffer.from("voice"), + fileExtension: ".ogg", + outputFormat: "ogg", + voiceCompatible: request.target === "voice-note", + })), ); const prepareSynthesisMock = vi.hoisted(() => vi.fn(async (_ctx: SpeechProviderPrepareSynthesisContext) => undefined), diff --git a/packages/terminal-core/src/table.ts b/packages/terminal-core/src/table.ts index c6c22e09de4a2..1f1e9b503d6cc 100644 --- a/packages/terminal-core/src/table.ts +++ b/packages/terminal-core/src/table.ts @@ -82,7 +82,7 @@ function wrapLine(text: string, width: number): string[] { type Token = { kind: "ansi" | "char"; value: string }; const tokens: Token[] = []; - for (let i = 0; i < text.length; ) { + for (let i = 0; i < text.length;) { if (text[i] === ESC) { // SGR: ESC [ ... m if (text[i + 1] === "[") { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 274643e64493a..0d01fea7283b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -274,8 +274,8 @@ importers: specifier: 3.3.3 version: 3.3.3 oxfmt: - specifier: 0.52.0 - version: 0.52.0 + specifier: 0.65.0 + version: 0.65.0 oxlint: specifier: 1.67.0 version: 1.67.0(oxlint-tsgolint@0.23.0) @@ -3220,124 +3220,124 @@ packages: '@oxc-project/types@0.133.0': resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} - '@oxfmt/binding-android-arm-eabi@0.52.0': - resolution: {integrity: sha512-17EMSJnQ9g+upVHrAUYDMfH5lvRKQ9Nvg8WtEoH72oDr1VpWz+7/o3tD97U1EToen2YAQ/68JmtDYkQUi20dfQ==} + '@oxfmt/binding-android-arm-eabi@0.65.0': + resolution: {integrity: sha512-M10Gs1SSpTNI6ahGx3M/OlIdUF4hkaP6OgUb+MS79t/Pgflk3r1nW5gPFqsZGUAXg0H1AfANT9AvLdBSTIhZKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.52.0': - resolution: {integrity: sha512-A2G1IdwGEW2lLJkIxcvuirRH1CzSl/e0NX11zTlW1gvxJThfwbI/BEoaKrTNpm7M2FchvIf6guvIQU7d5iz+OQ==} + '@oxfmt/binding-android-arm64@0.65.0': + resolution: {integrity: sha512-6DXH5sftNlaHpWJG50hFMF+Qxtq5D2TmahvcDPxWNcGIf8qrC9Y0YgHYcYZ2hlWzaccKXh/f3GcssH8vtkl4JA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.52.0': - resolution: {integrity: sha512-f9+bLvOYxy7NttCLFTvQ7afmqDOWY4wIP9xdvfj5trQ1qj6f2UFAGwZESlfsMjvJNTyRpXfIlOanCI9FOvoeQA==} + '@oxfmt/binding-darwin-arm64@0.65.0': + resolution: {integrity: sha512-K9m7lr53pcOLETNsC88sWes/GWHUGjZyHx95UhYcSXy0r30haLdeXlSufSenEAtoLaW753WN8/l4M7GYcRt6cg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.52.0': - resolution: {integrity: sha512-YSTB9sJ5nnQd/Q0ddHkgof0ZCHPAnWZT1IW2SJ8omz7CP7KluJhO1fNHrpqdxCtpztJwSs4hY1uAee35wKxxaw==} + '@oxfmt/binding-darwin-x64@0.65.0': + resolution: {integrity: sha512-sTNwIx1gre3MyiHOPLu7IGW4UyMScYL4DTmJT01p4vzB0En+OJUQz6KuH8t0PpsClRSaMuY3b0QmtoPItfO8Lg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.52.0': - resolution: {integrity: sha512-NIrRNTTPCs4UbmVs0bxLSCDlLCtIRMJIXklNKaXa5Oj2/K1UIMBvgE8+uPVo01Io3N9HF0+GAX+aAHjUgZS7vA==} + '@oxfmt/binding-freebsd-x64@0.65.0': + resolution: {integrity: sha512-lYZMVIiIpnjGu5hJb2jxA8NYQ/e0OTGuaiAf4dqlGPNnPmUTu23FZRMltmjro/KkQm1uE4NT4n5yJ2zWmKcpfA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.52.0': - resolution: {integrity: sha512-JXUCde8mn3GpgQouz2PXUokgy/uT1QrRJBL2s983VWcSQp62wTFYiNXgTKdeo1Jgbr0IgUnKKvzIk/YBlj/nVQ==} + '@oxfmt/binding-linux-arm-gnueabihf@0.65.0': + resolution: {integrity: sha512-gIdXFAt/bURnjxuoedDEWdZ0PEWEmdDcm8qdpoFYYvW3QMk/5D4vUaH4mlMeRpeTdST4izUgHVO6RawQ4QulJw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.52.0': - resolution: {integrity: sha512-psbUXaRZ+V8DaXz10Qf7LSHtdtdKAmC8fxXgeU608jjzrmWK4quamZMOpl6sf+dikoFHA85uE93Q0BqxrCdQrQ==} + '@oxfmt/binding-linux-arm-musleabihf@0.65.0': + resolution: {integrity: sha512-jJVyADto7gA2AaX5qAjAexrxx9PJQaKWOe8PICE7yKMbjBRyOHcmj9TtVJ+MZYDUQ3hodU0AcoTj0jFQ1W4C6Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.52.0': - resolution: {integrity: sha512-Jw7MgWUU9lcLCcy82updISP3EthTlfvAwR6gWNxPzqly7+fLvOi2gHQE9xXQjpqaVLm/8P+gOzlv9ODuoVlaaw==} + '@oxfmt/binding-linux-arm64-gnu@0.65.0': + resolution: {integrity: sha512-p3RFkB+u7u+8up99b/NEcI1hdpLDiGgJYNwDorB60n7eH+eKposAKuMBxx+NqB3b+sJP4CZmYDh9G7X62tUsKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.52.0': - resolution: {integrity: sha512-wZg6bLjDvh2KibyI3QFUYo8GTXneIFsd0JvehtvJiUmQ8WRPERgxd/VM4ctWb86U5FT1FkqgS8/wZKVB+AZScg==} + '@oxfmt/binding-linux-arm64-musl@0.65.0': + resolution: {integrity: sha512-5Prb0uFzJHr+OUD/qS/TmU526wD+PaHDsm3KoRiUXbMIDpTSErjeQYkK3OQeshAvD/PuLa9WGEi9WPajjdOZJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.52.0': - resolution: {integrity: sha512-IngE8uxhNvxcMrLjZNDo9xNLY7rEK33AKnaMd2B46he1e/mz2CfcW6If/U1wUjdRZddm1QzQaciqZkuMkdh1FA==} + '@oxfmt/binding-linux-ppc64-gnu@0.65.0': + resolution: {integrity: sha512-S8svxTp81obnF3admN9yd+u2rOYXtyzThLGBTg1PY6TPtGcC09BaaXLQD+TBSMa7yvqhCDZ8DFri+S/yG60qCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.52.0': - resolution: {integrity: sha512-H3+DdFMv/efN3Efmhsv18jDrpiWWqKG7wsfAlQBqAt6z/E2Bx+TwEj2Nowe51CPOWB8/mFBC2dAMSgVFLvvowA==} + '@oxfmt/binding-linux-riscv64-gnu@0.65.0': + resolution: {integrity: sha512-WtXBr75G/h2qOHy8SiGtC1R6aS3jt4mE52v1D8AtwMXIgoOmSNP9lKvbSaTRoL0e5wsMPoi6T72QWDYPu+S+nA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.52.0': - resolution: {integrity: sha512-zji+1kb7lJKohSDjzC1IsS+K/cKRs1hdVf0ZH0VbdbiakmtLvN9twBoXo/k8VdjFax7kfo+DyPxS7vv52br1aw==} + '@oxfmt/binding-linux-riscv64-musl@0.65.0': + resolution: {integrity: sha512-YwSLVvpaz4o/nv/miiPEBJz+eJ+VmbgNIrao6RccK9ce+L5EA8wP+ZD0uFeq6wKOza6zoWv/dR0sj6lip6R3EA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.52.0': - resolution: {integrity: sha512-hcLBYedpCy7ToUvvBidWk7+11Yhg1oAZ4+6hKPic/mQI6NaqXJSXMps5nFlwUuX2ewhtLZZDPg63TI042qGKBg==} + '@oxfmt/binding-linux-s390x-gnu@0.65.0': + resolution: {integrity: sha512-XQTPqgvyrgkKcFq+Tp2eK6JS7sqqJ+nRmy2Fav4j3I+i4dJoPJm7YwEdoeSDX9xkqj9jZ/lWfF3bXUWztIrn6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.52.0': - resolution: {integrity: sha512-IDO2loXK2OtTOhSPchU9MW25mWL2QCDGdJbjN8MXKZVS80qXe5gMTwQWu/gMJ3juoBHbkuUZNB2N1LHzNT7DoA==} + '@oxfmt/binding-linux-x64-gnu@0.65.0': + resolution: {integrity: sha512-cjZlx6S/VkeCNWCbwZriTnLnZeTcV3DEyeRGSw/2wwLP9viq+C0bJ4bC1k/ZLkFxDcB1lUgSasPkYGP1bdraOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.52.0': - resolution: {integrity: sha512-mAV2Hjn0SatJ+KoAzKUC3eJhdJ8wv+3m1KyuS0dTsbF0c5weq+QrCt/DRZZM+uj/XiKzCDEUKYsBF30e2qkcyw==} + '@oxfmt/binding-linux-x64-musl@0.65.0': + resolution: {integrity: sha512-2azCjxdLtK4zCcIOU1dlXlU0xxfbPi6EjwWx7Ac7teWPidIIDOcIhudup83xNCKYhtqeVd/gaVDOxbUq4syXWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.52.0': - resolution: {integrity: sha512-vd4npaUIwChxp7XzkqmepBWTT9YMcSe/NBApVGPC30/lLyOVaV3dvma1SKo03t8O73BPRAG7EyJzGlN5cJM5hQ==} + '@oxfmt/binding-openharmony-arm64@0.65.0': + resolution: {integrity: sha512-KXQ7xi1e/voP0IQaw6fG6XY4Z5+Llf1XmRSZS1t7pVFCecFJ0iXaboKmVwjFtp5MLlT5iWQrJ2U1C3GJdZ2u+Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.52.0': - resolution: {integrity: sha512-k2sz6gWQdMfh5HPpIS+Bw/0UEV/kaK2xuqJRrWL233sEHx9WLlsmvlPFM4HUNThkYbSN0U0vPW7LVKZWDS8hPQ==} + '@oxfmt/binding-win32-arm64-msvc@0.65.0': + resolution: {integrity: sha512-2FbbjG5jEqLSLKVJwBap84uJfpn5Y5A53KEO0aUNr+zeiRB9nyPUIFMcSbZVMFLitfBytFWRNngozXYjb6Rsbw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.52.0': - resolution: {integrity: sha512-rhke69GTcArodLHpjMTfNnvjTEBryDeZcUCKK/VjXDMtfTULl6QRh0ymX5/hbCUv2WjYm9h/QbW++q2vE15gWQ==} + '@oxfmt/binding-win32-ia32-msvc@0.65.0': + resolution: {integrity: sha512-LJ+ZacAPSjegDOnSLyA1TMWAhdDrsK4el3REdr1oL2UtVBCMhO2II/Sb3cEW6mF2MfLhl8hDNCSvc7KSbgk3LQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.52.0': - resolution: {integrity: sha512-q5xL7oeXkZdEtNZWBdvehJcmt+GRu9l2bK40yJs1jJXlqq+r0Hygb1rTjq+FM2o/2xyt4cufH6KRplHp3Jjsvw==} + '@oxfmt/binding-win32-x64-msvc@0.65.0': + resolution: {integrity: sha512-higu9cWEO6XXFzATD1jf0mCK34rNfN2H9JrJie7QB1IhleVpTh0QlLH9Ip2C1H/Nd5n0v5pvRtC+5R0uE4HpVg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -4268,40 +4268,47 @@ packages: resolution: {integrity: sha512-9/tnj1fXeXIONgr+5FGwr3bkqd4jaORdr3X9/k++rzHW+UIzvgIeXrJKv43403gtuKp0BoxdzsFxe2qsAQhhkw==} cpu: [arm64] os: [darwin] + deprecated: This package has been replaced by @agentclientprotocol/codex-acp. Please migrate to continue receiving updates. hasBin: true '@zed-industries/codex-acp-darwin-x64@0.15.0': resolution: {integrity: sha512-2cmflnVYM5yzvNu4ldff6OsfLzQThFToPszCT3t7jytWuG28V+W1cUEGsvFJGNkGC1Wo29Z4w5LZ3wyfOkvPxg==} cpu: [x64] os: [darwin] + deprecated: This package has been replaced by @agentclientprotocol/codex-acp. Please migrate to continue receiving updates. hasBin: true '@zed-industries/codex-acp-linux-arm64@0.15.0': resolution: {integrity: sha512-ioCXCiZMd4v7Eqyed9Iz4xcPKsZbSH157wOitsWQKxUiX43c1Ti5fykZcrh9cNSLOgiGmI3V2nbYp0aTf66grQ==} cpu: [arm64] os: [linux] + deprecated: This package has been replaced by @agentclientprotocol/codex-acp. Please migrate to continue receiving updates. hasBin: true '@zed-industries/codex-acp-linux-x64@0.15.0': resolution: {integrity: sha512-WtqI8KGX9z7XvdkazumYraoDwpip5lFBRtFXoIwYCSBoDZdOqQsfNQndIfTDttfQ1BdZYKczDnrfbRaiIFU9UA==} cpu: [x64] os: [linux] + deprecated: This package has been replaced by @agentclientprotocol/codex-acp. Please migrate to continue receiving updates. hasBin: true '@zed-industries/codex-acp-win32-arm64@0.15.0': resolution: {integrity: sha512-L+OFIPOzAuxsImlq8E227MZxgujMLMEJSqiR9QjZq8fiIFCKh/HnxmvyXvjWaHbJdb1pZ09WKe3MNwV9ln/+GQ==} cpu: [arm64] os: [win32] + deprecated: This package has been replaced by @agentclientprotocol/codex-acp. Please migrate to continue receiving updates. hasBin: true '@zed-industries/codex-acp-win32-x64@0.15.0': resolution: {integrity: sha512-LDnADpCg1Rzbkyxs4hMaOvRwNa68KLp8CoNVom8ZE/sChSvcDrj/RCoMsZWrARJGWs7EQ9zYeLoeVk5VcVQoPQ==} cpu: [x64] os: [win32] + deprecated: This package has been replaced by @agentclientprotocol/codex-acp. Please migrate to continue receiving updates. hasBin: true '@zed-industries/codex-acp@0.15.0': resolution: {integrity: sha512-eAv7sGBeiYrYkOulF729nrM51szS7WIhBtugRj5wWq6csRKZUhAZfoUZlF8xUWdHPtOIzd/eT6MNG6gMHu6z0w==} + deprecated: This package has been replaced by @agentclientprotocol/codex-acp. Please migrate to continue receiving updates. hasBin: true abort-controller@3.0.0: @@ -4417,6 +4424,7 @@ packages: audio-decode@2.2.3: resolution: {integrity: sha512-Z0lHvMayR/Pad9+O9ddzaBJE0DrhZkQlStrC1RwcAHF3AhQAsdwKHeLGK8fYKyp2DDU6xHxzGb4CLMui12yVrg==} + deprecated: Renamed to @audio/decode — same API; this name remains a thin alias. npm i @audio/decode audio-type@2.4.1: resolution: {integrity: sha512-dK9Z/P83C/rBfTrXXgPD3jZ+aXxx2o/P4rq8+H1JqxbXklitEeJw4CrcwMC5CkON3CX3yy2gaWnIEVYejYh0zQ==} @@ -4738,6 +4746,7 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained. css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} @@ -6098,8 +6107,8 @@ packages: opus-decoder@0.7.11: resolution: {integrity: sha512-+e+Jz3vGQLxRTBHs8YJQPRPc1Tr+/aC6coV/DlZylriA29BdHQAYXhvNRKtjftof17OFng0+P4wsFIqQu3a48A==} - oxfmt@0.52.0: - resolution: {integrity: sha512-nJlYM35F64zTDMecCNhoHNkf+D/eHv7xcjj9XDSj+bFAVtN93m7v8DQMdHd6nDG6Akf/kEYYHmDUBs2Dz27Sug==} + oxfmt@0.65.0: + resolution: {integrity: sha512-SgS5VgnP42T0zl3zWD+xoH8FCqg1SAFnSRoOT/qeoa6gxcYIqrDMOmcXIg/EWSN92Du4ogB4riuKhKd6Y4CGhw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -8847,61 +8856,61 @@ snapshots: '@oxc-project/types@0.133.0': {} - '@oxfmt/binding-android-arm-eabi@0.52.0': + '@oxfmt/binding-android-arm-eabi@0.65.0': optional: true - '@oxfmt/binding-android-arm64@0.52.0': + '@oxfmt/binding-android-arm64@0.65.0': optional: true - '@oxfmt/binding-darwin-arm64@0.52.0': + '@oxfmt/binding-darwin-arm64@0.65.0': optional: true - '@oxfmt/binding-darwin-x64@0.52.0': + '@oxfmt/binding-darwin-x64@0.65.0': optional: true - '@oxfmt/binding-freebsd-x64@0.52.0': + '@oxfmt/binding-freebsd-x64@0.65.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.52.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.65.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.52.0': + '@oxfmt/binding-linux-arm-musleabihf@0.65.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.52.0': + '@oxfmt/binding-linux-arm64-gnu@0.65.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.52.0': + '@oxfmt/binding-linux-arm64-musl@0.65.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.52.0': + '@oxfmt/binding-linux-ppc64-gnu@0.65.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.52.0': + '@oxfmt/binding-linux-riscv64-gnu@0.65.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.52.0': + '@oxfmt/binding-linux-riscv64-musl@0.65.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.52.0': + '@oxfmt/binding-linux-s390x-gnu@0.65.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.52.0': + '@oxfmt/binding-linux-x64-gnu@0.65.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.52.0': + '@oxfmt/binding-linux-x64-musl@0.65.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.52.0': + '@oxfmt/binding-openharmony-arm64@0.65.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.52.0': + '@oxfmt/binding-win32-arm64-msvc@0.65.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.52.0': + '@oxfmt/binding-win32-ia32-msvc@0.65.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.52.0': + '@oxfmt/binding-win32-x64-msvc@0.65.0': optional: true '@oxlint-tsgolint/darwin-arm64@0.23.0': @@ -11918,29 +11927,29 @@ snapshots: dependencies: '@wasm-audio-decoders/common': 9.0.7 - oxfmt@0.52.0: + oxfmt@0.65.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.52.0 - '@oxfmt/binding-android-arm64': 0.52.0 - '@oxfmt/binding-darwin-arm64': 0.52.0 - '@oxfmt/binding-darwin-x64': 0.52.0 - '@oxfmt/binding-freebsd-x64': 0.52.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.52.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.52.0 - '@oxfmt/binding-linux-arm64-gnu': 0.52.0 - '@oxfmt/binding-linux-arm64-musl': 0.52.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.52.0 - '@oxfmt/binding-linux-riscv64-musl': 0.52.0 - '@oxfmt/binding-linux-s390x-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-gnu': 0.52.0 - '@oxfmt/binding-linux-x64-musl': 0.52.0 - '@oxfmt/binding-openharmony-arm64': 0.52.0 - '@oxfmt/binding-win32-arm64-msvc': 0.52.0 - '@oxfmt/binding-win32-ia32-msvc': 0.52.0 - '@oxfmt/binding-win32-x64-msvc': 0.52.0 + '@oxfmt/binding-android-arm-eabi': 0.65.0 + '@oxfmt/binding-android-arm64': 0.65.0 + '@oxfmt/binding-darwin-arm64': 0.65.0 + '@oxfmt/binding-darwin-x64': 0.65.0 + '@oxfmt/binding-freebsd-x64': 0.65.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.65.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.65.0 + '@oxfmt/binding-linux-arm64-gnu': 0.65.0 + '@oxfmt/binding-linux-arm64-musl': 0.65.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.65.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.65.0 + '@oxfmt/binding-linux-riscv64-musl': 0.65.0 + '@oxfmt/binding-linux-s390x-gnu': 0.65.0 + '@oxfmt/binding-linux-x64-gnu': 0.65.0 + '@oxfmt/binding-linux-x64-musl': 0.65.0 + '@oxfmt/binding-openharmony-arm64': 0.65.0 + '@oxfmt/binding-win32-arm64-msvc': 0.65.0 + '@oxfmt/binding-win32-ia32-msvc': 0.65.0 + '@oxfmt/binding-win32-x64-msvc': 0.65.0 oxlint-tsgolint@0.23.0: optionalDependencies: diff --git a/qa/copilot-capabilities.md b/qa/copilot-capabilities.md index 53d4b46312c86..45fb00bdf385d 100644 --- a/qa/copilot-capabilities.md +++ b/qa/copilot-capabilities.md @@ -23,27 +23,27 @@ Sources: `package.json` (on-disk install): 2-32, 58-62; `dist/index.d.ts` (sdk-i Public methods/getters visible in `dist/client.d.ts`: -| Member | Signature | Return shape | What it does | +| Member | Signature | Return shape | What it does | | ------------------------ | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -| `rpc` | `get rpc(): ReturnType` | typed server RPC facade | Low-level server-scoped RPC surface; throws if not connected. | -| `start` | `start(): Promise` | `void` | Starts/spawns the CLI server and connects. | -| `stop` | `stop(): Promise` | cleanup errors array | Graceful shutdown: closes sessions, JSON-RPC connection, then spawned CLI; preserves on-disk session state. | -| `forceStop` | `forceStop(): Promise` | `void` | Force-kills client state/process without graceful cleanup. | -| `createSession` | `createSession(config: SessionConfig): Promise` | `CopilotSession` | Creates a new conversation session; auto-starts when enabled. | -| `resumeSession` | `resumeSession(sessionId: string, config: ResumeSessionConfig): Promise` | `CopilotSession` | Re-attaches to a persisted session; returns `workspacePath` when infinite sessions were enabled. | -| `getState` | `getState(): ConnectionState` | `"disconnected" \| "connecting" \| "connected" \| "error"` | Returns client connection state. | -| `ping` | `ping(message?: string): Promise<{ message: string; timestamp: number; protocolVersion?: number; }>` | echo payload | Connectivity/protocol sanity check. | -| `getStatus` | `getStatus(): Promise` | `{ version: string; protocolVersion: number }` | Returns CLI package version and negotiated protocol version. | -| `getAuthStatus` | `getAuthStatus(): Promise` | `{ isAuthenticated, authType?, host?, login?, statusMessage? }` | Returns current auth mode/status. | -| `listModels` | `listModels(): Promise` | model metadata array | Lists models; caches first successful result unless overridden by `onListModels`. | -| `getLastSessionId` | `getLastSessionId(): Promise` | optional session id | Returns most recently updated session id. | -| `deleteSession` | `deleteSession(sessionId: string): Promise` | `void` | Irreversibly deletes persisted session data from disk. | -| `listSessions` | `listSessions(filter?: SessionListFilter): Promise` | session metadata array | Lists persisted sessions, optionally filtered by cwd/git context. | -| `getSessionMetadata` | `getSessionMetadata(sessionId: string): Promise` | optional metadata | O(1)-style lookup for one session's metadata. | -| `getForegroundSessionId` | `getForegroundSessionId(): Promise` | optional session id | TUI+server-only: returns current foreground session. | -| `setForegroundSessionId` | `setForegroundSessionId(sessionId: string): Promise` | `void` | TUI+server-only: asks the TUI to foreground a session. | -| `on` (typed) | `on(eventType: K, handler: TypedSessionLifecycleHandler): () => void` | unsubscribe fn | Subscribes to one lifecycle event type. | -| `on` (catch-all) | `on(handler: SessionLifecycleHandler): () => void` | unsubscribe fn | Subscribes to all lifecycle events. | +| `rpc` | `get rpc(): ReturnType` | typed server RPC facade | Low-level server-scoped RPC surface; throws if not connected. | +| `start` | `start(): Promise` | `void` | Starts/spawns the CLI server and connects. | +| `stop` | `stop(): Promise` | cleanup errors array | Graceful shutdown: closes sessions, JSON-RPC connection, then spawned CLI; preserves on-disk session state. | +| `forceStop` | `forceStop(): Promise` | `void` | Force-kills client state/process without graceful cleanup. | +| `createSession` | `createSession(config: SessionConfig): Promise` | `CopilotSession` | Creates a new conversation session; auto-starts when enabled. | +| `resumeSession` | `resumeSession(sessionId: string, config: ResumeSessionConfig): Promise` | `CopilotSession` | Re-attaches to a persisted session; returns `workspacePath` when infinite sessions were enabled. | +| `getState` | `getState(): ConnectionState` | `"disconnected" \| "connecting" \| "connected" \| "error"` | Returns client connection state. | +| `ping` | `ping(message?: string): Promise<{ message: string; timestamp: number; protocolVersion?: number; }>` | echo payload | Connectivity/protocol sanity check. | +| `getStatus` | `getStatus(): Promise` | `{ version: string; protocolVersion: number }` | Returns CLI package version and negotiated protocol version. | +| `getAuthStatus` | `getAuthStatus(): Promise` | `{ isAuthenticated, authType?, host?, login?, statusMessage? }` | Returns current auth mode/status. | +| `listModels` | `listModels(): Promise` | model metadata array | Lists models; caches first successful result unless overridden by `onListModels`. | +| `getLastSessionId` | `getLastSessionId(): Promise` | optional session id | Returns most recently updated session id. | +| `deleteSession` | `deleteSession(sessionId: string): Promise` | `void` | Irreversibly deletes persisted session data from disk. | +| `listSessions` | `listSessions(filter?: SessionListFilter): Promise` | session metadata array | Lists persisted sessions, optionally filtered by cwd/git context. | +| `getSessionMetadata` | `getSessionMetadata(sessionId: string): Promise` | optional metadata | O(1)-style lookup for one session's metadata. | +| `getForegroundSessionId` | `getForegroundSessionId(): Promise` | optional session id | TUI+server-only: returns current foreground session. | +| `setForegroundSessionId` | `setForegroundSessionId(sessionId: string): Promise` | `void` | TUI+server-only: asks the TUI to foreground a session. | +| `on` (typed) | `on(eventType: K, handler: TypedSessionLifecycleHandler): () => void` | unsubscribe fn | Subscribes to one lifecycle event type. | +| `on` (catch-all) | `on(handler: SessionLifecycleHandler): () => void` | unsubscribe fn | Subscribes to all lifecycle events. | Lifecycle event types for `client.on(...)`: `session.created`, `session.deleted`, `session.updated`, `session.foreground`, `session.background`. @@ -53,23 +53,23 @@ Sources: `dist/client.d.ts` (sdk-inventory.txt:1081-1518), especially 1112-1477; Public properties/getters/methods visible in `dist/session.d.ts`: -| Member | Signature | Return shape | Notes | +| Member | Signature | Return shape | Notes | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| `rpc` | `get rpc(): ReturnType` | typed session RPC facade | Low-level session RPC surface. | -| `workspacePath` | `get workspacePath(): string | undefined` | optional path | Present only when infinite sessions are enabled; workspace contains `checkpoints/`, `plan.md`, `files/`. | -| `capabilities` | `get capabilities(): SessionCapabilities` | `{ ui?: { elicitation?: boolean } }` | Host capability snapshot; auto-updated on capability change events. | -| `ui` | `get ui(): SessionUiApi` | convenience UI API | Exposes `elicitation`, `confirm`, `select`, `input`; requires `capabilities.ui?.elicitation`. | -| `send` | `send(options: MessageOptions): Promise` | message id | Queues a user prompt and returns immediately. | -| `sendAndWait` | `sendAndWait(options: MessageOptions, timeout?: number): Promise` | final assistant message or `undefined` | Waits for `session.idle`; timeout defaults to 60000ms and does **not** abort in-flight work. | -| `on` (typed) | `on(eventType: K, handler: TypedSessionEventHandler): () => void` | unsubscribe fn | Subscribes to one event type. | -| `on` (catch-all) | `on(handler: SessionEventHandler): () => void` | unsubscribe fn | Subscribes to all session events. | -| `getMessages` | `getMessages(): Promise` | complete event history | Returns the full persisted conversation/event stream. | -| `disconnect` | `disconnect(): Promise` | `void` | Releases in-memory resources but preserves on-disk session state for resume. | -| `destroy` | `destroy(): Promise` | `void` | Deprecated alias for `disconnect()`. | -| `[Symbol.asyncDispose]` | `[Symbol.asyncDispose](): Promise` | `void` | Enables `await using`. | -| `abort` | `abort(): Promise` | `void` | Cancels the currently processing message without invalidating the session. | -| `setModel` | `setModel(model: string, options?: { reasoningEffort?: ReasoningEffort; modelCapabilities?: ModelCapabilitiesOverride; }): Promise` | `void` | Switches model for future turns while preserving history. | -| `log` | `log(message: string, options?: { level?: "info" \| "warning" \| "error"; ephemeral?: boolean; }): Promise` | `void` | Writes timeline messages; docs explicitly say to use this instead of `console.log()`. | +| `rpc` | `get rpc(): ReturnType` | typed session RPC facade | Low-level session RPC surface. | +| `workspacePath` | `get workspacePath(): string | undefined` | optional path | Present only when infinite sessions are enabled; workspace contains `checkpoints/`, `plan.md`, `files/`. | +| `capabilities` | `get capabilities(): SessionCapabilities` | `{ ui?: { elicitation?: boolean } }` | Host capability snapshot; auto-updated on capability change events. | +| `ui` | `get ui(): SessionUiApi` | convenience UI API | Exposes `elicitation`, `confirm`, `select`, `input`; requires `capabilities.ui?.elicitation`. | +| `send` | `send(options: MessageOptions): Promise` | message id | Queues a user prompt and returns immediately. | +| `sendAndWait` | `sendAndWait(options: MessageOptions, timeout?: number): Promise` | final assistant message or `undefined` | Waits for `session.idle`; timeout defaults to 60000ms and does **not** abort in-flight work. | +| `on` (typed) | `on(eventType: K, handler: TypedSessionEventHandler): () => void` | unsubscribe fn | Subscribes to one event type. | +| `on` (catch-all) | `on(handler: SessionEventHandler): () => void` | unsubscribe fn | Subscribes to all session events. | +| `getMessages` | `getMessages(): Promise` | complete event history | Returns the full persisted conversation/event stream. | +| `disconnect` | `disconnect(): Promise` | `void` | Releases in-memory resources but preserves on-disk session state for resume. | +| `destroy` | `destroy(): Promise` | `void` | Deprecated alias for `disconnect()`. | +| `[Symbol.asyncDispose]` | `[Symbol.asyncDispose](): Promise` | `void` | Enables `await using`. | +| `abort` | `abort(): Promise` | `void` | Cancels the currently processing message without invalidating the session. | +| `setModel` | `setModel(model: string, options?: { reasoningEffort?: ReasoningEffort; modelCapabilities?: ModelCapabilitiesOverride; }): Promise` | `void` | Switches model for future turns while preserving history. | +| `log` | `log(message: string, options?: { level?: "info" \| "warning" \| "error"; ephemeral?: boolean; }): Promise` | `void` | Writes timeline messages; docs explicitly say to use this instead of `console.log()`. | `MessageOptions` supports `prompt`, `attachments`, optional `mode` (`enqueue` or `immediate`), and per-turn `requestHeaders`. diff --git a/scripts/dev/discord-acp-plain-language-smoke.ts b/scripts/dev/discord-acp-plain-language-smoke.ts index 871a583bb8eaa..faf168940144e 100644 --- a/scripts/dev/discord-acp-plain-language-smoke.ts +++ b/scripts/dev/discord-acp-plain-language-smoke.ts @@ -1037,14 +1037,12 @@ async function main(): Promise { writeStdoutLine(usage()); return 0; } - const result = await run().catch( - (err: unknown): FailureResult => ({ - ok: false, - stage: "unexpected", - smokeId: "n/a", - error: safeErrorMessage(err), - }), - ); + const result = await run().catch((err: unknown): FailureResult => ({ + ok: false, + stage: "unexpected", + smokeId: "n/a", + error: safeErrorMessage(err), + })); printOutput({ json: hasFlag("--json"), payload: result, diff --git a/scripts/pre-commit/pnpm-audit-prod.mjs b/scripts/pre-commit/pnpm-audit-prod.mjs index 850e8c1a9a207..c20c4652b5665 100644 --- a/scripts/pre-commit/pnpm-audit-prod.mjs +++ b/scripts/pre-commit/pnpm-audit-prod.mjs @@ -360,7 +360,7 @@ function parsePnpmLockfileSections(lockfileText) { let hasImportersSection = false; let hasSnapshotsSection = false; - for (let index = 0; index < lines.length; ) { + for (let index = 0; index < lines.length;) { const line = lines[index]; const trimmed = line.trim(); const indentation = countIndentation(line); diff --git a/scripts/test-docker-all.mjs b/scripts/test-docker-all.mjs index c582a921b2940..69765796af390 100644 --- a/scripts/test-docker-all.mjs +++ b/scripts/test-docker-all.mjs @@ -1042,7 +1042,7 @@ async function runLanePool(poolLanes, baseEnv, logDir, parallelism, options) { while (pending.length > 0 || running.size > 0) { let started = false; if (!options.failFast || failures.length === 0) { - for (let index = 0; index < pending.length; ) { + for (let index = 0; index < pending.length;) { const candidate = pending[index]; if (!canStartLane(candidate)) { index += 1; diff --git a/src/agents/bash-tools.exec-host-gateway.test.ts b/src/agents/bash-tools.exec-host-gateway.test.ts index e2f16d400a1ee..a0c406a377be7 100644 --- a/src/agents/bash-tools.exec-host-gateway.test.ts +++ b/src/agents/bash-tools.exec-host-gateway.test.ts @@ -54,15 +54,13 @@ const createExecApprovalDecisionStateMock = vi.hoisted(() => ), ); const evaluateShellAllowlistMock = vi.hoisted(() => - vi.fn( - (): MockAllowlistResult => ({ - allowlistMatches: [], - analysisOk: true, - allowlistSatisfied: true, - segments: [{ resolution: null, argv: ["echo", "ok"] }], - segmentAllowlistEntries: [{ pattern: "/usr/bin/echo", source: "allow-always" }], - }), - ), + vi.fn((): MockAllowlistResult => ({ + allowlistMatches: [], + analysisOk: true, + allowlistSatisfied: true, + segments: [{ resolution: null, argv: ["echo", "ok"] }], + segmentAllowlistEntries: [{ pattern: "/usr/bin/echo", source: "allow-always" }], + })), ); const analyzeShellCommandMock = vi.hoisted(() => vi.fn((params: { command: string }) => ({ diff --git a/src/agents/bash-tools.exec-host-node.test.ts b/src/agents/bash-tools.exec-host-node.test.ts index b042f4e916e68..4aeb88338a77d 100644 --- a/src/agents/bash-tools.exec-host-node.test.ts +++ b/src/agents/bash-tools.exec-host-node.test.ts @@ -80,15 +80,13 @@ const listNodesMock = vi.hoisted(() => vi.fn()); const parsePreparedSystemRunPayloadMock = vi.hoisted(() => vi.fn()); const commandRequiresSecurityAuditSuppressionApprovalMock = vi.hoisted(() => vi.fn(() => false)); const evaluateShellAllowlistMock = vi.hoisted(() => - vi.fn( - (_raw?: ShellAllowlistMockParams): MockAllowlistResult => ({ - allowlistMatches: [], - analysisOk: true, - allowlistSatisfied: false, - segments: [{ resolution: null, argv: ["bun", "./script.ts"] }], - segmentAllowlistEntries: [], - }), - ), + vi.fn((_raw?: ShellAllowlistMockParams): MockAllowlistResult => ({ + allowlistMatches: [], + analysisOk: true, + allowlistSatisfied: false, + segments: [{ resolution: null, argv: ["bun", "./script.ts"] }], + segmentAllowlistEntries: [], + })), ); const hasNodeCommandAllowAlwaysMarkerMock = vi.hoisted(() => vi.fn((raw: unknown): boolean => @@ -104,18 +102,16 @@ const resolveAllowAlwaysPatternCoverageMock = vi.hoisted(() => })), ); const resolveExecApprovalsFromFileMock = vi.hoisted(() => - vi.fn( - (): MockExecApprovalsResolved => ({ - allowlist: [], - file: { version: 1, agents: {} }, - agent: { - security: "full", - ask: "off", - askFallback: "deny", - autoAllowSkills: false, - }, - }), - ), + vi.fn((): MockExecApprovalsResolved => ({ + allowlist: [], + file: { version: 1, agents: {} }, + agent: { + security: "full", + ask: "off", + askFallback: "deny", + autoAllowSkills: false, + }, + })), ); const requiresExecApprovalMock = vi.hoisted(() => vi.fn((_raw?: RequiresExecApprovalMockParams) => true), diff --git a/src/agents/command/delivery.test.ts b/src/agents/command/delivery.test.ts index f69a182af74c4..20b5c41e08404 100644 --- a/src/agents/command/delivery.test.ts +++ b/src/agents/command/delivery.test.ts @@ -298,13 +298,11 @@ describe("normalizeAgentCommandReplyPayloads", () => { }); it("normalizes reply-media paths before outbound delivery", async () => { - const normalizerFn = vi.fn( - async (payload: ReplyPayload): Promise => ({ - ...payload, - mediaUrl: "/tmp/agent-workspace/out/photo.png", - mediaUrls: ["/tmp/agent-workspace/out/photo.png"], - }), - ); + const normalizerFn = vi.fn(async (payload: ReplyPayload): Promise => ({ + ...payload, + mediaUrl: "/tmp/agent-workspace/out/photo.png", + mediaUrls: ["/tmp/agent-workspace/out/photo.png"], + })); createReplyMediaPathNormalizerMock.mockReturnValue(normalizerFn); deliverOutboundPayloadsMock.mockResolvedValue([]); diff --git a/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts b/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts index 73fb1bde9bf4b..e2ee292d84023 100644 --- a/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts +++ b/src/agents/embedded-agent-runner/run.overflow-compaction.harness.ts @@ -341,14 +341,12 @@ export function resetRunOverflowCompactionHarnessMocks(): void { mockedCoerceToFailoverError.mockReset(); mockedCoerceToFailoverError.mockReturnValue(null); mockedDescribeFailoverError.mockReset(); - mockedDescribeFailoverError.mockImplementation( - (err: unknown): MockFailoverErrorDescription => ({ - message: formatErrorMessage(err), - reason: undefined, - status: undefined, - code: undefined, - }), - ); + mockedDescribeFailoverError.mockImplementation((err: unknown): MockFailoverErrorDescription => ({ + message: formatErrorMessage(err), + reason: undefined, + status: undefined, + code: undefined, + })); mockedResolveFailoverStatus.mockReset(); mockedResolveFailoverStatus.mockReturnValue(undefined); diff --git a/src/agents/model-compat.test.ts b/src/agents/model-compat.test.ts index 3c3be478fff50..df2ceef223206 100644 --- a/src/agents/model-compat.test.ts +++ b/src/agents/model-compat.test.ts @@ -384,22 +384,29 @@ describe("isModernModelRef", () => { }); it("includes plugin-advertised modern models", () => { - providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation(({ provider, context }) => - provider === "openai" && - ["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini", "gpt-5.4-nano"].includes( - context.modelId, - ) - ? true - : provider === "openai" && - ["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini"].includes( - context.modelId, - ) + providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation( + ({ provider, context }) => + provider === "openai" && + [ + "gpt-5.5", + "gpt-5.5-pro", + "gpt-5.4", + "gpt-5.4-pro", + "gpt-5.4-mini", + "gpt-5.4-nano", + ].includes(context.modelId) ? true - : provider === "opencode" && ["claude-opus-4-6", "gemini-3-pro"].includes(context.modelId) + : provider === "openai" && + ["gpt-5.5", "gpt-5.5-pro", "gpt-5.4", "gpt-5.4-pro", "gpt-5.4-mini"].includes( + context.modelId, + ) ? true - : provider === "opencode-go" + : provider === "opencode" && + ["claude-opus-4-6", "gemini-3-pro"].includes(context.modelId) ? true - : undefined, + : provider === "opencode-go" + ? true + : undefined, ); expect(isModernModelRef({ provider: "openai", id: "gpt-5.5" })).toBe(true); @@ -421,8 +428,9 @@ describe("isModernModelRef", () => { }); it("matches plugin-advertised modern models only for exact provider ids", () => { - providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation(({ provider, context }) => - provider === "z.ai" && context.modelId === "glm-5" ? true : undefined, + providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation( + ({ provider, context }) => + provider === "z.ai" && context.modelId === "glm-5" ? true : undefined, ); expect(isModernModelRef({ provider: "z.ai", id: "glm-5" })).toBe(true); @@ -430,8 +438,9 @@ describe("isModernModelRef", () => { }); it("excludes provider-declined modern models", () => { - providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation(({ provider, context }) => - provider === "opencode" && context.modelId === "minimax-m2.7" ? false : undefined, + providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation( + ({ provider, context }) => + provider === "opencode" && context.modelId === "minimax-m2.7" ? false : undefined, ); expect(isModernModelRef({ provider: "opencode", id: "minimax-m2.7" })).toBe(false); @@ -440,10 +449,12 @@ describe("isModernModelRef", () => { describe("isHighSignalLiveModelRef", () => { it("keeps modern higher-signal Claude families", () => { - providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation(({ provider, context }) => - provider === "anthropic" && ["claude-sonnet-4-6", "claude-opus-4-6"].includes(context.modelId) - ? true - : undefined, + providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation( + ({ provider, context }) => + provider === "anthropic" && + ["claude-sonnet-4-6", "claude-opus-4-6"].includes(context.modelId) + ? true + : undefined, ); expect(isHighSignalLiveModelRef({ provider: "anthropic", id: "claude-sonnet-4-6" })).toBe(true); @@ -624,8 +635,9 @@ describe("isHighSignalLiveModelRef", () => { }); it("keeps DeepSeek V4 models in the default live matrix when the provider marks them modern", () => { - providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation(({ provider, context }) => - provider === "deepseek" && context.modelId.startsWith("deepseek-v4") ? true : undefined, + providerRuntimeMocks.resolveProviderModernModelRef.mockImplementation( + ({ provider, context }) => + provider === "deepseek" && context.modelId.startsWith("deepseek-v4") ? true : undefined, ); expect(isHighSignalLiveModelRef({ provider: "deepseek", id: "deepseek-v4-flash" })).toBe(true); diff --git a/src/agents/sandbox/fs-bridge.test-helpers.ts b/src/agents/sandbox/fs-bridge.test-helpers.ts index 9e62eae4bde54..76bb7e0677c13 100644 --- a/src/agents/sandbox/fs-bridge.test-helpers.ts +++ b/src/agents/sandbox/fs-bridge.test-helpers.ts @@ -17,12 +17,10 @@ type FsBridgeHoisted = { let actualOpenRootFile: OpenRootFileFn | undefined; -const hoisted = vi.hoisted( - (): FsBridgeHoisted => ({ - execDockerRaw: vi.fn(), - openRootFile: vi.fn(), - }), -); +const hoisted = vi.hoisted((): FsBridgeHoisted => ({ + execDockerRaw: vi.fn(), + openRootFile: vi.fn(), +})); vi.mock("./docker.js", () => ({ execDockerRaw: (args: ExecDockerArgs, opts?: Parameters[1]) => diff --git a/src/agents/subagent-announce.test.ts b/src/agents/subagent-announce.test.ts index 2e61c811042e1..43b374d1e732d 100644 --- a/src/agents/subagent-announce.test.ts +++ b/src/agents/subagent-announce.test.ts @@ -7,12 +7,10 @@ import { createSubagentAnnounceDeliveryRuntimeMock } from "./subagent-announce.t type AgentCallRequest = { method?: string; params?: Record }; type AgentCallResponse = { runId?: string; status: string; error?: string }; -const agentSpy = vi.fn( - async (_req: AgentCallRequest): Promise => ({ - runId: "run-main", - status: "ok", - }), -); +const agentSpy = vi.fn(async (_req: AgentCallRequest): Promise => ({ + runId: "run-main", + status: "ok", +})); const sessionsDeleteSpy = vi.fn((_req: AgentCallRequest) => undefined); const callGatewayMock = vi.fn(async (_request: unknown) => ({})); const loadSessionStoreMock = vi.fn((_storePath: string) => ({})); diff --git a/src/auto-reply/reply/commands-acp/shared.ts b/src/auto-reply/reply/commands-acp/shared.ts index dbfcffbd7f399..dd767a7bc2c89 100644 --- a/src/auto-reply/reply/commands-acp/shared.ts +++ b/src/auto-reply/reply/commands-acp/shared.ts @@ -195,7 +195,7 @@ export function parseSpawnInput( let label: string | undefined; let rawAgentId: string | undefined; - for (let i = 0; i < normalizedTokens.length; ) { + for (let i = 0; i < normalizedTokens.length;) { const token = normalizedTokens[i] ?? ""; const modeOption = readOptionValue({ tokens: normalizedTokens, index: i, flag: "--mode" }); @@ -332,7 +332,7 @@ export function parseSteerInput( let sessionToken: string | undefined; const instructionTokens: string[] = []; - for (let i = 0; i < normalizedTokens.length; ) { + for (let i = 0; i < normalizedTokens.length;) { const sessionOption = readOptionValue({ tokens: normalizedTokens, index: i, diff --git a/src/auto-reply/reply/commands-core.send-policy.test.ts b/src/auto-reply/reply/commands-core.send-policy.test.ts index 1ab5805288c70..d5cef8334ed3b 100644 --- a/src/auto-reply/reply/commands-core.send-policy.test.ts +++ b/src/auto-reply/reply/commands-core.send-policy.test.ts @@ -2,8 +2,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { CommandHandler, HandleCommandsParams } from "./commands-types.js"; -const loadCommandHandlersMock = vi.hoisted( - (): ReturnType CommandHandler[]>> => vi.fn<() => CommandHandler[]>(() => []), +const loadCommandHandlersMock = vi.hoisted((): ReturnType CommandHandler[]>> => + vi.fn<() => CommandHandler[]>(() => []), ); vi.mock("./commands-handlers.runtime.js", () => ({ diff --git a/src/auto-reply/reply/current-turn-images.ts b/src/auto-reply/reply/current-turn-images.ts index f73b92b66ae59..bcfa6ccf69aac 100644 --- a/src/auto-reply/reply/current-turn-images.ts +++ b/src/auto-reply/reply/current-turn-images.ts @@ -121,13 +121,11 @@ export async function resolveCurrentTurnImages(params: { cfg: params.cfg, includeRecentHistoryImages: false, }); - const images = resolved.attachments.map( - (attachment): ImageContent => ({ - type: "image", - data: attachment.data, - mimeType: attachment.mediaType, - }), - ); + const images = resolved.attachments.map((attachment): ImageContent => ({ + type: "image", + data: attachment.data, + mimeType: attachment.mediaType, + })); if (images.length < undescribedImageAttachments.length) { logVerbose( `agent-runner: native OpenClaw media resolution produced ${images.length}/${undescribedImageAttachments.length} current image attachment(s); falling back to prompt image refs`, diff --git a/src/channels/message/inbound-reply-dispatch.ts b/src/channels/message/inbound-reply-dispatch.ts index 80fa926db6ad9..4a96c934810ab 100644 --- a/src/channels/message/inbound-reply-dispatch.ts +++ b/src/channels/message/inbound-reply-dispatch.ts @@ -276,9 +276,7 @@ export async function recordChannelMessageReplyDispatch( dispatchReplyWithBufferedBlockDispatcher: params.dispatchReplyWithBufferedBlockDispatcher, delivery: { preparePayload: (payload) => - payload && typeof payload === "object" - ? normalizeOutboundReplyPayload(payload) - : {}, + payload && typeof payload === "object" ? normalizeOutboundReplyPayload(payload) : {}, deliver: async (payload, info) => { if (params.durable) { const durable = await deliverInboundReplyWithMessageSendContext({ diff --git a/src/channels/plugins/setup-wizard-binary.test.ts b/src/channels/plugins/setup-wizard-binary.test.ts index 73a0d1ffbc4c0..92fc30dc1a08d 100644 --- a/src/channels/plugins/setup-wizard-binary.test.ts +++ b/src/channels/plugins/setup-wizard-binary.test.ts @@ -88,20 +88,18 @@ describe("createCliPathTextInput", () => { describe("createDelegatedSetupWizardStatusResolvers", () => { it("forwards optional status resolvers to the loaded wizard", async () => { - const loadWizard = vi.fn( - async (): Promise => ({ - channel: "demo", - status: { - configuredLabel: "configured", - unconfiguredLabel: "needs setup", - resolveConfigured: () => true, - resolveStatusLines: async () => ["line"], - resolveSelectionHint: async () => "hint", - resolveQuickstartScore: async () => 7, - }, - credentials: [], - }), - ); + const loadWizard = vi.fn(async (): Promise => ({ + channel: "demo", + status: { + configuredLabel: "configured", + unconfiguredLabel: "needs setup", + resolveConfigured: () => true, + resolveStatusLines: async () => ["line"], + resolveSelectionHint: async () => "hint", + resolveQuickstartScore: async () => 7, + }, + credentials: [], + })); const status = createDelegatedSetupWizardStatusResolvers(loadWizard); @@ -113,24 +111,22 @@ describe("createDelegatedSetupWizardStatusResolvers", () => { describe("createDelegatedTextInputShouldPrompt", () => { it("forwards shouldPrompt for the requested input key", async () => { - const loadWizard = vi.fn( - async (): Promise => ({ - channel: "demo", - status: { - configuredLabel: "configured", - unconfiguredLabel: "needs setup", - resolveConfigured: () => true, + const loadWizard = vi.fn(async (): Promise => ({ + channel: "demo", + status: { + configuredLabel: "configured", + unconfiguredLabel: "needs setup", + resolveConfigured: () => true, + }, + credentials: [], + textInputs: [ + { + inputKey: "cliPath", + message: "CLI path", + shouldPrompt: async ({ currentValue }) => currentValue !== "imsg", }, - credentials: [], - textInputs: [ - { - inputKey: "cliPath", - message: "CLI path", - shouldPrompt: async ({ currentValue }) => currentValue !== "imsg", - }, - ], - }), - ); + ], + })); const shouldPrompt = createDelegatedTextInputShouldPrompt({ loadWizard, diff --git a/src/channels/plugins/setup-wizard-proxy.test.ts b/src/channels/plugins/setup-wizard-proxy.test.ts index 63272790d848d..7e53b3161ca8e 100644 --- a/src/channels/plugins/setup-wizard-proxy.test.ts +++ b/src/channels/plugins/setup-wizard-proxy.test.ts @@ -18,18 +18,16 @@ import type { ChannelSetupWizard } from "./setup-wizard.js"; describe("createDelegatedResolveConfigured", () => { it("forwards configured resolution to the loaded wizard", async () => { - const loadWizard = vi.fn( - async (): Promise => ({ - channel: "demo", - status: { - configuredLabel: "configured", - unconfiguredLabel: "needs setup", - resolveConfigured: async ({ cfg, accountId }) => - Boolean(cfg.channels?.[accountId ?? "demo"]), - }, - credentials: [], - }), - ); + const loadWizard = vi.fn(async (): Promise => ({ + channel: "demo", + status: { + configuredLabel: "configured", + unconfiguredLabel: "needs setup", + resolveConfigured: async ({ cfg, accountId }) => + Boolean(cfg.channels?.[accountId ?? "demo"]), + }, + credentials: [], + })); const resolveConfigured = createDelegatedResolveConfigured(loadWizard); @@ -42,18 +40,16 @@ describe("createDelegatedResolveConfigured", () => { describe("createDelegatedPrepare", () => { it("forwards prepare when the loaded wizard implements it", async () => { - const loadWizard = vi.fn( - async (): Promise => ({ - channel: "demo", - status: { - configuredLabel: "configured", - unconfiguredLabel: "needs setup", - resolveConfigured: () => true, - }, - credentials: [], - prepare: async ({ cfg }) => ({ cfg: { ...cfg, channels: { demo: { enabled: true } } } }), - }), - ); + const loadWizard = vi.fn(async (): Promise => ({ + channel: "demo", + status: { + configuredLabel: "configured", + unconfiguredLabel: "needs setup", + resolveConfigured: () => true, + }, + credentials: [], + prepare: async ({ cfg }) => ({ cfg: { ...cfg, channels: { demo: { enabled: true } } } }), + })); const prepare = createDelegatedPrepare(loadWizard); @@ -69,25 +65,23 @@ describe("createDelegatedPrepare", () => { describe("createDelegatedFinalize", () => { it("forwards finalize when the loaded wizard implements it", async () => { - const loadWizard = vi.fn( - async (): Promise => ({ - channel: "demo", - status: { - configuredLabel: "configured", - unconfiguredLabel: "needs setup", - resolveConfigured: () => true, - }, - credentials: [], - finalize: async ({ cfg, forceAllowFrom }) => ({ - cfg: { - ...cfg, - channels: { - demo: { forceAllowFrom }, - }, + const loadWizard = vi.fn(async (): Promise => ({ + channel: "demo", + status: { + configuredLabel: "configured", + unconfiguredLabel: "needs setup", + resolveConfigured: () => true, + }, + credentials: [], + finalize: async ({ cfg, forceAllowFrom }) => ({ + cfg: { + ...cfg, + channels: { + demo: { forceAllowFrom }, }, - }), + }, }), - ); + })); const finalize = createDelegatedFinalize(loadWizard); diff --git a/src/cli/daemon-cli/status.test.ts b/src/cli/daemon-cli/status.test.ts index d5404d1796f02..e1a61b585e668 100644 --- a/src/cli/daemon-cli/status.test.ts +++ b/src/cli/daemon-cli/status.test.ts @@ -3,21 +3,19 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { createCliRuntimeCapture } from "../test-runtime-capture.js"; import type { DaemonStatus } from "./status.gather.js"; -const gatherDaemonStatus = vi.fn( - async (_opts?: unknown): Promise => ({ - service: { - label: "LaunchAgent", - loaded: true, - loadedText: "loaded", - notLoadedText: "not loaded", - }, - rpc: { - ok: true, - url: "ws://127.0.0.1:18789", - }, - extraServices: [], - }), -); +const gatherDaemonStatus = vi.fn(async (_opts?: unknown): Promise => ({ + service: { + label: "LaunchAgent", + loaded: true, + loadedText: "loaded", + notLoadedText: "not loaded", + }, + rpc: { + ok: true, + url: "ws://127.0.0.1:18789", + }, + extraServices: [], +})); const printDaemonStatus = vi.fn(); const { runtimeErrors, defaultRuntime, resetRuntimeCapture } = createCliRuntimeCapture(); diff --git a/src/cli/windows-argv.ts b/src/cli/windows-argv.ts index 10f0e227dc2dc..22de646d02f6d 100644 --- a/src/cli/windows-argv.ts +++ b/src/cli/windows-argv.ts @@ -61,7 +61,7 @@ export function normalizeWindowsArgv( const argv0IsExecPath = isExecPath(argv[0]); const next = [...argv]; let removedLauncherPrefix = false; - for (const i = 1; i < next.length; ) { + for (const i = 1; i < next.length;) { if (isExecPath(next[i])) { next.splice(i, 1); removedLauncherPrefix = true; @@ -73,7 +73,7 @@ export function normalizeWindowsArgv( return next; } const cleaned = [...next]; - for (const i = 2; i < cleaned.length; ) { + for (const i = 2; i < cleaned.length;) { const arg = cleaned[i]; if (!arg || arg.startsWith("-")) { break; diff --git a/src/commands/auth-choice.test.ts b/src/commands/auth-choice.test.ts index 0754a3ce685ea..b36f4e7289f3b 100644 --- a/src/commands/auth-choice.test.ts +++ b/src/commands/auth-choice.test.ts @@ -705,21 +705,19 @@ describe("applyAuthChoice", () => { id: "setup-token", label: "Anthropic setup-token", kind: "token", - run: vi.fn( - async (): Promise => ({ - profiles: [ - { - profileId: "anthropic:default", - credential: { - type: "token", - provider: "anthropic", - token: `sk-ant-oat01-${"a".repeat(80)}`, - }, + run: vi.fn(async (): Promise => ({ + profiles: [ + { + profileId: "anthropic:default", + credential: { + type: "token", + provider: "anthropic", + token: `sk-ant-oat01-${"a".repeat(80)}`, }, - ], - defaultModel: "anthropic/claude-sonnet-4-6", - }), - ), + }, + ], + defaultModel: "anthropic/claude-sonnet-4-6", + })), }, }), ]); @@ -1093,21 +1091,19 @@ describe("applyAuthChoice", () => { id: "github", label: "GitHub Copilot", kind: "token", - run: vi.fn( - async (): Promise => ({ - profiles: [ - { - profileId: "github-copilot:github", - credential: { - type: "token", - provider: "github-copilot", - token: "gho_copilot_test", - }, + run: vi.fn(async (): Promise => ({ + profiles: [ + { + profileId: "github-copilot:github", + credential: { + type: "token", + provider: "github-copilot", + token: "gho_copilot_test", }, - ], - defaultModel: "github-copilot/claude-opus-4.7", - }), - ), + }, + ], + defaultModel: "github-copilot/claude-opus-4.7", + })), }, }); const manifestSpy = vi diff --git a/src/commands/doctor/shared/plugin-dependency-cleanup.ts b/src/commands/doctor/shared/plugin-dependency-cleanup.ts index bde8f7702a078..626f766b154d3 100644 --- a/src/commands/doctor/shared/plugin-dependency-cleanup.ts +++ b/src/commands/doctor/shared/plugin-dependency-cleanup.ts @@ -335,22 +335,18 @@ async function collectLegacyPluginDependencyTargetEntries( cwd: process.cwd(), }); const roots = uniqueSorted([resolveStateDir(env), resolveConfigDir(env), packageRoot]); - const stateDirectoryRoots = splitPathList(env.STATE_DIRECTORY).map( - (entry): CleanupTarget => ({ - kind: "legacy", - path: path.join(resolveUserPath(entry, env), "plugin-runtime-deps"), - }), - ); + const stateDirectoryRoots = splitPathList(env.STATE_DIRECTORY).map((entry): CleanupTarget => ({ + kind: "legacy", + path: path.join(resolveUserPath(entry, env), "plugin-runtime-deps"), + })); const targets: CleanupTarget[] = [ ...collectExplicitStageTargets(env), ...stateDirectoryRoots, ...roots.flatMap((root) => [ - ...[...LEGACY_DIRECT_CHILD_NAMES].map( - (name): CleanupTarget => ({ - kind: "legacy", - path: path.join(root, name), - }), - ), + ...[...LEGACY_DIRECT_CHILD_NAMES].map((name): CleanupTarget => ({ + kind: "legacy", + path: path.join(root, name), + })), { kind: "legacy", path: path.join(root, ".local", "bundled-plugin-runtime-deps"), diff --git a/src/commands/gateway-status.test.ts b/src/commands/gateway-status.test.ts index 9205591688bca..56709119bdfcc 100644 --- a/src/commands/gateway-status.test.ts +++ b/src/commands/gateway-status.test.ts @@ -40,13 +40,11 @@ const mocks = vi.hoisted(() => { stderr: [], stop: sshStop, })), - loadGatewayTlsRuntime: vi.fn( - async (): Promise => ({ - enabled: true, - required: true, - fingerprintSha256: "sha256:local-fingerprint", - }), - ), + loadGatewayTlsRuntime: vi.fn(async (): Promise => ({ + enabled: true, + required: true, + fingerprintSha256: "sha256:local-fingerprint", + })), probeGateway: vi.fn(async (opts: { url: string }): Promise => { const { url } = opts; if (url.includes("127.0.0.1")) { diff --git a/src/commands/models/list.auth-index.test.ts b/src/commands/models/list.auth-index.test.ts index 83ba45e96d3f5..e6d41c5626601 100644 --- a/src/commands/models/list.auth-index.test.ts +++ b/src/commands/models/list.auth-index.test.ts @@ -16,13 +16,11 @@ type PluginSnapshotResult = { }; const pluginRegistryMocks = vi.hoisted(() => ({ - loadPluginRegistrySnapshotWithMetadata: vi.fn( - (): PluginSnapshotResult => ({ - source: "persisted", - snapshot: { plugins: [] }, - diagnostics: [], - }), - ), + loadPluginRegistrySnapshotWithMetadata: vi.fn((): PluginSnapshotResult => ({ + source: "persisted", + snapshot: { plugins: [] }, + diagnostics: [], + })), })); const envCandidateMocks = vi.hoisted(() => ({ diff --git a/src/commands/onboard-auth.test.ts b/src/commands/onboard-auth.test.ts index 85a81df87d521..96fb4b032542e 100644 --- a/src/commands/onboard-auth.test.ts +++ b/src/commands/onboard-auth.test.ts @@ -15,17 +15,15 @@ import { setupAuthTestEnv, } from "./test-wizard-helpers.js"; -const providerEnvVarsById = vi.hoisted( - (): Record => ({ - "cloudflare-ai-gateway": ["CLOUDFLARE_AI_GATEWAY_API_KEY"], - byteplus: ["BYTEPLUS_API_KEY"], - moonshot: ["MOONSHOT_API_KEY"], - openai: ["OPENAI_API_KEY"], - opencode: ["OPENCODE_API_KEY"], - "opencode-go": ["OPENCODE_API_KEY"], - volcengine: ["VOLCANO_ENGINE_API_KEY"], - }), -); +const providerEnvVarsById = vi.hoisted((): Record => ({ + "cloudflare-ai-gateway": ["CLOUDFLARE_AI_GATEWAY_API_KEY"], + byteplus: ["BYTEPLUS_API_KEY"], + moonshot: ["MOONSHOT_API_KEY"], + openai: ["OPENAI_API_KEY"], + opencode: ["OPENCODE_API_KEY"], + "opencode-go": ["OPENCODE_API_KEY"], + volcengine: ["VOLCANO_ENGINE_API_KEY"], +})); vi.mock("../config/paths.js", () => ({ resolveStateDir: () => process.env.OPENCLAW_STATE_DIR ?? "/tmp/openclaw-state", diff --git a/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts b/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts index cbb54620a749f..6c66e6e8ffb63 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts @@ -6,26 +6,22 @@ import type { CopilotRuntimePluginInstallResult } from "../../copilot-runtime-pl import { applyNonInteractivePluginProviderChoice } from "./auth-choice.plugin-providers.js"; const ensureCodexRuntimePluginForModelSelection = vi.hoisted(() => - vi.fn( - async ({ cfg }: { cfg: OpenClawConfig }): Promise => ({ - cfg, - required: false, - installed: false, - }), - ), + vi.fn(async ({ cfg }: { cfg: OpenClawConfig }): Promise => ({ + cfg, + required: false, + installed: false, + })), ); vi.mock("../../codex-runtime-plugin-install.js", () => ({ CODEX_RUNTIME_PLUGIN_ID: "codex", ensureCodexRuntimePluginForModelSelection, })); const ensureCopilotRuntimePluginForModelSelection = vi.hoisted(() => - vi.fn( - async ({ cfg }: { cfg: OpenClawConfig }): Promise => ({ - cfg, - required: false, - installed: false, - }), - ), + vi.fn(async ({ cfg }: { cfg: OpenClawConfig }): Promise => ({ + cfg, + required: false, + installed: false, + })), ); vi.mock("../../copilot-runtime-plugin-install.js", () => ({ ensureCopilotRuntimePluginForModelSelection, diff --git a/src/config/io.write-config.test.ts b/src/config/io.write-config.test.ts index ff4c96a143998..56d06bdd5e0df 100644 --- a/src/config/io.write-config.test.ts +++ b/src/config/io.write-config.test.ts @@ -25,12 +25,10 @@ import type { ConfigFileSnapshot, OpenClawConfig } from "./types.openclaw.js"; // test exercise the exact code path that caused the bug: AJV injecting // defaults during the write-back validation pass. const mockLoadPluginManifestRegistry = vi.hoisted(() => - vi.fn( - (): PluginManifestRegistry => ({ - diagnostics: [], - plugins: [], - }), - ), + vi.fn((): PluginManifestRegistry => ({ + diagnostics: [], + plugins: [], + })), ); const mockMaintainConfigBackups = vi.hoisted(() => vi.fn(async () => {}), diff --git a/src/config/mutate.test.ts b/src/config/mutate.test.ts index 271244792b1c4..bd3932e900223 100644 --- a/src/config/mutate.test.ts +++ b/src/config/mutate.test.ts @@ -28,13 +28,11 @@ const ioMocks = vi.hoisted(() => ({ writeConfigFile: vi.fn(), })); const validationMocks = vi.hoisted(() => ({ - validateConfigObjectWithPlugins: vi.fn( - (config: OpenClawConfig): MockValidationResult => ({ - ok: true, - config, - warnings: [], - }), - ), + validateConfigObjectWithPlugins: vi.fn((config: OpenClawConfig): MockValidationResult => ({ + ok: true, + config, + warnings: [], + })), })); vi.mock("./io.js", async () => ({ diff --git a/src/config/validation.channel-metadata.test.ts b/src/config/validation.channel-metadata.test.ts index d71d50f44c85b..84fc6bb646839 100644 --- a/src/config/validation.channel-metadata.test.ts +++ b/src/config/validation.channel-metadata.test.ts @@ -7,12 +7,10 @@ import { } from "./validation.js"; const mockLoadPluginManifestRegistry = vi.hoisted(() => - vi.fn( - (): PluginManifestRegistry => ({ - diagnostics: [], - plugins: [], - }), - ), + vi.fn((): PluginManifestRegistry => ({ + diagnostics: [], + plugins: [], + })), ); function createTelegramSchemaRegistry(): PluginManifestRegistry { diff --git a/src/cron/service/timer.regression.test.ts b/src/cron/service/timer.regression.test.ts index 05a0108731bd9..7259bf429b6aa 100644 --- a/src/cron/service/timer.regression.test.ts +++ b/src/cron/service/timer.regression.test.ts @@ -1009,12 +1009,10 @@ describe("cron service timer regressions", () => { it("respects abort signals while retrying one-shot main-session wake-now heartbeat runs", async () => { const abortController = new AbortController(); - const runHeartbeatOnce = vi.fn( - async (): Promise => ({ - status: "skipped", - reason: "requests-in-flight", - }), - ); + const runHeartbeatOnce = vi.fn(async (): Promise => ({ + status: "skipped", + reason: "requests-in-flight", + })); const enqueueSystemEvent = vi.fn(); const requestHeartbeat = vi.fn(); const mainJob: CronJob = { diff --git a/src/flows/doctor-core-checks.ts b/src/flows/doctor-core-checks.ts index e026f8409094f..53a389a3bbce2 100644 --- a/src/flows/doctor-core-checks.ts +++ b/src/flows/doctor-core-checks.ts @@ -103,14 +103,12 @@ const defaultCoreHealthCheckDeps: CoreHealthCheckDeps = { export function configValidationIssuesToHealthFindings( issues: readonly ConfigValidationIssue[], ): readonly HealthFinding[] { - return issues.map( - (issue): HealthFinding => ({ - checkId: FINAL_CONFIG_VALIDATION_CHECK_ID, - severity: "error", - message: issue.message, - path: issue.path || "", - }), - ); + return issues.map((issue): HealthFinding => ({ + checkId: FINAL_CONFIG_VALIDATION_CHECK_ID, + severity: "error", + message: issue.message, + path: issue.path || "", + })); } const gatewayConfigCheck: HealthCheck = { @@ -352,15 +350,13 @@ const legacyStateCheck: HealthCheck = { async detect(ctx) { const { detectLegacyStateMigrations } = await import("../commands/doctor-state-migrations.js"); const detected = await detectLegacyStateMigrations({ cfg: ctx.cfg }); - return detected.preview.map( - (line): HealthFinding => ({ - checkId: "core/doctor/legacy-state", - severity: "warning", - message: line.replace(/^- /, ""), - path: detected.stateDir, - fixHint: "Run `openclaw doctor --fix` to migrate legacy state.", - }), - ); + return detected.preview.map((line): HealthFinding => ({ + checkId: "core/doctor/legacy-state", + severity: "warning", + message: line.replace(/^- /, ""), + path: detected.stateDir, + fixHint: "Run `openclaw doctor --fix` to migrate legacy state.", + })); }, }; @@ -625,28 +621,26 @@ const codexSessionRoutesCheck: HealthCheck = { description: "Codex runtime routes have a registered Codex plugin harness before sessions run.", source: "doctor", async detect(ctx) { - return collectDisabledCodexPluginRouteIssues(ctx.cfg).map( - (issue): HealthFinding => ({ - checkId: CODEX_SESSION_ROUTES_CHECK_ID, - severity: "warning", - message: [ - `${issue.path} routes ${issue.modelRef} to ${issue.canonicalModel}`, - "with Codex runtime, but the Codex plugin is disabled by config.", - ].join(" "), - path: issue.path, - target: issue.canonicalModel, - requirement: "Codex plugin enabled for routes that use the Codex runtime.", - fixHint: issue.blockedOutsideEntry - ? [ - "Enable plugin loading and remove codex from plugins.deny,", - "or set the affected OpenAI models to an OpenClaw runtime policy.", - ].join(" ") - : [ - "Run `openclaw doctor --fix`: it enables plugins.entries.codex,", - "or set the affected OpenAI models to an OpenClaw runtime policy.", - ].join(" "), - }), - ); + return collectDisabledCodexPluginRouteIssues(ctx.cfg).map((issue): HealthFinding => ({ + checkId: CODEX_SESSION_ROUTES_CHECK_ID, + severity: "warning", + message: [ + `${issue.path} routes ${issue.modelRef} to ${issue.canonicalModel}`, + "with Codex runtime, but the Codex plugin is disabled by config.", + ].join(" "), + path: issue.path, + target: issue.canonicalModel, + requirement: "Codex plugin enabled for routes that use the Codex runtime.", + fixHint: issue.blockedOutsideEntry + ? [ + "Enable plugin loading and remove codex from plugins.deny,", + "or set the affected OpenAI models to an OpenClaw runtime policy.", + ].join(" ") + : [ + "Run `openclaw doctor --fix`: it enables plugins.entries.codex,", + "or set the affected OpenAI models to an OpenClaw runtime policy.", + ].join(" "), + })); }, }; diff --git a/src/flows/search-setup.ts b/src/flows/search-setup.ts index 14980b3b85a49..9abd51aa4d661 100644 --- a/src/flows/search-setup.ts +++ b/src/flows/search-setup.ts @@ -125,9 +125,8 @@ function resolveSearchProviderSetupContributions( enabledByDefault: true, }).enabled, ) - .map( - (entry): SearchProviderEntryWithInstall => - Object.assign({}, entry.provider, { [SEARCH_INSTALL_CATALOG_ENTRY]: entry }), + .map((entry): SearchProviderEntryWithInstall => + Object.assign({}, entry.provider, { [SEARCH_INSTALL_CATALOG_ENTRY]: entry }), ); const providers = sortWebSearchProviders([...runtimeProviders, ...installCatalogProviders]); return sortFlowContributionsByLabel( diff --git a/src/gateway/chat-attachments.test.ts b/src/gateway/chat-attachments.test.ts index bbcc12abb73ac..ce8438e7366d4 100644 --- a/src/gateway/chat-attachments.test.ts +++ b/src/gateway/chat-attachments.test.ts @@ -447,15 +447,12 @@ describe("parseMessageWithAttachments validation errors", () => { it("caps text-only image offloads", async () => { const logs: string[] = []; - const attachments = Array.from( - { length: 11 }, - (_, index): ChatAttachment => ({ - type: "image", - mimeType: "image/png", - fileName: `dot-${index}.png`, - content: PNG_1x1, - }), - ); + const attachments = Array.from({ length: 11 }, (_, index): ChatAttachment => ({ + type: "image", + mimeType: "image/png", + fileName: `dot-${index}.png`, + content: PNG_1x1, + })); const parsed = await parseTextOnlyAttachments("see these", attachments, logs); try { diff --git a/src/gateway/config-reload-plan.ts b/src/gateway/config-reload-plan.ts index c1ddfccf65c9c..39b15f8174b28 100644 --- a/src/gateway/config-reload-plan.ts +++ b/src/gateway/config-reload-plan.ts @@ -166,20 +166,16 @@ function listReloadRules(): ReloadRule[] { // Channel docking: plugins contribute hot reload/no-op prefixes here. const channelReloadRules: ReloadRule[] = listChannelPlugins().flatMap((plugin) => (plugin.reload?.configPrefixes ?? []) - .map( - (prefix): ReloadRule => ({ - prefix, - kind: "hot", - actions: [`restart-channel:${plugin.id}` as ReloadAction], - }), - ) + .map((prefix): ReloadRule => ({ + prefix, + kind: "hot", + actions: [`restart-channel:${plugin.id}` as ReloadAction], + })) .concat( - (plugin.reload?.noopPrefixes ?? []).map( - (prefix): ReloadRule => ({ - prefix, - kind: "none", - }), - ), + (plugin.reload?.noopPrefixes ?? []).map((prefix): ReloadRule => ({ + prefix, + kind: "none", + })), ), ); const channelPluginStateRules: ReloadRule[] = listChannelPlugins().flatMap((plugin) => [ @@ -195,25 +191,19 @@ function listReloadRules(): ReloadRule[] { ]); const pluginReloadRules: ReloadRule[] = (registry?.reloads ?? []).flatMap((entry) => (entry.registration.restartPrefixes ?? []) - .map( - (prefix): ReloadRule => ({ - prefix, - kind: "restart", - }), - ) + .map((prefix): ReloadRule => ({ + prefix, + kind: "restart", + })) .concat( - (entry.registration.hotPrefixes ?? []).map( - (prefix): ReloadRule => ({ - prefix, - kind: "hot", - }), - ), - (entry.registration.noopPrefixes ?? []).map( - (prefix): ReloadRule => ({ - prefix, - kind: "none", - }), - ), + (entry.registration.hotPrefixes ?? []).map((prefix): ReloadRule => ({ + prefix, + kind: "hot", + })), + (entry.registration.noopPrefixes ?? []).map((prefix): ReloadRule => ({ + prefix, + kind: "none", + })), ), ); const rules = [ diff --git a/src/gateway/mcp-http.test.ts b/src/gateway/mcp-http.test.ts index d32338d8818d7..c6e4cbe44b38e 100644 --- a/src/gateway/mcp-http.test.ts +++ b/src/gateway/mcp-http.test.ts @@ -56,12 +56,10 @@ type McpToolResultPayload = { }; const runBeforeToolCallHookMock = vi.hoisted(() => - vi.fn( - async (args: { params: unknown }): Promise => ({ - blocked: false, - params: args.params, - }), - ), + vi.fn(async (args: { params: unknown }): Promise => ({ + blocked: false, + params: args.params, + })), ); const resolveGatewayScopedToolsMock = vi.hoisted(() => diff --git a/src/gateway/server-methods/channels.start.test.ts b/src/gateway/server-methods/channels.start.test.ts index 919333cd9c4ff..94b146abc988a 100644 --- a/src/gateway/server-methods/channels.start.test.ts +++ b/src/gateway/server-methods/channels.start.test.ts @@ -166,19 +166,17 @@ describe("channelsHandlers channels.stop", () => { context: { getRuntimeConfig: mocks.getRuntimeConfig, stopChannel, - getRuntimeSnapshot: vi.fn( - (): ChannelRuntimeSnapshot => ({ - channels: {}, - channelAccounts: { - whatsapp: { - "default-account": { - accountId: "default-account", - running: false, - }, + getRuntimeSnapshot: vi.fn((): ChannelRuntimeSnapshot => ({ + channels: {}, + channelAccounts: { + whatsapp: { + "default-account": { + accountId: "default-account", + running: false, }, }, - }), - ), + }, + })), } as unknown as GatewayRequestHandlerOptions["context"], }, ), diff --git a/src/gateway/server-methods/models-auth-status.test.ts b/src/gateway/server-methods/models-auth-status.test.ts index 9db5aeb1585bc..bbc10bacb3920 100644 --- a/src/gateway/server-methods/models-auth-status.test.ts +++ b/src/gateway/server-methods/models-auth-status.test.ts @@ -22,9 +22,10 @@ const mocks = vi.hoisted(() => ({ return { version: 1, profiles: {} }; }), listProfilesForProvider: vi.fn((): string[] => []), - removeProviderAuthProfilesWithLock: vi.fn( - async (): Promise => ({ version: 1, profiles: {} }), - ), + removeProviderAuthProfilesWithLock: vi.fn(async (): Promise => ({ + version: 1, + profiles: {}, + })), resolvePersistedAuthProfileOwnerAgentDir: vi.fn( (params: { agentDir?: string }) => params.agentDir, ), @@ -32,9 +33,12 @@ const mocks = vi.hoisted(() => ({ refreshActiveSecretsRuntimeSnapshot: vi.fn(async () => false), clearCurrentProviderAuthState: vi.fn(), warmCurrentProviderAuthStateOffMainThread: vi.fn(async (_cfg: unknown) => {}), - buildAuthHealthSummary: vi.fn( - (): AuthHealthSummary => ({ now: 0, warnAfterMs: 0, profiles: [], providers: [] }), - ), + buildAuthHealthSummary: vi.fn((): AuthHealthSummary => ({ + now: 0, + warnAfterMs: 0, + profiles: [], + providers: [], + })), loadProviderUsageSummary: vi.fn(async (): Promise => emptyUsageSummary()), })); diff --git a/src/gateway/server-reload-handlers.test.ts b/src/gateway/server-reload-handlers.test.ts index be7443f3f0e9f..cd55d04a0ceb5 100644 --- a/src/gateway/server-reload-handlers.test.ts +++ b/src/gateway/server-reload-handlers.test.ts @@ -146,12 +146,10 @@ function createReloadHandlersForTest(logReload = { info: vi.fn(), warn: vi.fn() startChannel: vi.fn(async () => {}), stopChannel: vi.fn(async () => {}), stopPostReadySidecars: vi.fn(), - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, @@ -301,12 +299,10 @@ describe("gateway restart deferral preflight", () => { setState: vi.fn(), startChannel, stopChannel, - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, @@ -391,12 +387,10 @@ describe("gateway restart deferral preflight", () => { setState: vi.fn(), startChannel, stopChannel, - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, @@ -482,12 +476,10 @@ describe("gateway restart deferral preflight", () => { setState: vi.fn(), startChannel, stopChannel, - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, @@ -573,12 +565,10 @@ describe("gateway restart deferral preflight", () => { setState: vi.fn(), startChannel, stopChannel, - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, @@ -847,12 +837,10 @@ describe("gateway channel hot reload handlers", () => { setState, startChannel, stopChannel, - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels, logCron: { error: vi.fn() }, @@ -903,12 +891,10 @@ describe("gateway channel hot reload handlers", () => { setState, startChannel, stopChannel, - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels, logCron: { error: vi.fn() }, @@ -976,12 +962,10 @@ describe("gateway Gmail hot reload handlers", () => { startChannel: vi.fn(async () => {}), stopChannel: vi.fn(async () => {}), stopPostReadySidecars, - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, @@ -1037,12 +1021,10 @@ describe("gateway Gmail hot reload handlers", () => { setState: vi.fn(), startChannel: vi.fn(async () => {}), stopChannel: vi.fn(async () => {}), - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, @@ -1131,12 +1113,10 @@ describe("gateway Gmail hot reload handlers", () => { setState: vi.fn(), startChannel: vi.fn(async () => {}), stopChannel: vi.fn(async () => {}), - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, @@ -1236,12 +1216,10 @@ describe("gateway Gmail hot reload handlers", () => { setState: vi.fn(), startChannel: vi.fn(async () => {}), stopChannel: vi.fn(async () => {}), - reloadPlugins: vi.fn( - async (): Promise => ({ - restartChannels: new Set(), - activeChannels: new Set(), - }), - ), + reloadPlugins: vi.fn(async (): Promise => ({ + restartChannels: new Set(), + activeChannels: new Set(), + })), logHooks: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, logChannels: { info: vi.fn(), error: vi.fn() }, logCron: { error: vi.fn() }, diff --git a/src/gateway/server-restart-sentinel.test.ts b/src/gateway/server-restart-sentinel.test.ts index 710b49bba9b97..47817d2679d6c 100644 --- a/src/gateway/server-restart-sentinel.test.ts +++ b/src/gateway/server-restart-sentinel.test.ts @@ -45,19 +45,17 @@ const mocks = vi.hoisted(() => { threadId: undefined, }), ), - loadSessionEntry: vi.fn( - (): LoadedSessionEntry => ({ - cfg: {}, - entry: { - sessionId: "agent:main:main", - updatedAt: 0, - }, - store: {}, - storePath: "/tmp/sessions.json", - canonicalKey: "agent:main:main", - legacyKey: undefined, - }), - ), + loadSessionEntry: vi.fn((): LoadedSessionEntry => ({ + cfg: {}, + entry: { + sessionId: "agent:main:main", + updatedAt: 0, + }, + store: {}, + storePath: "/tmp/sessions.json", + canonicalKey: "agent:main:main", + legacyKey: undefined, + })), deliveryContextFromSession: vi.fn( (): | { channel?: string; to?: string; accountId?: string; threadId?: string | number } diff --git a/src/gateway/server-startup-plugins.test.ts b/src/gateway/server-startup-plugins.test.ts index ab205acac15dc..9e0eb59c2043d 100644 --- a/src/gateway/server-startup-plugins.test.ts +++ b/src/gateway/server-startup-plugins.test.ts @@ -20,65 +20,61 @@ const loadGatewayStartupPlugins = vi.hoisted(() => gatewayMethods: ["ping"], })), ); -const pluginManifestRegistry = vi.hoisted( - (): PluginManifestRegistry => ({ - plugins: [ - { - id: "telegram", - origin: "bundled", - rootDir: "/package/dist/extensions/telegram", - source: "/package/dist/extensions/telegram/index.js", - manifestPath: "/package/dist/extensions/telegram/package.json", - channels: ["telegram"], - providers: [], - cliBackends: [], - skills: [], - hooks: [], - }, - ], - diagnostics: [], - }), -); -const pluginMetadataSnapshot = vi.hoisted( - (): PluginMetadataSnapshot => ({ - policyHash: "policy", - index: { - version: 1, - hostContractVersion: "test", - compatRegistryVersion: "test", - migrationVersion: 1, - policyHash: "policy", - generatedAtMs: 0, - installRecords: {}, - plugins: [], - diagnostics: [], +const pluginManifestRegistry = vi.hoisted((): PluginManifestRegistry => ({ + plugins: [ + { + id: "telegram", + origin: "bundled", + rootDir: "/package/dist/extensions/telegram", + source: "/package/dist/extensions/telegram/index.js", + manifestPath: "/package/dist/extensions/telegram/package.json", + channels: ["telegram"], + providers: [], + cliBackends: [], + skills: [], + hooks: [], }, - registryDiagnostics: [], - manifestRegistry: pluginManifestRegistry, + ], + diagnostics: [], +})); +const pluginMetadataSnapshot = vi.hoisted((): PluginMetadataSnapshot => ({ + policyHash: "policy", + index: { + version: 1, + hostContractVersion: "test", + compatRegistryVersion: "test", + migrationVersion: 1, + policyHash: "policy", + generatedAtMs: 0, + installRecords: {}, plugins: [], diagnostics: [], - byPluginId: new Map(), - normalizePluginId: (pluginId) => pluginId, - owners: { - channels: new Map(), - channelConfigs: new Map(), - providers: new Map(), - modelCatalogProviders: new Map(), - cliBackends: new Map(), - setupProviders: new Map(), - commandAliases: new Map(), - contracts: new Map(), - }, - metrics: { - registrySnapshotMs: 0, - manifestRegistryMs: 0, - ownerMapsMs: 0, - totalMs: 0, - indexPluginCount: 0, - manifestPluginCount: 0, - }, - }), -); + }, + registryDiagnostics: [], + manifestRegistry: pluginManifestRegistry, + plugins: [], + diagnostics: [], + byPluginId: new Map(), + normalizePluginId: (pluginId) => pluginId, + owners: { + channels: new Map(), + channelConfigs: new Map(), + providers: new Map(), + modelCatalogProviders: new Map(), + cliBackends: new Map(), + setupProviders: new Map(), + commandAliases: new Map(), + contracts: new Map(), + }, + metrics: { + registrySnapshotMs: 0, + manifestRegistryMs: 0, + ownerMapsMs: 0, + totalMs: 0, + indexPluginCount: 0, + manifestPluginCount: 0, + }, +})); const pluginLookUpTableMetrics = vi.hoisted(() => ({ registrySnapshotMs: 0, manifestRegistryMs: 0, diff --git a/src/infra/shell-inline-command.ts b/src/infra/shell-inline-command.ts index 4e66f0aea19d3..d4ec2c3d0cd9c 100644 --- a/src/infra/shell-inline-command.ts +++ b/src/infra/shell-inline-command.ts @@ -166,7 +166,7 @@ export function resolveInlineCommandMatch( valueOptions?: ReadonlySet; } = {}, ): { command: string | null; valueTokenIndex: number | null } { - for (let i = 1; i < argv.length; ) { + for (let i = 1; i < argv.length;) { const token = argv[i]?.trim(); if (!token) { i += 1; @@ -245,7 +245,7 @@ export function hasPosixInteractiveStartupBeforeInlineCommand( flags: ReadonlySet, ): boolean { let sawInteractiveMode = false; - for (let i = 1; i < argv.length; ) { + for (let i = 1; i < argv.length;) { const token = argv[i]?.trim(); if (!token) { i += 1; @@ -274,7 +274,7 @@ export function hasPosixLoginStartupBeforeInlineCommand( flags: ReadonlySet, ): boolean { let sawLoginMode = false; - for (let i = 1; i < argv.length; ) { + for (let i = 1; i < argv.length;) { const token = argv[i]?.trim(); if (!token) { i += 1; diff --git a/src/plugins/hooks.before-install.test.ts b/src/plugins/hooks.before-install.test.ts index 0f4c02fd991c5..8264b667b7bf5 100644 --- a/src/plugins/hooks.before-install.test.ts +++ b/src/plugins/hooks.before-install.test.ts @@ -123,34 +123,30 @@ describe("before_install hook merger", () => { }); it("short-circuits after block=true and preserves earlier findings", async () => { - const blocker = vi.fn( - (): PluginHookBeforeInstallResult => ({ - findings: [ - { - ruleId: "blocker", - severity: "critical", - file: "block.ts", - line: 3, - message: "blocked finding", - }, - ], - block: true, - blockReason: "policy blocked", - }), - ); - const skipped = vi.fn( - (): PluginHookBeforeInstallResult => ({ - findings: [ - { - ruleId: "skipped", - severity: "warn", - file: "skip.ts", - line: 4, - message: "should not appear", - }, - ], - }), - ); + const blocker = vi.fn((): PluginHookBeforeInstallResult => ({ + findings: [ + { + ruleId: "blocker", + severity: "critical", + file: "block.ts", + line: 3, + message: "blocked finding", + }, + ], + block: true, + blockReason: "policy blocked", + })); + const skipped = vi.fn((): PluginHookBeforeInstallResult => ({ + findings: [ + { + ruleId: "skipped", + severity: "warn", + file: "skip.ts", + line: 4, + message: "should not appear", + }, + ], + })); addBeforeInstallHook( registry, diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index 9fa99dfc5e0c8..101aaea4055bb 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -733,13 +733,11 @@ describe("resolvePluginProviders", () => { loadOpenClawPluginsMock.mockReturnValue(registry); loadPluginManifestRegistryMock.mockReset(); applyPluginAutoEnableMock.mockReset(); - applyPluginAutoEnableMock.mockImplementation( - (params): PluginAutoEnableResult => ({ - config: params.config ?? ({} as OpenClawConfig), - changes: [], - autoEnabledReasons: {}, - }), - ); + applyPluginAutoEnableMock.mockImplementation((params): PluginAutoEnableResult => ({ + config: params.config ?? ({} as OpenClawConfig), + changes: [], + autoEnabledReasons: {}, + })); setManifestPlugins([ createManifestProviderPlugin({ id: "google", diff --git a/src/shared/text/reasoning-tag-text-partitioner.ts b/src/shared/text/reasoning-tag-text-partitioner.ts index c3e441599f15c..9cf063399bdbb 100644 --- a/src/shared/text/reasoning-tag-text-partitioner.ts +++ b/src/shared/text/reasoning-tag-text-partitioner.ts @@ -247,7 +247,7 @@ function reasoningTagPrefixSuffixIndex( text: string, isIndexInsideCode: (index: number) => boolean, ): number { - for (let index = text.lastIndexOf("<"); index >= 0; ) { + for (let index = text.lastIndexOf("<"); index >= 0;) { if (!isIndexInsideCode(index) && isReasoningTagPrefix(text.slice(index))) { return index; } diff --git a/src/talk/activation-name.ts b/src/talk/activation-name.ts index 880e6b6b41677..5ed34e6e4956e 100644 --- a/src/talk/activation-name.ts +++ b/src/talk/activation-name.ts @@ -118,12 +118,10 @@ export function matchRealtimeVoiceActivationName( ...leadingActivationNameCandidates(text, maxWords), ...trailingActivationNameCandidates(text, maxWords), ] - .map( - (candidate): PreparedEdgeActivationNameCandidate => ({ - candidate, - compact: compactActivationName(candidate.heardName), - }), - ) + .map((candidate): PreparedEdgeActivationNameCandidate => ({ + candidate, + compact: compactActivationName(candidate.heardName), + })) .toSorted((left, right) => right.compact.length - left.compact.length); for (const { candidate, compact: heardCompact } of candidates) { diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index 715e228118b5d..6586b9f07fbd6 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -257,9 +257,10 @@ function renderCronFilterIcon(hiddenCount: number) { - ${hiddenCount > 0 - ? html`${hiddenCount}` - : ""} + >${hiddenCount}` + : "" + } `; } @@ -312,9 +314,9 @@ function renderChatAutoScrollToggle(state: AppViewState, options: { labelled?: b const active = mode !== "off"; return html` `; } @@ -609,11 +613,13 @@ export function renderChatMobileToggle(state: AppViewState) { state.sessionsHideCron = !hideCron; }} aria-pressed=${hideCron} - title=${hideCron - ? hiddenCronCount > 0 - ? t("chat.showCronSessionsHidden", { count: String(hiddenCronCount) }) - : t("chat.showCronSessions") - : t("chat.hideCronSessions")} + title=${ + hideCron + ? hiddenCronCount > 0 + ? t("chat.showCronSessionsHidden", { count: String(hiddenCronCount) }) + : t("chat.showCronSessions") + : t("chat.hideCronSessions") + } > ${renderCronFilterIcon(hiddenCronCount)} @@ -815,9 +821,9 @@ export function renderTopbarThemeModeToggle(state: AppViewState) { return html` - ${collapsed || recent.length === 0 - ? nothing - : html` - - `} + ` + }
`; } @@ -647,12 +651,14 @@ function renderSidebarRecentSession(state: AppViewState, row: GatewaySessionRow) ${label} ${meta} - ${row.hasActiveRun - ? html`` - : nothing} + ${ + row.hasActiveRun + ? html`` + : nothing + } `; } @@ -2227,11 +2233,11 @@ export function renderApp(state: AppViewState) { }, })}
` - : nothing} - ${state.tab === "config" || isChat - ? nothing - : html`
-
-
${titleForTab(state.tab)}
-
${subtitleForTab(state.tab)}
-
-
- ${state.tab === "skillWorkshop" - ? renderSkillWorkshopHeaderControls(state) - : nothing} - ${state.tab === "dreams" - ? html` -
- - -
- ` - : nothing} - ${headerError ? html`
${headerError}
` : nothing} -
-
`} - ${state.tab === "overview" - ? renderOverview({ - connected: state.connected, - hello: state.hello, - settings: state.settings, - password: state.password, - lastError: state.lastError, - lastErrorCode: state.lastErrorCode, - presenceCount, - sessionsCount, - cronEnabled: state.cronStatus?.enabled ?? null, - cronNext, - lastChannelsRefresh: state.channelsLastSuccess, - warnQueryToken, - modelAuthStatus: state.modelAuthStatusResult, - usageResult: state.usageResult, - sessionsResult: state.sessionsResult, - skillsReport: state.skillsReport, - cronJobs: state.cronJobs, - cronStatus: state.cronStatus, - attentionItems: state.attentionItems, - eventLog: state.eventLog, - overviewLogLines: state.overviewLogLines, - showGatewayToken: state.overviewShowGatewayToken, - showGatewayPassword: state.overviewShowGatewayPassword, - onSettingsChange: (next) => state.applySettings(next), - onPasswordChange: (next) => (state.password = next), - onSessionKeyChange: (next) => { - switchChatSession(state, next); - }, - onToggleGatewayTokenVisibility: () => { - state.overviewShowGatewayToken = !state.overviewShowGatewayToken; - }, - onToggleGatewayPasswordVisibility: () => { - state.overviewShowGatewayPassword = !state.overviewShowGatewayPassword; - }, - onConnect: () => state.connect(), - onRefresh: () => void state.loadOverview({ refresh: true }), - onNavigate: (tab) => state.setTab(tab as import("./navigation.ts").Tab), - onRefreshLogs: () => void state.loadOverview({ refresh: true }), - }) - : nothing} - ${state.tab === "activity" - ? renderLazyView(lazyActivity, (m) => - m.renderActivity({ - entries: state.activityEntries, - filterText: state.activityFilterText, - statusFilters: state.activityStatusFilters, - toolFilter: state.activityToolFilter, - expandedIds: state.activityExpandedIds, - autoFollow: state.activityAutoFollow, - onFilterTextChange: (next) => (state.activityFilterText = next), - onToolFilterChange: (next) => (state.activityToolFilter = next), - onStatusToggle: (status, enabled) => { - state.activityStatusFilters = { - ...state.activityStatusFilters, - [status]: enabled, - }; - }, - onToggleAutoFollow: (next) => { - state.activityAutoFollow = next; - if (next) { - state.scheduleActivityScroll(true); - } - }, - onClear: () => { - state.activityEntries = []; - state.activityExpandedIds = new Set(); - state.activityAtBottom = true; - }, - onExpandAll: () => { - state.activityExpandedIds = new Set( - state.activityEntries.map((entry) => entry.id), - ); - }, - onCollapseAll: () => { - state.activityExpandedIds = new Set(); - }, - onEntryToggle: (id, open) => { - const next = new Set(state.activityExpandedIds); - if (open) { - next.add(id); - } else { - next.delete(id); - } - state.activityExpandedIds = next; - }, - onScroll: (event) => state.handleActivityScroll(event), - }), - ) - : nothing} - ${state.tab === "instances" - ? renderLazyView(lazyInstances, (m) => - m.renderInstances({ - loading: state.presenceLoading, - entries: state.presenceEntries, - lastError: state.presenceError, - statusMessage: state.presenceStatus, - onRefresh: () => void loadPresence(state), - }), - ) - : nothing} - ${state.tab === "sessions" - ? renderLazyView(lazySessions, (m) => { - const workboardState = getWorkboardState(state); - const workboardEnabled = isPluginEnabledInConfigSnapshot( - state.configSnapshot, - "workboard", - { - enabledByDefault: false, - }, - ); - const operatorCanWrite = hasOperatorWriteAccess( - (state.hello as { auth?: { role?: string; scopes?: string[] } } | null)?.auth ?? - null, - ); - return m.renderSessions({ - loading: state.sessionsLoading, - result: state.sessionsResult, - error: state.sessionsError, - activeMinutes: state.sessionsFilterActive, - limit: state.sessionsFilterLimit, - includeGlobal: state.sessionsIncludeGlobal, - includeUnknown: state.sessionsIncludeUnknown, - showArchived: state.sessionsShowArchived, - filtersCollapsed: state.sessionsFiltersCollapsed, - basePath: state.basePath, - searchQuery: state.sessionsSearchQuery, - agentIdentityById: state.agentIdentityById, - sortColumn: state.sessionsSortColumn, - sortDir: state.sessionsSortDir, - page: state.sessionsPage, - pageSize: state.sessionsPageSize, - selectedKeys: state.sessionsSelectedKeys, - workboardSessionKeys: new Set( - workboardState.cards - .flatMap((card) => [card.sessionKey, card.execution?.sessionKey]) - .filter((key): key is string => typeof key === "string" && key.length > 0), - ), - workboardBusySessionKey: [...workboardState.capturingSessionKeys][0] ?? null, - expandedCheckpointKey: state.sessionsExpandedCheckpointKey, - checkpointItemsByKey: state.sessionsCheckpointItemsByKey, - checkpointLoadingKey: state.sessionsCheckpointLoadingKey, - checkpointBusyKey: state.sessionsCheckpointBusyKey, - checkpointErrorByKey: state.sessionsCheckpointErrorByKey, - onFiltersChange: (next) => { - state.sessionsFilterActive = next.activeMinutes; - state.sessionsFilterLimit = next.limit; - state.sessionsIncludeGlobal = next.includeGlobal; - state.sessionsIncludeUnknown = next.includeUnknown; - state.sessionsShowArchived = next.showArchived; - state.sessionsSelectedKeys = new Set(); - state.sessionsPage = 0; - void loadSessions(state, { - activeMinutes: parseSessionsFilterInteger(next.activeMinutes), - limit: parseSessionsFilterInteger(next.limit), - includeGlobal: next.includeGlobal, - includeUnknown: next.includeUnknown, - showArchived: next.showArchived, - }); - }, - onToggleFiltersCollapsed: () => { - state.sessionsFiltersCollapsed = !state.sessionsFiltersCollapsed; - }, - onClearFilters: () => { - state.sessionsFilterActive = ""; - state.sessionsFilterLimit = ""; - state.sessionsIncludeGlobal = true; - state.sessionsIncludeUnknown = true; - state.sessionsShowArchived = true; - state.sessionsSearchQuery = ""; - state.sessionsSelectedKeys = new Set(); - state.sessionsPage = 0; - void loadSessions(state, { - activeMinutes: 0, - limit: 0, - includeGlobal: true, - includeUnknown: true, - showArchived: true, - }); - }, - onSearchChange: (q) => { - state.sessionsSearchQuery = q; - state.sessionsPage = 0; - }, - onSortChange: (col, dir) => { - state.sessionsSortColumn = col; - state.sessionsSortDir = dir; - state.sessionsPage = 0; - }, - onPageChange: (p) => { - state.sessionsPage = p; - }, - onPageSizeChange: (s) => { - state.sessionsPageSize = s; - state.sessionsPage = 0; - }, - onRefresh: () => void loadSessions(state), - onPatch: (key, patch) => void patchSession(state, key, patch), - onToggleSelect: (key) => { - const next = new Set(state.sessionsSelectedKeys); - if (next.has(key)) { - next.delete(key); - } else { - next.add(key); +
+
${titleForTab(state.tab)}
+
${subtitleForTab(state.tab)}
+
+
+ ${ + state.tab === "skillWorkshop" + ? renderSkillWorkshopHeaderControls(state) + : nothing } - state.sessionsSelectedKeys = next; - }, - onSelectPage: (keys) => { - const next = new Set(state.sessionsSelectedKeys); - for (const k of keys) { - next.add(k); - } - state.sessionsSelectedKeys = next; - }, - onDeselectPage: (keys) => { - const next = new Set(state.sessionsSelectedKeys); - for (const k of keys) { - next.delete(k); + ${ + state.tab === "dreams" + ? html` +
+ + +
+ ` + : nothing } - state.sessionsSelectedKeys = next; - }, - onDeselectAll: () => { - state.sessionsSelectedKeys = new Set(); - }, - onDeleteSelected: runUiTask(async () => { - const keys = [...state.sessionsSelectedKeys]; - const deleted = await deleteSessionsAndRefresh(state, keys); - if (deleted.length > 0) { + ${headerError ? html`
${headerError}
` : nothing} +
+ ` + } + ${ + state.tab === "overview" + ? renderOverview({ + connected: state.connected, + hello: state.hello, + settings: state.settings, + password: state.password, + lastError: state.lastError, + lastErrorCode: state.lastErrorCode, + presenceCount, + sessionsCount, + cronEnabled: state.cronStatus?.enabled ?? null, + cronNext, + lastChannelsRefresh: state.channelsLastSuccess, + warnQueryToken, + modelAuthStatus: state.modelAuthStatusResult, + usageResult: state.usageResult, + sessionsResult: state.sessionsResult, + skillsReport: state.skillsReport, + cronJobs: state.cronJobs, + cronStatus: state.cronStatus, + attentionItems: state.attentionItems, + eventLog: state.eventLog, + overviewLogLines: state.overviewLogLines, + showGatewayToken: state.overviewShowGatewayToken, + showGatewayPassword: state.overviewShowGatewayPassword, + onSettingsChange: (next) => state.applySettings(next), + onPasswordChange: (next) => (state.password = next), + onSessionKeyChange: (next) => { + switchChatSession(state, next); + }, + onToggleGatewayTokenVisibility: () => { + state.overviewShowGatewayToken = !state.overviewShowGatewayToken; + }, + onToggleGatewayPasswordVisibility: () => { + state.overviewShowGatewayPassword = !state.overviewShowGatewayPassword; + }, + onConnect: () => state.connect(), + onRefresh: () => void state.loadOverview({ refresh: true }), + onNavigate: (tab) => state.setTab(tab as import("./navigation.ts").Tab), + onRefreshLogs: () => void state.loadOverview({ refresh: true }), + }) + : nothing + } + ${ + state.tab === "activity" + ? renderLazyView(lazyActivity, (m) => + m.renderActivity({ + entries: state.activityEntries, + filterText: state.activityFilterText, + statusFilters: state.activityStatusFilters, + toolFilter: state.activityToolFilter, + expandedIds: state.activityExpandedIds, + autoFollow: state.activityAutoFollow, + onFilterTextChange: (next) => (state.activityFilterText = next), + onToolFilterChange: (next) => (state.activityToolFilter = next), + onStatusToggle: (status, enabled) => { + state.activityStatusFilters = { + ...state.activityStatusFilters, + [status]: enabled, + }; + }, + onToggleAutoFollow: (next) => { + state.activityAutoFollow = next; + if (next) { + state.scheduleActivityScroll(true); + } + }, + onClear: () => { + state.activityEntries = []; + state.activityExpandedIds = new Set(); + state.activityAtBottom = true; + }, + onExpandAll: () => { + state.activityExpandedIds = new Set( + state.activityEntries.map((entry) => entry.id), + ); + }, + onCollapseAll: () => { + state.activityExpandedIds = new Set(); + }, + onEntryToggle: (id, open) => { + const next = new Set(state.activityExpandedIds); + if (open) { + next.add(id); + } else { + next.delete(id); + } + state.activityExpandedIds = next; + }, + onScroll: (event) => state.handleActivityScroll(event), + }), + ) + : nothing + } + ${ + state.tab === "instances" + ? renderLazyView(lazyInstances, (m) => + m.renderInstances({ + loading: state.presenceLoading, + entries: state.presenceEntries, + lastError: state.presenceError, + statusMessage: state.presenceStatus, + onRefresh: () => void loadPresence(state), + }), + ) + : nothing + } + ${ + state.tab === "sessions" + ? renderLazyView(lazySessions, (m) => { + const workboardState = getWorkboardState(state); + const workboardEnabled = isPluginEnabledInConfigSnapshot( + state.configSnapshot, + "workboard", + { + enabledByDefault: false, + }, + ); + const operatorCanWrite = hasOperatorWriteAccess( + (state.hello as { auth?: { role?: string; scopes?: string[] } } | null)?.auth ?? + null, + ); + return m.renderSessions({ + loading: state.sessionsLoading, + result: state.sessionsResult, + error: state.sessionsError, + activeMinutes: state.sessionsFilterActive, + limit: state.sessionsFilterLimit, + includeGlobal: state.sessionsIncludeGlobal, + includeUnknown: state.sessionsIncludeUnknown, + showArchived: state.sessionsShowArchived, + filtersCollapsed: state.sessionsFiltersCollapsed, + basePath: state.basePath, + searchQuery: state.sessionsSearchQuery, + agentIdentityById: state.agentIdentityById, + sortColumn: state.sessionsSortColumn, + sortDir: state.sessionsSortDir, + page: state.sessionsPage, + pageSize: state.sessionsPageSize, + selectedKeys: state.sessionsSelectedKeys, + workboardSessionKeys: new Set( + workboardState.cards + .flatMap((card) => [card.sessionKey, card.execution?.sessionKey]) + .filter((key): key is string => typeof key === "string" && key.length > 0), + ), + workboardBusySessionKey: [...workboardState.capturingSessionKeys][0] ?? null, + expandedCheckpointKey: state.sessionsExpandedCheckpointKey, + checkpointItemsByKey: state.sessionsCheckpointItemsByKey, + checkpointLoadingKey: state.sessionsCheckpointLoadingKey, + checkpointBusyKey: state.sessionsCheckpointBusyKey, + checkpointErrorByKey: state.sessionsCheckpointErrorByKey, + onFiltersChange: (next) => { + state.sessionsFilterActive = next.activeMinutes; + state.sessionsFilterLimit = next.limit; + state.sessionsIncludeGlobal = next.includeGlobal; + state.sessionsIncludeUnknown = next.includeUnknown; + state.sessionsShowArchived = next.showArchived; + state.sessionsSelectedKeys = new Set(); + state.sessionsPage = 0; + void loadSessions(state, { + activeMinutes: parseSessionsFilterInteger(next.activeMinutes), + limit: parseSessionsFilterInteger(next.limit), + includeGlobal: next.includeGlobal, + includeUnknown: next.includeUnknown, + showArchived: next.showArchived, + }); + }, + onToggleFiltersCollapsed: () => { + state.sessionsFiltersCollapsed = !state.sessionsFiltersCollapsed; + }, + onClearFilters: () => { + state.sessionsFilterActive = ""; + state.sessionsFilterLimit = ""; + state.sessionsIncludeGlobal = true; + state.sessionsIncludeUnknown = true; + state.sessionsShowArchived = true; + state.sessionsSearchQuery = ""; + state.sessionsSelectedKeys = new Set(); + state.sessionsPage = 0; + void loadSessions(state, { + activeMinutes: 0, + limit: 0, + includeGlobal: true, + includeUnknown: true, + showArchived: true, + }); + }, + onSearchChange: (q) => { + state.sessionsSearchQuery = q; + state.sessionsPage = 0; + }, + onSortChange: (col, dir) => { + state.sessionsSortColumn = col; + state.sessionsSortDir = dir; + state.sessionsPage = 0; + }, + onPageChange: (p) => { + state.sessionsPage = p; + }, + onPageSizeChange: (s) => { + state.sessionsPageSize = s; + state.sessionsPage = 0; + }, + onRefresh: () => void loadSessions(state), + onPatch: (key, patch) => void patchSession(state, key, patch), + onToggleSelect: (key) => { + const next = new Set(state.sessionsSelectedKeys); + if (next.has(key)) { + next.delete(key); + } else { + next.add(key); + } + state.sessionsSelectedKeys = next; + }, + onSelectPage: (keys) => { const next = new Set(state.sessionsSelectedKeys); - for (const k of deleted) { + for (const k of keys) { + next.add(k); + } + state.sessionsSelectedKeys = next; + }, + onDeselectPage: (keys) => { + const next = new Set(state.sessionsSelectedKeys); + for (const k of keys) { next.delete(k); } state.sessionsSelectedKeys = next; - } - }), - onNavigateToChat: (sessionKey) => { - switchChatSession(state, sessionKey); - state.setTab("chat" as import("./navigation.ts").Tab); - }, - onAddToWorkboard: - workboardEnabled && operatorCanWrite - ? runUiTask(async (session) => { - await captureSessionToWorkboard({ - host: state, - client: state.client, - session, - requestUpdate: requestHostUpdate, - }); - state.setTab("workboard" as import("./navigation.ts").Tab); - }) - : undefined, - onToggleCheckpointDetails: (sessionKey) => - void toggleSessionCompactionCheckpoints(state, sessionKey), - onBranchFromCheckpoint: runUiTask(async (sessionKey, checkpointId) => { - const nextKey = await branchSessionFromCheckpoint( - state, - sessionKey, - checkpointId, - ); - if (nextKey) { - switchChatSession(state, nextKey); + }, + onDeselectAll: () => { + state.sessionsSelectedKeys = new Set(); + }, + onDeleteSelected: runUiTask(async () => { + const keys = [...state.sessionsSelectedKeys]; + const deleted = await deleteSessionsAndRefresh(state, keys); + if (deleted.length > 0) { + const next = new Set(state.sessionsSelectedKeys); + for (const k of deleted) { + next.delete(k); + } + state.sessionsSelectedKeys = next; + } + }), + onNavigateToChat: (sessionKey) => { + switchChatSession(state, sessionKey); state.setTab("chat" as import("./navigation.ts").Tab); - } - }), - onRestoreCheckpoint: (sessionKey, checkpointId) => - void restoreSessionFromCheckpoint(state, sessionKey, checkpointId), - }); - }) - : nothing} - ${state.tab === "workboard" - ? renderLazyView(lazyWorkboard, (m) => { - const auth = - (state.hello as { auth?: { role?: string; scopes?: string[] } } | null)?.auth ?? - null; - return m.renderWorkboard({ - host: state, - client: state.client, - connected: state.connected, - canWrite: hasOperatorWriteAccess(auth), - canModelOverride: hasOperatorAdminAccess(auth), - pluginEnabled: isPluginEnabledInConfigSnapshot(state.configSnapshot, "workboard", { - enabledByDefault: false, - }), - agentsList: state.agentsList, - sessions: state.sessionsResult?.sessions ?? [], - onOpenSession: (sessionKey) => { - switchChatSession(state, sessionKey); - state.setTab("chat" as import("./navigation.ts").Tab); - }, - onRequestUpdate: requestHostUpdate, - }); - }) - : nothing} + }, + onAddToWorkboard: + workboardEnabled && operatorCanWrite + ? runUiTask(async (session) => { + await captureSessionToWorkboard({ + host: state, + client: state.client, + session, + requestUpdate: requestHostUpdate, + }); + state.setTab("workboard" as import("./navigation.ts").Tab); + }) + : undefined, + onToggleCheckpointDetails: (sessionKey) => + void toggleSessionCompactionCheckpoints(state, sessionKey), + onBranchFromCheckpoint: runUiTask(async (sessionKey, checkpointId) => { + const nextKey = await branchSessionFromCheckpoint( + state, + sessionKey, + checkpointId, + ); + if (nextKey) { + switchChatSession(state, nextKey); + state.setTab("chat" as import("./navigation.ts").Tab); + } + }), + onRestoreCheckpoint: (sessionKey, checkpointId) => + void restoreSessionFromCheckpoint(state, sessionKey, checkpointId), + }); + }) + : nothing + } + ${ + state.tab === "workboard" + ? renderLazyView(lazyWorkboard, (m) => { + const auth = + (state.hello as { auth?: { role?: string; scopes?: string[] } } | null)?.auth ?? + null; + return m.renderWorkboard({ + host: state, + client: state.client, + connected: state.connected, + canWrite: hasOperatorWriteAccess(auth), + canModelOverride: hasOperatorAdminAccess(auth), + pluginEnabled: isPluginEnabledInConfigSnapshot( + state.configSnapshot, + "workboard", + { + enabledByDefault: false, + }, + ), + agentsList: state.agentsList, + sessions: state.sessionsResult?.sessions ?? [], + onOpenSession: (sessionKey) => { + switchChatSession(state, sessionKey); + state.setTab("chat" as import("./navigation.ts").Tab); + }, + onRequestUpdate: requestHostUpdate, + }); + }) + : nothing + } ${renderUsageTab(state, lazyUsage)} ${state.tab === "cron" ? renderCronQuickCreateForTab(state, requestHostUpdate) : nothing} - ${state.tab === "cron" - ? renderLazyView(lazyCron, (m) => - m.renderCron({ - basePath: state.basePath, - loading: state.cronLoading, - status: state.cronStatus, - jobs: visibleCronJobs, - jobsLoadingMore: state.cronJobsLoadingMore, - jobsTotal: state.cronJobsTotal, - jobsHasMore: state.cronJobsHasMore, - jobsQuery: state.cronJobsQuery, - jobsEnabledFilter: state.cronJobsEnabledFilter, - jobsScheduleKindFilter: state.cronJobsScheduleKindFilter, - jobsLastStatusFilter: state.cronJobsLastStatusFilter, - jobsSortBy: state.cronJobsSortBy, - jobsSortDir: state.cronJobsSortDir, - editingJobId: state.cronEditingJobId, - error: state.cronError, - busy: state.cronBusy, - form: state.cronForm, - cronFormCollapsed: state.cronFormCollapsed, - channels: state.channelsSnapshot?.channelMeta?.length - ? state.channelsSnapshot.channelMeta.map((entry) => entry.id) - : (state.channelsSnapshot?.channelOrder ?? []), - channelLabels: state.channelsSnapshot?.channelLabels ?? {}, - channelMeta: state.channelsSnapshot?.channelMeta ?? [], - runsJobId: state.cronRunsJobId, - runs: state.cronRuns, - runsTotal: state.cronRunsTotal, - runsHasMore: state.cronRunsHasMore, - runsLoadingMore: state.cronRunsLoadingMore, - runsScope: state.cronRunsScope, - runsStatuses: state.cronRunsStatuses, - runsDeliveryStatuses: state.cronRunsDeliveryStatuses, - runsStatusFilter: state.cronRunsStatusFilter, - runsQuery: state.cronRunsQuery, - runsSortDir: state.cronRunsSortDir, - fieldErrors: state.cronFieldErrors, - canSubmit: !hasCronFormErrors(state.cronFieldErrors), - agentSuggestions: cronAgentSuggestions, - modelSuggestions: cronModelSuggestions, - thinkingSuggestions: CRON_THINKING_SUGGESTIONS, - timezoneSuggestions: CRON_TIMEZONE_SUGGESTIONS, - deliveryToSuggestions, - accountSuggestions, - onFormChange: (patch) => { - state.cronForm = normalizeCronFormState({ ...state.cronForm, ...patch }); - state.cronFieldErrors = validateCronForm(state.cronForm); - }, - onRefresh: () => void state.loadCron(), - onAdd: () => { - void (async () => { - const saved = await addCronJob(state); - if (saved) { - state.cronFormCollapsed = true; - } - requestHostUpdate?.(); - })(); - }, - onEdit: (job) => { - state.cronFormCollapsed = false; - startCronEdit(state, job); - }, - onClone: (job) => { - state.cronFormCollapsed = false; - startCronClone(state, job); - }, - onCancelEdit: () => { - cancelCronEdit(state); - state.cronFormCollapsed = true; - requestHostUpdate?.(); - }, - onToggleFormCollapsed: (collapsed) => { - state.cronFormCollapsed = collapsed; - requestHostUpdate?.(); - }, - onToggle: (job, enabled) => void toggleCronJob(state, job, enabled), - onRun: (job, mode) => void runCronJob(state, job, mode ?? "force"), - onRemove: (job) => void removeCronJob(state, job), - onQuickCreate: () => { - state.cronQuickCreateOpen = true; - state.cronQuickCreateStep = "what"; - state.cronQuickCreateDraft = createDefaultDraft(); - requestHostUpdate?.(); - }, - onLoadRuns: runUiTask(async (jobId) => { - updateCronRunsFilter(state, { cronRunsScope: "job" }); - await loadCronRuns(state, jobId); - }), - onLoadMoreJobs: () => - void loadCronJobsPage(state, { append: true, tableFilters: true }), - onJobsFiltersChange: runUiTask(async (patch) => { - updateCronJobsFilter(state, patch); - const shouldReload = - typeof patch.cronJobsQuery === "string" || - Boolean(patch.cronJobsEnabledFilter) || - Boolean(patch.cronJobsScheduleKindFilter) || - Boolean(patch.cronJobsLastStatusFilter) || - Boolean(patch.cronJobsSortBy) || - Boolean(patch.cronJobsSortDir); - if (shouldReload) { - await loadCronJobsPage(state, { append: false, tableFilters: true }); - } - }), - onJobsFiltersReset: runUiTask(async () => { - updateCronJobsFilter(state, { - cronJobsQuery: "", - cronJobsEnabledFilter: "all", - cronJobsScheduleKindFilter: "all", - cronJobsLastStatusFilter: "all", - cronJobsSortBy: "nextRunAtMs", - cronJobsSortDir: "asc", - }); - await loadCronJobsPage(state, { append: false, tableFilters: true }); - }), - onLoadMoreRuns: () => void loadMoreCronRuns(state), - onRunsFiltersChange: runUiTask(async (patch) => { - updateCronRunsFilter(state, patch); - if (state.cronRunsScope === "all") { - await loadCronRuns(state, null); - return; - } - await loadCronRuns(state, state.cronRunsJobId); - }), - onNavigateToChat: (sessionKey) => { - switchChatSession(state, sessionKey); - state.setTab("chat" as import("./navigation.ts").Tab); - }, - }), - ) - : nothing} - ${state.tab === "agents" - ? renderLazyView(lazyAgents, (m) => - m.renderAgents({ - basePath: state.basePath ?? "", - loading: state.agentsLoading, - error: state.agentsError, - agentsList: state.agentsList, - selectedAgentId: resolvedAgentId, - activePanel: state.agentsPanel, - config: { - form: configValue, - loading: state.configLoading, - saving: state.configSaving, - dirty: state.configFormDirty, - }, - channels: { - snapshot: state.channelsSnapshot, - loading: state.channelsLoading, - error: state.channelsError, - lastSuccess: state.channelsLastSuccess, - }, - cron: { - status: state.cronStatus, - jobs: state.cronJobs, + ${ + state.tab === "cron" + ? renderLazyView(lazyCron, (m) => + m.renderCron({ + basePath: state.basePath, loading: state.cronLoading, + status: state.cronStatus, + jobs: visibleCronJobs, + jobsLoadingMore: state.cronJobsLoadingMore, + jobsTotal: state.cronJobsTotal, + jobsHasMore: state.cronJobsHasMore, + jobsQuery: state.cronJobsQuery, + jobsEnabledFilter: state.cronJobsEnabledFilter, + jobsScheduleKindFilter: state.cronJobsScheduleKindFilter, + jobsLastStatusFilter: state.cronJobsLastStatusFilter, + jobsSortBy: state.cronJobsSortBy, + jobsSortDir: state.cronJobsSortDir, + editingJobId: state.cronEditingJobId, error: state.cronError, - }, - agentFiles: { - list: state.agentFilesList, - loading: state.agentFilesLoading, - error: state.agentFilesError, - active: state.agentFileActive, - contents: state.agentFileContents, - drafts: state.agentFileDrafts, - saving: state.agentFileSaving, - }, - agentIdentityLoading: state.agentIdentityLoading, - agentIdentityError: state.agentIdentityError, - agentIdentityById: state.agentIdentityById, - agentSkills: { - report: state.agentSkillsReport, - loading: state.agentSkillsLoading, - error: state.agentSkillsError, - agentId: state.agentSkillsAgentId, - filter: state.skillsFilter, - }, - toolsCatalog: { - loading: state.toolsCatalogLoading, - error: state.toolsCatalogError, - result: state.toolsCatalogResult, - }, - toolsEffective: { - loading: state.toolsEffectiveLoading, - error: state.toolsEffectiveError, - result: state.toolsEffectiveResult, - }, - runtimeSessionKey: state.sessionKey, - runtimeSessionMatchesSelectedAgent: toolsPanelUsesActiveSession, - modelCatalog: state.chatModelCatalog ?? [], - onRefresh: runUiTask(async () => { - await loadAgents(state); - const agentIds = state.agentsList?.agents?.map((entry) => entry.id) ?? []; - if (agentIds.length > 0) { - void loadAgentIdentities(state, agentIds); - } - loadAgentPanelDataForSelectedAgent(resolveSelectedAgentId()); - refreshAgentsPanelSupplementalData(state.agentsPanel); + busy: state.cronBusy, + form: state.cronForm, + cronFormCollapsed: state.cronFormCollapsed, + channels: state.channelsSnapshot?.channelMeta?.length + ? state.channelsSnapshot.channelMeta.map((entry) => entry.id) + : (state.channelsSnapshot?.channelOrder ?? []), + channelLabels: state.channelsSnapshot?.channelLabels ?? {}, + channelMeta: state.channelsSnapshot?.channelMeta ?? [], + runsJobId: state.cronRunsJobId, + runs: state.cronRuns, + runsTotal: state.cronRunsTotal, + runsHasMore: state.cronRunsHasMore, + runsLoadingMore: state.cronRunsLoadingMore, + runsScope: state.cronRunsScope, + runsStatuses: state.cronRunsStatuses, + runsDeliveryStatuses: state.cronRunsDeliveryStatuses, + runsStatusFilter: state.cronRunsStatusFilter, + runsQuery: state.cronRunsQuery, + runsSortDir: state.cronRunsSortDir, + fieldErrors: state.cronFieldErrors, + canSubmit: !hasCronFormErrors(state.cronFieldErrors), + agentSuggestions: cronAgentSuggestions, + modelSuggestions: cronModelSuggestions, + thinkingSuggestions: CRON_THINKING_SUGGESTIONS, + timezoneSuggestions: CRON_TIMEZONE_SUGGESTIONS, + deliveryToSuggestions, + accountSuggestions, + onFormChange: (patch) => { + state.cronForm = normalizeCronFormState({ ...state.cronForm, ...patch }); + state.cronFieldErrors = validateCronForm(state.cronForm); + }, + onRefresh: () => void state.loadCron(), + onAdd: () => { + void (async () => { + const saved = await addCronJob(state); + if (saved) { + state.cronFormCollapsed = true; + } + requestHostUpdate?.(); + })(); + }, + onEdit: (job) => { + state.cronFormCollapsed = false; + startCronEdit(state, job); + }, + onClone: (job) => { + state.cronFormCollapsed = false; + startCronClone(state, job); + }, + onCancelEdit: () => { + cancelCronEdit(state); + state.cronFormCollapsed = true; + requestHostUpdate?.(); + }, + onToggleFormCollapsed: (collapsed) => { + state.cronFormCollapsed = collapsed; + requestHostUpdate?.(); + }, + onToggle: (job, enabled) => void toggleCronJob(state, job, enabled), + onRun: (job, mode) => void runCronJob(state, job, mode ?? "force"), + onRemove: (job) => void removeCronJob(state, job), + onQuickCreate: () => { + state.cronQuickCreateOpen = true; + state.cronQuickCreateStep = "what"; + state.cronQuickCreateDraft = createDefaultDraft(); + requestHostUpdate?.(); + }, + onLoadRuns: runUiTask(async (jobId) => { + updateCronRunsFilter(state, { cronRunsScope: "job" }); + await loadCronRuns(state, jobId); + }), + onLoadMoreJobs: () => + void loadCronJobsPage(state, { append: true, tableFilters: true }), + onJobsFiltersChange: runUiTask(async (patch) => { + updateCronJobsFilter(state, patch); + const shouldReload = + typeof patch.cronJobsQuery === "string" || + Boolean(patch.cronJobsEnabledFilter) || + Boolean(patch.cronJobsScheduleKindFilter) || + Boolean(patch.cronJobsLastStatusFilter) || + Boolean(patch.cronJobsSortBy) || + Boolean(patch.cronJobsSortDir); + if (shouldReload) { + await loadCronJobsPage(state, { append: false, tableFilters: true }); + } + }), + onJobsFiltersReset: runUiTask(async () => { + updateCronJobsFilter(state, { + cronJobsQuery: "", + cronJobsEnabledFilter: "all", + cronJobsScheduleKindFilter: "all", + cronJobsLastStatusFilter: "all", + cronJobsSortBy: "nextRunAtMs", + cronJobsSortDir: "asc", + }); + await loadCronJobsPage(state, { append: false, tableFilters: true }); + }), + onLoadMoreRuns: () => void loadMoreCronRuns(state), + onRunsFiltersChange: runUiTask(async (patch) => { + updateCronRunsFilter(state, patch); + if (state.cronRunsScope === "all") { + await loadCronRuns(state, null); + return; + } + await loadCronRuns(state, state.cronRunsJobId); + }), + onNavigateToChat: (sessionKey) => { + switchChatSession(state, sessionKey); + state.setTab("chat" as import("./navigation.ts").Tab); + }, }), - onSelectAgent: (agentId) => { - if (state.agentsSelectedId === agentId) { - return; - } - state.agentsSelectedId = agentId; - resetAgentSelectionPanelState(); - void loadAgentIdentity(state, agentId); - loadAgentPanelDataForSelectedAgent(agentId); - }, - onSelectPanel: (panel) => { - state.agentsPanel = panel; - if ( - panel === "files" && - resolvedAgentId && - state.agentFilesList?.agentId !== resolvedAgentId - ) { - resetAgentFilesState(); - void loadAgentFiles(state, resolvedAgentId); - } - if (panel === "skills" && resolvedAgentId) { - void loadAgentSkills(state, resolvedAgentId); - } - if (panel === "tools" && resolvedAgentId) { + ) + : nothing + } + ${ + state.tab === "agents" + ? renderLazyView(lazyAgents, (m) => + m.renderAgents({ + basePath: state.basePath ?? "", + loading: state.agentsLoading, + error: state.agentsError, + agentsList: state.agentsList, + selectedAgentId: resolvedAgentId, + activePanel: state.agentsPanel, + config: { + form: configValue, + loading: state.configLoading, + saving: state.configSaving, + dirty: state.configFormDirty, + }, + channels: { + snapshot: state.channelsSnapshot, + loading: state.channelsLoading, + error: state.channelsError, + lastSuccess: state.channelsLastSuccess, + }, + cron: { + status: state.cronStatus, + jobs: state.cronJobs, + loading: state.cronLoading, + error: state.cronError, + }, + agentFiles: { + list: state.agentFilesList, + loading: state.agentFilesLoading, + error: state.agentFilesError, + active: state.agentFileActive, + contents: state.agentFileContents, + drafts: state.agentFileDrafts, + saving: state.agentFileSaving, + }, + agentIdentityLoading: state.agentIdentityLoading, + agentIdentityError: state.agentIdentityError, + agentIdentityById: state.agentIdentityById, + agentSkills: { + report: state.agentSkillsReport, + loading: state.agentSkillsLoading, + error: state.agentSkillsError, + agentId: state.agentSkillsAgentId, + filter: state.skillsFilter, + }, + toolsCatalog: { + loading: state.toolsCatalogLoading, + error: state.toolsCatalogError, + result: state.toolsCatalogResult, + }, + toolsEffective: { + loading: state.toolsEffectiveLoading, + error: state.toolsEffectiveError, + result: state.toolsEffectiveResult, + }, + runtimeSessionKey: state.sessionKey, + runtimeSessionMatchesSelectedAgent: toolsPanelUsesActiveSession, + modelCatalog: state.chatModelCatalog ?? [], + onRefresh: runUiTask(async () => { + await loadAgents(state); + const agentIds = state.agentsList?.agents?.map((entry) => entry.id) ?? []; + if (agentIds.length > 0) { + void loadAgentIdentities(state, agentIds); + } + loadAgentPanelDataForSelectedAgent(resolveSelectedAgentId()); + refreshAgentsPanelSupplementalData(state.agentsPanel); + }), + onSelectAgent: (agentId) => { + if (state.agentsSelectedId === agentId) { + return; + } + state.agentsSelectedId = agentId; + resetAgentSelectionPanelState(); + void loadAgentIdentity(state, agentId); + loadAgentPanelDataForSelectedAgent(agentId); + }, + onSelectPanel: (panel) => { + state.agentsPanel = panel; if ( - state.toolsCatalogResult?.agentId !== resolvedAgentId || - state.toolsCatalogError + panel === "files" && + resolvedAgentId && + state.agentFilesList?.agentId !== resolvedAgentId ) { - void loadToolsCatalog(state, resolvedAgentId); + resetAgentFilesState(); + void loadAgentFiles(state, resolvedAgentId); } - if (resolvedAgentId === chatAgentId) { - const toolsRequestKey = buildToolsEffectiveRequestKey(state, { - agentId: resolvedAgentId, - sessionKey: state.sessionKey, - }); + if (panel === "skills" && resolvedAgentId) { + void loadAgentSkills(state, resolvedAgentId); + } + if (panel === "tools" && resolvedAgentId) { if ( - state.toolsEffectiveResultKey !== toolsRequestKey || - state.toolsEffectiveError + state.toolsCatalogResult?.agentId !== resolvedAgentId || + state.toolsCatalogError ) { - void loadToolsEffective(state, { + void loadToolsCatalog(state, resolvedAgentId); + } + if (resolvedAgentId === chatAgentId) { + const toolsRequestKey = buildToolsEffectiveRequestKey(state, { agentId: resolvedAgentId, sessionKey: state.sessionKey, }); + if ( + state.toolsEffectiveResultKey !== toolsRequestKey || + state.toolsEffectiveError + ) { + void loadToolsEffective(state, { + agentId: resolvedAgentId, + sessionKey: state.sessionKey, + }); + } + } else { + resetToolsEffectiveState(state); } + } + refreshAgentsPanelSupplementalData(panel); + }, + onLoadFiles: (agentId) => void loadAgentFiles(state, agentId), + onSelectFile: (name) => { + state.agentFileActive = name; + if (!resolvedAgentId) { + return; + } + void loadAgentFileContent(state, resolvedAgentId, name); + }, + onFileDraftChange: (name, content) => { + state.agentFileDrafts = { ...state.agentFileDrafts, [name]: content }; + }, + onFileReset: (name) => { + const base = state.agentFileContents[name] ?? ""; + state.agentFileDrafts = { ...state.agentFileDrafts, [name]: base }; + }, + onFileSave: (name) => { + if (!resolvedAgentId) { + return; + } + const content = + state.agentFileDrafts[name] ?? state.agentFileContents[name] ?? ""; + void saveAgentFile(state, resolvedAgentId, name, content); + }, + onToolsProfileChange: (agentId, profile, clearAllow) => { + const basePathItem = resolveAgentToolsPath( + agentId, + Boolean(profile || clearAllow), + ); + if (!basePathItem) { + return; + } + if (profile) { + updateConfigFormValue(state, [...basePathItem, "profile"], profile); } else { - resetToolsEffectiveState(state); + removeConfigFormValue(state, [...basePathItem, "profile"]); } - } - refreshAgentsPanelSupplementalData(panel); - }, - onLoadFiles: (agentId) => void loadAgentFiles(state, agentId), - onSelectFile: (name) => { - state.agentFileActive = name; - if (!resolvedAgentId) { - return; - } - void loadAgentFileContent(state, resolvedAgentId, name); - }, - onFileDraftChange: (name, content) => { - state.agentFileDrafts = { ...state.agentFileDrafts, [name]: content }; - }, - onFileReset: (name) => { - const base = state.agentFileContents[name] ?? ""; - state.agentFileDrafts = { ...state.agentFileDrafts, [name]: base }; - }, - onFileSave: (name) => { - if (!resolvedAgentId) { - return; - } - const content = - state.agentFileDrafts[name] ?? state.agentFileContents[name] ?? ""; - void saveAgentFile(state, resolvedAgentId, name, content); - }, - onToolsProfileChange: (agentId, profile, clearAllow) => { - const basePathItem = resolveAgentToolsPath( - agentId, - Boolean(profile || clearAllow), - ); - if (!basePathItem) { - return; - } - if (profile) { - updateConfigFormValue(state, [...basePathItem, "profile"], profile); - } else { - removeConfigFormValue(state, [...basePathItem, "profile"]); - } - if (clearAllow) { - removeConfigFormValue(state, [...basePathItem, "allow"]); - } - }, - onToolsOverridesChange: (agentId, alsoAllow, deny) => { - const basePathCandidate = resolveAgentToolsPath( - agentId, - alsoAllow.length > 0 || deny.length > 0, - ); - if (!basePathCandidate) { - return; - } - if (alsoAllow.length > 0) { - updateConfigFormValue(state, [...basePathCandidate, "alsoAllow"], alsoAllow); - } else { - removeConfigFormValue(state, [...basePathCandidate, "alsoAllow"]); - } - if (deny.length > 0) { - updateConfigFormValue(state, [...basePathCandidate, "deny"], deny); - } else { - removeConfigFormValue(state, [...basePathCandidate, "deny"]); - } - }, - onConfigReload: () => void loadConfig(state, { discardPendingChanges: true }), - onConfigSave: () => void saveAgentsConfig(state), - onChannelsRefresh: () => void loadChannels(state, false), - onCronRefresh: () => void state.loadCron(), - onCronRunNow: (jobId) => { - const job = state.cronJobs.find((entry) => entry.id === jobId); - if (!job) { - return; - } - void runCronJob(state, job, "force"); - }, - onSkillsFilterChange: (next) => (state.skillsFilter = next), - onSkillsRefresh: () => { - if (resolvedAgentId) { - void loadAgentSkills(state, resolvedAgentId); - } - }, - onAgentSkillToggle: (agentId, skillName, enabled) => { - const index = ensureAgentIndex(agentId); - if (index < 0) { - return; - } - const list = (getCurrentConfigValue() as { agents?: { list?: unknown[] } } | null) - ?.agents?.list; - const entry = Array.isArray(list) - ? (list[index] as { skills?: unknown }) - : undefined; - const normalizedSkill = skillName.trim(); - if (!normalizedSkill) { - return; - } - const allSkills = - state.agentSkillsReport?.skills?.map((skill) => skill.name).filter(Boolean) ?? - []; - const existing = Array.isArray(entry?.skills) - ? normalizeStringEntries(entry.skills) - : undefined; - const base = existing ?? allSkills; - const next = new Set(base); - if (enabled) { - next.add(normalizedSkill); - } else { - next.delete(normalizedSkill); - } - updateConfigFormValue(state, ["agents", "list", index, "skills"], [...next]); - }, - onAgentSkillsClear: (agentId) => { - const index = findAgentIndex(agentId); - if (index < 0) { - return; - } - removeConfigFormValue(state, ["agents", "list", index, "skills"]); - }, - onAgentSkillsDisableAll: (agentId) => { - const index = ensureAgentIndex(agentId); - if (index < 0) { - return; - } - updateConfigFormValue(state, ["agents", "list", index, "skills"], []); - }, - onModelChange: (agentId, modelId) => { - const index = modelId ? ensureAgentIndex(agentId) : findAgentIndex(agentId); - if (index < 0) { - return; - } - const modelEntry = resolveAgentModelFormEntry(index); - const { basePath: basePathEntry, existing } = modelEntry; - if (!modelId) { - removeConfigFormValue(state, basePathEntry); - } else if (existing && typeof existing === "object" && !Array.isArray(existing)) { - const fallbacks = (existing as { fallbacks?: unknown }).fallbacks; - const next = { - primary: modelId, - ...(Array.isArray(fallbacks) ? { fallbacks } : {}), - }; - updateConfigFormValue(state, basePathEntry, next); - } else { - updateConfigFormValue(state, basePathEntry, modelId); - } - void refreshVisibleToolsEffectiveForCurrentSession(state); - }, - onModelFallbacksChange: (agentId, fallbacks) => { - const normalized = normalizeStringEntries(fallbacks); - const currentConfig = getCurrentConfigValue(); - const resolvedConfig = resolveAgentConfig(currentConfig, agentId); - const effectivePrimary = - resolveModelPrimary(resolvedConfig.entry?.model) ?? - resolveModelPrimary(resolvedConfig.defaults?.model); - const effectiveFallbacks = resolveEffectiveModelFallbacks( - resolvedConfig.entry?.model, - resolvedConfig.defaults?.model, - ); - const index = - normalized.length > 0 - ? effectivePrimary - ? ensureAgentIndex(agentId) - : -1 - : (effectiveFallbacks?.length ?? 0) > 0 || findAgentIndex(agentId) >= 0 - ? ensureAgentIndex(agentId) - : -1; - if (index < 0) { - return; - } - const { basePath: basePathResult, existing } = resolveAgentModelFormEntry(index); - const resolvePrimary = () => { - if (typeof existing === "string") { - return existing.trim() || null; + if (clearAllow) { + removeConfigFormValue(state, [...basePathItem, "allow"]); } - if (existing && typeof existing === "object" && !Array.isArray(existing)) { - const primary = (existing as { primary?: unknown }).primary; - if (typeof primary === "string") { - const trimmed = primary.trim(); - return trimmed || null; - } + }, + onToolsOverridesChange: (agentId, alsoAllow, deny) => { + const basePathCandidate = resolveAgentToolsPath( + agentId, + alsoAllow.length > 0 || deny.length > 0, + ); + if (!basePathCandidate) { + return; + } + if (alsoAllow.length > 0) { + updateConfigFormValue(state, [...basePathCandidate, "alsoAllow"], alsoAllow); + } else { + removeConfigFormValue(state, [...basePathCandidate, "alsoAllow"]); + } + if (deny.length > 0) { + updateConfigFormValue(state, [...basePathCandidate, "deny"], deny); + } else { + removeConfigFormValue(state, [...basePathCandidate, "deny"]); + } + }, + onConfigReload: () => void loadConfig(state, { discardPendingChanges: true }), + onConfigSave: () => void saveAgentsConfig(state), + onChannelsRefresh: () => void loadChannels(state, false), + onCronRefresh: () => void state.loadCron(), + onCronRunNow: (jobId) => { + const job = state.cronJobs.find((entry) => entry.id === jobId); + if (!job) { + return; + } + void runCronJob(state, job, "force"); + }, + onSkillsFilterChange: (next) => (state.skillsFilter = next), + onSkillsRefresh: () => { + if (resolvedAgentId) { + void loadAgentSkills(state, resolvedAgentId); + } + }, + onAgentSkillToggle: (agentId, skillName, enabled) => { + const index = ensureAgentIndex(agentId); + if (index < 0) { + return; + } + const list = ( + getCurrentConfigValue() as { agents?: { list?: unknown[] } } | null + )?.agents?.list; + const entry = Array.isArray(list) + ? (list[index] as { skills?: unknown }) + : undefined; + const normalizedSkill = skillName.trim(); + if (!normalizedSkill) { + return; } - return null; - }; - const primary = resolvePrimary() ?? effectivePrimary; - if (normalized.length === 0) { - if (primary) { - updateConfigFormValue(state, basePathResult, primary); + const allSkills = + state.agentSkillsReport?.skills?.map((skill) => skill.name).filter(Boolean) ?? + []; + const existing = Array.isArray(entry?.skills) + ? normalizeStringEntries(entry.skills) + : undefined; + const base = existing ?? allSkills; + const next = new Set(base); + if (enabled) { + next.add(normalizedSkill); } else { - removeConfigFormValue(state, basePathResult); + next.delete(normalizedSkill); + } + updateConfigFormValue(state, ["agents", "list", index, "skills"], [...next]); + }, + onAgentSkillsClear: (agentId) => { + const index = findAgentIndex(agentId); + if (index < 0) { + return; + } + removeConfigFormValue(state, ["agents", "list", index, "skills"]); + }, + onAgentSkillsDisableAll: (agentId) => { + const index = ensureAgentIndex(agentId); + if (index < 0) { + return; + } + updateConfigFormValue(state, ["agents", "list", index, "skills"], []); + }, + onModelChange: (agentId, modelId) => { + const index = modelId ? ensureAgentIndex(agentId) : findAgentIndex(agentId); + if (index < 0) { + return; + } + const modelEntry = resolveAgentModelFormEntry(index); + const { basePath: basePathEntry, existing } = modelEntry; + if (!modelId) { + removeConfigFormValue(state, basePathEntry); + } else if ( + existing && + typeof existing === "object" && + !Array.isArray(existing) + ) { + const fallbacks = (existing as { fallbacks?: unknown }).fallbacks; + const next = { + primary: modelId, + ...(Array.isArray(fallbacks) ? { fallbacks } : {}), + }; + updateConfigFormValue(state, basePathEntry, next); + } else { + updateConfigFormValue(state, basePathEntry, modelId); + } + void refreshVisibleToolsEffectiveForCurrentSession(state); + }, + onModelFallbacksChange: (agentId, fallbacks) => { + const normalized = normalizeStringEntries(fallbacks); + const currentConfig = getCurrentConfigValue(); + const resolvedConfig = resolveAgentConfig(currentConfig, agentId); + const effectivePrimary = + resolveModelPrimary(resolvedConfig.entry?.model) ?? + resolveModelPrimary(resolvedConfig.defaults?.model); + const effectiveFallbacks = resolveEffectiveModelFallbacks( + resolvedConfig.entry?.model, + resolvedConfig.defaults?.model, + ); + const index = + normalized.length > 0 + ? effectivePrimary + ? ensureAgentIndex(agentId) + : -1 + : (effectiveFallbacks?.length ?? 0) > 0 || findAgentIndex(agentId) >= 0 + ? ensureAgentIndex(agentId) + : -1; + if (index < 0) { + return; + } + const { basePath: basePathResult, existing } = + resolveAgentModelFormEntry(index); + const resolvePrimary = () => { + if (typeof existing === "string") { + return existing.trim() || null; + } + if (existing && typeof existing === "object" && !Array.isArray(existing)) { + const primary = (existing as { primary?: unknown }).primary; + if (typeof primary === "string") { + const trimmed = primary.trim(); + return trimmed || null; + } + } + return null; + }; + const primary = resolvePrimary() ?? effectivePrimary; + if (normalized.length === 0) { + if (primary) { + updateConfigFormValue(state, basePathResult, primary); + } else { + removeConfigFormValue(state, basePathResult); + } + return; + } + if (!primary) { + return; + } + updateConfigFormValue(state, basePathResult, { + primary, + fallbacks: normalized, + }); + }, + onSetDefault: (agentId) => { + stageDefaultAgentConfigEntry(state, agentId); + }, + }), + ) + : nothing + } + ${ + state.tab === "skills" + ? renderLazyView(lazySkills, (m) => + m.renderSkills({ + connected: state.connected, + loading: state.skillsLoading, + report: state.skillsReport, + error: state.skillsError, + filter: state.skillsFilter, + statusFilter: state.skillsStatusFilter, + edits: state.skillEdits, + messages: state.skillMessages, + busyKey: state.skillsBusyKey, + detailKey: state.skillsDetailKey, + detailTab: state.skillsDetailTab, + clawhubVerdicts: state.clawhubVerdicts, + clawhubVerdictsLoading: state.clawhubVerdictsLoading, + clawhubVerdictsError: state.clawhubVerdictsError, + skillCardContents: state.skillCardContents, + skillCardLoadingKey: state.skillCardLoadingKey, + skillCardErrors: state.skillCardErrors, + clawhubQuery: state.clawhubSearchQuery, + clawhubResults: state.clawhubSearchResults, + clawhubSearchLoading: state.clawhubSearchLoading, + clawhubSearchError: state.clawhubSearchError, + clawhubDetail: state.clawhubDetail, + clawhubDetailSlug: state.clawhubDetailSlug, + clawhubDetailLoading: state.clawhubDetailLoading, + clawhubDetailError: state.clawhubDetailError, + clawhubInstallSlug: state.clawhubInstallSlug, + clawhubInstallMessage: state.clawhubInstallMessage, + onFilterChange: (next) => (state.skillsFilter = next), + onStatusFilterChange: (next) => (state.skillsStatusFilter = next), + onRefresh: () => void loadSkills(state, { clearMessages: true }), + onToggle: (key, enabled) => void updateSkillEnabled(state, key, enabled), + onEdit: (key, value) => updateSkillEdit(state, key, value), + onSaveKey: (key) => void saveSkillApiKey(state, key), + onInstall: (skillKey, name, installId) => + void installSkill(state, skillKey, name, installId), + onDetailOpen: (key) => { + state.skillsDetailKey = key; + state.skillsDetailTab = "overview"; + }, + onDetailClose: () => (state.skillsDetailKey = null), + onDetailTabChange: (tab) => { + state.skillsDetailTab = tab; + if (tab === "card" && state.skillsDetailKey) { + void loadSkillCard(state, state.skillsDetailKey); } + }, + onClawHubQueryChange: (query) => { + setClawHubSearchQuery(state, query); + if (clawhubSearchTimer) { + clearTimeout(clawhubSearchTimer); + } + clawhubSearchTimer = setTimeout(() => { + void searchClawHub(state, query); + }, 300); + }, + onClawHubDetailOpen: (slug) => void loadClawHubDetail(state, slug), + onClawHubDetailClose: () => closeClawHubDetail(state), + onClawHubInstall: (slug) => void installFromClawHub(state, slug), + }), + ) + : nothing + } + ${ + state.tab === "skillWorkshop" + ? renderLazyView(lazySkillWorkshop, (m) => { + const visibleProposals = m.filterSkillWorkshopProposals( + state.skillWorkshopProposals, + state.skillWorkshopStatusFilter, + state.skillWorkshopQuery, + ); + const selectedIndex = visibleProposals.findIndex( + (proposal) => proposal.key === state.skillWorkshopSelectedKey, + ); + const selectRelativeProposal = (delta: -1 | 1) => { + if (visibleProposals.length === 0) { return; } - if (!primary) { + const nextIndex = + selectedIndex < 0 + ? 0 + : (selectedIndex + delta + visibleProposals.length) % visibleProposals.length; + selectSkillWorkshopProposal(state, visibleProposals[nextIndex].key); + }; + const selectVisibleFallback = (proposals: typeof visibleProposals) => { + if ( + proposals.length === 0 || + proposals.some((proposal) => proposal.key === state.skillWorkshopSelectedKey) + ) { return; } - updateConfigFormValue(state, basePathResult, { primary, fallbacks: normalized }); - }, - onSetDefault: (agentId) => { - stageDefaultAgentConfigEntry(state, agentId); - }, - }), - ) - : nothing} - ${state.tab === "skills" - ? renderLazyView(lazySkills, (m) => - m.renderSkills({ - connected: state.connected, - loading: state.skillsLoading, - report: state.skillsReport, - error: state.skillsError, - filter: state.skillsFilter, - statusFilter: state.skillsStatusFilter, - edits: state.skillEdits, - messages: state.skillMessages, - busyKey: state.skillsBusyKey, - detailKey: state.skillsDetailKey, - detailTab: state.skillsDetailTab, - clawhubVerdicts: state.clawhubVerdicts, - clawhubVerdictsLoading: state.clawhubVerdictsLoading, - clawhubVerdictsError: state.clawhubVerdictsError, - skillCardContents: state.skillCardContents, - skillCardLoadingKey: state.skillCardLoadingKey, - skillCardErrors: state.skillCardErrors, - clawhubQuery: state.clawhubSearchQuery, - clawhubResults: state.clawhubSearchResults, - clawhubSearchLoading: state.clawhubSearchLoading, - clawhubSearchError: state.clawhubSearchError, - clawhubDetail: state.clawhubDetail, - clawhubDetailSlug: state.clawhubDetailSlug, - clawhubDetailLoading: state.clawhubDetailLoading, - clawhubDetailError: state.clawhubDetailError, - clawhubInstallSlug: state.clawhubInstallSlug, - clawhubInstallMessage: state.clawhubInstallMessage, - onFilterChange: (next) => (state.skillsFilter = next), - onStatusFilterChange: (next) => (state.skillsStatusFilter = next), - onRefresh: () => void loadSkills(state, { clearMessages: true }), - onToggle: (key, enabled) => void updateSkillEnabled(state, key, enabled), - onEdit: (key, value) => updateSkillEdit(state, key, value), - onSaveKey: (key) => void saveSkillApiKey(state, key), - onInstall: (skillKey, name, installId) => - void installSkill(state, skillKey, name, installId), - onDetailOpen: (key) => { - state.skillsDetailKey = key; - state.skillsDetailTab = "overview"; - }, - onDetailClose: () => (state.skillsDetailKey = null), - onDetailTabChange: (tab) => { - state.skillsDetailTab = tab; - if (tab === "card" && state.skillsDetailKey) { - void loadSkillCard(state, state.skillsDetailKey); - } - }, - onClawHubQueryChange: (query) => { - setClawHubSearchQuery(state, query); - if (clawhubSearchTimer) { - clearTimeout(clawhubSearchTimer); - } - clawhubSearchTimer = setTimeout(() => { - void searchClawHub(state, query); - }, 300); - }, - onClawHubDetailOpen: (slug) => void loadClawHubDetail(state, slug), - onClawHubDetailClose: () => closeClawHubDetail(state), - onClawHubInstall: (slug) => void installFromClawHub(state, slug), - }), - ) - : nothing} - ${state.tab === "skillWorkshop" - ? renderLazyView(lazySkillWorkshop, (m) => { - const visibleProposals = m.filterSkillWorkshopProposals( - state.skillWorkshopProposals, - state.skillWorkshopStatusFilter, - state.skillWorkshopQuery, - ); - const selectedIndex = visibleProposals.findIndex( - (proposal) => proposal.key === state.skillWorkshopSelectedKey, - ); - const selectRelativeProposal = (delta: -1 | 1) => { - if (visibleProposals.length === 0) { - return; - } - const nextIndex = - selectedIndex < 0 - ? 0 - : (selectedIndex + delta + visibleProposals.length) % visibleProposals.length; - selectSkillWorkshopProposal(state, visibleProposals[nextIndex].key); - }; - const selectVisibleFallback = (proposals: typeof visibleProposals) => { - if ( - proposals.length === 0 || - proposals.some((proposal) => proposal.key === state.skillWorkshopSelectedKey) - ) { - return; - } - state.skillWorkshopFilePreviewKey = null; - selectSkillWorkshopProposal(state, proposals[0].key); - }; - return m.renderSkillWorkshop({ - loading: state.skillWorkshopLoading, - error: state.skillWorkshopError, - inspectingKey: state.skillWorkshopInspectingKey, - proposals: state.skillWorkshopProposals, - selectedKey: state.skillWorkshopSelectedKey, - statusFilter: state.skillWorkshopStatusFilter, - query: state.skillWorkshopQuery, - filePreviewKey: state.skillWorkshopFilePreviewKey, - filePreviewQuery: state.skillWorkshopFilePreviewQuery, - queueWidth: state.skillWorkshopQueueWidth, - mode: state.skillWorkshopMode, - actionBusy: state.skillWorkshopActionBusy, - actionNotice: state.skillWorkshopActionNotice, - revisionKey: state.skillWorkshopRevisionKey, - revisionDraft: state.skillWorkshopRevisionDraft, - assistantName: state.assistantName, - counts: countSkillWorkshopProposals(state.skillWorkshopProposals), - onStatusFilterChange: (status) => { - state.skillWorkshopStatusFilter = status; - selectVisibleFallback( - m.filterSkillWorkshopProposals( - state.skillWorkshopProposals, - status, - state.skillWorkshopQuery, - ), - ); - }, - onQueryChange: (query) => { - state.skillWorkshopQuery = query; - selectVisibleFallback( - m.filterSkillWorkshopProposals( - state.skillWorkshopProposals, - state.skillWorkshopStatusFilter, - query, - ), - ); - }, - onFilePreviewQueryChange: (query) => (state.skillWorkshopFilePreviewQuery = query), - onQueueWidthChange: (width) => (state.skillWorkshopQueueWidth = width), - onModeChange: (mode) => setSkillWorkshopMode(state, mode), - onSelect: (key) => { state.skillWorkshopFilePreviewKey = null; - selectSkillWorkshopProposal(state, key); - }, - onPrev: () => selectRelativeProposal(-1), - onNext: () => selectRelativeProposal(1), - onApply: (key) => void runSkillWorkshopLifecycleAction(state, "apply", key), - onRevise: (key) => { - state.skillWorkshopRevisionKey = key; - state.skillWorkshopRevisionDraft = ""; - }, - onReject: (key) => void runSkillWorkshopLifecycleAction(state, "reject", key), - onRevisionDraftChange: (draft) => (state.skillWorkshopRevisionDraft = draft), - onRevisionCancel: () => { - state.skillWorkshopRevisionKey = null; - state.skillWorkshopRevisionDraft = ""; - }, - onRevisionSubmit: (key) => - void requestSkillWorkshopRevision(state, key, (message, proposal) => - sendSkillWorkshopRevisionRequest(state, message, proposal), - ), - onPreviewFile: (key, path) => { - state.skillWorkshopSelectedKey = key; - state.skillWorkshopFilePreviewKey = path; + selectSkillWorkshopProposal(state, proposals[0].key); + }; + return m.renderSkillWorkshop({ + loading: state.skillWorkshopLoading, + error: state.skillWorkshopError, + inspectingKey: state.skillWorkshopInspectingKey, + proposals: state.skillWorkshopProposals, + selectedKey: state.skillWorkshopSelectedKey, + statusFilter: state.skillWorkshopStatusFilter, + query: state.skillWorkshopQuery, + filePreviewKey: state.skillWorkshopFilePreviewKey, + filePreviewQuery: state.skillWorkshopFilePreviewQuery, + queueWidth: state.skillWorkshopQueueWidth, + mode: state.skillWorkshopMode, + actionBusy: state.skillWorkshopActionBusy, + actionNotice: state.skillWorkshopActionNotice, + revisionKey: state.skillWorkshopRevisionKey, + revisionDraft: state.skillWorkshopRevisionDraft, + assistantName: state.assistantName, + counts: countSkillWorkshopProposals(state.skillWorkshopProposals), + onStatusFilterChange: (status) => { + state.skillWorkshopStatusFilter = status; + selectVisibleFallback( + m.filterSkillWorkshopProposals( + state.skillWorkshopProposals, + status, + state.skillWorkshopQuery, + ), + ); + }, + onQueryChange: (query) => { + state.skillWorkshopQuery = query; + selectVisibleFallback( + m.filterSkillWorkshopProposals( + state.skillWorkshopProposals, + state.skillWorkshopStatusFilter, + query, + ), + ); + }, + onFilePreviewQueryChange: (query) => + (state.skillWorkshopFilePreviewQuery = query), + onQueueWidthChange: (width) => (state.skillWorkshopQueueWidth = width), + onModeChange: (mode) => setSkillWorkshopMode(state, mode), + onSelect: (key) => { + state.skillWorkshopFilePreviewKey = null; + selectSkillWorkshopProposal(state, key); + }, + onPrev: () => selectRelativeProposal(-1), + onNext: () => selectRelativeProposal(1), + onApply: (key) => void runSkillWorkshopLifecycleAction(state, "apply", key), + onRevise: (key) => { + state.skillWorkshopRevisionKey = key; + state.skillWorkshopRevisionDraft = ""; + }, + onReject: (key) => void runSkillWorkshopLifecycleAction(state, "reject", key), + onRevisionDraftChange: (draft) => (state.skillWorkshopRevisionDraft = draft), + onRevisionCancel: () => { + state.skillWorkshopRevisionKey = null; + state.skillWorkshopRevisionDraft = ""; + }, + onRevisionSubmit: (key) => + void requestSkillWorkshopRevision(state, key, (message, proposal) => + sendSkillWorkshopRevisionRequest(state, message, proposal), + ), + onPreviewFile: (key, path) => { + state.skillWorkshopSelectedKey = key; + state.skillWorkshopFilePreviewKey = path; + }, + onClosePreview: () => { + state.skillWorkshopFilePreviewKey = null; + state.skillWorkshopFilePreviewQuery = ""; + }, + }); + }) + : nothing + } + ${ + state.tab === "nodes" + ? renderLazyView(lazyNodes, (m) => + m.renderNodes({ + loading: state.nodesLoading, + nodes: state.nodes, + devicesLoading: state.devicesLoading, + devicesError: state.devicesError, + devicesList: state.devicesList, + configForm: + state.configForm ?? + (state.configSnapshot?.config as Record | null), + configLoading: state.configLoading, + configSaving: state.configSaving, + configDirty: state.configFormDirty, + configFormMode: state.configFormMode, + execApprovalsLoading: state.execApprovalsLoading, + execApprovalsSaving: state.execApprovalsSaving, + execApprovalsDirty: state.execApprovalsDirty, + execApprovalsSnapshot: state.execApprovalsSnapshot, + execApprovalsForm: state.execApprovalsForm, + execApprovalsSelectedAgent: state.execApprovalsSelectedAgent, + execApprovalsTarget: state.execApprovalsTarget, + execApprovalsTargetNodeId: state.execApprovalsTargetNodeId, + onRefresh: () => void loadNodes(state), + onDevicesRefresh: () => void loadDevices(state), + onDeviceApprove: (requestId) => void approveDevicePairing(state, requestId), + onDeviceReject: (requestId) => void rejectDevicePairing(state, requestId), + onDeviceRotate: (deviceId, role, scopes) => + void rotateDeviceToken(state, { deviceId, role, scopes }), + onDeviceRevoke: (deviceId, role) => + void revokeDeviceToken(state, { deviceId, role }), + onLoadConfig: () => void loadConfig(state, { discardPendingChanges: true }), + onLoadExecApprovals: () => { + const target = + state.execApprovalsTarget === "node" && state.execApprovalsTargetNodeId + ? { kind: "node" as const, nodeId: state.execApprovalsTargetNodeId } + : { kind: "gateway" as const }; + void loadExecApprovals(state, target); + }, + onBindDefault: (nodeId) => { + if (nodeId) { + updateConfigFormValue(state, ["tools", "exec", "node"], nodeId); + } else { + removeConfigFormValue(state, ["tools", "exec", "node"]); + } + }, + onBindAgent: (agentIndex, nodeId) => { + const basePathLocal = ["agents", "list", agentIndex, "tools", "exec", "node"]; + if (nodeId) { + updateConfigFormValue(state, basePathLocal, nodeId); + } else { + removeConfigFormValue(state, basePathLocal); + } + }, + onSaveBindings: () => void saveConfig(state), + onExecApprovalsTargetChange: (kind, nodeId) => { + state.execApprovalsTarget = kind; + state.execApprovalsTargetNodeId = nodeId; + state.execApprovalsSnapshot = null; + state.execApprovalsForm = null; + state.execApprovalsDirty = false; + state.execApprovalsSelectedAgent = null; + }, + onExecApprovalsSelectAgent: (agentId) => { + state.execApprovalsSelectedAgent = agentId; + }, + onExecApprovalsPatch: (path, value) => + updateExecApprovalsFormValue(state, path, value), + onExecApprovalsRemove: (path) => removeExecApprovalsFormValue(state, path), + onSaveExecApprovals: () => { + const target = + state.execApprovalsTarget === "node" && state.execApprovalsTargetNodeId + ? { kind: "node" as const, nodeId: state.execApprovalsTargetNodeId } + : { kind: "gateway" as const }; + void saveExecApprovals(state, target); + }, + }), + ) + : nothing + } + ${ + state.tab === "chat" + ? renderMeasured( + state, + "chat", + { + messageCount: state.chatMessages.length, + toolMessageCount: state.chatToolMessages.length, + streamSegmentCount: state.chatStreamSegments.length, + queueCount: state.chatQueue.length, + }, + () => + renderChat({ + sessionKey: state.sessionKey, + onSessionKeyChange: (next) => { + switchChatSession(state, next); + }, + thinkingLevel: state.chatThinkingLevel, + showThinking, + showToolCalls, + loading: state.chatLoading, + sending: state.chatSending, + compactionStatus: state.compactionStatus, + fallbackStatus: state.fallbackStatus, + assistantAvatarUrl: chatAvatarUrl, + messages: state.chatMessages, + sideResult: state.chatSideResult, + toolMessages: state.chatToolMessages, + streamSegments: state.chatStreamSegments, + stream: state.chatStream, + streamStartedAt: state.chatStreamStartedAt, + draft: state.chatMessage, + queue: state.chatQueue, + realtimeTalkActive: state.realtimeTalkActive, + realtimeTalkStatus: state.realtimeTalkStatus, + realtimeTalkDetail: state.realtimeTalkDetail, + realtimeTalkTranscript: state.realtimeTalkTranscript, + realtimeTalkConversation: state.realtimeTalkConversation, + realtimeTalkOptionsOpen: state.realtimeTalkOptionsOpen, + realtimeTalkOptions: state.realtimeTalkOptions, + connected: state.connected, + canSend: state.connected, + disabledReason: chatDisabledReason, + error: chatViewError, + runStatus: state.chatRunStatus, + onDismissError: () => dismissChatError(state), + sessions: state.sessionsResult, + composerControls: renderGuardedChatControls(state), + workspaceFiles: { + agentId: chatAgentId, + list: + chatWorkspaceFiles.list?.agentId === chatAgentId + ? chatWorkspaceFiles.list + : null, + loading: chatWorkspaceFiles.loading, + error: chatWorkspaceFiles.error, + activeName: chatWorkspaceFiles.activeName, + onRefresh: refreshChatWorkspaceFiles, + onOpenFile: openChatWorkspaceFile, + }, + autoExpandToolCalls: false, + onRefresh: () => { + state.chatSideResult = null; + state.resetToolStream(); + void refreshChat(state, { awaitHistory: true, scheduleScroll: false }); + }, + onChatScroll: (event) => state.handleChatScroll(event), + getDraft: () => state.chatMessage, + onDraftChange: (next) => state.handleChatDraftChange(next), + onRequestUpdate: requestHostUpdate, + onHistoryKeydown: (input) => state.handleChatInputHistoryKey(input), + attachments: state.chatAttachments, + onAttachmentsChange: (next) => (state.chatAttachments = next), + onSend: () => void state.handleSendChat(), + onCompact: () => void state.handleSendChat("/compact", { restoreDraft: true }), + onOpenSessionCheckpoints: () => { + state.sessionsExpandedCheckpointKey = state.sessionKey; + state.setTab("sessions" as import("./navigation.ts").Tab); + void loadSessions(state, { + ...createChatSessionsLoadOverrides(state), + ...scopedAgentListParamsForSession(state, state.sessionKey), + }); + }, + onToggleRealtimeTalk: () => void state.toggleRealtimeTalk(), + onToggleRealtimeTalkOptions: () => { + state.realtimeTalkOptionsOpen = !state.realtimeTalkOptionsOpen; + }, + onRealtimeTalkOptionsChange: (next) => state.updateRealtimeTalkOptions(next), + canAbort: hasAbortableSessionRun(state), + onAbort: () => void state.handleAbortChat({ preserveDraft: true }), + onQueueRemove: (id) => state.removeQueuedMessage(id), + onQueueRetry: (id) => void state.retryQueuedChatMessage(id), + onQueueSteer: (id) => void state.steerQueuedChatMessage(id), + onDismissSideResult: () => { + state.chatSideResult = null; + }, + onNewSession: () => void createChatSession(state), + onClearHistory: runUiTask(async () => { + if (!state.client || !state.connected) { + return; + } + const hadActiveRun = hasAbortableSessionRun(state); + try { + await state.client.request("sessions.reset", { + key: state.sessionKey, + ...scopedAgentParamsForSession(state, state.sessionKey), + }); + state.chatMessages = []; + state.chatSideResult = null; + reconcileChatRunLifecycle( + state as unknown as Parameters[0], + { + outcome: hadActiveRun ? "interrupted" : undefined, + sessionStatus: "killed", + runId: state.chatRunId, + sessionKey: state.sessionKey, + clearLocalRun: true, + clearChatStream: true, + clearToolStream: true, + clearSideResultTerminalRuns: true, + clearRunStatus: !hadActiveRun, + }, + ); + await loadChatHistory(state); + } catch (err) { + state.lastError = String(err); + state.chatError = state.lastError; + } + }), + agentsList: state.agentsList, + currentAgentId: chatAgentId, + fullMessageAgentId: scopedAgentParamsForSession(state, state.sessionKey) + .agentId, + onAgentChange: (agentId: string) => { + switchChatSession(state, buildAgentMainSessionKey({ agentId })); + }, + onNavigateToAgent: () => { + state.agentsSelectedId = resolvedAgentId; + state.setTab("agents" as import("./navigation.ts").Tab); + }, + onSessionSelect: (key: string) => { + switchChatSession(state, key); + }, + showNewMessages: state.chatNewMessagesBelow && !state.chatManualRefreshInFlight, + onScrollToBottom: () => state.scrollToBottom(), + // Sidebar props for tool output viewing + sidebarOpen: state.sidebarOpen, + sidebarContent: state.sidebarContent, + sidebarError: state.sidebarError, + splitRatio: state.splitRatio, + canvasPluginSurfaceUrl: state.hello?.pluginSurfaceUrls?.canvas ?? null, + onOpenSidebar: (content) => state.handleOpenSidebar(content), + onCloseSidebar: () => state.handleCloseSidebar(), + onSplitRatioChange: (ratio: number) => state.handleSplitRatioChange(ratio), + assistantName: state.assistantName, + assistantAvatar: effectiveAssistantAvatar, + userName: state.userName ?? null, + userAvatar: state.userAvatar ?? null, + localMediaPreviewRoots: state.localMediaPreviewRoots, + embedSandboxMode: state.embedSandboxMode, + allowExternalEmbedUrls: state.allowExternalEmbedUrls, + assistantAttachmentAuthToken: resolveAssistantAttachmentAuthToken(state), + basePath: state.basePath ?? "", + }), + ) + : nothing + } + ${ + isSettingsTab(state.tab) && state.tab !== "debug" && state.tab !== "logs" + ? renderSettingsWorkspace(state, renderConfigTabForActiveTab()) + : renderConfigTabForActiveTab() + } + ${ + state.tab === "debug" + ? renderSettingsWorkspace( + state, + renderLazyView(lazyDebug, (m) => + m.renderDebug({ + loading: state.debugLoading, + status: state.debugStatus, + health: state.debugHealth, + models: state.debugModels, + heartbeat: state.debugHeartbeat, + eventLog: state.eventLog, + methods: (state.hello?.features?.methods ?? []).toSorted(), + callMethod: state.debugCallMethod, + callParams: state.debugCallParams, + callResult: state.debugCallResult, + callError: state.debugCallError, + onCallMethodChange: (next) => (state.debugCallMethod = next), + onCallParamsChange: (next) => (state.debugCallParams = next), + onRefresh: () => void loadDebug(state), + onCall: () => void callDebugMethod(state), + }), + ), + ) + : nothing + } + ${ + state.tab === "logs" + ? renderSettingsWorkspace( + state, + renderLazyView(lazyLogs, (m) => + m.renderLogs({ + loading: state.logsLoading, + error: state.logsError, + file: state.logsFile, + entries: state.logsEntries, + filterText: state.logsFilterText, + levelFilters: state.logsLevelFilters, + autoFollow: state.logsAutoFollow, + truncated: state.logsTruncated, + onFilterTextChange: (next) => (state.logsFilterText = next), + onLevelToggle: (level, enabled) => { + state.logsLevelFilters = { ...state.logsLevelFilters, [level]: enabled }; + }, + onToggleAutoFollow: (next) => (state.logsAutoFollow = next), + onRefresh: () => void loadLogs(state, { reset: true }), + onExport: (lines, label) => state.exportLogs(lines, label), + onScroll: (event) => state.handleLogsScroll(event), + }), + ), + ) + : nothing + } + ${ + state.tab === "dreams" + ? renderDreaming({ + active: dreamingOn, + selectedAgentId: dreamingSelectedAgentId, + agentOptions: dreamingAgentOptions, + shortTermCount: state.dreamingStatus?.shortTermCount ?? 0, + groundedSignalCount: state.dreamingStatus?.groundedSignalCount ?? 0, + totalSignalCount: state.dreamingStatus?.totalSignalCount ?? 0, + promotedCount: state.dreamingStatus?.promotedToday ?? 0, + phases: state.dreamingStatus?.phases ?? undefined, + shortTermEntries: state.dreamingStatus?.shortTermEntries ?? [], + promotedEntries: state.dreamingStatus?.promotedEntries ?? [], + dreamingOf: null, + nextCycle: dreamingNextCycle, + timezone: state.dreamingStatus?.timezone ?? null, + statusLoading: state.dreamingStatusLoading, + statusError: state.dreamingStatusError, + modeSaving: state.dreamingModeSaving, + dreamDiaryLoading: state.dreamDiaryLoading, + dreamDiaryActionLoading: state.dreamDiaryActionLoading, + dreamDiaryActionMessage: state.dreamDiaryActionMessage, + dreamDiaryActionArchivePath: state.dreamDiaryActionArchivePath, + dreamDiaryError: state.dreamDiaryError, + dreamDiaryPath: state.dreamDiaryPath, + dreamDiaryContent: state.dreamDiaryContent, + memoryWikiEnabled: isPluginEnabledInConfigSnapshot( + state.configSnapshot, + "memory-wiki", + { enabledByDefault: false }, + ), + wikiImportInsightsLoading: state.wikiImportInsightsLoading, + wikiImportInsightsError: state.wikiImportInsightsError, + wikiImportInsights: state.wikiImportInsights, + wikiMemoryPalaceLoading: state.wikiMemoryPalaceLoading, + wikiMemoryPalaceError: state.wikiMemoryPalaceError, + wikiMemoryPalace: state.wikiMemoryPalace, + onRefresh: refreshDreaming, + onSelectAgent: (agentId: string) => { + state.selectedAgentId = agentId; + switchChatSession(state, resolvePreferredSessionForAgent(state, agentId)); + void loadDreamingStatus(state); + void loadDreamDiary(state); + }, + onRefreshDiary: () => { + syncDreamingSelectedAgent(); + void loadDreamDiary(state); + }, + onRefreshImports: () => { + void (async () => { + await loadConfig(state); + await loadWikiImportInsights(state); + })(); }, - onClosePreview: () => { - state.skillWorkshopFilePreviewKey = null; - state.skillWorkshopFilePreviewQuery = ""; + onRefreshMemoryPalace: () => { + void (async () => { + await loadConfig(state); + await loadWikiMemoryPalace(state); + })(); }, - }); - }) - : nothing} - ${state.tab === "nodes" - ? renderLazyView(lazyNodes, (m) => - m.renderNodes({ - loading: state.nodesLoading, - nodes: state.nodes, - devicesLoading: state.devicesLoading, - devicesError: state.devicesError, - devicesList: state.devicesList, - configForm: - state.configForm ?? - (state.configSnapshot?.config as Record | null), - configLoading: state.configLoading, - configSaving: state.configSaving, - configDirty: state.configFormDirty, - configFormMode: state.configFormMode, - execApprovalsLoading: state.execApprovalsLoading, - execApprovalsSaving: state.execApprovalsSaving, - execApprovalsDirty: state.execApprovalsDirty, - execApprovalsSnapshot: state.execApprovalsSnapshot, - execApprovalsForm: state.execApprovalsForm, - execApprovalsSelectedAgent: state.execApprovalsSelectedAgent, - execApprovalsTarget: state.execApprovalsTarget, - execApprovalsTargetNodeId: state.execApprovalsTargetNodeId, - onRefresh: () => void loadNodes(state), - onDevicesRefresh: () => void loadDevices(state), - onDeviceApprove: (requestId) => void approveDevicePairing(state, requestId), - onDeviceReject: (requestId) => void rejectDevicePairing(state, requestId), - onDeviceRotate: (deviceId, role, scopes) => - void rotateDeviceToken(state, { deviceId, role, scopes }), - onDeviceRevoke: (deviceId, role) => - void revokeDeviceToken(state, { deviceId, role }), - onLoadConfig: () => void loadConfig(state, { discardPendingChanges: true }), - onLoadExecApprovals: () => { - const target = - state.execApprovalsTarget === "node" && state.execApprovalsTargetNodeId - ? { kind: "node" as const, nodeId: state.execApprovalsTargetNodeId } - : { kind: "gateway" as const }; - void loadExecApprovals(state, target); + onOpenConfig: () => void openConfigFile(state), + onOpenWikiPage: (lookup: string) => openWikiPage(lookup), + onBackfillDiary: () => { + syncDreamingSelectedAgent(); + void backfillDreamDiary(state); }, - onBindDefault: (nodeId) => { - if (nodeId) { - updateConfigFormValue(state, ["tools", "exec", "node"], nodeId); - } else { - removeConfigFormValue(state, ["tools", "exec", "node"]); - } + onCopyDreamingArchivePath: () => { + void copyDreamingArchivePath(state); }, - onBindAgent: (agentIndex, nodeId) => { - const basePathLocal = ["agents", "list", agentIndex, "tools", "exec", "node"]; - if (nodeId) { - updateConfigFormValue(state, basePathLocal, nodeId); - } else { - removeConfigFormValue(state, basePathLocal); - } + onDedupeDreamDiary: () => { + syncDreamingSelectedAgent(); + void dedupeDreamDiary(state); }, - onSaveBindings: () => void saveConfig(state), - onExecApprovalsTargetChange: (kind, nodeId) => { - state.execApprovalsTarget = kind; - state.execApprovalsTargetNodeId = nodeId; - state.execApprovalsSnapshot = null; - state.execApprovalsForm = null; - state.execApprovalsDirty = false; - state.execApprovalsSelectedAgent = null; + onResetDiary: () => { + syncDreamingSelectedAgent(); + void resetDreamDiary(state); }, - onExecApprovalsSelectAgent: (agentId) => { - state.execApprovalsSelectedAgent = agentId; + onResetGroundedShortTerm: () => { + syncDreamingSelectedAgent(); + void resetGroundedShortTerm(state); }, - onExecApprovalsPatch: (path, value) => - updateExecApprovalsFormValue(state, path, value), - onExecApprovalsRemove: (path) => removeExecApprovalsFormValue(state, path), - onSaveExecApprovals: () => { - const target = - state.execApprovalsTarget === "node" && state.execApprovalsTargetNodeId - ? { kind: "node" as const, nodeId: state.execApprovalsTargetNodeId } - : { kind: "gateway" as const }; - void saveExecApprovals(state, target); + onRepairDreamingArtifacts: () => { + syncDreamingSelectedAgent(); + void repairDreamingArtifacts(state); }, - }), - ) - : nothing} - ${state.tab === "chat" - ? renderMeasured( - state, - "chat", - { - messageCount: state.chatMessages.length, - toolMessageCount: state.chatToolMessages.length, - streamSegmentCount: state.chatStreamSegments.length, - queueCount: state.chatQueue.length, - }, - () => - renderChat({ - sessionKey: state.sessionKey, - onSessionKeyChange: (next) => { - switchChatSession(state, next); - }, - thinkingLevel: state.chatThinkingLevel, - showThinking, - showToolCalls, - loading: state.chatLoading, - sending: state.chatSending, - compactionStatus: state.compactionStatus, - fallbackStatus: state.fallbackStatus, - assistantAvatarUrl: chatAvatarUrl, - messages: state.chatMessages, - sideResult: state.chatSideResult, - toolMessages: state.chatToolMessages, - streamSegments: state.chatStreamSegments, - stream: state.chatStream, - streamStartedAt: state.chatStreamStartedAt, - draft: state.chatMessage, - queue: state.chatQueue, - realtimeTalkActive: state.realtimeTalkActive, - realtimeTalkStatus: state.realtimeTalkStatus, - realtimeTalkDetail: state.realtimeTalkDetail, - realtimeTalkTranscript: state.realtimeTalkTranscript, - realtimeTalkConversation: state.realtimeTalkConversation, - realtimeTalkOptionsOpen: state.realtimeTalkOptionsOpen, - realtimeTalkOptions: state.realtimeTalkOptions, - connected: state.connected, - canSend: state.connected, - disabledReason: chatDisabledReason, - error: chatViewError, - runStatus: state.chatRunStatus, - onDismissError: () => dismissChatError(state), - sessions: state.sessionsResult, - composerControls: renderGuardedChatControls(state), - workspaceFiles: { - agentId: chatAgentId, - list: - chatWorkspaceFiles.list?.agentId === chatAgentId - ? chatWorkspaceFiles.list - : null, - loading: chatWorkspaceFiles.loading, - error: chatWorkspaceFiles.error, - activeName: chatWorkspaceFiles.activeName, - onRefresh: refreshChatWorkspaceFiles, - onOpenFile: openChatWorkspaceFile, - }, - autoExpandToolCalls: false, - onRefresh: () => { - state.chatSideResult = null; - state.resetToolStream(); - void refreshChat(state, { awaitHistory: true, scheduleScroll: false }); - }, - onChatScroll: (event) => state.handleChatScroll(event), - getDraft: () => state.chatMessage, - onDraftChange: (next) => state.handleChatDraftChange(next), - onRequestUpdate: requestHostUpdate, - onHistoryKeydown: (input) => state.handleChatInputHistoryKey(input), - attachments: state.chatAttachments, - onAttachmentsChange: (next) => (state.chatAttachments = next), - onSend: () => void state.handleSendChat(), - onCompact: () => void state.handleSendChat("/compact", { restoreDraft: true }), - onOpenSessionCheckpoints: () => { - state.sessionsExpandedCheckpointKey = state.sessionKey; - state.setTab("sessions" as import("./navigation.ts").Tab); - void loadSessions(state, { - ...createChatSessionsLoadOverrides(state), - ...scopedAgentListParamsForSession(state, state.sessionKey), - }); - }, - onToggleRealtimeTalk: () => void state.toggleRealtimeTalk(), - onToggleRealtimeTalkOptions: () => { - state.realtimeTalkOptionsOpen = !state.realtimeTalkOptionsOpen; - }, - onRealtimeTalkOptionsChange: (next) => state.updateRealtimeTalkOptions(next), - canAbort: hasAbortableSessionRun(state), - onAbort: () => void state.handleAbortChat({ preserveDraft: true }), - onQueueRemove: (id) => state.removeQueuedMessage(id), - onQueueRetry: (id) => void state.retryQueuedChatMessage(id), - onQueueSteer: (id) => void state.steerQueuedChatMessage(id), - onDismissSideResult: () => { - state.chatSideResult = null; - }, - onNewSession: () => void createChatSession(state), - onClearHistory: runUiTask(async () => { - if (!state.client || !state.connected) { - return; - } - const hadActiveRun = hasAbortableSessionRun(state); - try { - await state.client.request("sessions.reset", { - key: state.sessionKey, - ...scopedAgentParamsForSession(state, state.sessionKey), - }); - state.chatMessages = []; - state.chatSideResult = null; - reconcileChatRunLifecycle( - state as unknown as Parameters[0], - { - outcome: hadActiveRun ? "interrupted" : undefined, - sessionStatus: "killed", - runId: state.chatRunId, - sessionKey: state.sessionKey, - clearLocalRun: true, - clearChatStream: true, - clearToolStream: true, - clearSideResultTerminalRuns: true, - clearRunStatus: !hadActiveRun, - }, - ); - await loadChatHistory(state); - } catch (err) { - state.lastError = String(err); - state.chatError = state.lastError; - } - }), - agentsList: state.agentsList, - currentAgentId: chatAgentId, - fullMessageAgentId: scopedAgentParamsForSession(state, state.sessionKey).agentId, - onAgentChange: (agentId: string) => { - switchChatSession(state, buildAgentMainSessionKey({ agentId })); - }, - onNavigateToAgent: () => { - state.agentsSelectedId = resolvedAgentId; - state.setTab("agents" as import("./navigation.ts").Tab); - }, - onSessionSelect: (key: string) => { - switchChatSession(state, key); - }, - showNewMessages: state.chatNewMessagesBelow && !state.chatManualRefreshInFlight, - onScrollToBottom: () => state.scrollToBottom(), - // Sidebar props for tool output viewing - sidebarOpen: state.sidebarOpen, - sidebarContent: state.sidebarContent, - sidebarError: state.sidebarError, - splitRatio: state.splitRatio, - canvasPluginSurfaceUrl: state.hello?.pluginSurfaceUrls?.canvas ?? null, - onOpenSidebar: (content) => state.handleOpenSidebar(content), - onCloseSidebar: () => state.handleCloseSidebar(), - onSplitRatioChange: (ratio: number) => state.handleSplitRatioChange(ratio), - assistantName: state.assistantName, - assistantAvatar: effectiveAssistantAvatar, - userName: state.userName ?? null, - userAvatar: state.userAvatar ?? null, - localMediaPreviewRoots: state.localMediaPreviewRoots, - embedSandboxMode: state.embedSandboxMode, - allowExternalEmbedUrls: state.allowExternalEmbedUrls, - assistantAttachmentAuthToken: resolveAssistantAttachmentAuthToken(state), - basePath: state.basePath ?? "", - }), - ) - : nothing} - ${isSettingsTab(state.tab) && state.tab !== "debug" && state.tab !== "logs" - ? renderSettingsWorkspace(state, renderConfigTabForActiveTab()) - : renderConfigTabForActiveTab()} - ${state.tab === "debug" - ? renderSettingsWorkspace( - state, - renderLazyView(lazyDebug, (m) => - m.renderDebug({ - loading: state.debugLoading, - status: state.debugStatus, - health: state.debugHealth, - models: state.debugModels, - heartbeat: state.debugHeartbeat, - eventLog: state.eventLog, - methods: (state.hello?.features?.methods ?? []).toSorted(), - callMethod: state.debugCallMethod, - callParams: state.debugCallParams, - callResult: state.debugCallResult, - callError: state.debugCallError, - onCallMethodChange: (next) => (state.debugCallMethod = next), - onCallParamsChange: (next) => (state.debugCallParams = next), - onRefresh: () => void loadDebug(state), - onCall: () => void callDebugMethod(state), - }), - ), - ) - : nothing} - ${state.tab === "logs" - ? renderSettingsWorkspace( - state, - renderLazyView(lazyLogs, (m) => - m.renderLogs({ - loading: state.logsLoading, - error: state.logsError, - file: state.logsFile, - entries: state.logsEntries, - filterText: state.logsFilterText, - levelFilters: state.logsLevelFilters, - autoFollow: state.logsAutoFollow, - truncated: state.logsTruncated, - onFilterTextChange: (next) => (state.logsFilterText = next), - onLevelToggle: (level, enabled) => { - state.logsLevelFilters = { ...state.logsLevelFilters, [level]: enabled }; - }, - onToggleAutoFollow: (next) => (state.logsAutoFollow = next), - onRefresh: () => void loadLogs(state, { reset: true }), - onExport: (lines, label) => state.exportLogs(lines, label), - onScroll: (event) => state.handleLogsScroll(event), - }), - ), - ) - : nothing} - ${state.tab === "dreams" - ? renderDreaming({ - active: dreamingOn, - selectedAgentId: dreamingSelectedAgentId, - agentOptions: dreamingAgentOptions, - shortTermCount: state.dreamingStatus?.shortTermCount ?? 0, - groundedSignalCount: state.dreamingStatus?.groundedSignalCount ?? 0, - totalSignalCount: state.dreamingStatus?.totalSignalCount ?? 0, - promotedCount: state.dreamingStatus?.promotedToday ?? 0, - phases: state.dreamingStatus?.phases ?? undefined, - shortTermEntries: state.dreamingStatus?.shortTermEntries ?? [], - promotedEntries: state.dreamingStatus?.promotedEntries ?? [], - dreamingOf: null, - nextCycle: dreamingNextCycle, - timezone: state.dreamingStatus?.timezone ?? null, - statusLoading: state.dreamingStatusLoading, - statusError: state.dreamingStatusError, - modeSaving: state.dreamingModeSaving, - dreamDiaryLoading: state.dreamDiaryLoading, - dreamDiaryActionLoading: state.dreamDiaryActionLoading, - dreamDiaryActionMessage: state.dreamDiaryActionMessage, - dreamDiaryActionArchivePath: state.dreamDiaryActionArchivePath, - dreamDiaryError: state.dreamDiaryError, - dreamDiaryPath: state.dreamDiaryPath, - dreamDiaryContent: state.dreamDiaryContent, - memoryWikiEnabled: isPluginEnabledInConfigSnapshot( - state.configSnapshot, - "memory-wiki", - { enabledByDefault: false }, - ), - wikiImportInsightsLoading: state.wikiImportInsightsLoading, - wikiImportInsightsError: state.wikiImportInsightsError, - wikiImportInsights: state.wikiImportInsights, - wikiMemoryPalaceLoading: state.wikiMemoryPalaceLoading, - wikiMemoryPalaceError: state.wikiMemoryPalaceError, - wikiMemoryPalace: state.wikiMemoryPalace, - onRefresh: refreshDreaming, - onSelectAgent: (agentId: string) => { - state.selectedAgentId = agentId; - switchChatSession(state, resolvePreferredSessionForAgent(state, agentId)); - void loadDreamingStatus(state); - void loadDreamDiary(state); - }, - onRefreshDiary: () => { - syncDreamingSelectedAgent(); - void loadDreamDiary(state); - }, - onRefreshImports: () => { - void (async () => { - await loadConfig(state); - await loadWikiImportInsights(state); - })(); - }, - onRefreshMemoryPalace: () => { - void (async () => { - await loadConfig(state); - await loadWikiMemoryPalace(state); - })(); - }, - onOpenConfig: () => void openConfigFile(state), - onOpenWikiPage: (lookup: string) => openWikiPage(lookup), - onBackfillDiary: () => { - syncDreamingSelectedAgent(); - void backfillDreamDiary(state); - }, - onCopyDreamingArchivePath: () => { - void copyDreamingArchivePath(state); - }, - onDedupeDreamDiary: () => { - syncDreamingSelectedAgent(); - void dedupeDreamDiary(state); - }, - onResetDiary: () => { - syncDreamingSelectedAgent(); - void resetDreamDiary(state); - }, - onResetGroundedShortTerm: () => { - syncDreamingSelectedAgent(); - void resetGroundedShortTerm(state); - }, - onRepairDreamingArtifacts: () => { - syncDreamingSelectedAgent(); - void repairDreamingArtifacts(state); - }, - onRequestUpdate: requestHostUpdate, - }) - : nothing} + onRequestUpdate: requestHostUpdate, + }) + : nothing + } ${renderExecApprovalPrompt(state)} ${renderGatewayUrlConfirmation(state)} ${renderDreamingRestartConfirmation({ diff --git a/ui/src/ui/chat/chat-queue.ts b/ui/src/ui/chat/chat-queue.ts index 2776924072cbb..66c5ae83eb222 100644 --- a/ui/src/ui/chat/chat-queue.ts +++ b/ui/src/ui/chat/chat-queue.ts @@ -42,51 +42,61 @@ export function renderChatQueue(props: ChatQueueProps) { class="chat-queue__item ${item.kind === "steered" ? "chat-queue__item--steered" : ""}" >
- ${item.kind === "steered" - ? html`Steered` - : nothing} + ${ + item.kind === "steered" + ? html`Steered` + : nothing + } ${stateLabel ? html`${stateLabel}` : nothing}
- ${item.text || - (item.attachments?.length ? `Image (${item.attachments.length})` : "")} + ${ + item.text || + (item.attachments?.length ? `Image (${item.attachments.length})` : "") + }
- ${item.sendError - ? html`
${item.sendError}
` - : nothing} + ${ + item.sendError + ? html`
${item.sendError}
` + : nothing + }
- ${item.sendState === "failed" && props.onQueueRetry - ? html` - - ` - : nothing} - ${props.canAbort && - props.onQueueSteer && - item.kind !== "steered" && - !item.sendState && - !item.localCommandName - ? html` - - ` - : nothing} + ${ + item.sendState === "failed" && props.onQueueRetry + ? html` + + ` + : nothing + } + ${ + props.canAbort && + props.onQueueSteer && + item.kind !== "steered" && + !item.sendState && + !item.localCommandName + ? html` + + ` + : nothing + } - ` - : nothing} + ${ + canRenderCompact + ? html` + + ` + : nothing + }
`; } diff --git a/ui/src/ui/chat/grouped-render.ts b/ui/src/ui/chat/grouped-render.ts index 3c2d86506e1a9..249f13acb5248 100644 --- a/ui/src/ui/chat/grouped-render.ts +++ b/ui/src/ui/chat/grouped-render.ts @@ -506,9 +506,9 @@ export function renderMessageGroup(
- ${activityExpanded - ? html` -
- ${group.messages.map((item, index) => - renderGroupedMessage( - item.message, - item.key, - { - isStreaming: group.isStreaming && index === group.messages.length - 1, - sessionKey: opts.sessionKey, - agentId: opts.agentId, - duplicateCount: item.duplicateCount ?? 1, - showReasoning: opts.showReasoning, - showToolCalls: opts.showToolCalls ?? true, - autoExpandToolCalls: opts.autoExpandToolCalls ?? false, - isToolMessageExpanded: opts.isToolMessageExpanded, - onToggleToolMessageExpanded: opts.onToggleToolMessageExpanded, - isToolExpanded: opts.isToolExpanded, - onToggleToolExpanded: opts.onToggleToolExpanded, - onRequestUpdate: opts.onRequestUpdate, - canvasPluginSurfaceUrl: opts.canvasPluginSurfaceUrl, - basePath: opts.basePath, - localMediaPreviewRoots: opts.localMediaPreviewRoots, - assistantAttachmentAuthToken: opts.assistantAttachmentAuthToken, - embedSandboxMode: opts.embedSandboxMode, - allowExternalEmbedUrls: opts.allowExternalEmbedUrls, - }, - opts.onOpenSidebar, - ), - )} -
- ` - : nothing} + ${ + activityExpanded + ? html` +
+ ${group.messages.map((item, index) => + renderGroupedMessage( + item.message, + item.key, + { + isStreaming: group.isStreaming && index === group.messages.length - 1, + sessionKey: opts.sessionKey, + agentId: opts.agentId, + duplicateCount: item.duplicateCount ?? 1, + showReasoning: opts.showReasoning, + showToolCalls: opts.showToolCalls ?? true, + autoExpandToolCalls: opts.autoExpandToolCalls ?? false, + isToolMessageExpanded: opts.isToolMessageExpanded, + onToggleToolMessageExpanded: opts.onToggleToolMessageExpanded, + isToolExpanded: opts.isToolExpanded, + onToggleToolExpanded: opts.onToggleToolExpanded, + onRequestUpdate: opts.onRequestUpdate, + canvasPluginSurfaceUrl: opts.canvasPluginSurfaceUrl, + basePath: opts.basePath, + localMediaPreviewRoots: opts.localMediaPreviewRoots, + assistantAttachmentAuthToken: opts.assistantAttachmentAuthToken, + embedSandboxMode: opts.embedSandboxMode, + allowExternalEmbedUrls: opts.allowExternalEmbedUrls, + }, + opts.onOpenSidebar, + ), + )} +
+ ` + : nothing + }
@@ -1000,9 +1008,11 @@ function renderReplyPill(replyTarget: NormalizedMessage["replyTarget"]) {
${icons.messageSquare} - ${replyTarget.kind === "current" - ? "Replying to current message" - : `Replying to ${replyTarget.id}`} + ${ + replyTarget.kind === "current" + ? "Replying to current message" + : `Replying to ${replyTarget.id}` + }
`; @@ -1369,9 +1379,11 @@ function renderAssistantAttachmentStatusCard(params: { >${params.badge}
- ${params.reason - ? html`
${params.reason}
` - : nothing} + ${ + params.reason + ? html`
${params.reason}
` + : nothing + } `; } @@ -1423,22 +1435,26 @@ function renderAssistantAttachments(
${attachment.label} - ${!attachmentUrl - ? html`${availability.status === "checking" ? "Checking..." : "Unavailable"}` - : attachment.isVoiceNote - ? html`Voice note` - : nothing} + ${ + !attachmentUrl + ? html`${availability.status === "checking" ? "Checking..." : "Unavailable"}` + : attachment.isVoiceNote + ? html`Voice note` + : nothing + }
- ${attachmentUrl - ? html`` - : availability.status === "unavailable" - ? html`
- ${availability.reason} -
` - : nothing} + ${ + attachmentUrl + ? html`` + : availability.status === "unavailable" + ? html`
+ ${availability.reason} +
` + : nothing + }
`; } @@ -1767,166 +1783,200 @@ function renderGroupedMessage( return html`
${renderReplyPill(normalizedMessage.replyTarget)} - ${hasActions - ? html`
- ${canExpand - ? renderExpandButton(markdown!, onOpenSidebar!, { - sessionKey: opts.sessionKey, - agentId: opts.agentId, - messageId: shouldFetchFullMessage ? sidebarMessageId : undefined, - }) - : nothing} - ${canCopyMarkdown ? renderCopyAsMarkdownButton(markdown!) : nothing} -
` - : nothing} - ${isToolMessage - ? html` -
- - ${toolMessageExpanded - ? html` -
- ${renderMessageImages(images, imageRenderOptions)} - ${renderAssistantAttachments( - visibleAttachments, - opts.localMediaPreviewRoots ?? [], - opts.basePath, - opts.assistantAttachmentAuthToken, - opts.onRequestUpdate, - )} - ${reasoningMarkdown - ? html`
- ${unsafeHTML(toSanitizedMarkdownHtml(reasoningMarkdown))} -
` - : nothing} - ${jsonResult - ? html`
- - JSON - ${jsonSummaryLabel(jsonResult.parsed)} - -
${jsonResult.pretty}
-
` - : markdown - ? renderMarkdownText(markdown, opts.isStreaming, markdownRenderOptions) - : nothing} - ${hasToolCards - ? singleToolCard && !markdown && !hasImages - ? renderExpandedToolCardContent( - singleToolCard, - opts.sessionKey, - onOpenSidebar, - opts.canvasPluginSurfaceUrl, - opts.embedSandboxMode ?? "scripts", - opts.allowExternalEmbedUrls ?? false, - ) - : renderInlineToolCards(toolCards, { - messageKey, - sessionKey: opts.sessionKey, - agentId: opts.agentId, - onOpenSidebar, - isToolExpanded: opts.isToolExpanded, - onToggleToolExpanded: opts.onToggleToolExpanded, - canvasPluginSurfaceUrl: opts.canvasPluginSurfaceUrl, - embedSandboxMode: opts.embedSandboxMode ?? "scripts", - allowExternalEmbedUrls: opts.allowExternalEmbedUrls ?? false, - }) - : nothing} -
- ` - : nothing} -
- ` - : html` - ${renderMessageImages(images, imageRenderOptions)} - ${renderAssistantAttachments( - visibleAttachments, - opts.localMediaPreviewRoots ?? [], - opts.basePath, - opts.assistantAttachmentAuthToken, - opts.onRequestUpdate, - )} - ${reasoningMarkdown - ? html`
- ${unsafeHTML(toSanitizedMarkdownHtml(reasoningMarkdown))} -
` - : nothing} - ${normalizedRole === "assistant" && assistantViewBlocks.length > 0 - ? html`${assistantViewBlocks.map( - (block) => html`${renderToolPreview(block.preview, "chat_message", { - onOpenSidebar, - rawText: block.rawText ?? null, - canvasPluginSurfaceUrl: opts.canvasPluginSurfaceUrl, - embedSandboxMode: opts.embedSandboxMode ?? "scripts", - })} - ${block.rawText ? renderRawOutputToggle(block.rawText) : nothing}`, - )}` - : nothing} - ${jsonResult - ? html`
- - JSON - ${jsonSummaryLabel(jsonResult.parsed)} - -
${jsonResult.pretty}
-
` - : markdown - ? renderMarkdownText(markdown, opts.isStreaming, markdownRenderOptions) - : nothing} - ${hasToolCards - ? renderInlineToolCards(toolCards, { - messageKey, - sessionKey: opts.sessionKey, - agentId: opts.agentId, - onOpenSidebar, - isToolExpanded: opts.isToolExpanded, - onToggleToolExpanded: opts.onToggleToolExpanded, - canvasPluginSurfaceUrl: opts.canvasPluginSurfaceUrl, - embedSandboxMode: opts.embedSandboxMode ?? "scripts", - allowExternalEmbedUrls: opts.allowExternalEmbedUrls ?? false, - }) - : nothing} - `} - ${duplicateCount > 1 - ? html`
- ×${duplicateCount} -
` - : nothing} + + ${ + toolMessageExpanded + ? html` +
+ ${renderMessageImages(images, imageRenderOptions)} + ${renderAssistantAttachments( + visibleAttachments, + opts.localMediaPreviewRoots ?? [], + opts.basePath, + opts.assistantAttachmentAuthToken, + opts.onRequestUpdate, + )} + ${ + reasoningMarkdown + ? html`
+ ${unsafeHTML(toSanitizedMarkdownHtml(reasoningMarkdown))} +
` + : nothing + } + ${ + jsonResult + ? html`
+ + JSON + ${jsonSummaryLabel(jsonResult.parsed)} + +
${jsonResult.pretty}
+
` + : markdown + ? renderMarkdownText( + markdown, + opts.isStreaming, + markdownRenderOptions, + ) + : nothing + } + ${ + hasToolCards + ? singleToolCard && !markdown && !hasImages + ? renderExpandedToolCardContent( + singleToolCard, + opts.sessionKey, + onOpenSidebar, + opts.canvasPluginSurfaceUrl, + opts.embedSandboxMode ?? "scripts", + opts.allowExternalEmbedUrls ?? false, + ) + : renderInlineToolCards(toolCards, { + messageKey, + sessionKey: opts.sessionKey, + agentId: opts.agentId, + onOpenSidebar, + isToolExpanded: opts.isToolExpanded, + onToggleToolExpanded: opts.onToggleToolExpanded, + canvasPluginSurfaceUrl: opts.canvasPluginSurfaceUrl, + embedSandboxMode: opts.embedSandboxMode ?? "scripts", + allowExternalEmbedUrls: opts.allowExternalEmbedUrls ?? false, + }) + : nothing + } +
+ ` + : nothing + } +
+ ` + : html` + ${renderMessageImages(images, imageRenderOptions)} + ${renderAssistantAttachments( + visibleAttachments, + opts.localMediaPreviewRoots ?? [], + opts.basePath, + opts.assistantAttachmentAuthToken, + opts.onRequestUpdate, + )} + ${ + reasoningMarkdown + ? html`
+ ${unsafeHTML(toSanitizedMarkdownHtml(reasoningMarkdown))} +
` + : nothing + } + ${ + normalizedRole === "assistant" && assistantViewBlocks.length > 0 + ? html`${assistantViewBlocks.map( + (block) => html`${renderToolPreview(block.preview, "chat_message", { + onOpenSidebar, + rawText: block.rawText ?? null, + canvasPluginSurfaceUrl: opts.canvasPluginSurfaceUrl, + embedSandboxMode: opts.embedSandboxMode ?? "scripts", + })} + ${block.rawText ? renderRawOutputToggle(block.rawText) : nothing}`, + )}` + : nothing + } + ${ + jsonResult + ? html`
+ + JSON + ${jsonSummaryLabel(jsonResult.parsed)} + +
${jsonResult.pretty}
+
` + : markdown + ? renderMarkdownText(markdown, opts.isStreaming, markdownRenderOptions) + : nothing + } + ${ + hasToolCards + ? renderInlineToolCards(toolCards, { + messageKey, + sessionKey: opts.sessionKey, + agentId: opts.agentId, + onOpenSidebar, + isToolExpanded: opts.isToolExpanded, + onToggleToolExpanded: opts.onToggleToolExpanded, + canvasPluginSurfaceUrl: opts.canvasPluginSurfaceUrl, + embedSandboxMode: opts.embedSandboxMode ?? "scripts", + allowExternalEmbedUrls: opts.allowExternalEmbedUrls ?? false, + }) + : nothing + } + ` + } + ${ + duplicateCount > 1 + ? html`
+ ×${duplicateCount} +
` + : nothing + } `; } diff --git a/ui/src/ui/chat/run-controls.ts b/ui/src/ui/chat/run-controls.ts index 8fee62a50493f..774245cd6f30e 100644 --- a/ui/src/ui/chat/run-controls.ts +++ b/ui/src/ui/chat/run-controls.ts @@ -22,81 +22,89 @@ export function renderChatRunControls(props: ChatRunControlsProps) { const showSecondary = props.showSecondary ?? true; return html`
- ${showSecondary && !props.canAbort - ? html` - - ` - : nothing} - ${showSecondary - ? html` - - ` - : nothing} - ${props.canAbort - ? html` - - - ` - : html` - + ` + : nothing + } + ${ + showSecondary + ? html` + + ` + : nothing + } + ${ + props.canAbort + ? html` + + + ` + : html` + - `} + ${icons.send} + ${props.isBusy ? t("chat.runControls.queue") : t("chat.runControls.send")} + + ` + }
`; } diff --git a/ui/src/ui/chat/session-controls.ts b/ui/src/ui/chat/session-controls.ts index 585e58420e84a..f699f5067f269 100644 --- a/ui/src/ui/chat/session-controls.ts +++ b/ui/src/ui/chat/session-controls.ts @@ -570,11 +570,13 @@ function renderChatSessionPicker(params: { } }} > - ${compact - ? html`` - : ""} + ${ + compact + ? html`` + : "" + } ${selectedSessionLabel}
`; @@ -260,15 +266,19 @@ export function renderActivity(props: ActivityProps) { aria-label=${t("activity.streamLabel")} @scroll=${props.onScroll} > - ${filtered.length === 0 - ? html` -
- ${props.entries.length === 0 || !hasAnyFilters - ? t("activity.empty") - : t("activity.emptyFiltered")} -
- ` - : filtered.map((entry) => renderEntry(props, entry))} + ${ + filtered.length === 0 + ? html` +
+ ${ + props.entries.length === 0 || !hasAnyFilters + ? t("activity.empty") + : t("activity.emptyFiltered") + } +
+ ` + : filtered.map((entry) => renderEntry(props, entry)) + } `; diff --git a/ui/src/ui/views/agents-panels-overview.ts b/ui/src/ui/views/agents-panels-overview.ts index 037eb62c89784..df2b2815fa1af 100644 --- a/ui/src/ui/views/agents-panels-overview.ts +++ b/ui/src/ui/views/agents-panels-overview.ts @@ -140,13 +140,15 @@ export function renderAgentOverview(params: { - ${configDirty - ? html` -
- You have unsaved config changes. -
- ` - : nothing} + ${ + configDirty + ? html` +
+ You have unsaved config changes. +
+ ` + : nothing + }
Model Selection
@@ -159,13 +161,15 @@ export function renderAgentOverview(params: { @change=${(e: Event) => onModelChange(agent.id, (e.target as HTMLSelectElement).value || null)} > - ${isDefault - ? html` ` - : html` - - `} + ${ + isDefault + ? html` ` + : html` + + ` + } ${buildModelOptions( configForm, effectivePrimary ?? undefined, diff --git a/ui/src/ui/views/agents-panels-status-files.ts b/ui/src/ui/views/agents-panels-status-files.ts index 3dcb8e47b638f..09438fea2581e 100644 --- a/ui/src/ui/views/agents-panels-status-files.ts +++ b/ui/src/ui/views/agents-panels-status-files.ts @@ -251,71 +251,83 @@ export function renderAgentChannels(params: {
${t("agents.channels.lastRefresh", { time: lastSuccessLabel })}
- ${params.error - ? html`
${params.error}
` - : nothing} - ${!params.snapshot - ? html` -
- ${t("agents.channels.loadHint")} -
- ` - : nothing} - ${entries.length === 0 - ? html`
${t("agents.channels.empty")}
` - : html` -
- ${entries.map((entry) => { - const summary = summarizeChannelAccounts(entry.accounts); - const status = summary.total - ? t("agents.channels.connectedCount", { - connected: String(summary.connected), - total: String(summary.total), - }) - : t("agents.channels.noAccounts"); - const configLabel = summary.configured - ? t("agents.channels.configuredCount", { count: String(summary.configured) }) - : t("agents.channels.notConfigured"); - const enabled = summary.total - ? t("agents.channels.enabledCount", { count: String(summary.enabled) }) - : t("common.disabled"); - const extras = resolveChannelExtrasFromConfig({ - configForm: params.configForm, - channelId: entry.id, - fields: CHANNEL_EXTRA_FIELDS, - }); - return html` -
-
-
${entry.label}
-
${entry.id}
-
-
-
${status}
-
${configLabel}
-
${enabled}
- ${summary.configured === 0 - ? html` - - ` - : nothing} - ${extras.length > 0 - ? extras.map((extra) => html`
${extra.label}: ${extra.value}
`) - : nothing} + ${ + params.error + ? html`
${params.error}
` + : nothing + } + ${ + !params.snapshot + ? html` +
+ ${t("agents.channels.loadHint")} +
+ ` + : nothing + } + ${ + entries.length === 0 + ? html`
${t("agents.channels.empty")}
` + : html` +
+ ${entries.map((entry) => { + const summary = summarizeChannelAccounts(entry.accounts); + const status = summary.total + ? t("agents.channels.connectedCount", { + connected: String(summary.connected), + total: String(summary.total), + }) + : t("agents.channels.noAccounts"); + const configLabel = summary.configured + ? t("agents.channels.configuredCount", { count: String(summary.configured) }) + : t("agents.channels.notConfigured"); + const enabled = summary.total + ? t("agents.channels.enabledCount", { count: String(summary.enabled) }) + : t("common.disabled"); + const extras = resolveChannelExtrasFromConfig({ + configForm: params.configForm, + channelId: entry.id, + fields: CHANNEL_EXTRA_FIELDS, + }); + return html` +
+
+
${entry.label}
+
${entry.id}
+
+
+
${status}
+
${configLabel}
+
${enabled}
+ ${ + summary.configured === 0 + ? html` + + ` + : nothing + } + ${ + extras.length > 0 + ? extras.map( + (extra) => html`
${extra.label}: ${extra.value}
`, + ) + : nothing + } +
-
- `; - })} -
- `} + `; + })} +
+ ` + } `; @@ -354,11 +366,13 @@ export function renderAgentCron(params: {
${t("common.enabled")}
- ${params.status - ? params.status.enabled - ? t("common.yes") - : t("common.no") - : t("common.na")} + ${ + params.status + ? params.status.enabled + ? t("common.yes") + : t("common.no") + : t("common.na") + }
@@ -370,51 +384,57 @@ export function renderAgentCron(params: {
${formatNextRun(params.status?.nextWakeAtMs ?? null)}
- ${params.error - ? html`
${params.error}
` - : nothing} + ${ + params.error + ? html`
${params.error}
` + : nothing + }
${t("agents.cronPanel.agentJobsTitle")}
${t("agents.cronPanel.agentJobsSubtitle")}
- ${jobs.length === 0 - ? html`
${t("agents.cronPanel.noJobs")}
` - : html` -
- ${jobs.map( - (job) => html` -
-
-
${job.name}
- ${job.description - ? html`
${job.description}
` - : nothing} -
- ${formatCronSchedule(job)} - - ${job.enabled ? t("common.enabled") : t("common.disabled")} - - ${job.sessionTarget} + ${ + jobs.length === 0 + ? html`
${t("agents.cronPanel.noJobs")}
` + : html` +
+ ${jobs.map( + (job) => html` +
+
+
${job.name}
+ ${ + job.description + ? html`
${job.description}
` + : nothing + } +
+ ${formatCronSchedule(job)} + + ${job.enabled ? t("common.enabled") : t("common.disabled")} + + ${job.sessionTarget} +
+
+
+
${formatCronState(job)}
+
${formatCronPayload(job)}
+
-
-
${formatCronState(job)}
-
${formatCronPayload(job)}
- -
-
- `, - )} -
- `} + `, + )} +
+ ` + }
`; } @@ -484,216 +504,231 @@ export function renderAgentFiles(params: { ${params.agentFilesLoading ? t("common.loading") : t("common.refresh")}
- ${list - ? html`
- ${t("agents.files.workspace")}: ${list.workspace} -
` - : nothing} - ${params.agentFilesError - ? html`
- ${params.agentFilesError} -
` - : nothing} - ${!list - ? html` -
${t("agents.files.loadHint")}
- ` - : files.length === 0 - ? html`
${t("agents.files.empty")}
` - : html` -
- ${files.map((file) => { - const isActive = active === file.name; - const label = file.name.replace(/\.md$/i, ""); - return html` - - `; - })} -
- ${!activeEntry - ? html`
- ${t("agents.files.selectFile")} -
` - : html` -
-
-
${activeEntry.path}
-
-
- - - -
-
- ${activeEntry.missing - ? html` -
- ${t("agents.files.missingHint")} -
- ` - : nothing} - - { - const dialog = e.currentTarget as HTMLDialogElement; - if (e.target === dialog) { - dialog.close(); + ${ + list + ? html`
+ ${t("agents.files.workspace")}: ${list.workspace} +
` + : nothing + } + ${ + params.agentFilesError + ? html`
+ ${params.agentFilesError} +
` + : nothing + } + ${ + !list + ? html` +
${t("agents.files.loadHint")}
+ ` + : files.length === 0 + ? html`
${t("agents.files.empty")}
` + : html` +
+ ${files.map((file) => { + const isActive = active === file.name; + const label = file.name.replace(/\.md$/i, ""); + return html` + + `; + })} +
+ ${ + !activeEntry + ? html`
+ ${t("agents.files.selectFile")} +
` + : html` +
+
+
${activeEntry.path}
-
+
-
-
- ${previewStatusLabel} -
-
- ${estimateReadingTimeLabel(draftWordCount)} - ${t("agents.files.words", { count: String(draftWordCount) })} -
-
- ${draftLineCount} - ${t("agents.files.lines")} -
-
- ${draftByteSize} - ${previewUpdatedLabel} + ${ + activeEntry.missing + ? html` +
+ ${t("agents.files.missingHint")} +
+ ` + : nothing + } + + { + const dialog = e.currentTarget as HTMLDialogElement; + if (e.target === dialog) { + dialog.close(); + } + }} + @close=${(e: Event) => { + const dialog = e.currentTarget as HTMLElement; + dialog + .querySelector(".md-preview-dialog__panel") + ?.classList.remove("fullscreen"); + setPreviewExpandButtonState( + dialog.querySelector(".md-preview-expand-btn"), + false, + ); + }} + > +
+
+
+
+ ${icons.scrollText} + ${getExtensionLabel(activeEntry.name)} +
+
+
+ ${activeEntry.name} +
+
+ ${activePathLabel} +
+
+
+
+ + + +
+
+
+
+ ${previewStatusLabel} +
+
+ ${estimateReadingTimeLabel(draftWordCount)} + ${t("agents.files.words", { count: String(draftWordCount) })} +
+
+ ${draftLineCount} + ${t("agents.files.lines")} +
+
+ ${draftByteSize} + ${previewUpdatedLabel} +
+
+
+ +
-
-
- -
-
-
- `} - `} + + ` + } + ` + } `; } diff --git a/ui/src/ui/views/agents-panels-tools-skills.ts b/ui/src/ui/views/agents-panels-tools-skills.ts index 66c734b14643c..7496701dc354f 100644 --- a/ui/src/ui/views/agents-panels-tools-skills.ts +++ b/ui/src/ui/views/agents-panels-tools-skills.ts @@ -392,41 +392,51 @@ export function renderAgentTools(params: { - ${!params.configForm - ? html` -
- Load the gateway config to adjust tool profiles. -
- ` - : nothing} - ${hasAgentAllow - ? html` -
- This agent is using an explicit allowlist in config. Tool overrides are managed in the - Config tab. -
- ` - : nothing} - ${hasGlobalAllow - ? html` -
- Global tools.allow is set. Agent overrides cannot enable tools that are globally - blocked. -
- ` - : nothing} - ${params.toolsCatalogLoading && !params.toolsCatalogResult && !params.toolsCatalogError - ? html` -
Loading runtime tool catalog…
- ` - : nothing} - ${params.toolsCatalogError - ? html` -
- Could not load runtime tool catalog. Showing built-in fallback list instead. -
- ` - : nothing} + ${ + !params.configForm + ? html` +
+ Load the gateway config to adjust tool profiles. +
+ ` + : nothing + } + ${ + hasAgentAllow + ? html` +
+ This agent is using an explicit allowlist in config. Tool overrides are managed in + the Config tab. +
+ ` + : nothing + } + ${ + hasGlobalAllow + ? html` +
+ Global tools.allow is set. Agent overrides cannot enable tools that are globally + blocked. +
+ ` + : nothing + } + ${ + params.toolsCatalogLoading && !params.toolsCatalogResult && !params.toolsCatalogError + ? html` +
Loading runtime tool catalog…
+ ` + : nothing + } + ${ + params.toolsCatalogError + ? html` +
+ Could not load runtime tool catalog. Showing built-in fallback list instead. +
+ ` + : nothing + }
@@ -437,61 +447,65 @@ export function renderAgentTools(params: { ${params.runtimeSessionKey || "no session"}
${renderEffectiveToolNotices(params.toolsEffectiveResult)} - ${!params.runtimeSessionMatchesSelectedAgent - ? html` -
- Switch chat to this agent to view its live runtime tools. -
- ` - : params.toolsEffectiveLoading && - !params.toolsEffectiveResult && - !params.toolsEffectiveError + ${ + !params.runtimeSessionMatchesSelectedAgent ? html`
- Loading available tools… + Switch chat to this agent to view its live runtime tools.
` - : params.toolsEffectiveError + : params.toolsEffectiveLoading && + !params.toolsEffectiveResult && + !params.toolsEffectiveError ? html`
- Could not load available tools for this session. + Loading available tools…
` - : (params.toolsEffectiveResult?.groups?.length ?? 0) === 0 + : params.toolsEffectiveError ? html`
- No tools are available for this session right now. + Could not load available tools for this session.
` - : html` -
- ${visibleEffectiveTools.map((tool) => { - const anchorId = toToolAnchorId(tool.id); - return html` - handleRuntimeToolJump(event, anchorId)} - > - ${tool.label} - ${renderEffectiveToolBadge(tool)} - - `; - })} - ${hiddenEffectiveToolCount > 0 - ? html` - + No tools are available for this session right now. +
+ ` + : html` +
+ ${visibleEffectiveTools.map((tool) => { + const anchorId = toToolAnchorId(tool.id); + return html` + handleRuntimeToolJump(event, anchorId)} > - +${hiddenEffectiveToolCount} more live tools - - ` - : nothing} -
- `} + ${tool.label} + ${renderEffectiveToolBadge(tool)} + + `; + })} + ${ + hiddenEffectiveToolCount > 0 + ? html` + + +${hiddenEffectiveToolCount} more live tools + + ` + : nothing + } +
+ ` + }
@@ -562,9 +576,11 @@ export function renderAgentTools(params: { ${section.label} - ${section.source === "plugin" && section.pluginId - ? html`Plugin: ${section.pluginId}` - : nothing} + ${ + section.source === "plugin" && section.pluginId + ? html`Plugin: ${section.pluginId}` + : nothing + } ${previewTools.map( @@ -573,17 +589,21 @@ export function renderAgentTools(params: { >${tool.label}`, )} - ${remainingPreviewCount > 0 - ? html`+${remainingPreviewCount} more` - : nothing} + ${ + remainingPreviewCount > 0 + ? html`+${remainingPreviewCount} more` + : nothing + } ${formatCountLabel(section.tools.length, "Tool")} ${formatCountLabel(enabledSectionCount, "Enabled Tool")} - ${activeSectionCount > 0 - ? html`${formatCountLabel(activeSectionCount, "Live Tool")}` - : nothing} + ${ + activeSectionCount > 0 + ? html`${formatCountLabel(activeSectionCount, "Live Tool")}` + : nothing + }
@@ -650,27 +670,31 @@ export function renderAgentTools(params: {
Source
${formatToolSourceLabel(section, tool)}
- ${defaultProfiles.length > 0 - ? html` -
-
Default Presets
-
- ${defaultProfiles.map( - (profileId) => - html`${profileId}`, - )} + ${ + defaultProfiles.length > 0 + ? html` +
+
Default Presets
+
+ ${defaultProfiles.map( + (profileId) => + html`${profileId}`, + )} +
-
- ` - : nothing} + ` + : nothing + }
Current Session
- ${activeEntry - ? `Available now via ${renderEffectiveToolBadge(activeEntry)}.` - : params.runtimeSessionMatchesSelectedAgent - ? "Not available in this chat session right now." - : "Switch chat to this agent to inspect live availability."} + ${ + activeEntry + ? `Available now via ${renderEffectiveToolBadge(activeEntry)}.` + : params.runtimeSessionMatchesSelectedAgent + ? "Not available in this chat session right now." + : "Switch chat to this agent to inspect live availability." + }
Link to This Tool @@ -735,9 +759,11 @@ export function renderAgentSkills(params: {
Skills
Per-agent skill allowlist and workspace skills. - ${totalCount > 0 - ? html`${enabledCount}/${totalCount}` - : nothing} + ${ + totalCount > 0 + ? html`${enabledCount}/${totalCount}` + : nothing + }
@@ -788,34 +814,42 @@ export function renderAgentSkills(params: {
- ${!params.configForm - ? html` -
- Load the gateway config to set per-agent skills. -
- ` - : nothing} - ${usingAllowlist - ? html` -
- This agent uses a custom skill allowlist. -
- ` - : html` -
- All skills are enabled. Disabling any skill will create a per-agent allowlist. -
- `} - ${!reportReady && !params.loading - ? html` -
- Load skills for this agent to view workspace-specific entries. -
- ` - : nothing} - ${params.error - ? html`
${params.error}
` - : nothing} + ${ + !params.configForm + ? html` +
+ Load the gateway config to set per-agent skills. +
+ ` + : nothing + } + ${ + usingAllowlist + ? html` +
+ This agent uses a custom skill allowlist. +
+ ` + : html` +
+ All skills are enabled. Disabling any skill will create a per-agent allowlist. +
+ ` + } + ${ + !reportReady && !params.loading + ? html` +
+ Load skills for this agent to view workspace-specific entries. +
+ ` + : nothing + } + ${ + params.error + ? html`
${params.error}
` + : nothing + }
- ${filtered.length === 0 - ? html`
No skills found.
` - : html` -
- ${groups.map((group) => - renderAgentSkillGroup(group, { - agentId: params.agentId, - allowSet, - usingAllowlist, - editable, - onToggle: params.onToggle, - }), - )} -
- `} + ${ + filtered.length === 0 + ? html`
No skills found.
` + : html` +
+ ${groups.map((group) => + renderAgentSkillGroup(group, { + agentId: params.agentId, + allowSet, + usingAllowlist, + editable, + onToggle: params.onToggle, + }), + )} +
+ ` + } `; } @@ -901,12 +937,16 @@ function renderAgentSkillRow(
${skill.emoji ? `${skill.emoji} ` : ""}${skill.name}
${skill.description}
${renderSkillStatusChips({ skill })} - ${missing.length > 0 - ? html`
Missing: ${missing.join(", ")}
` - : nothing} - ${reasons.length > 0 - ? html`
Reason: ${reasons.join(", ")}
` - : nothing} + ${ + missing.length > 0 + ? html`
Missing: ${missing.join(", ")}
` + : nothing + } + ${ + reasons.length > 0 + ? html`
Reason: ${reasons.join(", ")}
` + : nothing + }
- ${selectedAgent - ? html` - - - ` - : nothing} + ${ + selectedAgent + ? html` + + + ` + : nothing + }
- ${props.error - ? html`
${props.error}
` - : nothing} + ${ + props.error + ? html`
${props.error}
` + : nothing + }
- ${!selectedAgent - ? html` -
-
${t("agents.selectTitle")}
-
${t("agents.selectSubtitle")}
-
- ` - : html` - ${renderAgentTabs( - props.activePanel, - (panel) => props.onSelectPanel(panel), - tabCounts, - )} - ${props.activePanel === "overview" - ? keyed( - selectedAgent.id, - renderAgentOverview({ - agent: selectedAgent, - basePath: props.basePath, - defaultId, - configForm: props.config.form, - agentFilesList: props.agentFiles.list, - agentIdentity: props.agentIdentityById[selectedAgent.id] ?? null, - agentIdentityError: props.agentIdentityError, - agentIdentityLoading: props.agentIdentityLoading, - configLoading: props.config.loading, - configSaving: props.config.saving, - configDirty: props.config.dirty, - modelCatalog: props.modelCatalog, - onConfigReload: props.onConfigReload, - onConfigSave: props.onConfigSave, - onModelChange: props.onModelChange, - onModelFallbacksChange: props.onModelFallbacksChange, - onSelectPanel: props.onSelectPanel, - }), - ) - : nothing} - ${props.activePanel === "files" - ? renderAgentFiles({ - agentId: selectedAgent.id, - agentFilesList: props.agentFiles.list, - agentFilesLoading: props.agentFiles.loading, - agentFilesError: props.agentFiles.error, - agentFileActive: props.agentFiles.active, - agentFileContents: props.agentFiles.contents, - agentFileDrafts: props.agentFiles.drafts, - agentFileSaving: props.agentFiles.saving, - onLoadFiles: props.onLoadFiles, - onSelectFile: props.onSelectFile, - onFileDraftChange: props.onFileDraftChange, - onFileReset: props.onFileReset, - onFileSave: props.onFileSave, - }) - : nothing} - ${props.activePanel === "tools" - ? renderAgentTools({ - agentId: selectedAgent.id, - configForm: props.config.form, - configLoading: props.config.loading, - configSaving: props.config.saving, - configDirty: props.config.dirty, - toolsCatalogLoading: props.toolsCatalog.loading, - toolsCatalogError: props.toolsCatalog.error, - toolsCatalogResult: props.toolsCatalog.result, - toolsEffectiveLoading: props.toolsEffective.loading, - toolsEffectiveError: props.toolsEffective.error, - toolsEffectiveResult: props.toolsEffective.result, - runtimeSessionKey: props.runtimeSessionKey, - runtimeSessionMatchesSelectedAgent: props.runtimeSessionMatchesSelectedAgent, - onProfileChange: props.onToolsProfileChange, - onOverridesChange: props.onToolsOverridesChange, - onConfigReload: props.onConfigReload, - onConfigSave: props.onConfigSave, - }) - : nothing} - ${props.activePanel === "skills" - ? renderAgentSkills({ - agentId: selectedAgent.id, - report: props.agentSkills.report, - loading: props.agentSkills.loading, - error: props.agentSkills.error, - activeAgentId: props.agentSkills.agentId, - configForm: props.config.form, - configLoading: props.config.loading, - configSaving: props.config.saving, - configDirty: props.config.dirty, - filter: props.agentSkills.filter, - onFilterChange: props.onSkillsFilterChange, - onRefresh: props.onSkillsRefresh, - onToggle: props.onAgentSkillToggle, - onClear: props.onAgentSkillsClear, - onDisableAll: props.onAgentSkillsDisableAll, - onConfigReload: props.onConfigReload, - onConfigSave: props.onConfigSave, - }) - : nothing} - ${props.activePanel === "channels" - ? renderAgentChannels({ - context: buildAgentContext( - selectedAgent, - props.config.form, - props.agentFiles.list, - defaultId, - props.agentIdentityById[selectedAgent.id] ?? null, - ), - configForm: props.config.form, - snapshot: props.channels.snapshot, - loading: props.channels.loading, - error: props.channels.error, - lastSuccess: props.channels.lastSuccess, - onRefresh: props.onChannelsRefresh, - onSelectPanel: props.onSelectPanel, - }) - : nothing} - ${props.activePanel === "cron" - ? renderAgentCron({ - context: buildAgentContext( - selectedAgent, - props.config.form, - props.agentFiles.list, - defaultId, - props.agentIdentityById[selectedAgent.id] ?? null, - ), - agentId: selectedAgent.id, - jobs: props.cron.jobs, - status: props.cron.status, - loading: props.cron.loading, - error: props.cron.error, - onRefresh: props.onCronRefresh, - onRunNow: props.onCronRunNow, - onSelectPanel: props.onSelectPanel, - }) - : nothing} - `} + ${ + !selectedAgent + ? html` +
+
${t("agents.selectTitle")}
+
${t("agents.selectSubtitle")}
+
+ ` + : html` + ${renderAgentTabs( + props.activePanel, + (panel) => props.onSelectPanel(panel), + tabCounts, + )} + ${ + props.activePanel === "overview" + ? keyed( + selectedAgent.id, + renderAgentOverview({ + agent: selectedAgent, + basePath: props.basePath, + defaultId, + configForm: props.config.form, + agentFilesList: props.agentFiles.list, + agentIdentity: props.agentIdentityById[selectedAgent.id] ?? null, + agentIdentityError: props.agentIdentityError, + agentIdentityLoading: props.agentIdentityLoading, + configLoading: props.config.loading, + configSaving: props.config.saving, + configDirty: props.config.dirty, + modelCatalog: props.modelCatalog, + onConfigReload: props.onConfigReload, + onConfigSave: props.onConfigSave, + onModelChange: props.onModelChange, + onModelFallbacksChange: props.onModelFallbacksChange, + onSelectPanel: props.onSelectPanel, + }), + ) + : nothing + } + ${ + props.activePanel === "files" + ? renderAgentFiles({ + agentId: selectedAgent.id, + agentFilesList: props.agentFiles.list, + agentFilesLoading: props.agentFiles.loading, + agentFilesError: props.agentFiles.error, + agentFileActive: props.agentFiles.active, + agentFileContents: props.agentFiles.contents, + agentFileDrafts: props.agentFiles.drafts, + agentFileSaving: props.agentFiles.saving, + onLoadFiles: props.onLoadFiles, + onSelectFile: props.onSelectFile, + onFileDraftChange: props.onFileDraftChange, + onFileReset: props.onFileReset, + onFileSave: props.onFileSave, + }) + : nothing + } + ${ + props.activePanel === "tools" + ? renderAgentTools({ + agentId: selectedAgent.id, + configForm: props.config.form, + configLoading: props.config.loading, + configSaving: props.config.saving, + configDirty: props.config.dirty, + toolsCatalogLoading: props.toolsCatalog.loading, + toolsCatalogError: props.toolsCatalog.error, + toolsCatalogResult: props.toolsCatalog.result, + toolsEffectiveLoading: props.toolsEffective.loading, + toolsEffectiveError: props.toolsEffective.error, + toolsEffectiveResult: props.toolsEffective.result, + runtimeSessionKey: props.runtimeSessionKey, + runtimeSessionMatchesSelectedAgent: + props.runtimeSessionMatchesSelectedAgent, + onProfileChange: props.onToolsProfileChange, + onOverridesChange: props.onToolsOverridesChange, + onConfigReload: props.onConfigReload, + onConfigSave: props.onConfigSave, + }) + : nothing + } + ${ + props.activePanel === "skills" + ? renderAgentSkills({ + agentId: selectedAgent.id, + report: props.agentSkills.report, + loading: props.agentSkills.loading, + error: props.agentSkills.error, + activeAgentId: props.agentSkills.agentId, + configForm: props.config.form, + configLoading: props.config.loading, + configSaving: props.config.saving, + configDirty: props.config.dirty, + filter: props.agentSkills.filter, + onFilterChange: props.onSkillsFilterChange, + onRefresh: props.onSkillsRefresh, + onToggle: props.onAgentSkillToggle, + onClear: props.onAgentSkillsClear, + onDisableAll: props.onAgentSkillsDisableAll, + onConfigReload: props.onConfigReload, + onConfigSave: props.onConfigSave, + }) + : nothing + } + ${ + props.activePanel === "channels" + ? renderAgentChannels({ + context: buildAgentContext( + selectedAgent, + props.config.form, + props.agentFiles.list, + defaultId, + props.agentIdentityById[selectedAgent.id] ?? null, + ), + configForm: props.config.form, + snapshot: props.channels.snapshot, + loading: props.channels.loading, + error: props.channels.error, + lastSuccess: props.channels.lastSuccess, + onRefresh: props.onChannelsRefresh, + onSelectPanel: props.onSelectPanel, + }) + : nothing + } + ${ + props.activePanel === "cron" + ? renderAgentCron({ + context: buildAgentContext( + selectedAgent, + props.config.form, + props.agentFiles.list, + defaultId, + props.agentIdentityById[selectedAgent.id] ?? null, + ), + agentId: selectedAgent.id, + jobs: props.cron.jobs, + status: props.cron.status, + loading: props.cron.loading, + error: props.cron.error, + onRefresh: props.onCronRefresh, + onRunNow: props.onCronRunNow, + onSelectPanel: props.onSelectPanel, + }) + : nothing + } + ` + }
`; @@ -371,9 +398,11 @@ function renderAgentTabs( type="button" @click=${() => onSelect(tab.id)} > - ${tab.label}${counts[tab.id] != null - ? html`${counts[tab.id]}` - : nothing} + ${tab.label}${ + counts[tab.id] != null + ? html`${counts[tab.id]}` + : nothing + } `, )} diff --git a/ui/src/ui/views/channels.config.ts b/ui/src/ui/views/channels.config.ts index 9c4dc4d604eaa..74b493d6db43e 100644 --- a/ui/src/ui/views/channels.config.ts +++ b/ui/src/ui/views/channels.config.ts @@ -118,16 +118,18 @@ export function renderChannelConfigSection(params: { channelId: string; props: C const disabled = props.configSaving || props.configSchemaLoading; return html`
- ${props.configSchemaLoading - ? html`
Loading config schema…
` - : renderChannelConfigForm({ - channelId, - configValue: props.configForm, - schema: props.configSchema, - uiHints: props.configUiHints, - disabled, - onPatch: props.onConfigPatch, - })} + ${ + props.configSchemaLoading + ? html`
Loading config schema…
` + : renderChannelConfigForm({ + channelId, + configValue: props.configForm, + schema: props.configSchema, + uiHints: props.configUiHints, + disabled, + onPatch: props.onConfigPatch, + }) + }
`; } @@ -141,16 +145,20 @@ export function renderNostrProfileForm(params: { }} ?disabled=${state.saving} /> - ${help - ? html`
- ${help} -
` - : nothing} - ${error - ? html`
- ${error} -
` - : nothing} + ${ + help + ? html`
+ ${help} +
` + : nothing + } + ${ + error + ? html`
+ ${error} +
` + : nothing + }
`; }; @@ -194,12 +202,16 @@ export function renderNostrProfileForm(params: { - ${state.error - ? html`
${state.error}
` - : nothing} - ${state.success - ? html`
${state.success}
` - : nothing} + ${ + state.error + ? html`
${state.error}
` + : nothing + } + ${ + state.success + ? html`
${state.success}
` + : nothing + } ${renderPicturePreview()} ${renderField("name", t("channels.nostr.username"), { placeholder: "satoshi", @@ -222,36 +234,38 @@ export function renderNostrProfileForm(params: { placeholder: "https://example.com/avatar.jpg", help: t("channels.nostr.avatarHelp"), })} - ${state.showAdvanced - ? html` -
-
- ${t("channels.nostr.advanced")} -
+ ${ + state.showAdvanced + ? html` +
+
+ ${t("channels.nostr.advanced")} +
- ${renderField("banner", t("channels.nostr.bannerUrl"), { - type: "url", - placeholder: "https://example.com/banner.jpg", - help: t("channels.nostr.bannerHelp"), - })} - ${renderField("website", t("channels.nostr.website"), { - type: "url", - placeholder: "https://example.com", - help: t("channels.nostr.websiteHelp"), - })} - ${renderField("nip05", t("channels.nostr.nip05Identifier"), { - placeholder: "you@example.com", - help: t("channels.nostr.nip05Help"), - })} - ${renderField("lud16", t("channels.nostr.lightningAddress"), { - placeholder: "you@getalby.com", - help: t("channels.nostr.lightningHelp"), - })} -
- ` - : nothing} + ${renderField("banner", t("channels.nostr.bannerUrl"), { + type: "url", + placeholder: "https://example.com/banner.jpg", + help: t("channels.nostr.bannerHelp"), + })} + ${renderField("website", t("channels.nostr.website"), { + type: "url", + placeholder: "https://example.com", + help: t("channels.nostr.websiteHelp"), + })} + ${renderField("nip05", t("channels.nostr.nip05Identifier"), { + placeholder: "you@example.com", + help: t("channels.nostr.nip05Help"), + })} + ${renderField("lud16", t("channels.nostr.lightningAddress"), { + placeholder: "you@getalby.com", + help: t("channels.nostr.lightningHelp"), + })} +
+ ` + : nothing + }
- ${isDirty - ? html` -
- ${t("common.unsavedChanges")} -
- ` - : nothing} + ${ + isDirty + ? html` +
+ ${t("common.unsavedChanges")} +
+ ` + : nothing + } `; } diff --git a/ui/src/ui/views/channels.nostr.ts b/ui/src/ui/views/channels.nostr.ts index e9e53d3146f1f..d463c66c3e977 100644 --- a/ui/src/ui/views/channels.nostr.ts +++ b/ui/src/ui/views/channels.nostr.ts @@ -82,14 +82,18 @@ export function renderNostrCard(params: {
${t("common.lastInbound")} ${account.lastInboundAt - ? formatRelativeTimestamp(account.lastInboundAt) - : t("common.na")}${ + account.lastInboundAt + ? formatRelativeTimestamp(account.lastInboundAt) + : t("common.na") + }
- ${account.lastError - ? html` ` - : nothing} + ${ + account.lastError + ? html` ` + : nothing + } `; @@ -130,64 +134,79 @@ export function renderNostrCard(params: { style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;" >
${t("channels.nostr.profile")}
- ${summaryConfigured + ${ + summaryConfigured + ? html` + + ` + : nothing + } + + ${ + hasAnyProfileData ? html` - +
+ ${ + picture + ? html` +
+ ${t("channels.nostr.profilePicture")} { + (e.target as HTMLImageElement).style.display = "none"; + }} + /> +
+ ` + : nothing + } + ${ + name + ? html`
+ ${t("channels.nostr.name")}${name} +
` + : nothing + } + ${ + displayName + ? html`
+ ${t("channels.nostr.displayName")}${displayName} +
` + : nothing + } + ${ + about + ? html`
+ ${t("channels.nostr.about")}${about} +
` + : nothing + } + ${ + nip05 + ? html`
NIP-05${nip05}
` + : nothing + } +
` - : nothing} - - ${hasAnyProfileData - ? html` -
- ${picture - ? html` -
- ${t("channels.nostr.profilePicture")} { - (e.target as HTMLImageElement).style.display = "none"; - }} - /> -
- ` - : nothing} - ${name - ? html`
- ${t("channels.nostr.name")}${name} -
` - : nothing} - ${displayName - ? html`
- ${t("channels.nostr.displayName")}${displayName} -
` - : nothing} - ${about - ? html`
- ${t("channels.nostr.about")}${about} -
` - : nothing} - ${nip05 - ? html`
NIP-05${nip05}
` - : nothing} -
- ` - : html` -
- ${t("channels.nostr.noProfile")} ${t("channels.nostr.noProfileHint")} -
- `} + : html` +
+ ${t("channels.nostr.noProfile")} ${t("channels.nostr.noProfileHint")} +
+ ` + } `; }; @@ -197,41 +216,47 @@ export function renderNostrCard(params: {
Nostr
Decentralized DMs via Nostr relays (NIP-04).
${accountCountLabel} - ${hasMultipleAccounts - ? html` - - ` - : html` -
-
- ${t("common.configured")} - ${summaryConfigured ? t("common.yes") : t("common.no")} -
-
- ${t("common.running")} - ${summaryRunning ? t("common.yes") : t("common.no")} -
-
- ${t("common.publicKey")} - ${truncatePubkey(summaryPublicKey)} + ${ + hasMultipleAccounts + ? html` + -
- ${t("common.lastStart")} - - ${summaryLastStartAt - ? formatRelativeTimestamp(summaryLastStartAt) - : t("common.na")} - + ` + : html` +
+
+ ${t("common.configured")} + ${summaryConfigured ? t("common.yes") : t("common.no")} +
+
+ ${t("common.running")} + ${summaryRunning ? t("common.yes") : t("common.no")} +
+
+ ${t("common.publicKey")} + ${truncatePubkey(summaryPublicKey)} +
+
+ ${t("common.lastStart")} + + ${ + summaryLastStartAt + ? formatRelativeTimestamp(summaryLastStartAt) + : t("common.na") + } + +
-
- `} - ${summaryLastError - ? html`
${summaryLastError}
` - : nothing} + ` + } + ${ + summaryLastError + ? html`
${summaryLastError}
` + : nothing + } ${renderProfileSection()} ${renderChannelConfigSection({ channelId: "nostr", props })}
diff --git a/ui/src/ui/views/channels.shared.ts b/ui/src/ui/views/channels.shared.ts index 6fffd690d64ba..66e7afecd767c 100644 --- a/ui/src/ui/views/channels.shared.ts +++ b/ui/src/ui/views/channels.shared.ts @@ -122,9 +122,11 @@ export function renderSingleAccountChannelCard(params: { )}
- ${params.lastError - ? html`
${params.lastError}
` - : nothing} + ${ + params.lastError + ? html`
${params.lastError}
` + : nothing + } ${params.secondaryCallout ?? nothing} ${params.extraContent ?? nothing} ${params.configSection} ${params.footer ?? nothing}
diff --git a/ui/src/ui/views/channels.telegram.ts b/ui/src/ui/views/channels.telegram.ts index 30d9994f098be..2176517ea0caa 100644 --- a/ui/src/ui/views/channels.telegram.ts +++ b/ui/src/ui/views/channels.telegram.ts @@ -43,14 +43,18 @@ export function renderTelegramCard(params: {
${t("common.lastInbound")} ${account.lastInboundAt - ? formatRelativeTimestamp(account.lastInboundAt) - : t("common.na")}${ + account.lastInboundAt + ? formatRelativeTimestamp(account.lastInboundAt) + : t("common.na") + }
- ${account.lastError - ? html` ` - : nothing} + ${ + account.lastError + ? html` ` + : nothing + }
`; @@ -67,15 +71,21 @@ export function renderTelegramCard(params: { ${telegramAccounts.map((account) => renderAccountCard(account))} - ${telegram?.lastError - ? html`
${telegram.lastError}
` - : nothing} - ${telegram?.probe - ? html`
- ${telegram.probe.ok ? t("common.probeOk") : t("common.probeFailed")} · - ${telegram.probe.status ?? ""} ${telegram.probe.error ?? ""} -
` - : nothing} + ${ + telegram?.lastError + ? html`
+ ${telegram.lastError} +
` + : nothing + } + ${ + telegram?.probe + ? html`
+ ${telegram.probe.ok ? t("common.probeOk") : t("common.probeFailed")} · + ${telegram.probe.status ?? ""} ${telegram.probe.error ?? ""} +
` + : nothing + } ${renderChannelConfigSection({ channelId: "telegram", props })}
diff --git a/ui/src/ui/views/channels.ts b/ui/src/ui/views/channels.ts index e64ff8714bcfd..be25ce952ee11 100644 --- a/ui/src/ui/views/channels.ts +++ b/ui/src/ui/views/channels.ts @@ -86,28 +86,33 @@ export function renderChannels(props: ChannelsProps) { ${props.lastSuccessAt ? formatRelativeTimestamp(props.lastSuccessAt) : t("common.na")}
- ${showingStaleSnapshot - ? html` -
- Refreshing channel status in the background; showing the last successful snapshot. -
- ` - : nothing} - ${props.snapshot?.partial - ? html` -
- Some channel checks did not finish before the UI budget. - ${partialWarnings.length > 0 ? partialWarnings.slice(0, 3).join("; ") : ""} -
- ` - : nothing} - ${props.lastError - ? html`
${props.lastError}
` - : nothing} + ${ + showingStaleSnapshot + ? html` +
+ Refreshing channel status in the background; showing the last successful snapshot. +
+ ` + : nothing + } + ${ + props.snapshot?.partial + ? html` +
+ Some channel checks did not finish before the UI budget. + ${partialWarnings.length > 0 ? partialWarnings.slice(0, 3).join("; ") : ""} +
+ ` + : nothing + } + ${ + props.lastError + ? html`
${props.lastError}
` + : nothing + }
 ${props.snapshot ? JSON.stringify(props.snapshot, null, 2) : t("channels.health.noSnapshotYet")}
-      
+ `; } @@ -217,31 +222,35 @@ function renderGenericChannelCard(
${label}
${t("channels.generic.subtitle")}
${accountCountLabel} - ${accounts.length > 0 - ? html` - - ` - : html` -
-
- ${t("common.configured")} - ${formatNullableBoolean(displayState.configured)} -
-
- ${t("common.running")} - ${formatNullableBoolean(displayState.running)} + ${ + accounts.length > 0 + ? html` + -
- ${t("common.connected")} - ${formatNullableBoolean(displayState.connected)} + ` + : html` +
+
+ ${t("common.configured")} + ${formatNullableBoolean(displayState.configured)} +
+
+ ${t("common.running")} + ${formatNullableBoolean(displayState.running)} +
+
+ ${t("common.connected")} + ${formatNullableBoolean(displayState.connected)} +
-
- `} - ${lastError - ? html`
${lastError}
` - : nothing} + ` + } + ${ + lastError + ? html`
${lastError}
` + : nothing + } ${renderChannelConfigSection({ channelId: key, props })}
`; @@ -321,14 +330,18 @@ function renderGenericAccount(account: ChannelAccountSnapshot) {
${t("common.lastInbound")} ${account.lastInboundAt - ? formatRelativeTimestamp(account.lastInboundAt) - : t("common.na")}${ + account.lastInboundAt + ? formatRelativeTimestamp(account.lastInboundAt) + : t("common.na") + }
- ${account.lastError - ? html` ` - : nothing} + ${ + account.lastError + ? html` ` + : nothing + }
`; diff --git a/ui/src/ui/views/channels.whatsapp.ts b/ui/src/ui/views/channels.whatsapp.ts index 26f6908ac8cec..ab381563c8653 100644 --- a/ui/src/ui/views/channels.whatsapp.ts +++ b/ui/src/ui/views/channels.whatsapp.ts @@ -53,41 +53,49 @@ export function renderWhatsAppCard(params: { ], lastError: whatsapp?.lastError, extraContent: html` - ${props.whatsappMessage - ? html`
${props.whatsappMessage}
` - : nothing} - ${props.whatsappQrDataUrl - ? html`
- WhatsApp QR -
` - : nothing} + ${ + props.whatsappMessage + ? html`
${props.whatsappMessage}
` + : nothing + } + ${ + props.whatsappQrDataUrl + ? html`
+ WhatsApp QR +
` + : nothing + } `, configSection: renderChannelConfigSection({ channelId: "whatsapp", props }), footer: html`
- ${linked - ? html`` - : html``} - ${hasQr - ? html`` - : nothing} + ${ + linked + ? html`` + : html`` + } + ${ + hasQr + ? html`` + : nothing + }
- - `; - })} - - `} + `; + })} + + ` + } `; } diff --git a/ui/src/ui/views/config-form.render.ts b/ui/src/ui/views/config-form.render.ts index 61f931f711376..654a789889586 100644 --- a/ui/src/ui/views/config-form.render.ts +++ b/ui/src/ui/views/config-form.render.ts @@ -445,19 +445,23 @@ export function renderConfigForm(props: ConfigFormProps) { path: Array; }) => html`
- ${params.showHeader - ? html` -
- ${getSectionIcon(params.sectionKey)} -
-

${params.label}

- ${params.description - ? html`

${params.description}

` - : nothing} + ${ + params.showHeader + ? html` +
+ ${getSectionIcon(params.sectionKey)} +
+

${params.label}

+ ${ + params.description + ? html`

${params.description}

` + : nothing + } +
-
- ` - : nothing} + ` + : nothing + }
${renderNode({ schema: params.node, @@ -480,45 +484,47 @@ export function renderConfigForm(props: ConfigFormProps) { return html`
- ${subsectionContext - ? (() => { - const { sectionKey, subsectionKey, schema: node } = subsectionContext; - const hint = hintForPath([sectionKey, subsectionKey], props.uiHints); - const label = hint?.label ?? node.title ?? humanize(subsectionKey); - const description = hint?.help ?? node.description ?? ""; - const sectionValue = value[sectionKey]; - const scopedValue = - sectionValue && typeof sectionValue === "object" - ? (sectionValue as Record)[subsectionKey] - : undefined; - return renderSectionCard({ - id: `config-section-${sectionKey}-${subsectionKey}`, - sectionKey, - label, - description, - showHeader: false, - node, - nodeValue: scopedValue, - path: [sectionKey, subsectionKey], - }); - })() - : filteredEntries.map(([key, node]) => { - const meta = SECTION_META[key] ?? { - label: key.charAt(0).toUpperCase() + key.slice(1), - description: node.description ?? "", - }; + ${ + subsectionContext + ? (() => { + const { sectionKey, subsectionKey, schema: node } = subsectionContext; + const hint = hintForPath([sectionKey, subsectionKey], props.uiHints); + const label = hint?.label ?? node.title ?? humanize(subsectionKey); + const description = hint?.help ?? node.description ?? ""; + const sectionValue = value[sectionKey]; + const scopedValue = + sectionValue && typeof sectionValue === "object" + ? (sectionValue as Record)[subsectionKey] + : undefined; + return renderSectionCard({ + id: `config-section-${sectionKey}-${subsectionKey}`, + sectionKey, + label, + description, + showHeader: false, + node, + nodeValue: scopedValue, + path: [sectionKey, subsectionKey], + }); + })() + : filteredEntries.map(([key, node]) => { + const meta = SECTION_META[key] ?? { + label: key.charAt(0).toUpperCase() + key.slice(1), + description: node.description ?? "", + }; - return renderSectionCard({ - id: `config-section-${key}`, - sectionKey: key, - label: meta.label, - description: meta.description, - showHeader: activeSection == null, - node, - nodeValue: value[key], - path: [key], - }); - })} + return renderSectionCard({ + id: `config-section-${key}`, + sectionKey: key, + label: meta.label, + description: meta.description, + showHeader: activeSection == null, + node, + nodeValue: value[key], + path: [key], + }); + }) + }
`; } diff --git a/ui/src/ui/views/config-quick.ts b/ui/src/ui/views/config-quick.ts index 95adbfbaebfce..76d0cd79359b1 100644 --- a/ui/src/ui/views/config-quick.ts +++ b/ui/src/ui/views/config-quick.ts @@ -424,9 +424,9 @@ function renderModelCard(props: QuickSettingsProps) { ${THINKING_LEVELS.map( (level) => html` `} - -
- `, - )} + ${ + props.channels.length === 0 + ? html`
No channels configured
` + : props.channels.map( + (ch) => html` +
+ + + ${ch.label} + + + ${ + ch.connected + ? html`${ch.detail ?? "Connected"}` + : html`` + } + +
+ `, + ) + }
`; @@ -564,10 +568,9 @@ function renderSecurityCard(props: QuickSettingsProps) { ${toolProfiles.map( (profile) => html` - ` - : nothing} + ${ + assistantAvatarSource + ? html` +
+ ${assistantAvatarSourceLabel} + ${assistantAvatarSource}
-
- Stores a Control UI override. Clear it to return to IDENTITY.md. + ` + : nothing + } + ${ + assistantAvatarIssue + ? html`
${assistantAvatarIssue}
` + : nothing + } + ${ + canOverrideAssistantAvatar + ? html` +
+
+ + ${ + assistantAvatarOverride + ? html` + + ` + : nothing + } +
+
+ Stores a Control UI override. Clear it to return to IDENTITY.md. +
-
- ` - : nothing} - ${props.assistantAvatarUploadError - ? html`
- ${props.assistantAvatarUploadError} -
` - : nothing} + ` + : nothing + } + ${ + props.assistantAvatarUploadError + ? html`
+ ${props.assistantAvatarUploadError} +
` + : nothing + }
@@ -959,12 +972,16 @@ function renderPresetsCard(props: QuickSettingsProps) {
- ${preset.id === savedPresetId - ? html`Current` - : nothing} - ${hasPendingProfileChange && preset.id === selectedPresetId - ? html`Selected` - : nothing} + ${ + preset.id === savedPresetId + ? html`Current` + : nothing + } + ${ + hasPendingProfileChange && preset.id === selectedPresetId + ? html`Selected` + : nothing + }
@@ -1013,46 +1030,52 @@ function renderPresetsCard(props: QuickSettingsProps) { })}
- ${hasPendingConfigChange - ? html` -
-
${commitCopy}
-
- - - + ${ + hasPendingConfigChange + ? html` +
+
${commitCopy}
+
+ + + +
-
- ` - : html` - - `} + ` + : html` + + ` + }
diff --git a/ui/src/ui/views/config.ts b/ui/src/ui/views/config.ts index a63477fa90fca..820317f0044f5 100644 --- a/ui/src/ui/views/config.ts +++ b/ui/src/ui/views/config.ts @@ -891,9 +891,9 @@ function renderNotificationsSection(props: ConfigProps) { Status ${subscriptionLabel} @@ -901,42 +901,44 @@ function renderNotificationsSection(props: ConfigProps) {
- ${push.supported && push.permission !== "denied" - ? push.subscribed - ? html` - - - ` - : html` - - ` - : push.permission === "denied" - ? html` -
- Notifications are blocked. Update your browser site permissions to allow - notifications. -
- ` - : nothing} + ${ + push.supported && push.permission !== "denied" + ? push.subscribed + ? html` + + + ` + : html` + + ` + : push.permission === "denied" + ? html` +
+ Notifications are blocked. Update your browser site permissions to allow + notifications. +
+ ` + : nothing + }
@@ -975,9 +977,9 @@ function renderAppearanceSection(props: ConfigProps) { ${themeOptions.map( (opt) => html` `, )} - ${showCustomThemeImport - ? html` -
-
-
Import from tweakcn
-

- Open tweakcn.com, choose or create a theme, click Share, then paste the copied - theme link here. Share links, editor URLs, registry URLs, theme IDs, and default - theme names like amethyst-haze are accepted. -

-
- - Browse tweakcn themes ${icons.externalLink} - - -
- - ${props.hasCustomTheme - ? html` - - ` - : nothing} + Browse tweakcn themes ${icons.externalLink} + + +
+ + ${ + props.hasCustomTheme + ? html` + + ` + : nothing + } +
+ ${ + props.hasCustomTheme + ? html` +
+ Loaded + ${importedName} · ${props.customThemeSourceUrl ?? "tweakcn"} +
+ ` + : nothing + } + ${ + props.customThemeImportMessage + ? html` +
+ ${props.customThemeImportMessage.text} +
+ ` + : nothing + }
- ${props.hasCustomTheme - ? html` -
- Loaded - ${importedName} · ${props.customThemeSourceUrl ?? "tweakcn"} -
- ` - : nothing} - ${props.customThemeImportMessage - ? html` -
- ${props.customThemeImportMessage.text} -
- ` - : nothing} -
- ` - : html` -

- Click Import to add one browser-local tweakcn theme. In tweakcn, - use Share and paste the copied link here. -

- `} + ` + : html` +

+ Click Import to add one browser-local tweakcn theme. In tweakcn, + use Share and paste the copied link here. +

+ ` + }
@@ -1148,14 +1165,16 @@ function renderAppearanceSection(props: ConfigProps) { ${props.connected ? t("common.connected") : t("common.offline")}
- ${props.assistantName - ? html` -
- Assistant - ${props.assistantName} -
- ` - : nothing} + ${ + props.assistantName + ? html` +
+ Assistant + ${props.assistantName} +
+ ` + : nothing + } @@ -1322,31 +1341,35 @@ export function renderConfig(props: ConfigProps) { function renderAccordionNav() { return html`
- ${props.onBackToQuick - ? html` - - ` - : nothing} + ${ + props.onBackToQuick + ? html` + + ` + : nothing + } ${allCategories.map( (cat) => html`
- ${cat.sections.some((s) => s.key === props.activeSection) - ? html` -
- ${cat.sections.map( - (s) => html` - - `, - )} -
- ` - : nothing} + ${ + cat.sections.some((s) => s.key === props.activeSection) + ? html` +
+ ${cat.sections.map( + (s) => html` + + `, + )} +
+ ` + : nothing + }
`, )} @@ -1451,60 +1478,72 @@ export function renderConfig(props: ConfigProps) {
- ${showModeToggle - ? html` -
- - + +
+ ` + : nothing + } + ${ + hasChanges + ? html` + ${ + formMode === "raw" + ? "Unsaved changes" + : `${diff.length} unsaved change${diff.length !== 1 ? "s" : ""}` + } - Raw - -
- ` - : nothing} - ${hasChanges - ? html` - ${formMode === "raw" - ? "Unsaved changes" - : `${diff.length} unsaved change${diff.length !== 1 ? "s" : ""}`} - ` - : html` No changes `} + ` + : html` No changes ` + }
- ${!rawAvailable - ? html` - Raw mode disabled (snapshot cannot safely round-trip raw text). - ` - : nothing} -
- ${props.onOpenFile + ${ + !rawAvailable ? html` - ` - : nothing} + : nothing + } +
+ ${ + props.onOpenFile + ? html` + + ` + : nothing + } @@ -1539,370 +1578,405 @@ export function renderConfig(props: ConfigProps) {
- ${settingsLayout === "accordion" - ? renderAccordionNav() - : html` -
- ${formMode === "form" - ? html` - - ` - : nothing} - -
- ${topTabs.map( - (tab) => html` - - `, - )} -
-
- `} - ${validity === "invalid" && !cvs.validityDismissed - ? html` -
- - - - - - Your configuration is invalid. Some settings may not work as expected. - -
- ` - : nothing} + ${ + settingsLayout === "accordion" + ? renderAccordionNav() + : html` +
+ ${ + formMode === "form" + ? html` + + ` + : nothing + } - - ${hasChanges && formMode === "form" - ? html` -
- - View ${diff.length} pending change${diff.length !== 1 ? "s" : ""} - - - - -
- ${diff.map( - (change) => html` -
-
${formatConfigDiffPath(change.path)}
-
- ${renderDiffValue(change.path, change.from, props.uiHints)} - - ${renderDiffValue(change.path, change.to, props.uiHints)} -
-
- `, - )} + ${topTabs.map( + (tab) => html` + + `, + )} +
-
- ` - : nothing} - ${hasRawChanges && formMode === "raw" - ? html` -
{ - const details = e.target as HTMLDetailsElement; - if (cvs.rawDiffOpen === details.open) { - return; - } - cvs.rawDiffOpen = details.open; - if (!details.open) { - rawDiffCache = undefined; - } - requestUpdate(); - }} - > - - View pending changes + ` + } + ${ + validity === "invalid" && !cvs.validityDismissed + ? html` +
- + + + -
-
- ${rawDiff.length > 0 - ? rawDiff.map( - (change) => html` -
-
- ${formatConfigDiffPath(change.path)} -
-
- ${renderRawDiffValue( - change.path, - change.from, - props.uiHints, - cvs.rawRevealed, - )} - - ${renderRawDiffValue( - change.path, - change.to, - props.uiHints, - cvs.rawRevealed, - )} -
-
- `, - ) - : html` + Your configuration is invalid. Some settings may not work as expected. + +
+ ` + : nothing + } + + + ${ + hasChanges && formMode === "form" + ? html` +
+ + View ${diff.length} pending change${diff.length !== 1 ? "s" : ""} + + + + +
+ ${diff.map( + (change) => html`
- Changes detected (JSON diff not available) +
${formatConfigDiffPath(change.path)}
+
+ ${renderDiffValue(change.path, change.from, props.uiHints)} + + ${renderDiffValue(change.path, change.to, props.uiHints)} +
- `} -
-
- ` - : nothing} - ${activeSectionMeta && formMode === "form" - ? html` -
-
- ${getSectionIcon(props.activeSection ?? "")} -
-
-
${activeSectionMeta.label}
- ${activeSectionMeta.description - ? html`
- ${activeSectionMeta.description} -
` - : nothing} + `, + )} +
+
+ ` + : nothing + } + ${ + hasRawChanges && formMode === "raw" + ? html` +
{ + const details = e.target as HTMLDetailsElement; + if (cvs.rawDiffOpen === details.open) { + return; + } + cvs.rawDiffOpen = details.open; + if (!details.open) { + rawDiffCache = undefined; + } + requestUpdate(); + }} + > + + View pending changes + + + + +
+ ${ + rawDiff.length > 0 + ? rawDiff.map( + (change) => html` +
+
+ ${formatConfigDiffPath(change.path)} +
+
+ ${renderRawDiffValue( + change.path, + change.from, + props.uiHints, + cvs.rawRevealed, + )} + + ${renderRawDiffValue( + change.path, + change.to, + props.uiHints, + cvs.rawRevealed, + )} +
+
+ `, + ) + : html` +
+ Changes detected (JSON diff not available) +
+ ` + } +
+
+ ` + : nothing + } + ${ + activeSectionMeta && formMode === "form" + ? html` +
+
+ ${getSectionIcon(props.activeSection ?? "")} +
+
+
${activeSectionMeta.label}
+ ${ + activeSectionMeta.description + ? html`
+ ${activeSectionMeta.description} +
` + : nothing + } +
+ ${ + props.activeSection === "env" + ? html` + + ` + : nothing + }
- ${props.activeSection === "env" - ? html` - - ` - : nothing} - - ` - : nothing} + ` + : nothing + }
- ${props.activeSection === "__appearance__" - ? includeVirtualSections - ? renderAppearanceSection(props) - : nothing - : props.activeSection === "__notifications__" + ${ + props.activeSection === "__appearance__" ? includeVirtualSections - ? renderNotificationsSection(props) + ? renderAppearanceSection(props) : nothing - : formMode === "form" - ? html` - ${showAppearanceOnRoot ? renderAppearanceSection(props) : nothing} - ${props.schemaLoading - ? html` -
-
- Loading schema… -
- ` - : renderConfigForm({ - schema: analysis.schema, - uiHints: props.uiHints, - value: props.formValue, - rawAvailable, - disabled: props.loading || !props.formValue, - unsupportedPaths: analysis.unsupportedPaths, - onPatch: props.onFormPatch, - searchQuery: props.searchQuery, - activeSection: props.activeSection, - activeSubsection: effectiveSubsection, - revealSensitive: - props.activeSection === "env" ? envSensitiveVisible : false, - isSensitivePathRevealed, - onToggleSensitivePath: (path) => { - toggleSensitivePathReveal(path); - requestUpdate(); - }, - })} - ` - : (() => { - const sensitiveCount = countSensitiveConfigValues( - props.formValue, - [], - props.uiHints, - ); - const blurred = sensitiveCount > 0 && !cvs.rawRevealed; - return html` - ${formUnsafe - ? html` -
- Your config contains fields the form editor can't safely represent. - Use Raw mode to edit those entries. -
- ` - : nothing} -
- - Raw config (JSON/JSON5) - ${sensitiveCount > 0 - ? html` - ${sensitiveCount} secret${sensitiveCount === 1 ? "" : "s"} - ${blurred ? "redacted" : "visible"} - - ` - : nothing} - - ${blurred + : props.activeSection === "__notifications__" + ? includeVirtualSections + ? renderNotificationsSection(props) + : nothing + : formMode === "form" + ? html` + ${showAppearanceOnRoot ? renderAppearanceSection(props) : nothing} + ${ + props.schemaLoading ? html` -
- ${sensitiveCount} sensitive value${sensitiveCount === 1 ? "" : "s"} - hidden. Use the reveal button above to edit the raw config. +
+
+ Loading schema…
` - : html` - - `} -
- `; - })()} + : renderConfigForm({ + schema: analysis.schema, + uiHints: props.uiHints, + value: props.formValue, + rawAvailable, + disabled: props.loading || !props.formValue, + unsupportedPaths: analysis.unsupportedPaths, + onPatch: props.onFormPatch, + searchQuery: props.searchQuery, + activeSection: props.activeSection, + activeSubsection: effectiveSubsection, + revealSensitive: + props.activeSection === "env" ? envSensitiveVisible : false, + isSensitivePathRevealed, + onToggleSensitivePath: (path) => { + toggleSensitivePathReveal(path); + requestUpdate(); + }, + }) + } + ` + : (() => { + const sensitiveCount = countSensitiveConfigValues( + props.formValue, + [], + props.uiHints, + ); + const blurred = sensitiveCount > 0 && !cvs.rawRevealed; + return html` + ${ + formUnsafe + ? html` +
+ Your config contains fields the form editor can't safely + represent. Use Raw mode to edit those entries. +
+ ` + : nothing + } +
+ + Raw config (JSON/JSON5) + ${ + sensitiveCount > 0 + ? html` + ${sensitiveCount} secret${sensitiveCount === 1 ? "" : "s"} + ${blurred ? "redacted" : "visible"} + + ` + : nothing + } + + ${ + blurred + ? html` +
+ ${sensitiveCount} sensitive + value${sensitiveCount === 1 ? "" : "s"} hidden. Use the reveal + button above to edit the raw config. +
+ ` + : html` + + ` + } +
+ `; + })() + }
- ${props.issues.length > 0 - ? html`
-
${JSON.stringify(props.issues, null, 2)}
-
` - : nothing} + ${ + props.issues.length > 0 + ? html`
+
${JSON.stringify(props.issues, null, 2)}
+
` + : nothing + }
`; diff --git a/ui/src/ui/views/cron-quick-create.ts b/ui/src/ui/views/cron-quick-create.ts index 6edab3e05172c..18c30ff38a5ef 100644 --- a/ui/src/ui/views/cron-quick-create.ts +++ b/ui/src/ui/views/cron-quick-create.ts @@ -223,9 +223,11 @@ function renderStepIndicator(current: CronQuickCreateStep) { ${state === "done" ? "✓" : idx + 1} ${t(STEP_LABELS[step])} - ${idx < STEPS.length - 1 - ? html`
` - : nothing} + ${ + idx < STEPS.length - 1 + ? html`
` + : nothing + } `; })} @@ -295,9 +297,9 @@ function renderWhenStep(props: CronQuickCreateProps) { ${SCHEDULE_PRESETS.map( (preset) => html` - ` - : nothing} + ${ + openNewJob + ? html` + + ` + : nothing + }
- ${props.jobs.length === 0 - ? html` -
-
- ${hasActiveJobsFilters ? t("cron.jobs.noMatching") : t("cron.jobs.emptyTitle")} + ${ + props.jobs.length === 0 + ? html` +
+
+ ${hasActiveJobsFilters ? t("cron.jobs.noMatching") : t("cron.jobs.emptyTitle")} +
+
+ ${ + hasActiveJobsFilters + ? t("cron.jobs.emptyFilteredHint") + : t("cron.jobs.emptyHint") + } +
+ ${ + openNewJob && !hasActiveJobsFilters + ? html` + + ` + : nothing + }
-
- ${hasActiveJobsFilters - ? t("cron.jobs.emptyFilteredHint") - : t("cron.jobs.emptyHint")} + ` + : html` +
+ ${props.jobs.map((job) => renderJob(job, props))}
- ${openNewJob && !hasActiveJobsFilters - ? html` - - ` - : nothing} -
- ` - : html` -
- ${props.jobs.map((job) => renderJob(job, props))} -
- `} - ${props.jobsHasMore - ? html` -
- -
- ` - : nothing} + ` + } + ${ + props.jobsHasMore + ? html` +
+ +
+ ` + : nothing + }
@@ -635,9 +651,11 @@ export function renderCron(props: CronProps) {
${t("cron.runs.title")}
- ${props.runsScope === "all" - ? t("cron.runs.subtitleAll") - : t("cron.runs.subtitleJob", { title: selectedRunTitle })} + ${ + props.runsScope === "all" + ? t("cron.runs.subtitleAll") + : t("cron.runs.subtitleJob", { title: selectedRunTitle }) + }
@@ -650,9 +668,11 @@ export function renderCron(props: CronProps) {
${t("sessionsView.filters")} - ${hasActiveRunsFilters - ? html`${t("common.active")}` - : nothing} + ${ + hasActiveRunsFilters + ? html`${t("common.active")}` + : nothing + }
@@ -736,747 +756,813 @@ export function renderCron(props: CronProps) {
- ${props.runsScope === "job" && props.runsJobId == null - ? html` -
${t("cron.runs.selectJobHint")}
- ` - : runs.length === 0 + ${ + props.runsScope === "job" && props.runsJobId == null ? html` -
${t("cron.runs.noMatching")}
+
${t("cron.runs.selectJobHint")}
` - : html` -
- ${runs.map((entry) => renderRun(entry, props.basePath, props.onNavigateToChat))} + : runs.length === 0 + ? html` +
${t("cron.runs.noMatching")}
+ ` + : html` +
+ ${runs.map((entry) => renderRun(entry, props.basePath, props.onNavigateToChat))} +
+ ` + } + ${ + (props.runsScope === "all" || props.runsJobId != null) && props.runsHasMore + ? html` +
+
- `} - ${(props.runsScope === "all" || props.runsJobId != null) && props.runsHasMore - ? html` -
- -
- ` - : nothing} + ` + : nothing + }
- ${formOpen - ? html` -
-
- - ${blockedByValidation - ? html` -
-
${t("cron.form.cantAddYet")}
-
${t("cron.form.fillRequired")}
- -
- ` - : nothing} -
- - ${submitDisabledReason - ? html` -
- ${submitDisabledReason} -
- ` - : nothing} - ${isEditing - ? html` - - ` - : nothing} -
- - - ` - : nothing} + : nothing + } + + + + ` + : nothing + } ${renderSuggestionList("cron-agent-suggestions", props.agentSuggestions)} ${renderSuggestionList("cron-model-suggestions", props.modelSuggestions)} ${renderSuggestionList("cron-thinking-suggestions", props.thinkingSuggestions)} @@ -1599,11 +1685,13 @@ function renderJob(job: CronJob, props: CronProps) {
${job.name}
${formatCronSchedule(job)}
- ${job.agentId - ? html`
- ${t("cron.jobDetail.agent")}: ${job.agentId} -
` - : nothing} + ${ + job.agentId + ? html`
+ ${t("cron.jobDetail.agent")}: ${job.agentId} +
` + : nothing + }
${renderJobState(job)}
@@ -1723,12 +1811,14 @@ function renderJobPayload(job: CronJob) { ${unsafeHTML(toSanitizedMarkdownHtml(payload.message))} - ${delivery - ? html`
- ${t("cron.jobDetail.delivery")} - ${delivery.mode}${deliveryTarget} -
` - : nothing} + ${ + delivery + ? html`
+ ${t("cron.jobDetail.delivery")} + ${delivery.mode}${deliveryTarget} +
` + : nothing + } `; } @@ -1860,38 +1950,46 @@ function renderRun(
${formatMs(entry.ts)}
- ${typeof entry.runAtMs === "number" - ? html`
${t("cron.runEntry.runAt")} ${formatMs(entry.runAtMs)}
` - : nothing} + ${ + typeof entry.runAtMs === "number" + ? html`
+ ${t("cron.runEntry.runAt")} ${formatMs(entry.runAtMs)} +
` + : nothing + }
${entry.durationMs ?? 0}ms
- ${typeof entry.nextRunAtMs === "number" - ? html`
${formatRunNextLabel(entry.nextRunAtMs)}
` - : nothing} - ${chatUrl - ? html`
- { - if ( - e.defaultPrevented || - e.button !== 0 || - e.metaKey || - e.ctrlKey || - e.shiftKey || - e.altKey - ) { - return; - } - if (onNavigateToChat && entry.sessionKey) { - e.preventDefault(); - onNavigateToChat(entry.sessionKey); - } - }} - >${t("cron.runEntry.openRunChat")} -
` - : nothing} + ${ + typeof entry.nextRunAtMs === "number" + ? html`
${formatRunNextLabel(entry.nextRunAtMs)}
` + : nothing + } + ${ + chatUrl + ? html`
+ { + if ( + e.defaultPrevented || + e.button !== 0 || + e.metaKey || + e.ctrlKey || + e.shiftKey || + e.altKey + ) { + return; + } + if (onNavigateToChat && entry.sessionKey) { + e.preventDefault(); + onNavigateToChat(entry.sessionKey); + } + }} + >${t("cron.runEntry.openRunChat")} +
` + : nothing + } ${showErrorInMeta ? html`
${entry.error}
` : nothing} ${entry.deliveryError ? html`
${entry.deliveryError}
` : nothing}
diff --git a/ui/src/ui/views/debug.ts b/ui/src/ui/views/debug.ts index b08e0a2ba6dbd..982dbb80fa3ee 100644 --- a/ui/src/ui/views/debug.ts +++ b/ui/src/ui/views/debug.ts @@ -55,17 +55,19 @@ export function renderDebug(props: DebugProps) {
${t("debug.status")}
- ${securitySummary - ? html`
- ${t("debug.security.audit")}: - ${securityLabel}${info > 0 - ? ` · ${t("debug.security.info", { count: String(info) })}` - : ""}. - ${t("debug.security.runPrefix")} - openclaw security audit --deep - ${t("debug.security.runSuffix")} -
` - : nothing} + ${ + securitySummary + ? html`
+ ${t("debug.security.audit")}: + ${securityLabel}${ + info > 0 ? ` · ${t("debug.security.info", { count: String(info) })}` : "" + }. + ${t("debug.security.runPrefix")} + openclaw security audit --deep + ${t("debug.security.runSuffix")} +
` + : nothing + }
${JSON.stringify(props.status ?? {}, null, 2)}
@@ -90,9 +92,11 @@ export function renderDebug(props: DebugProps) { @change=${(e: Event) => props.onCallMethodChange((e.target as HTMLSelectElement).value)} > - ${!props.callMethod - ? html` ` - : nothing} + ${ + !props.callMethod + ? html` ` + : nothing + } ${props.methods.map((m) => html``)} @@ -109,12 +113,16 @@ export function renderDebug(props: DebugProps) {
- ${props.callError - ? html`
${props.callError}
` - : nothing} - ${props.callResult - ? html`
${props.callResult}
` - : nothing} + ${ + props.callError + ? html`
${props.callError}
` + : nothing + } + ${ + props.callResult + ? html`
${props.callResult}
` + : nothing + }
@@ -122,34 +130,34 @@ export function renderDebug(props: DebugProps) {
${t("debug.modelsTitle")}
${t("debug.modelsSubtitle")}
-${JSON.stringify(props.models ?? [], null, 2)}
+${JSON.stringify(props.models ?? [], null, 2)}
${t("debug.eventLogTitle")}
${t("debug.eventLogSubtitle")}
- ${props.eventLog.length === 0 - ? html`
${t("debug.noEvents")}
` - : html` -
- ${props.eventLog.map( - (evt) => html` -
-
-
${evt.event}
-
${formatTimeMs(evt.ts, undefined, "")}
+ ${ + props.eventLog.length === 0 + ? html`
${t("debug.noEvents")}
` + : html` +
+ ${props.eventLog.map( + (evt) => html` +
+
+
${evt.event}
+
${formatTimeMs(evt.ts, undefined, "")}
+
+
+
+${formatEventPayload(evt.payload)}
+
-
-
-${formatEventPayload(evt.payload)}
-
-
- `, - )} -
- `} + `, + )} +
+ ` + }
`; } diff --git a/ui/src/ui/views/dreaming-restart-confirmation.ts b/ui/src/ui/views/dreaming-restart-confirmation.ts index 25cfffbb53aef..fa91b32a7d8c7 100644 --- a/ui/src/ui/views/dreaming-restart-confirmation.ts +++ b/ui/src/ui/views/dreaming-restart-confirmation.ts @@ -37,14 +37,20 @@ export function renderDreamingRestartConfirmation(props: DreamingRestartConfirma
${t("dreaming.restartConfirmation.warning")}
- ${props.hasError - ? html`
${t("dreaming.restartConfirmation.failed")}
` - : nothing} + ${ + props.hasError + ? html`
+ ${t("dreaming.restartConfirmation.failed")} +
` + : nothing + }
- ${props.agentOptions.length > 1 - ? html`` - : nothing} + ${ + props.agentOptions.length > 1 + ? html`` + : nothing + }
- ${activeSubTab === "scene" - ? renderScene(props, idle, dreamText) - : activeSubTab === "diary" - ? renderDiarySection(props) - : renderAdvancedSection(props)} + ${ + activeSubTab === "scene" + ? renderScene(props, idle, dreamText) + : activeSubTab === "diary" + ? renderDiarySection(props) + : renderAdvancedSection(props) + }
`; } @@ -428,21 +435,23 @@ function renderScene(props: DreamingProps, idle: boolean, dreamText: string) {
- ${props.active - ? html` -
- ${dreamText} -
-
-
- ` - : nothing} + ${ + props.active + ? html` +
+ ${dreamText} +
+
+
+ ` + : nothing + }
${sleepingLobster}
@@ -458,9 +467,11 @@ function renderScene(props: DreamingProps, idle: boolean, dreamText: string) {
${props.promotedCount} ${t("dreaming.status.promotedSuffix")} - ${props.nextCycle - ? html`· ${t("dreaming.status.nextSweepPrefix")} ${props.nextCycle}` - : nothing} + ${ + props.nextCycle + ? html`· ${t("dreaming.status.nextSweepPrefix")} ${props.nextCycle}` + : nothing + } ${props.timezone ? html`· ${props.timezone}` : nothing} @@ -487,9 +498,11 @@ function renderScene(props: DreamingProps, idle: boolean, dreamText: string) { )} - ${props.statusError - ? html`
${props.statusError}
` - : nothing} + ${ + props.statusError + ? html`
${props.statusError}
` + : nothing + } `; } @@ -685,23 +698,29 @@ function renderWikiPreviewOverlay(props: DreamingProps) {
- ${wikiPreviewLoading - ? html`
Loading wiki page…
` - : wikiPreviewError - ? html`
${wikiPreviewError}
` - : html` - ${wikiPreviewTruncated - ? html` -
- Showing the first chunk of this - page${wikiPreviewTotalLines !== null - ? ` (${wikiPreviewTotalLines} total lines)` - : ""}. -
- ` - : nothing} -
${wikiPreviewContent}
- `} + ${ + wikiPreviewLoading + ? html`
Loading wiki page…
` + : wikiPreviewError + ? html`
${wikiPreviewError}
` + : html` + ${ + wikiPreviewTruncated + ? html` +
+ Showing the first chunk of this + page${ + wikiPreviewTotalLines !== null + ? ` (${wikiPreviewTotalLines} total lines)` + : "" + }. +
+ ` + : nothing + } +
${wikiPreviewContent}
+ ` + }
@@ -806,36 +825,40 @@ function renderAdvancedEntryList(params: { ${params.entries.length} - ${params.entries.length === 0 - ? html`
${t(params.emptyKey)}
` - : html` -
- ${params.entries.map( - (entry) => html` -
- ${params.badge - ? (() => { - const label = params.badge?.(entry); - return label - ? html`${label}` - : nothing; - })() - : nothing} -
${entry.snippet}
-
- ${formatRange(entry.path, entry.startLine, entry.endLine)} -
-
- ${params - .meta(entry) - .filter((part) => part.length > 0) - .join(" · ")} -
-
- `, - )} -
- `} + ${ + params.entries.length === 0 + ? html`
${t(params.emptyKey)}
` + : html` +
+ ${params.entries.map( + (entry) => html` +
+ ${ + params.badge + ? (() => { + const label = params.badge?.(entry); + return label + ? html`${label}` + : nothing; + })() + : nothing + } +
${entry.snippet}
+
+ ${formatRange(entry.path, entry.startLine, entry.endLine)} +
+
+ ${params + .meta(entry) + .filter((part) => part.length > 0) + .join(" · ")} +
+
+ `, + )} +
+ ` + } `; } @@ -856,9 +879,9 @@ function renderAdvancedSection(props: DreamingProps) {
${t("dreaming.advanced.eyebrow")}

${t("dreaming.advanced.title")}

- ${description - ? html`

${description}

` - : nothing} + ${ + description ? html`

${description}

` : nothing + }
${summary}
@@ -881,9 +904,11 @@ function renderAdvancedSection(props: DreamingProps) { ?disabled=${props.modeSaving || props.dreamDiaryActionLoading} @click=${() => props.onBackfillDiary()} > - ${props.dreamDiaryActionLoading - ? t("dreaming.scene.working") - : t("dreaming.scene.backfill")} + ${ + props.dreamDiaryActionLoading + ? t("dreaming.scene.working") + : t("dreaming.scene.backfill") + }
- ${props.dreamDiaryActionMessage - ? html` -
-
- ${props.dreamDiaryActionMessage.text} - ${props.dreamDiaryActionArchivePath - ? html` - - ` - : nothing} + ${ + props.dreamDiaryActionMessage + ? html` +
+
+ ${props.dreamDiaryActionMessage.text} + ${ + props.dreamDiaryActionArchivePath + ? html` + + ` + : nothing + } +
-
- ` - : nothing} + ` + : nothing + }
${renderAdvancedEntryList({ @@ -959,9 +988,9 @@ function renderAdvancedSection(props: DreamingProps) { controls: html`
- ${props.statusError - ? html`
${props.statusError}
` - : nothing} + ${ + props.statusError + ? html`
${props.statusError}
` + : nothing + } `; } @@ -1052,9 +1083,9 @@ function renderDiaryImportsSection(props: DreamingProps) { ${clusters.map( (entry, index) => html`

Full vault breakdown: ${pageBreakdown}.

@@ -1300,56 +1353,69 @@ function renderMemoryPalaceSection(props: DreamingProps) { ${item.updatedAt ? formatCompactDateTime(item.updatedAt) : basename(item.pagePath)} · ${item.pagePath}
- ${item.snippet - ? html`

${item.snippet}

` - : nothing} - ${item.claims.length > 0 - ? html` -
- Claims - ${item.claims.map( - (claim) => html`

• ${claim}

`, - )} -
- ` - : nothing} - ${item.questions.length > 0 - ? html` -
- Open questions - ${item.questions.map( - (question) => html`

• ${question}

`, - )} -
- ` - : nothing} - ${item.contradictions.length > 0 - ? html` -
- Contradictions - ${item.contradictions.map( - (entry) => html`

• ${entry}

`, - )} -
- ` - : nothing} - ${expanded - ? html` -
- Page details -

- Wiki page: ${item.pagePath} -

- ${item.id - ? html` -

- Id: ${item.id} -

- ` - : nothing} -
- ` - : nothing} + ${ + item.snippet + ? html`

${item.snippet}

` + : nothing + } + ${ + item.claims.length > 0 + ? html` +
+ Claims + ${item.claims.map( + (claim) => html`

• ${claim}

`, + )} +
+ ` + : nothing + } + ${ + item.questions.length > 0 + ? html` +
+ Open questions + ${item.questions.map( + (question) => + html`

• ${question}

`, + )} +
+ ` + : nothing + } + ${ + item.contradictions.length > 0 + ? html` +
+ Contradictions + ${item.contradictions.map( + (entry) => html`

• ${entry}

`, + )} +
+ ` + : nothing + } + ${ + expanded + ? html` +
+ Page details +

+ Wiki page: ${item.pagePath} +

+ ${ + item.id + ? html` +

+ Id: ${item.id} +

+ ` + : nothing + } +
+ ` + : nothing + }
${renderDiarySubtabExplainer()} - ${memoryWikiUnavailable - ? html` -
-
Memory Wiki is not enabled
-
- Imported Insights and Memory Palace are provided by the bundled - memory-wiki plugin. + ${ + memoryWikiUnavailable + ? html` +
+
Memory Wiki is not enabled
+
+ Imported Insights and Memory Palace are provided by the bundled + memory-wiki plugin. +
+
+ Enable plugins.entries.memory-wiki.enabled = true, then reload this + tab. +
+
+ +
-
- Enable plugins.entries.memory-wiki.enabled = true, then reload this - tab. -
-
- -
-
- ` - : activeDiarySubTab === "dreams" - ? renderDreamDiaryEntries(props) - : activeDiarySubTab === "insights" - ? renderDiaryImportsSection(props) - : renderMemoryPalaceSection(props)} + ` + : activeDiarySubTab === "dreams" + ? renderDreamDiaryEntries(props) + : activeDiarySubTab === "insights" + ? renderDiaryImportsSection(props) + : renderMemoryPalaceSection(props) + } ${renderWikiPreviewOverlay(props)} `; diff --git a/ui/src/ui/views/exec-approval.ts b/ui/src/ui/views/exec-approval.ts index 372be14a5d6d7..07e4cb702fd51 100644 --- a/ui/src/ui/views/exec-approval.ts +++ b/ui/src/ui/views/exec-approval.ts @@ -101,11 +101,12 @@ function renderExecBody(request: ExecApprovalRequestPayload) { function renderPluginBody(active: ExecApprovalRequest) { return html` - ${active.pluginDescription - ? html`
-${active.pluginDescription}
` - : nothing} + ${ + active.pluginDescription + ? html`
+${active.pluginDescription}
` + : nothing + }
${renderMetaRow(t("execApproval.labels.severity"), active.pluginSeverity)} ${renderMetaRow(t("execApproval.labels.plugin"), active.pluginId)} @@ -190,17 +191,21 @@ export function renderExecApprovalPrompt(state: AppViewState) {
${title}
${remaining}
- ${queueCount > 1 - ? html`
- ${t("execApproval.pending", { count: String(queueCount) })} -
` - : nothing} + ${ + queueCount > 1 + ? html`
+ ${t("execApproval.pending", { count: String(queueCount) })} +
` + : nothing + }
${isPlugin ? renderPluginBody(active) : renderExecBody(request)} ${renderUnavailableDecisionWarning(active, decisions)} - ${state.execApprovalError - ? html`
${state.execApprovalError}
` - : nothing} + ${ + state.execApprovalError + ? html`
${state.execApprovalError}
` + : nothing + }
${decisions.map( (decision) => html` diff --git a/ui/src/ui/views/instances.ts b/ui/src/ui/views/instances.ts index 4b0ea8c51a780..3b00e0c2ee1e0 100644 --- a/ui/src/ui/views/instances.ts +++ b/ui/src/ui/views/instances.ts @@ -44,16 +44,22 @@ export function renderInstances(props: InstancesProps) {
- ${props.lastError - ? html`
${props.lastError}
` - : nothing} - ${props.statusMessage - ? html`
${props.statusMessage}
` - : nothing} + ${ + props.lastError + ? html`
${props.lastError}
` + : nothing + } + ${ + props.statusMessage + ? html`
${props.statusMessage}
` + : nothing + }
- ${props.entries.length === 0 - ? html`
${t("instances.noInstances")}
` - : props.entries.map((entry) => renderEntry(entry, masked))} + ${ + props.entries.length === 0 + ? html`
${t("instances.noInstances")}
` + : props.entries.map((entry) => renderEntry(entry, masked)) + }
`; @@ -91,9 +97,11 @@ function renderEntry(entry: PresenceEntry, masked: boolean) { ${scopesLabel ? html`${scopesLabel}` : nothing} ${entry.platform ? html`${entry.platform}` : nothing} ${entry.deviceFamily ? html`${entry.deviceFamily}` : nothing} - ${entry.modelIdentifier - ? html`${entry.modelIdentifier}` - : nothing} + ${ + entry.modelIdentifier + ? html`${entry.modelIdentifier}` + : nothing + } ${entry.version ? html`${entry.version}` : nothing} diff --git a/ui/src/ui/views/login-gate.ts b/ui/src/ui/views/login-gate.ts index fc735b3034af8..4319b45d4831b 100644 --- a/ui/src/ui/views/login-gate.ts +++ b/ui/src/ui/views/login-gate.ts @@ -356,9 +356,9 @@ export function renderLoginGate(state: AppViewState) { + ` + : nothing + } + ` + : content + ? content.kind === "canvas" ? html` - - ` - : nothing} - ` - : content - ? content.kind === "canvas" - ? html` -
-
- ${keyed( - `${canvasSandbox}\u0000${canvasSrc ?? ""}\u0000${content.preferredHeight ?? ""}`, - html` - - `, - )} +
+
+ ${keyed( + `${canvasSandbox}\u0000${canvasSrc ?? ""}\u0000${content.preferredHeight ?? ""}`, + html` + + `, + )} +
+ ${ + content.rawText?.trim() + ? html` +
+ +
+ ` + : nothing + }
- ${content.rawText?.trim() - ? html` -
- + ` + : html` + + ` + : html`
No content available
` + }
`; diff --git a/ui/src/ui/views/mcp.ts b/ui/src/ui/views/mcp.ts index fc88d5642b3fe..0cc774948b24c 100644 --- a/ui/src/ui/views/mcp.ts +++ b/ui/src/ui/views/mcp.ts @@ -160,21 +160,22 @@ export function renderMcp(props: McpViewProps) {
- ${rows.length - ? html`
- ${rows.map((row) => renderServerRow(props, row))} -
` - : html`
No MCP servers configured.
`} + ${ + rows.length + ? html`
+ ${rows.map((row) => renderServerRow(props, row))} +
` + : html`
No MCP servers configured.
` + } ${props.editor} diff --git a/ui/src/ui/views/nodes-exec-approvals.ts b/ui/src/ui/views/nodes-exec-approvals.ts index 19cefc29e51b0..66a02c1234cae 100644 --- a/ui/src/ui/views/nodes-exec-approvals.ts +++ b/ui/src/ui/views/nodes-exec-approvals.ts @@ -213,19 +213,23 @@ export function renderExecApprovals(state: ExecApprovalsState) { ${renderExecApprovalsTarget(state)} - ${!ready - ? html`
-
Load exec approvals to edit allowlists.
- -
` - : html` - ${renderExecApprovalsTabs(state)} ${renderExecApprovalsPolicy(state)} - ${state.selectedScope === EXEC_APPROVALS_DEFAULT_SCOPE - ? nothing - : renderExecApprovalsAllowlist(state)} - `} + ${ + !ready + ? html`
+
Load exec approvals to edit allowlists.
+ +
` + : html` + ${renderExecApprovalsTabs(state)} ${renderExecApprovalsPolicy(state)} + ${ + state.selectedScope === EXEC_APPROVALS_DEFAULT_SCOPE + ? nothing + : renderExecApprovalsAllowlist(state) + } + ` + } `; } @@ -260,34 +264,38 @@ function renderExecApprovalsTarget(state: ExecApprovalsState) { - ${state.target === "node" - ? html` - - ` - : nothing} + ${ + state.target === "node" + ? html` + + ` + : nothing + } - ${state.target === "node" && !hasNodes - ? html`
No nodes advertise exec approvals yet.
` - : nothing} + ${ + state.target === "node" && !hasNodes + ? html`
No nodes advertise exec approvals yet.
` + : nothing + } `; } @@ -298,9 +306,9 @@ function renderExecApprovalsTabs(state: ExecApprovalsState) { Scope
` - : nothing} + ${ + !isDefaults && !autoIsDefault + ? html`` + : nothing + }
@@ -515,9 +538,11 @@ function renderExecApprovalsAllowlist(state: ExecApprovalsState) {
- ${entries.length === 0 - ? html`
No allowlist entries yet.
` - : entries.map((entry, index) => renderAllowlistEntry(state, entry, index))} + ${ + entries.length === 0 + ? html`
No allowlist entries yet.
` + : entries.map((entry, index) => renderAllowlistEntry(state, entry, index)) + }
`; } diff --git a/ui/src/ui/views/nodes.ts b/ui/src/ui/views/nodes.ts index a50b4d62eb0ee..0a10e2bc12f4d 100644 --- a/ui/src/ui/views/nodes.ts +++ b/ui/src/ui/views/nodes.ts @@ -30,9 +30,11 @@ export function renderNodes(props: NodesProps) {
- ${props.nodes.length === 0 - ? html`
No nodes found.
` - : props.nodes.map((n) => renderNode(n))} + ${ + props.nodes.length === 0 + ? html`
No nodes found.
` + : props.nodes.map((n) => renderNode(n)) + }
`; @@ -58,27 +60,35 @@ function renderDevices(props: NodesProps) { ${props.devicesLoading ? t("common.loading") : t("common.refresh")} - ${props.devicesError - ? html`
${props.devicesError}
` - : nothing} + ${ + props.devicesError + ? html`
${props.devicesError}
` + : nothing + }
- ${pending.length > 0 - ? html` -
Pending
- ${pending.map((req) => - renderPendingDevice(req, props, lookupPairedDevice(pairedByDeviceId, req)), - )} - ` - : nothing} - ${paired.length > 0 - ? html` -
Paired
- ${paired.map((device) => renderPairedDevice(device, props))} - ` - : nothing} - ${pending.length === 0 && paired.length === 0 - ? html`
No paired devices.
` - : nothing} + ${ + pending.length > 0 + ? html` +
Pending
+ ${pending.map((req) => + renderPendingDevice(req, props, lookupPairedDevice(pairedByDeviceId, req)), + )} + ` + : nothing + } + ${ + paired.length > 0 + ? html` +
Paired
+ ${paired.map((device) => renderPairedDevice(device, props))} + ` + : nothing + } + ${ + pending.length === 0 && paired.length === 0 + ? html`
No paired devices.
` + : nothing + }
`; @@ -144,13 +154,15 @@ function renderPendingDevice(req: PendingDevice, props: NodesProps, paired?: Pai
requested: ${formatAccessSummary(approval.requested)}
- ${approval.approved - ? html` -
- approved now: ${formatAccessSummary(approval.approved)} -
- ` - : nothing} + ${ + approval.approved + ? html` +
+ approved now: ${formatAccessSummary(approval.approved)} +
+ ` + : nothing + }
@@ -178,14 +190,16 @@ function renderPairedDevice(device: PairedDevice, props: NodesProps) {
${name}
${device.deviceId}${ip}
${roles} · ${scopes}
- ${tokens.length === 0 - ? html`
Tokens: none
` - : html` -
Tokens
-
- ${tokens.map((token) => renderTokenRow(device.deviceId, token, props))} -
- `} + ${ + tokens.length === 0 + ? html`
Tokens: none
` + : html` +
Tokens
+
+ ${tokens.map((token) => renderTokenRow(device.deviceId, token, props))} +
+ ` + }
`; @@ -207,16 +221,18 @@ function renderTokenRow(deviceId: string, token: DeviceTokenSummary, props: Node > Rotate - ${token.revokedAtMs - ? nothing - : html` - - `} + ${ + token.revokedAtMs + ? nothing + : html` + + ` + } `; @@ -290,58 +306,66 @@ function renderBindings(state: BindingState) { - ${state.formMode === "raw" - ? html` -
- ${t("nodes.binding.formModeHint")} -
- ` - : nothing} - ${!state.ready - ? html`
-
${t("nodes.binding.loadConfigHint")}
- -
` - : html` -
-
-
-
${t("nodes.binding.defaultBinding")}
-
${t("nodes.binding.defaultBindingHint")}
-
-
- - ${!supportsBinding - ? html`
No nodes with system.run available.
` - : nothing} -
+ ${ + state.formMode === "raw" + ? html` +
+ ${t("nodes.binding.formModeHint")}
+ ` + : nothing + } + ${ + !state.ready + ? html`
+
${t("nodes.binding.loadConfigHint")}
+ +
` + : html` +
+
+
+
${t("nodes.binding.defaultBinding")}
+
${t("nodes.binding.defaultBindingHint")}
+
+
+ + ${ + !supportsBinding + ? html`
No nodes with system.run available.
` + : nothing + } +
+
- ${state.agents.length === 0 - ? html`
No agents found.
` - : state.agents.map((agent) => renderAgentBinding(agent, state))} -
- `} + ${ + state.agents.length === 0 + ? html`
No agents found.
` + : state.agents.map((agent) => renderAgentBinding(agent, state)) + } +
+ ` + } `; } @@ -356,9 +380,11 @@ function renderAgentBinding(agent: BindingAgent, state: BindingState) {
${label}
${agent.isDefault ? "default agent" : "agent"} · - ${bindingValue === "__default__" - ? `uses default (${state.defaultBinding ?? "any"})` - : `override: ${agent.binding}`} + ${ + bindingValue === "__default__" + ? `uses default (${state.defaultBinding ?? "any"})` + : `override: ${agent.binding}` + }
diff --git a/ui/src/ui/views/overview-attention.ts b/ui/src/ui/views/overview-attention.ts index 688e057669b44..5ea70a3f5e8cf 100644 --- a/ui/src/ui/views/overview-attention.ts +++ b/ui/src/ui/views/overview-attention.ts @@ -43,15 +43,17 @@ export function renderOverviewAttention(props: OverviewAttentionProps) {
${item.title}
${item.description}
- ${item.href - ? html`${t("common.docs")}` - : nothing} + ${ + item.href + ? html`${t("common.docs")}` + : nothing + } `, )} diff --git a/ui/src/ui/views/overview-cards.ts b/ui/src/ui/views/overview-cards.ts index 4a1c99f59de13..4da62d60ce4df 100644 --- a/ui/src/ui/views/overview-cards.ts +++ b/ui/src/ui/views/overview-cards.ts @@ -281,27 +281,29 @@ export function renderOverviewCards(props: OverviewCardsProps) { return html`
${cards.map((c) => renderStatCard(c, props.onNavigate))}
- ${sessions.length > 0 - ? html` -
-

${t("overview.cards.recentSessions")}

- -
- ` - : nothing} + ${ + sessions.length > 0 + ? html` +
+

${t("overview.cards.recentSessions")}

+ +
+ ` + : nothing + } `; } diff --git a/ui/src/ui/views/overview-event-log.ts b/ui/src/ui/views/overview-event-log.ts index 7cb982aa0957c..33b4e5ddfccbc 100644 --- a/ui/src/ui/views/overview-event-log.ts +++ b/ui/src/ui/views/overview-event-log.ts @@ -30,11 +30,13 @@ export function renderOverviewEventLog(props: OverviewEventLogProps) {
${formatTimeMs(entry.ts, undefined, "")} ${entry.event} - ${entry.payload - ? html`${formatEventPayload(entry.payload).slice(0, 120)}` - : nothing} + ${ + entry.payload + ? html`${formatEventPayload(entry.payload).slice(0, 120)}` + : nothing + }
`, )} diff --git a/ui/src/ui/views/overview.ts b/ui/src/ui/views/overview.ts index 9335e97a9fbe7..9d86b3aec4419 100644 --- a/ui/src/ui/views/overview.ts +++ b/ui/src/ui/views/overview.ts @@ -115,14 +115,16 @@ export function renderOverview(props: OverviewProps) { return html`
${title} - ${copy.summaryKey - ? html`
${t(copy.summaryKey)}
` - : nothing} + ${ + copy.summaryKey ? html`
${t(copy.summaryKey)}
` : nothing + }
- ${pairingState.requestId - ? html`openclaw devices approve ${pairingState.requestId}
` - : nothing} + ${ + pairingState.requestId + ? html`openclaw devices approve ${pairingState.requestId}
` + : nothing + } openclaw devices list
${t("overview.pairing.mobileHint")}
@@ -275,68 +277,74 @@ export function renderOverview(props: OverviewProps) { placeholder="ws://100.x.y.z:18789" /> - ${isTrustedProxy - ? "" - : html` - - - `} + ${ + isTrustedProxy + ? "" + : html` + + + ` + }
- ${!props.connected - ? html` -
-
${t("overview.connection.title")}
-
    -
  1. - ${t("overview.connection.step1")} - ${renderConnectCommand("openclaw gateway run")} -
  2. -
  3. - ${t("overview.connection.step2")} ${renderConnectCommand("openclaw dashboard")} -
  4. -
  5. ${t("overview.connection.step3")}
  6. -
  7. - ${t("overview.connection.step4")}openclaw doctor --generate-gateway-token + + +
  8. -
-
- ${t("overview.connection.docsHint")} - ${t("overview.connection.docsLink")} +
- - ` - : nothing} + ` + : nothing + }
@@ -430,23 +441,27 @@ export function renderOverview(props: OverviewProps) {
${t("overview.snapshot.lastChannelsRefresh")}
- ${props.lastChannelsRefresh - ? formatRelativeTimestamp(props.lastChannelsRefresh) - : t("common.na")} + ${ + props.lastChannelsRefresh + ? formatRelativeTimestamp(props.lastChannelsRefresh) + : t("common.na") + }
- ${props.lastError - ? html`
-
${props.lastError}
- ${pairingHint ?? ""} ${authHint ?? ""} ${insecureContextHint ?? ""} - ${queryTokenHint ?? ""} -
` - : html` -
- ${t("overview.snapshot.channelsHint")} -
- `} + ${ + props.lastError + ? html`
+
${props.lastError}
+ ${pairingHint ?? ""} ${authHint ?? ""} ${insecureContextHint ?? ""} + ${queryTokenHint ?? ""} +
` + : html` +
+ ${t("overview.snapshot.channelsHint")} +
+ ` + } diff --git a/ui/src/ui/views/sessions.ts b/ui/src/ui/views/sessions.ts index d810ca36b2658..583020e205942 100644 --- a/ui/src/ui/views/sessions.ts +++ b/ui/src/ui/views/sessions.ts @@ -544,9 +544,11 @@ export function renderSessions(props: SessionsProps) {
${t("sessionsView.title")}
- ${props.result - ? t("sessionsView.store", { path: props.result.path }) - : t("sessionsView.subtitle")} + ${ + props.result + ? t("sessionsView.store", { path: props.result.path }) + : t("sessionsView.subtitle") + }
- ${filtersExpanded - ? html` -
-
-
- ` - : nothing} + ` + : nothing + }
- ${props.error - ? html`
${props.error}
` - : nothing} + ${ + props.error + ? html`
${props.error}
` + : nothing + }
@@ -680,49 +686,59 @@ export function renderSessions(props: SessionsProps) {
- ${props.selectedKeys.size > 0 - ? html` -
- ${t("sessionsView.selected", { count: String(props.selectedKeys.size) })} - - -
- ` - : nothing} + ${ + props.selectedKeys.size > 0 + ? html` +
+ ${t("sessionsView.selected", { count: String(props.selectedKeys.size) })} + + +
+ ` + : nothing + }
${sortHeader("key", t("sessionsView.key"), "data-table-key-col")} @@ -740,57 +756,67 @@ export function renderSessions(props: SessionsProps) { - ${paginated.length === 0 - ? html` - - - - ` - : paginated.flatMap((row) => renderRows(row, props))} + ${ + paginated.length === 0 + ? html` + + + + ` + : paginated.flatMap((row) => renderRows(row, props)) + }
- ${paginated.length > 0 - ? html` 0 && - paginated.every((r) => props.selectedKeys.has(r.key))} - .indeterminate=${paginated.some((r) => props.selectedKeys.has(r.key)) && - !paginated.every((r) => props.selectedKeys.has(r.key))} - @change=${() => { - const allSelected = paginated.every((r) => props.selectedKeys.has(r.key)); - if (allSelected) { - props.onDeselectPage(paginated.map((r) => r.key)); - } else { - props.onSelectPage(paginated.map((r) => r.key)); + ${ + paginated.length > 0 + ? html` 0 && + paginated.every((r) => props.selectedKeys.has(r.key)) + } + .indeterminate=${ + paginated.some((r) => props.selectedKeys.has(r.key)) && + !paginated.every((r) => props.selectedKeys.has(r.key)) } - }} - aria-label=${t("sessionsView.selectAllOnPage")} - />` - : nothing} + @change=${() => { + const allSelected = paginated.every((r) => + props.selectedKeys.has(r.key), + ); + if (allSelected) { + props.onDeselectPage(paginated.map((r) => r.key)); + } else { + props.onSelectPage(paginated.map((r) => r.key)); + } + }} + aria-label=${t("sessionsView.selectAllOnPage")} + />` + : nothing + } ${t("sessionsView.label")}
- ${emptyBecauseFiltered - ? html` -
-
${t("sessionsView.noSessionsMatchFilters")}
- -
- ` - : t("sessionsView.noSessions")} -
+ ${ + emptyBecauseFiltered + ? html` +
+
${t("sessionsView.noSessionsMatchFilters")}
+ +
+ ` + : t("sessionsView.noSessions") + } +
- ${totalRows > 0 - ? html` -
-
- ${page * props.pageSize + 1}-${Math.min((page + 1) * props.pageSize, totalRows)} - of ${totalRows} row${totalRows === 1 ? "" : "s"} -
-
- - - + ${ + totalRows > 0 + ? html` +
+
+ ${page * props.pageSize + 1}-${Math.min((page + 1) * props.pageSize, totalRows)} + of ${totalRows} row${totalRows === 1 ? "" : "s"} +
+
+ + + +
-
- ` - : nothing} + ` + : nothing + }
`; @@ -904,32 +930,36 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) { class=${friendlyKeyLabel ? "session-key-cell" : "mono session-key-cell"} title=${keyCellTitle} > - ${canLink - ? html` { - if ( - e.defaultPrevented || - e.button !== 0 || - e.metaKey || - e.ctrlKey || - e.shiftKey || - e.altKey - ) { - return; - } - if (props.onNavigateToChat) { - e.preventDefault(); - props.onNavigateToChat(row.key); - } - }} - >${friendlyKeyLabel ?? row.key}` - : (friendlyKeyLabel ?? row.key)} - ${showDisplayName - ? html`${displayName}` - : nothing} + ${ + canLink + ? html` { + if ( + e.defaultPrevented || + e.button !== 0 || + e.metaKey || + e.ctrlKey || + e.shiftKey || + e.altKey + ) { + return; + } + if (props.onNavigateToChat) { + e.preventDefault(); + props.onNavigateToChat(row.key); + } + }} + >${friendlyKeyLabel ?? row.key}` + : (friendlyKeyLabel ?? row.key) + } + ${ + showDisplayName + ? html`${displayName}` + : nothing + } @@ -959,25 +989,29 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) { ${formatSessionTokens(row)}
- ${hasCheckpoints - ? html` - - ` - : html`${t("common.none")}`} + ${ + hasCheckpoints + ? html` + + ` + : html`${t("common.none")}` + }
@@ -1051,23 +1085,27 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) { - ${props.onAddToWorkboard && canLink - ? html` - - ` - : nothing} + ${ + props.onAddToWorkboard && canLink + ? html` + + ` + : nothing + } `, ...(isExpanded && hasCheckpoints @@ -1081,11 +1119,13 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) { ${t("sessionsView.sessionDetails")}
${friendlyKeyLabel ?? row.key}
- ${showDisplayName - ? html` -
${displayName}
- ` - : nothing} + ${ + showDisplayName + ? html` +
${displayName}
+ ` + : nothing + }
${renderSessionStatusBadge(row)} ${renderSessionGoalChip(row.goal)} @@ -1115,68 +1155,76 @@ function renderRows(row: GatewaySessionRow, props: SessionsProps) {
${checkpointLabel}
- ${props.checkpointLoadingKey === row.key - ? html`
- ${t("sessionsView.loadingCheckpoints")} -
` - : checkpointError - ? html`
${checkpointError}
` - : checkpointItems.length === 0 - ? html`
- ${t("sessionsView.noCheckpoints")} -
` - : html` -
- ${checkpointItems.map( - (checkpoint) => html` -
-
- - ${formatCheckpointReason(checkpoint.reason)} · - ${formatRelativeTimestamp(checkpoint.createdAt)} - - - ${formatCheckpointDelta(checkpoint)} - + ${ + props.checkpointLoadingKey === row.key + ? html`
+ ${t("sessionsView.loadingCheckpoints")} +
` + : checkpointError + ? html`
${checkpointError}
` + : checkpointItems.length === 0 + ? html`
+ ${t("sessionsView.noCheckpoints")} +
` + : html` +
+ ${checkpointItems.map( + (checkpoint) => html` +
+
+ + ${formatCheckpointReason(checkpoint.reason)} · + ${formatRelativeTimestamp(checkpoint.createdAt)} + + + ${formatCheckpointDelta(checkpoint)} + +
+ ${ + checkpoint.summary + ? html`
+ ${checkpoint.summary} +
` + : html` +
+ ${t("sessionsView.noSummary")} +
+ ` + } +
+ + +
- ${checkpoint.summary - ? html`
- ${checkpoint.summary} -
` - : html` -
${t("sessionsView.noSummary")}
- `} -
- - -
-
- `, - )} -
- `} + `, + )} +
+ ` + }
diff --git a/ui/src/ui/views/skill-workshop.ts b/ui/src/ui/views/skill-workshop.ts index 56c223053e280..b21cc3085f597 100644 --- a/ui/src/ui/views/skill-workshop.ts +++ b/ui/src/ui/views/skill-workshop.ts @@ -147,21 +147,23 @@ export function renderSkillWorkshop(props: SkillWorkshopProps) { ${keyed(props.mode, html`
${body}
`)} - ${preview && selected - ? html` - ) => - props.onFilePreviewQueryChange(event.detail)} - @file-preview-select=${(event: CustomEvent) => - props.onPreviewFile(selected.key, event.detail)} - @file-preview-close=${props.onClosePreview} - > - ` - : nothing} + ${ + preview && selected + ? html` + ) => + props.onFilePreviewQueryChange(event.detail)} + @file-preview-select=${(event: CustomEvent) => + props.onPreviewFile(selected.key, event.detail)} + @file-preview-close=${props.onClosePreview} + > + ` + : nothing + } ${revisionProposal ? renderRevisionDialog(props, revisionProposal) : nothing} `; } @@ -208,14 +210,16 @@ function renderRevisionDialog(props: SkillWorkshopProps, proposal: SkillWorkshop @input=${(event: Event) => props.onRevisionDraftChange((event.target as HTMLTextAreaElement).value ?? "")} > - ${busy - ? html` -
- - Preparing revision handoff -
- ` - : nothing} + ${ + busy + ? html` +
+ + Preparing revision handoff +
+ ` + : nothing + }
- ${total === 0 - ? html`
${queueEmptyText(props)}
` - : groups.map( - (group) => html` -
- ${group.label} ${group.items.length} -
- ${group.items.map((proposal) => renderRow(props, proposal, selected))} - `, - )} + ${ + total === 0 + ? html`
${queueEmptyText(props)}
` + : groups.map( + (group) => html` +
+ ${group.label} ${group.items.length} +
+ ${group.items.map((proposal) => renderRow(props, proposal, selected))} + `, + ) + }
`; @@ -402,14 +408,16 @@ function renderDetail(props: SkillWorkshopProps, proposal: SkillWorkshopProposal · v${proposal.version} · - ${proposal.supportFiles.length > 0 - ? html`` - : html`0 support files`} + ${ + proposal.supportFiles.length > 0 + ? html`` + : html`0 support files` + }
@@ -421,34 +429,39 @@ function renderDetail(props: SkillWorkshopProps, proposal: SkillWorkshopProposal

${proposal.slug}

- ${detailLoading - ? html`

Loading proposal…

` - : renderProposalBody(proposal.body)} + ${ + detailLoading + ? html`

Loading proposal…

` + : renderProposalBody(proposal.body) + }
- ${proposal.supportFiles.length > 0 - ? html` -
- -
- ${proposal.supportFiles.map( - (file) => html` - - `, - )} + 📄 + ${file.path} + ${file.size} + · click to preview + + `, + )} +
-
- ` - : nothing} + ` + : nothing + }
${props.actionNotice?.key === proposal.key ? renderActionNotice(props.actionNotice) : nothing} @@ -674,29 +687,31 @@ function renderToday(
${dateLine}

${pending.length} proposals waiting

- ${pending.length === 0 - ? html`
Browse what's already applied.
` - : nothing} - ${pending.length > 0 - ? html` -
- ${heroIndex + 1} of ${total} -
- ${pending.map( - (_, i) => html` - - `, - )} + ${ + pending.length === 0 + ? html`
Browse what's already applied.
` + : nothing + } + ${ + pending.length > 0 + ? html` +
+ ${heroIndex + 1} of ${total} +
+ ${pending.map( + (_, i) => html` + + `, + )} +
-
- ` - : nothing} + ` + : nothing + }
@@ -713,111 +728,119 @@ function renderToday( v${hero.version} Drafted by ${assistantName} · ${ageLabel}. - ${hero.supportFiles.length > 0 - ? html` - - come with it. - ` - : nothing} + ${ + hero.supportFiles.length > 0 + ? html` + + come with it. + ` + : nothing + }
- ${isPending - ? html` -
- - - -
- ` - : nothing} + ${ + isPending + ? html` +
+ + + +
+ ` + : nothing + } ${props.actionNotice?.key === hero.key ? renderActionNotice(props.actionNotice) : nothing} - ${upNext.length > 0 - ? html` -
-
-

Up next · ${pending.length - 1} more waiting

- -
-
- ${upNext.map( - (p) => html` - - `, - )} -
-
- ` - : nothing} - ${applied.length > 0 - ? html` -
-
-

Your collection · ${props.counts.applied} in use

- -
-
- ${applied.map( - (p) => html` - - `, - )} -
-
- ` - : nothing} + ${ + upNext.length > 0 + ? html` +
+
+

Up next · ${pending.length - 1} more waiting

+ +
+
+ ${upNext.map( + (p) => html` + + `, + )} +
+
+ ` + : nothing + } + ${ + applied.length > 0 + ? html` +
+
+

Your collection · ${props.counts.applied} in use

+ +
+
+ ${applied.map( + (p) => html` + + `, + )} +
+
+ ` + : nothing + } `; } diff --git a/ui/src/ui/views/skills.ts b/ui/src/ui/views/skills.ts index 0760c9f2469a3..5d2c834997b73 100644 --- a/ui/src/ui/views/skills.ts +++ b/ui/src/ui/views/skills.ts @@ -271,50 +271,60 @@ export function renderSkills(props: SkillsProps) { ${props.clawhubSearchLoading ? html`Searching…` : nothing} - ${props.clawhubSearchError - ? html`
- ${props.clawhubSearchError} -
` - : nothing} - ${props.clawhubInstallMessage - ? html`
- ${props.clawhubInstallMessage.text} -
` - : nothing} + ${ + props.clawhubSearchError + ? html`
+ ${props.clawhubSearchError} +
` + : nothing + } + ${ + props.clawhubInstallMessage + ? html`
+ ${props.clawhubInstallMessage.text} +
` + : nothing + } ${renderClawHubResults(props)} - ${props.error - ? html`
${props.error}
` - : nothing} - ${filtered.length === 0 - ? html` -
- ${!props.connected && !props.report - ? "Not connected to gateway." - : "No skills found."} -
- ` - : html` -
- ${groups.map((group) => { - return html` -
- - ${group.label} - ${group.skills.length} - -
- ${group.skills.map((skill) => renderSkill(skill, props))} -
-
- `; - })} -
- `} + ${ + props.error + ? html`
${props.error}
` + : nothing + } + ${ + filtered.length === 0 + ? html` +
+ ${ + !props.connected && !props.report + ? "Not connected to gateway." + : "No skills found." + } +
+ ` + : html` +
+ ${groups.map((group) => { + return html` +
+ + ${group.label} + ${group.skills.length} + +
+ ${group.skills.map((skill) => renderSkill(skill, props))} +
+
+ `; + })} +
+ ` + } ${detailSkill ? renderSkillDetail(detailSkill, props) : nothing} @@ -343,9 +353,11 @@ function renderClawHubResults(props: SkillsProps) {
${r.summary ? clampText(r.summary, 120) : r.slug}
- ${r.version - ? html`v${r.version}` - : nothing} + ${ + r.version + ? html`v${r.version}` + : nothing + }
- ${props.clawhubDetailLoading - ? html`
${t("common.loading")}
` - : props.clawhubDetailError - ? html`
${props.clawhubDetailError}
` - : detail?.skill - ? html` -
- ${detail.skill.summary ?? ""} -
- ${detail.owner?.displayName - ? html`
- By - ${detail.owner.displayName}${detail.owner.handle - ? html` (@${detail.owner.handle})` - : nothing} -
` - : nothing} - ${detail.latestVersion - ? html`
- Latest: v${detail.latestVersion.version} -
` - : nothing} - ${detail.latestVersion?.changelog - ? html`
- ${detail.latestVersion.changelog} -
` - : nothing} - ${detail.metadata?.os - ? html`
- Platforms: ${detail.metadata.os.join(", ")} -
` - : nothing} - - ` - : html`
Skill not found.
`} + + ` + : html`
Skill not found.
` + }
@@ -468,11 +492,13 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) { class="list-meta" style="display: flex; align-items: center; justify-content: flex-end; gap: 10px;" > - ${skill.clawhub?.status === "linked" - ? html`${verdictLabel(verdict)}` - : skill.clawhub?.status === "invalid" - ? html`ClawHub link invalid` - : nothing} + ${ + skill.clawhub?.status === "linked" + ? html`${verdictLabel(verdict)}` + : skill.clawhub?.status === "invalid" + ? html`ClawHub link invalid` + : nothing + }