diff --git a/src/app-server/client.ts b/src/app-server/client.ts index 16b12890..00ceb0fe 100644 --- a/src/app-server/client.ts +++ b/src/app-server/client.ts @@ -193,10 +193,7 @@ export class AppServerClient { } setHookEnabled(hook: HookMetadata, enabled: boolean): Promise { - const state: Record = { enabled }; - if (hook.trustStatus === "trusted") { - state.trusted_hash = hook.currentHash; - } + const state: Record = hook.trustStatus === "trusted" ? { enabled, trusted_hash: hook.currentHash } : { enabled }; return this.writeHookState(hook.key, state); } diff --git a/src/panel/app-server-logs.ts b/src/panel/app-server-logs.ts index 93beed5c..e8543315 100644 --- a/src/panel/app-server-logs.ts +++ b/src/panel/app-server-logs.ts @@ -9,10 +9,11 @@ export function classifyAppServerLog(message: string): ClassifiedAppServerLog { const parsed = parseAppServerLog(normalized); if (!parsed) return null; - const level = logString(parsed.level).toUpperCase(); - const fields = parsed.fields && typeof parsed.fields === "object" ? (parsed.fields as Record) : {}; - const text = stripAnsi(logString(fields.message, normalized)); - const target = logString(parsed.target); + const level = logString(parsed["level"]).toUpperCase(); + const parsedFields = parsed["fields"]; + const fields = parsedFields && typeof parsedFields === "object" ? (parsedFields as Record) : {}; + const text = stripAnsi(logString(fields["message"], normalized)); + const target = logString(parsed["target"]); if (target.includes("rmcp::transport::worker") && isMcpTokenRefreshLog(text)) return null; if (target.includes("codex_core::tools::router") && text.includes("apply_patch verification failed")) return null; if (level === "ERROR") return { kind: "error", text }; diff --git a/src/panel/session-controller.ts b/src/panel/session-controller.ts index 1aa16a30..2e80ace2 100644 --- a/src/panel/session-controller.ts +++ b/src/panel/session-controller.ts @@ -85,7 +85,7 @@ export class PanelSessionController { try { const response = await client.readAccountRateLimits(); const rateLimitsByLimitId = response.rateLimitsByLimitId; - const codexRateLimit = rateLimitsByLimitId && Object.hasOwn(rateLimitsByLimitId, "codex") ? rateLimitsByLimitId.codex : undefined; + const codexRateLimit = rateLimitsByLimitId && Object.hasOwn(rateLimitsByLimitId, "codex") ? rateLimitsByLimitId["codex"] : undefined; this.host.state.rateLimit = codexRateLimit ?? response.rateLimits; } catch { this.host.state.rateLimit = null; diff --git a/src/runtime/state.ts b/src/runtime/state.ts index 8d932617..ee00ab03 100644 --- a/src/runtime/state.ts +++ b/src/runtime/state.ts @@ -43,8 +43,9 @@ export function configRecord(effectiveConfig: ConfigReadResponse | null): Record } export function selectedProfileConfig(config: Record): Record { - const profileName = typeof config.profile === "string" && config.profile.length > 0 ? config.profile : null; - return profileName ? asRecord(asRecord(config.profiles)[profileName]) : {}; + const profile = config["profile"]; + const profileName = typeof profile === "string" && profile.length > 0 ? profile : null; + return profileName ? asRecord(asRecord(config["profiles"])[profileName]) : {}; } export function resolvedConfigValue(config: Record, key: string): unknown { diff --git a/src/runtime/view.ts b/src/runtime/view.ts index c992ac22..51e0801c 100644 --- a/src/runtime/view.ts +++ b/src/runtime/view.ts @@ -47,7 +47,7 @@ export interface EffectiveConfigSection { export function contextSummary(snapshot: RuntimeSnapshot): ContextSummary | null { const usage = snapshot.tokenUsage; const config = configRecord(snapshot.effectiveConfig); - const contextWindow = usage?.modelContextWindow ?? toNumber(config.model_context_window); + const contextWindow = usage?.modelContextWindow ?? toNumber(config["model_context_window"]); if (!usage) { if (!snapshot.activeThreadId) return null; if (!snapshot.displayItems.some((item) => item.turnId)) { @@ -106,15 +106,15 @@ export function rateLimitSummary(snapshot: RuntimeSnapshot, nowMs = Date.now()): export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: string): EffectiveConfigSection[] { const config = configRecord(snapshot.effectiveConfig); - const features = asRecord(config.features); + const features = asRecord(config["features"]); const tools = asRecord(resolvedConfigValue(config, "tools")); - const workspaceWrite = asRecord(config.sandbox_workspace_write); + const workspaceWrite = asRecord(config["sandbox_workspace_write"]); return [ { title: "Scope", rows: [ { key: "cwd", value: vaultPath }, - { key: "profile", value: stringValue(config.profile, "(default)") }, + { key: "profile", value: stringValue(config["profile"], "(default)") }, { key: "model provider", value: stringValue(resolvedConfigValue(config, "model_provider"), "(from default)") }, ], }, @@ -141,8 +141,8 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st { key: "config service tier", value: configuredServiceTier(config) ?? "(not reported)" }, { key: "active service tier", value: activeRuntimeValueLabel(snapshot.activeServiceTier) }, { key: "fast mode", value: fastModeLabel(snapshot, config) }, - { key: "context window", value: tokenLimitLabel(config.model_context_window) }, - { key: "auto compact limit", value: tokenLimitLabel(config.model_auto_compact_token_limit) }, + { key: "context window", value: tokenLimitLabel(config["model_context_window"]) }, + { key: "auto compact limit", value: tokenLimitLabel(config["model_auto_compact_token_limit"]) }, ], }, { @@ -156,19 +156,19 @@ export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: st key: "active reviewer", value: activeRuntimeValueLabel(snapshot.activeApprovalsReviewer), }, - { key: "sandbox", value: stringValue(config.sandbox_mode, "(from default)") }, - { key: "workspace network", value: stringValue(workspaceWrite.network_access, "(from default)") }, - { key: "writable roots", value: writableRootsLabel(workspaceWrite.writable_roots) }, + { key: "sandbox", value: stringValue(config["sandbox_mode"], "(from default)") }, + { key: "workspace network", value: stringValue(workspaceWrite["network_access"], "(from default)") }, + { key: "writable roots", value: writableRootsLabel(workspaceWrite["writable_roots"]) }, { key: "web search", value: stringValue(resolvedConfigValue(config, "web_search"), "(from default)") }, ], }, { title: "Features", rows: [ - { key: "hooks", value: stringValue(features.hooks, "(from default)") }, - { key: "apply patch freeform", value: stringValue(features.apply_patch_freeform, "(from default)") }, - { key: "tool web search", value: stringValue(tools.web_search, "(from default)") }, - { key: "apps", value: enabledAppsLabel(config.apps) }, + { key: "hooks", value: stringValue(features["hooks"], "(from default)") }, + { key: "apply patch freeform", value: stringValue(features["apply_patch_freeform"], "(from default)") }, + { key: "tool web search", value: stringValue(tools["web_search"], "(from default)") }, + { key: "apps", value: enabledAppsLabel(config["apps"]) }, ], }, ]; @@ -275,12 +275,12 @@ function writableRootsLabel(value: unknown): string { function enabledAppsLabel(value: unknown): string { const apps = asRecord(value); const enabled = Object.entries(apps) - .filter(([key, app]) => key !== "_default" && asRecord(app).enabled === true) + .filter(([key, app]) => key !== "_default" && asRecord(app)["enabled"] === true) .map(([key]) => key) .sort(); if (enabled.length > 0) return enabled.join(", "); - const defaultConfig = asRecord(apps._default); - return stringValue(defaultConfig.enabled, "(from default)"); + const defaultConfig = asRecord(apps["_default"]); + return stringValue(defaultConfig["enabled"], "(from default)"); } function asRecord(value: unknown): Record { diff --git a/src/settings/model.ts b/src/settings/model.ts index 5c2dec99..3fd91b3f 100644 --- a/src/settings/model.ts +++ b/src/settings/model.ts @@ -35,18 +35,21 @@ export const DEFAULT_SETTINGS: CodexPanelSettings = { export function normalizeSettings(data: unknown): CodexPanelSettings { const record = asRecord(data); return { - codexPath: stringOrDefault(record.codexPath, DEFAULT_CODEX_PATH).trim() || DEFAULT_CODEX_PATH, - threadNamingModel: threadNamingModelOrDefault(record.threadNamingModel), - threadNamingEffort: reasoningEffortOrDefault(record.threadNamingEffort), - rewriteSelectionModel: modelOrDefault(record.rewriteSelectionModel), - rewriteSelectionEffort: reasoningEffortOrDefault(record.rewriteSelectionEffort), - sendShortcut: sendShortcutOrDefault(record.sendShortcut), - archiveExportEnabled: booleanOrDefault(record.archiveExportEnabled, DEFAULT_SETTINGS.archiveExportEnabled), - archiveExportFolderTemplate: stringOrDefault(record.archiveExportFolderTemplate, DEFAULT_SETTINGS.archiveExportFolderTemplate).trim(), + codexPath: stringOrDefault(record["codexPath"], DEFAULT_CODEX_PATH).trim() || DEFAULT_CODEX_PATH, + threadNamingModel: threadNamingModelOrDefault(record["threadNamingModel"]), + threadNamingEffort: reasoningEffortOrDefault(record["threadNamingEffort"]), + rewriteSelectionModel: modelOrDefault(record["rewriteSelectionModel"]), + rewriteSelectionEffort: reasoningEffortOrDefault(record["rewriteSelectionEffort"]), + sendShortcut: sendShortcutOrDefault(record["sendShortcut"]), + archiveExportEnabled: booleanOrDefault(record["archiveExportEnabled"], DEFAULT_SETTINGS.archiveExportEnabled), + archiveExportFolderTemplate: stringOrDefault( + record["archiveExportFolderTemplate"], + DEFAULT_SETTINGS.archiveExportFolderTemplate, + ).trim(), archiveExportFilenameTemplate: - stringOrDefault(record.archiveExportFilenameTemplate, DEFAULT_SETTINGS.archiveExportFilenameTemplate).trim() || + stringOrDefault(record["archiveExportFilenameTemplate"], DEFAULT_SETTINGS.archiveExportFilenameTemplate).trim() || DEFAULT_SETTINGS.archiveExportFilenameTemplate, - archiveExportTags: stringOrDefault(record.archiveExportTags, DEFAULT_SETTINGS.archiveExportTags).trim(), + archiveExportTags: stringOrDefault(record["archiveExportTags"], DEFAULT_SETTINGS.archiveExportTags).trim(), }; } @@ -54,16 +57,16 @@ export function settingsMatchNormalizedData(data: unknown, settings: CodexPanelS const record = asRecord(data); return ( Object.keys(record).length === 10 && - record.codexPath === settings.codexPath && - record.threadNamingModel === settings.threadNamingModel && - record.threadNamingEffort === settings.threadNamingEffort && - record.rewriteSelectionModel === settings.rewriteSelectionModel && - record.rewriteSelectionEffort === settings.rewriteSelectionEffort && - record.sendShortcut === settings.sendShortcut && - record.archiveExportEnabled === settings.archiveExportEnabled && - record.archiveExportFolderTemplate === settings.archiveExportFolderTemplate && - record.archiveExportFilenameTemplate === settings.archiveExportFilenameTemplate && - record.archiveExportTags === settings.archiveExportTags + record["codexPath"] === settings.codexPath && + record["threadNamingModel"] === settings.threadNamingModel && + record["threadNamingEffort"] === settings.threadNamingEffort && + record["rewriteSelectionModel"] === settings.rewriteSelectionModel && + record["rewriteSelectionEffort"] === settings.rewriteSelectionEffort && + record["sendShortcut"] === settings.sendShortcut && + record["archiveExportEnabled"] === settings.archiveExportEnabled && + record["archiveExportFolderTemplate"] === settings.archiveExportFolderTemplate && + record["archiveExportFilenameTemplate"] === settings.archiveExportFilenameTemplate && + record["archiveExportTags"] === settings.archiveExportTags ); } diff --git a/src/ui/composer.ts b/src/ui/composer.ts index f1e3d99c..589b8a27 100644 --- a/src/ui/composer.ts +++ b/src/ui/composer.ts @@ -90,8 +90,8 @@ export function syncComposerControls( sendButton.classList.toggle("is-interrupt", interruptMode); sendButton.classList.toggle("is-steer", canSteer); const mode = interruptMode ? "interrupt" : canSteer ? "steer" : "send"; - if (sendButton.dataset.codexMode !== mode) { - sendButton.dataset.codexMode = mode; + if (sendButton.dataset["codexMode"] !== mode) { + sendButton.dataset["codexMode"] = mode; setButtonIcon(sendButton, interruptMode ? "square" : canSteer ? "corner-down-right" : "send"); } } diff --git a/src/ui/message-stream.ts b/src/ui/message-stream.ts index 3ed810cd..c087b45e 100644 --- a/src/ui/message-stream.ts +++ b/src/ui/message-stream.ts @@ -117,7 +117,7 @@ export function messageRenderBlocks(context: MessageStreamContext): MessageRende export function syncMessageRenderBlocks(parent: HTMLElement, blocks: MessageRenderBlock[], signatures: Map): void { const existing = new Map(); parent.querySelectorAll(":scope > [data-codex-panel-block-key]").forEach((element) => { - const key = element.dataset.codexPanelBlockKey; + const key = element.dataset["codexPanelBlockKey"]; if (key) existing.set(key, element); }); @@ -129,8 +129,8 @@ export function syncMessageRenderBlocks(parent: HTMLElement, blocks: MessageRend const currentWasNext = current === nextPosition; if (!element || signatures.get(block.key) !== block.signature) { element = block.render(); - element.dataset.codexPanelBlockKey = block.key; - element.dataset.codexPanelBlockSignature = shortSignature(block.signature); + element.dataset["codexPanelBlockKey"] = block.key; + element.dataset["codexPanelBlockSignature"] = shortSignature(block.signature); signatures.set(block.key, block.signature); if (current) { current.replaceWith(element); diff --git a/tests/app-server/app-server-client.test.ts b/tests/app-server/app-server-client.test.ts index f53fc69f..02f5a682 100644 --- a/tests/app-server/app-server-client.test.ts +++ b/tests/app-server/app-server-client.test.ts @@ -244,7 +244,7 @@ describe("AppServerClient", () => { input: [{ type: "text", text: "default", text_elements: [] }], }, }); - expect((transport.sent[2] as { params?: Record }).params?.collaborationMode).toBeUndefined(); + expect((transport.sent[2] as { params?: Record }).params?.["collaborationMode"]).toBeUndefined(); transport.emitLine({ id: 2, result: { turn: { id: "turn-default" } } }); await defaultTurn; @@ -492,7 +492,7 @@ describe("AppServerClient", () => { method: "skills/list", params: { cwds: ["/vault"], forceReload: false }, }); - expect((transport.sent[2] as { params?: Record }).params?.perCwdExtraUserRoots).toBeUndefined(); + expect((transport.sent[2] as { params?: Record }).params?.["perCwdExtraUserRoots"]).toBeUndefined(); transport.emitLine({ id: 2, result: { data: [] } }); await skills; diff --git a/tests/mocks/obsidian.ts b/tests/mocks/obsidian.ts index 03110dc9..1d15b495 100644 --- a/tests/mocks/obsidian.ts +++ b/tests/mocks/obsidian.ts @@ -248,7 +248,7 @@ class ToggleComponent { } export function setIcon(element: HTMLElement, icon: string): void { - element.dataset.icon = icon; + element.dataset["icon"] = icon; } function ensureElementHelpers(): void { diff --git a/tests/runtime/runtime-settings.test.ts b/tests/runtime/runtime-settings.test.ts index 6b457c1c..3e889a6e 100644 --- a/tests/runtime/runtime-settings.test.ts +++ b/tests/runtime/runtime-settings.test.ts @@ -149,7 +149,7 @@ describe("runtime settings", () => { effectiveConfig: { config } as unknown as RuntimeSnapshot["effectiveConfig"], }); - expect(config.approvals_reviewer).toBe("auto_review"); + expect(config["approvals_reviewer"]).toBe("auto_review"); expect(autoReviewActive(snapshot, config)).toBe(false); }); @@ -360,8 +360,8 @@ describe("runtime settings", () => { "active service tier": "(not reported)", "fast mode": "on", }); - expect(policyRows.approval).toBe("never"); - expect(policyRows.reviewer).toBe("auto_review"); + expect(policyRows["approval"]).toBe("never"); + expect(policyRows["reviewer"]).toBe("auto_review"); expect(policyRows["auto-review"]).toBe("on"); expect(policyRows["config reviewer"]).toBe("auto_review"); expect(policyRows["active reviewer"]).toBe("(not reported)"); diff --git a/tests/ui/view-renderers.test.ts b/tests/ui/view-renderers.test.ts index 4b1c077f..58cd8744 100644 --- a/tests/ui/view-renderers.test.ts +++ b/tests/ui/view-renderers.test.ts @@ -2157,14 +2157,14 @@ describe("composer renderer decisions", () => { expect(sendButton?.getAttribute("aria-label")).toBe("Interrupt"); expect(sendButton?.classList.contains("is-interrupt")).toBe(true); expect(sendButton?.classList.contains("is-steer")).toBe(false); - expect(sendButton?.dataset.icon).toBe("square"); + expect(sendButton?.dataset["icon"]).toBe("square"); composer.value = "adjust course"; syncComposerControls(parent, composer, true, true); expect(sendButton?.getAttribute("aria-label")).toBe("Steer"); expect(sendButton?.classList.contains("is-interrupt")).toBe(false); expect(sendButton?.classList.contains("is-steer")).toBe(true); - expect(sendButton?.dataset.icon).toBe("corner-down-right"); + expect(sendButton?.dataset["icon"]).toBe("corner-down-right"); }); it("honors the smaller viewport branch of the composer max-height CSS", () => { diff --git a/tsconfig.json b/tsconfig.json index f906832b..6ab3e910 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "noImplicitOverride": true, "exactOptionalPropertyTypes": true, "noUncheckedIndexedAccess": true, + "noPropertyAccessFromIndexSignature": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true,