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/ci.yml b/.github/workflows/ci.yml index ef86c6574f189..bc281e9e97acc 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 ;; diff --git a/.github/workflows/docs-agent.yml b/.github/workflows/docs-agent.yml index fc073e0cbb0a1..cd6af679ec41e 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/full-release-validation.yml b/.github/workflows/full-release-validation.yml index 93ab7101f9d2d..31c3bd0c0395e 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/.github/workflows/test-performance-agent.yml b/.github/workflows/test-performance-agent.yml index 18f04b658bee2..df126e2420643 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/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/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/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/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/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/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 28cdc1a63e7ad..17320bb6f4e1d 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 934e63a163f65..3c8dcff21e3a1 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,84 +3220,128 @@ 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-linux-arm-gnueabihf@0.52.0': - resolution: {integrity: sha512-JXUCde8mn3GpgQouz2PXUokgy/uT1QrRJBL2s983VWcSQp62wTFYiNXgTKdeo1Jgbr0IgUnKKvzIk/YBlj/nVQ==} + '@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.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.65.0': + resolution: {integrity: sha512-sTNwIx1gre3MyiHOPLu7IGW4UyMScYL4DTmJT01p4vzB0En+OJUQz6KuH8t0PpsClRSaMuY3b0QmtoPItfO8Lg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@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.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-openharmony-arm64@0.52.0': - resolution: {integrity: sha512-vd4npaUIwChxp7XzkqmepBWTT9YMcSe/NBApVGPC30/lLyOVaV3dvma1SKo03t8O73BPRAG7EyJzGlN5cJM5hQ==} + '@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.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.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.65.0': + resolution: {integrity: sha512-higu9cWEO6XXFzATD1jf0mCK34rNfN2H9JrJie7QB1IhleVpTh0QlLH9Ip2C1H/Nd5n0v5pvRtC+5R0uE4HpVg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxlint-tsgolint/darwin-arm64@0.23.0': resolution: {integrity: sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==} cpu: [arm64] @@ -4104,6 +4148,48 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260527.2': + resolution: {integrity: sha512-3LqSu4DlxkEfeC/Z/29QMCJn5jjkDtXI7LYuxfmjdmAatS6umDKqm8J17fnP/7fyrZUMBTIYRwSDpChGV3G1ew==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260527.2': + resolution: {integrity: sha512-H4+sxE9qaBbLF83wMdWE0FsgfK0Pom+/O+/oxqyGzhVkDJlNt3vfpgQZMit48/Gm44AacGfBggJ9Dhbi3aeSFw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260527.2': + resolution: {integrity: sha512-BGUDMjC2Z3TTdZRkGGwhBLelkP5UYgO2rbep8aF4dS3fu7T5lFPPrnfS6EgqJgie+cF5Fsev7xEq8wWyBDM+lg==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/native-preview-linux-arm@7.0.0-dev.20260527.2': + resolution: {integrity: sha512-6I9Cv9ozwfS9zB9vRQDPIYseLX3artEO9jl3yVgLj4ishwlSF4cWAbIsjl5IztPaEgHv8coej/6tX1D0uaBzXg==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/native-preview-linux-x64@7.0.0-dev.20260527.2': + resolution: {integrity: sha512-vpazOu+ozlxBo8U57YJMzsOPuxAV8H7fu36KJ8ea8At/D8pdGmOAy5TuB+9OBQV9JDe0OXJMy2kmbhOpmkTAmA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260527.2': + resolution: {integrity: sha512-DBFnFE3V6AITkPO1K1VxXf3yEZKjU2FwtXlNwRqhzDu0rrL2SsJHOSrBDX+OacTxQFzZMxFcpiuhV8jHZALPEg==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/native-preview-win32-x64@7.0.0-dev.20260527.2': + resolution: {integrity: sha512-1tBlErMvQgcMqqYwsx4tytupcjCJcOUXD3vBn1Wb/kAvus1FzWQAFE0fcKBvLfcqLQfTiiEwKKEtbLjGmakqqg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@typescript/native-preview@7.0.0-dev.20260527.2': resolution: {integrity: sha512-piqkDwikVeizCFqA1lcwI5F4wOAtBdxuliWe77ApBNRyBPPvfCJB+u/HYi9/8t5nd0sWvFs6/qt/AzJ1CCoykQ==} engines: {node: '>=16.20.0'} @@ -6024,8 +6110,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: @@ -8776,40 +8862,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.65.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.65.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.65.0': + optional: true + + '@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-openharmony-arm64@0.52.0': + '@oxfmt/binding-linux-x64-gnu@0.65.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.52.0': + '@oxfmt/binding-linux-x64-musl@0.65.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.52.0': + '@oxfmt/binding-openharmony-arm64@0.65.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.65.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.65.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.65.0': optional: true '@oxlint-tsgolint/darwin-arm64@0.23.0': @@ -9543,7 +9650,36 @@ snapshots: dependencies: '@types/node': 25.9.1 - '@typescript/native-preview@7.0.0-dev.20260527.2': {} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260527.2': + optional: true + + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260527.2': + optional: true + + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260527.2': + optional: true + + '@typescript/native-preview-linux-arm@7.0.0-dev.20260527.2': + optional: true + + '@typescript/native-preview-linux-x64@7.0.0-dev.20260527.2': + optional: true + + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260527.2': + optional: true + + '@typescript/native-preview-win32-x64@7.0.0-dev.20260527.2': + optional: true + + '@typescript/native-preview@7.0.0-dev.20260527.2': + optionalDependencies: + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260527.2 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260527.2 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260527.2 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260527.2 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260527.2 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260527.2 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260527.2 '@typespec/ts-http-runtime@0.3.5': dependencies: @@ -11801,22 +11937,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-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-openharmony-arm64': 0.52.0 - '@oxfmt/binding-win32-arm64-msvc': 0.52.0 - '@oxfmt/binding-win32-ia32-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/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"] }, ], }, { 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/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); 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 + }