@geminixiang/mikan 1.0.0-beta.63 → 1.0.0-beta.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/adapters/web/admin/portal.d.ts.map +1 -1
- package/dist/adapters/web/admin/portal.js +0 -2
- package/dist/adapters/web/admin/portal.js.map +1 -1
- package/dist/cli/boot.js +1 -1
- package/dist/cli/boot.js.map +1 -1
- package/dist/cli/office.d.ts.map +1 -1
- package/dist/cli/office.js +33 -1
- package/dist/cli/office.js.map +1 -1
- package/dist/config.d.ts +18 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +81 -20
- package/dist/config.js.map +1 -1
- package/dist/office/projection.d.ts.map +1 -1
- package/dist/office/projection.js +1 -8
- package/dist/office/projection.js.map +1 -1
- package/dist/office/types.d.ts +0 -6
- package/dist/office/types.d.ts.map +1 -1
- package/dist/office/types.js.map +1 -1
- package/dist/types.d.ts +0 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portal.js","sourceRoot":"","sources":["../../../../src/adapters/web/admin/portal.ts"],"names":[],"mappings":"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","sourcesContent":["import { validateEventFilename } from \"../../../events/index.js\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { basename, join, resolve as pathResolve, sep as pathSep } from \"node:path\";\nimport { MikanModels, parseFrontmatter } from \"../../../harness/index.js\";\nimport { SessionStore } from \"../../../sessions/session-store.js\";\nimport type { EventStore } from \"../../../events/index.js\";\nimport type { PlatformName } from \"../../../adapter.js\";\nimport { InMemoryTokenStore } from \"../token-store.js\";\nimport type { AdminToken } from \"./types.js\";\nexport type { AdminToken } from \"./types.js\";\n\nconst ADMIN_TOKEN_TTL_MS = 30 * 60 * 1000;\n\nexport class InMemoryAdminTokenStore extends InMemoryTokenStore<AdminToken> {\n create(args: {\n platform: PlatformName;\n platformUserId: string;\n conversationId: string;\n platformUserName?: string;\n }): AdminToken {\n this.deleteWhere(\n (token) => token.platform === args.platform && token.platformUserId === args.platformUserId,\n );\n return this.createRecord(ADMIN_TOKEN_TTL_MS, {\n platform: args.platform,\n platformUserId: args.platformUserId,\n ...(args.platformUserName ? { platformUserName: args.platformUserName } : {}),\n conversationId: args.conversationId,\n });\n }\n}\n\nimport {\n loadOfficeVisibilityOverride,\n loadGlobalSettings,\n loadScopeMcpServers,\n resolveConversationSettings,\n type AgentConfig,\n type SandboxSettings,\n} from \"../../../config.js\";\nimport { findMcpPreset, listMcpPresets, materializeMcpPreset } from \"../../../harness/mcp.js\";\nimport { loadMcpTools } from \"../../../harness/mcp.js\";\nimport {\n isValidMcpServerName,\n parseStandardMcpServers,\n redactMcpUrl,\n} from \"../../../harness/mcp.js\";\nimport type { McpServerConfig } from \"../../../harness/types.js\";\nimport {\n applyConversationSettings,\n applyOfficeVisibility,\n applyGlobalSettings,\n} from \"../../../settings-mutation.js\";\nimport {\n escapeHtml,\n jsonResponse as jsonRes,\n readJsonBody,\n renderPortalShell,\n} from \"../portal-shell.js\";\nimport { resolveExistingSessionFile } from \"../session-view/portal.js\";\nimport { PRODUCT_NAME } from \"../../../platform-messages.js\";\nimport { credentialAuthorizationKey } from \"../../../sandbox/identity.js\";\nimport { resolveWorkspaceProjection } from \"../../../office/projection.js\";\nimport { sharedVaultKey } from \"../../../vault/index.js\";\nimport { modelKey, resolveAdminModelAccessStatuses } from \"./provider-models.js\";\n\nexport type { AdminRuntimeBridge, AdminServices, EventSummary } from \"./types.js\";\nimport type { AdminServices, EventSummary } from \"./types.js\";\nimport type { OfficeAddress } from \"../../../adapter.js\";\nimport {\n assertPlatformName,\n createOfficeAddress,\n listRegisteredOffices,\n sameOffice,\n type Office,\n type Workspace,\n} from \"../../../office/index.js\";\n\n// ── Handler ────────────────────────────────────────────────────────────────────\n\nexport async function handleAdminRequest(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<boolean> {\n if (!url.pathname.startsWith(\"/admin\")) return false;\n\n if (req.method === \"GET\" && url.pathname === \"/admin\") {\n const provided = url.searchParams.get(\"token\") ?? \"\";\n const token = services.adminTokenStore.peek(provided);\n if (!token) {\n res.writeHead(403, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderAdminErrorPage(\n \"Admin link is missing, invalid, or expired. Send `/admin` to the bot to get a fresh link.\",\n ),\n );\n return true;\n }\n res.writeHead(200, {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n });\n res.end(renderAdminPage(token));\n return true;\n }\n\n if (url.pathname.startsWith(\"/admin/api/\")) {\n await routeApiRequest(req, res, url, services);\n return true;\n }\n\n return false;\n}\n\n// ── API routing ────────────────────────────────────────────────────────────────\n\nasync function routeApiRequest(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<void> {\n if (req.method === \"GET\") {\n await routeGetApiRequest(res, url, services);\n return;\n }\n if (req.method !== \"POST\") {\n jsonRes(res, 405, { error: \"Method not allowed\" });\n return;\n }\n await routePostApiRequest(req, res, url, services);\n}\n\nasync function routeGetApiRequest(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<void> {\n const token = services.adminTokenStore.peek(url.searchParams.get(\"token\") ?? \"\");\n if (!token) {\n jsonRes(res, 403, { error: \"Unauthorized\" });\n return;\n }\n switch (url.pathname) {\n case \"/admin/api/conversations\":\n return serveConversationsList(res, services);\n case \"/admin/api/session-usage\":\n return serveSessionUsage(res, services);\n case \"/admin/api/conversation-usage\":\n return serveConversationUsage(res, url, services);\n case \"/admin/api/conversation-state\":\n return serveConversationState(res, url, services, token);\n case \"/admin/api/settings/global\":\n return serveGlobalSettings(res);\n case \"/admin/api/models\":\n return serveModelsList(res);\n case \"/admin/api/workspace/tree\":\n return serveWorkspaceTree(res, url, services, token);\n case \"/admin/api/workspace/file\":\n return serveWorkspaceFile(res, url, services, token);\n case \"/admin/api/skills\":\n return serveSkillsList(res, url, services, token);\n case \"/admin/api/skills/file\":\n return serveSkillFile(res, url, services, token);\n case \"/admin/api/mcp-servers\":\n return serveMcpServersList(res, url, services, token);\n case \"/admin/api/events\":\n return serveEventsList(res, services);\n case \"/admin/api/conversations/events\":\n return serveConversationEventsList(res, url, services, token);\n default:\n jsonRes(res, 404, { error: \"Not found\" });\n }\n}\n\nasync function routePostApiRequest(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<void> {\n const body = await readJsonBody(req, res, 32 * 1024);\n if (!body) return;\n const token = services.adminTokenStore.peek(typeof body.token === \"string\" ? body.token : \"\");\n if (!token) {\n jsonRes(res, 403, { error: \"Unauthorized\" });\n return;\n }\n switch (url.pathname) {\n case \"/admin/api/conversations/model\":\n return serveConversationModelUpdate(res, body, services, token);\n case \"/admin/api/conversations/visibility\":\n return serveConversationVisibilityUpdate(res, body, services, token);\n case \"/admin/api/conversations/slack\":\n return serveConversationSlackUpdate(res, body, services, token);\n case \"/admin/api/conversations/session-link\":\n return serveConversationSessionLink(res, body, services, token);\n case \"/admin/api/conversations/login-link\":\n return serveConversationLoginLink(res, body, services, token);\n case \"/admin/api/conversations/events/delete\":\n return serveConversationEventDelete(res, body, services, token);\n case \"/admin/api/mcp-servers/mutate\":\n return serveMcpServerMutation(res, body, services, token);\n case \"/admin/api/settings/model\":\n return serveGlobalModelUpdate(res, body, services);\n case \"/admin/api/settings/sandbox\":\n return serveGlobalSandboxUpdate(res, body, services);\n case \"/admin/api/settings/slack\":\n return serveGlobalSlackUpdate(res, body, services);\n default:\n jsonRes(res, 404, { error: \"Not found\" });\n }\n}\n\n// ── Scope helpers ──────────────────────────────────────────────────────────────\n\ninterface AdminConversationScope {\n address: OfficeAddress;\n conversationId: string;\n error?: string;\n}\n\n/**\n * Admin scope is a full office address. The platform defaults to the token's\n * (an admin invoked from Slack browses Slack offices); cross-platform targets\n * name theirs explicitly. Identity validation is the address factory's.\n */\nexport function resolveConversationScope(\n requestedId: string,\n requestedPlatform: string,\n token: AdminToken,\n): AdminConversationScope {\n const fallback = createOfficeAddress(token.platform, token.conversationId);\n try {\n const address = createOfficeAddress(\n requestedPlatform ? assertPlatformName(requestedPlatform) : token.platform,\n requestedId || token.conversationId,\n );\n return { address, conversationId: address.conversationId };\n } catch {\n return {\n address: fallback,\n conversationId: fallback.conversationId,\n error: \"Invalid conversation scope.\",\n };\n }\n}\n\nfunction resolveTargetConversation(\n body: Record<string, unknown>,\n token: AdminToken,\n): AdminConversationScope {\n const requested = typeof body.conversationId === \"string\" ? body.conversationId.trim() : \"\";\n const platform = typeof body.platform === \"string\" ? body.platform.trim() : \"\";\n return resolveConversationScope(requested, platform, token);\n}\n\nfunction requireAdminWorkspace(res: ServerResponse, services: AdminServices): Workspace | null {\n if (!services.workspace) {\n jsonRes(res, 503, { error: \"Working directory not available\" });\n return null;\n }\n return services.workspace;\n}\n\nfunction requireConversationWorkspace(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): { scope: AdminConversationScope; workspace: Workspace } | undefined {\n const scope = resolveTargetConversation(body, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return undefined;\n }\n const workspace = requireAdminWorkspace(res, services);\n return workspace ? { scope, workspace } : undefined;\n}\n\n// ── API handlers ───────────────────────────────────────────────────────────────\n\n/**\n * Office directories are office-key named and not reversible to raw ids, so\n * enumeration reads the office registry — the durable raw-id ↔ office\n * mapping — instead of scanning the workspace. Offices whose directory\n * disappeared are filtered out.\n */\nfunction listAdminOffices(workspace: Workspace): OfficeAddress[] {\n return listRegisteredOffices(workspace.stateDir)\n .filter((office) => existsSync(workspace.office(office).dir))\n .map((office) => createOfficeAddress(office.platform, office.conversationId))\n .toSorted(\n (a, b) =>\n a.platform.localeCompare(b.platform) || a.conversationId.localeCompare(b.conversationId),\n );\n}\n\nfunction conversationLastActivity(workspace: Workspace, office: OfficeAddress): number | null {\n const dir = workspace.office(office).dir;\n if (!existsSync(dir)) return null;\n let latest = 0;\n const visit = (path: string, depth: number): void => {\n if (depth > 3) return;\n let entries;\n try {\n entries = readdirSync(path, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = join(path, entry.name);\n if (entry.isDirectory()) {\n visit(full, depth + 1);\n continue;\n }\n try {\n const stats = statSync(full);\n if (stats.mtimeMs > latest) latest = stats.mtimeMs;\n } catch {\n // ignore\n }\n }\n };\n visit(dir, 0);\n return latest > 0 ? latest : null;\n}\n\nfunction conversationDisplayLabel(services: AdminServices, office: OfficeAddress): string {\n const bot = services.botsByPlatform?.[office.platform];\n const channel = bot?.getMessagingInfo().channels.find((c) => c.id === office.conversationId);\n if (channel) return `${office.platform}:#${channel.name}:${office.conversationId}`;\n return `${office.platform}:${office.conversationId}`;\n}\n\nfunction serveConversationsList(res: ServerResponse, services: AdminServices): void {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const running = services.runtime?.getRunningSessions() ?? [];\n\n const conversations = listAdminOffices(workspace).map((office) => ({\n platform: office.platform,\n conversationId: office.conversationId,\n label: conversationDisplayLabel(services, office),\n running: running.some((session) => sameOffice(session.address, office)),\n lastActivityAt: conversationLastActivity(workspace, office),\n }));\n\n jsonRes(res, 200, { conversations });\n}\n\ninterface SessionUsageRow {\n conversationId: string;\n label: string;\n fileName: string;\n sessionId: string;\n updatedAt: string;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n total: number;\n cost: number;\n}\n\nasync function serveSessionUsage(res: ServerResponse, services: AdminServices): Promise<void> {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const usageLists: SessionUsageRow[][] = [];\n for (const office of listAdminOffices(workspace)) {\n usageLists.push(\n await listConversationSessionUsage(\n workspace,\n office,\n conversationDisplayLabel(services, office),\n ),\n );\n }\n const rows = usageLists\n .flat()\n .toSorted((a, b) => b.total - a.total)\n .slice(0, 20);\n\n jsonRes(res, 200, { sessions: rows });\n}\n\nasync function listConversationSessionUsage(\n workspace: Workspace,\n office: OfficeAddress,\n label: string,\n): Promise<SessionUsageRow[]> {\n const sessionDir = workspace.office(office).sessionsDir;\n let files: string[];\n try {\n files = readdirSync(sessionDir, { withFileTypes: true })\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".jsonl\"))\n .map((entry) => entry.name);\n } catch {\n return [];\n }\n const rows: SessionUsageRow[] = [];\n for (const name of files) {\n rows.push(...(await readSessionUsage(join(sessionDir, name), office.conversationId, label)));\n }\n return rows;\n}\n\nasync function readSessionUsage(\n sessionFile: string,\n conversationId: string,\n label: string,\n): Promise<SessionUsageRow[]> {\n try {\n const manager = await SessionStore.inspect(sessionFile);\n const header = manager.getHeader();\n\n const entries = await manager.getEntries();\n const usage = entries.reduce(\n (sum, entry) => {\n if (entry.type !== \"message\" || entry.message.role !== \"assistant\") return sum;\n const message = entry.message as unknown as AssistantUsageMessage;\n const item = message.usage;\n if (!item) return sum;\n sum.input += numberOrZero(item.input);\n sum.output += numberOrZero(item.output);\n sum.cacheRead += numberOrZero(item.cacheRead);\n sum.cacheWrite += numberOrZero(item.cacheWrite);\n sum.cost += numberOrZero(item.cost?.total);\n return sum;\n },\n { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 },\n );\n const total = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n if (total <= 0) return [];\n\n return [\n {\n conversationId,\n label,\n fileName: basename(sessionFile),\n sessionId: header.id,\n updatedAt:\n entries.length > 0 ? new Date(entries.at(-1)!.timestamp).toISOString() : header.timestamp,\n ...usage,\n total,\n },\n ];\n } catch {\n return [];\n }\n}\n\ninterface AssistantUsageMessage {\n usage?: {\n input?: unknown;\n output?: unknown;\n cacheRead?: unknown;\n cacheWrite?: unknown;\n cost?: { total?: unknown };\n };\n}\n\nfunction numberOrZero(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\ninterface UsageBucket {\n date: string;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n total: number;\n cost: number;\n}\n\nfunction localDayKey(d: Date): string {\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction emptyBucket(date: string): UsageBucket {\n return { date, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0, cost: 0 };\n}\n\n/** Per-conversation daily token usage over the last N days (N clamped to 1..7). */\nasync function serveConversationUsage(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<void> {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const conversationId = (url.searchParams.get(\"conversationId\") ?? \"\").trim();\n const platformParam = (url.searchParams.get(\"platform\") ?? \"\").trim();\n const office = listAdminOffices(workspace).find(\n (candidate) =>\n candidate.conversationId === conversationId &&\n (!platformParam || candidate.platform === platformParam),\n );\n if (!office) {\n jsonRes(res, 400, { error: \"Unknown conversationId\" });\n return;\n }\n\n const days = 14;\n\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n const buckets = new Map<string, UsageBucket>();\n const order: string[] = [];\n for (let i = days - 1; i >= 0; i--) {\n const d = new Date(today);\n d.setDate(today.getDate() - i);\n const key = localDayKey(d);\n order.push(key);\n buckets.set(key, emptyBucket(key));\n }\n const cutoff = new Date(today);\n cutoff.setDate(today.getDate() - (days - 1));\n\n const flags = { hasOlder: false };\n const sessionDir = workspace.office(office).sessionsDir;\n try {\n for (const entry of readdirSync(sessionDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".jsonl\")) continue;\n await accumulateSessionUsageByDay(join(sessionDir, entry.name), cutoff, buckets, flags);\n }\n } catch {\n // No sessions directory yet — return empty buckets.\n }\n\n const series = order.map((key) => buckets.get(key) ?? emptyBucket(key));\n const totals = series.reduce((sum, b) => {\n sum.input += b.input;\n sum.output += b.output;\n sum.cacheRead += b.cacheRead;\n sum.cacheWrite += b.cacheWrite;\n sum.total += b.total;\n sum.cost += b.cost;\n return sum;\n }, emptyBucket(\"\"));\n\n jsonRes(res, 200, {\n platform: office.platform,\n conversationId,\n label: conversationDisplayLabel(services, office),\n days,\n hasOlder: flags.hasOlder,\n buckets: series,\n totals: {\n input: totals.input,\n output: totals.output,\n cacheRead: totals.cacheRead,\n cacheWrite: totals.cacheWrite,\n total: totals.total,\n cost: totals.cost,\n },\n });\n}\n\nasync function accumulateSessionUsageByDay(\n sessionFile: string,\n cutoff: Date,\n buckets: Map<string, UsageBucket>,\n flags: { hasOlder: boolean },\n): Promise<void> {\n try {\n const manager = await SessionStore.inspect(sessionFile);\n\n for (const entry of await manager.getEntries()) {\n if (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n const usage = (entry.message as unknown as AssistantUsageMessage).usage;\n if (!usage || !entry.timestamp) continue;\n\n const when = new Date(entry.timestamp);\n if (Number.isNaN(when.getTime())) continue;\n if (when < cutoff) {\n flags.hasOlder = true;\n continue;\n }\n\n const bucket = buckets.get(localDayKey(when));\n if (!bucket) continue;\n\n const input = numberOrZero(usage.input);\n const output = numberOrZero(usage.output);\n const cacheRead = numberOrZero(usage.cacheRead);\n const cacheWrite = numberOrZero(usage.cacheWrite);\n bucket.input += input;\n bucket.output += output;\n bucket.cacheRead += cacheRead;\n bucket.cacheWrite += cacheWrite;\n bucket.total += input + output + cacheRead + cacheWrite;\n bucket.cost += numberOrZero(usage.cost?.total);\n }\n } catch {\n // Skip unreadable session files.\n }\n}\n\nfunction serveConversationState(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 400, { error: scope.error });\n return;\n }\n const conversationId = scope.conversationId;\n\n const office = workspace.office(scope.address);\n const globalConfig = loadGlobalSettings();\n const conversationConfig = resolveConversationSettings(office);\n const conversationWorkspace = resolveWorkspaceProjection(office);\n\n jsonRes(res, 200, {\n conversationId,\n provider: conversationConfig.provider,\n model: conversationConfig.model,\n thinkingLevel: conversationConfig.thinkingLevel,\n globalProvider: globalConfig.provider,\n globalModel: globalConfig.model,\n globalThinkingLevel: globalConfig.thinkingLevel,\n officeVisibility: conversationWorkspace.visibility,\n officeVisibilitySource: conversationWorkspace.source,\n officeVisibilityOverride: loadOfficeVisibilityOverride(office),\n officeLegacyFull: conversationWorkspace.legacyFull,\n slack: {\n replyMode:\n conversationConfig.slack?.replyMode ?? globalConfig.slack?.replyMode ?? \"top-level\",\n globalReplyMode: globalConfig.slack?.replyMode ?? \"top-level\",\n },\n });\n}\n\nfunction serveGlobalSettings(res: ServerResponse): void {\n try {\n const config = loadGlobalSettings();\n jsonRes(res, 200, {\n provider: config.provider,\n model: config.model,\n thinkingLevel: config.thinkingLevel,\n sandboxCpus: config.sandbox?.cpus ?? null,\n sandboxMemory: config.sandbox?.memory ?? null,\n sandboxBoostCpus: config.sandbox?.boost?.cpus ?? null,\n sandboxBoostMemory: config.sandbox?.boost?.memory ?? null,\n defaultSharedVault: config.sandbox?.defaultSharedVault ?? null,\n slack: {\n replyMode: config.slack?.replyMode ?? \"top-level\",\n },\n });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nasync function serveModelsList(res: ServerResponse): Promise<void> {\n try {\n const registry = MikanModels.create();\n const availableModels = await registry.getAvailable();\n const statuses = await resolveAdminModelAccessStatuses(registry, availableModels);\n const models = availableModels.map((model) => ({\n provider: model.provider,\n id: model.id,\n name: model.name ?? model.id,\n reasoning: model.reasoning,\n input: model.input,\n contextWindow: model.contextWindow,\n maxTokens: model.maxTokens,\n status: statuses.get(modelKey(model.provider, model.id))?.status ?? \"available\",\n }));\n jsonRes(res, 200, { models });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nconst VALID_THINKING_LEVELS = new Set([\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nfunction serveConversationModelUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n const provider = typeof body.provider === \"string\" ? body.provider.trim() : \"\";\n const model = typeof body.model === \"string\" ? body.model.trim() : \"\";\n const thinkingLevel =\n typeof body.thinkingLevel === \"string\" && VALID_THINKING_LEVELS.has(body.thinkingLevel)\n ? (body.thinkingLevel as AgentConfig[\"thinkingLevel\"])\n : undefined;\n\n if (!provider || !model) {\n jsonRes(res, 400, { error: \"Missing provider or model\" });\n return;\n }\n const target = requireConversationWorkspace(res, body, services, token);\n if (!target) return;\n const { scope, workspace } = target;\n\n try {\n const result = applyConversationSettings(services.runtime, workspace.office(scope.address), {\n provider,\n model,\n ...(thinkingLevel ? { thinkingLevel } : {}),\n });\n if (!result.ok) {\n jsonRes(res, 409, {\n error: \"Conversation has a running job; retry after it finishes (or /stop it).\",\n });\n return;\n }\n jsonRes(res, 200, { ok: true, runtimeSwitched: result.runtimeSwitched });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction serveConversationVisibilityUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n if (body.visibility !== \"private\" && body.visibility !== \"default\") {\n jsonRes(res, 400, { error: \"visibility must be 'private' or 'default'\" });\n return;\n }\n const target = requireConversationWorkspace(res, body, services, token);\n if (!target) return;\n const { scope, workspace } = target;\n try {\n const result = applyOfficeVisibility(\n services.runtime,\n workspace.office(scope.address),\n body.visibility === \"private\" ? \"private\" : null,\n );\n if (!result.ok) {\n jsonRes(res, 409, { error: \"Conversation is busy; retry when the current run finishes\" });\n return;\n }\n jsonRes(res, 200, { ok: true });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction serveConversationSlackUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n const replyMode = body.replyMode;\n if (replyMode !== \"top-level\" && replyMode !== \"thread\") {\n jsonRes(res, 400, { error: \"replyMode must be 'top-level' or 'thread'\" });\n return;\n }\n const target = requireConversationWorkspace(res, body, services, token);\n if (!target) return;\n const { scope, workspace } = target;\n respondWithSettingsUpdate(res, () => {\n applyConversationSettings(services.runtime, workspace.office(scope.address), {\n slack: { replyMode },\n });\n return { ok: true };\n });\n}\n\nfunction serveConversationSessionLink(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n const target = requireConversationWorkspace(res, body, services, token);\n if (!target) return;\n const { scope, workspace } = target;\n if (!services.sessionViewTokenStore) {\n jsonRes(res, 503, { error: \"Session view token store not available\" });\n return;\n }\n if (!services.portalBaseUrl) {\n jsonRes(res, 503, {\n error: \"Portal URL not configured. Set MIKAN_LINK_URL to enable link generation.\",\n });\n return;\n }\n\n const sessionFile = resolveExistingSessionFile(\n workspace.office(scope.address).dir,\n scope.conversationId,\n );\n if (!sessionFile) {\n jsonRes(res, 404, { error: \"No session file found for this conversation\" });\n return;\n }\n\n try {\n const { token: viewToken } = services.sessionViewTokenStore.create({\n platform: token.platform,\n platformUserId: token.platformUserId,\n conversationId: scope.conversationId,\n sessionKey: scope.conversationId,\n sessionFile,\n platformUserName: token.platformUserName,\n });\n const url = `${services.portalBaseUrl}/session?token=${encodeURIComponent(viewToken)}`;\n jsonRes(res, 200, { ok: true, url });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction serveConversationLoginLink(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveTargetConversation(body, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n if (!services.portalBaseUrl) {\n jsonRes(res, 503, { error: \"Portal URL not configured.\" });\n return;\n }\n if (!services.sandbox) {\n jsonRes(res, 503, { error: \"Sandbox config not available.\" });\n return;\n }\n const sharedName = typeof body.sharedVault === \"string\" ? body.sharedVault.trim() : \"\";\n let vaultId: string;\n if (sharedName) {\n const key = sharedVaultKey(sharedName);\n if (!key) {\n jsonRes(res, 400, { error: \"Invalid shared vault name\" });\n return;\n }\n vaultId = key;\n } else {\n try {\n vaultId = credentialAuthorizationKey(services.sandbox, {\n userId: token.platformUserId,\n address: scope.address,\n });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n return;\n }\n }\n try {\n const { token: linkToken } = services.linkTokenStore.create(\n token.platform,\n token.platformUserId,\n scope.conversationId,\n vaultId,\n \"\",\n );\n const url = `${services.portalBaseUrl}/link?token=${encodeURIComponent(linkToken)}`;\n jsonRes(res, 200, { ok: true, url, vaultId });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction serveGlobalModelUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n): void {\n const provider = typeof body.provider === \"string\" ? body.provider.trim() : \"\";\n const model = typeof body.model === \"string\" ? body.model.trim() : \"\";\n const thinkingLevel =\n typeof body.thinkingLevel === \"string\" && VALID_THINKING_LEVELS.has(body.thinkingLevel)\n ? (body.thinkingLevel as AgentConfig[\"thinkingLevel\"])\n : undefined;\n\n if (!provider || !model) {\n jsonRes(res, 400, { error: \"Missing provider or model\" });\n return;\n }\n\n respondWithSettingsUpdate(res, () => {\n const result = applyGlobalSettings(services.runtime, {\n provider,\n model,\n ...(thinkingLevel ? { thinkingLevel } : {}),\n });\n return {\n ok: true,\n staleConversations: result.staleConversations.map(\n (office) => `${office.platform}:${office.conversationId}`,\n ),\n };\n });\n}\n\nfunction serveGlobalSlackUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n): void {\n const replyMode = body.replyMode;\n if (replyMode !== \"top-level\" && replyMode !== \"thread\") {\n jsonRes(res, 400, { error: \"replyMode must be 'top-level' or 'thread'\" });\n return;\n }\n\n respondWithSettingsUpdate(res, () => {\n applyGlobalSettings(services.runtime, { slack: { replyMode } });\n return { ok: true };\n });\n}\n\nfunction serveGlobalSandboxUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n): void {\n const cpus = typeof body.cpus === \"string\" ? body.cpus.trim() : \"\";\n const memory = typeof body.memory === \"string\" ? body.memory.trim() : \"\";\n const boostCpus = typeof body.boostCpus === \"string\" ? body.boostCpus.trim() : \"\";\n const boostMemory = typeof body.boostMemory === \"string\" ? body.boostMemory.trim() : \"\";\n const update: SandboxSettings = {\n ...(cpus ? { cpus } : {}),\n ...(memory ? { memory } : {}),\n ...(boostCpus || boostMemory\n ? {\n boost: {\n ...(boostCpus ? { cpus: boostCpus } : {}),\n ...(boostMemory ? { memory: boostMemory } : {}),\n },\n }\n : {}),\n };\n\n if (Object.keys(update).length === 0) {\n jsonRes(res, 400, { error: \"No valid sandbox fields provided\" });\n return;\n }\n\n respondWithSettingsUpdate(res, () => {\n applyGlobalSettings(services.runtime, { sandbox: update });\n return { ok: true };\n });\n}\n\nfunction respondWithSettingsUpdate(res: ServerResponse, update: () => object): void {\n try {\n jsonRes(res, 200, update());\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n// ── Workspace ──────────────────────────────────────────────────────────────────\n\nconst WORKSPACE_TREE_MAX_DEPTH = 4;\nconst WORKSPACE_TREE_MAX_ENTRIES = 800;\nconst PREVIEW_FILE_MAX_BYTES = 256 * 1024;\n\nconst WORKSPACE_TOP_DIRS = new Set([\"scratch\"]);\n\n/**\n * Limit what the admin UI can browse under a conversation directory.\n * Allowed: top-level \"scratch/\" subtree.\n */\nfunction isWorkspacePathAllowed(rel: string): boolean {\n if (rel === \"\") return true;\n const first = rel.split(\"/\").find(Boolean);\n if (first === undefined) return true;\n return WORKSPACE_TOP_DIRS.has(first);\n}\n\nfunction resolveConversationFromQuery(url: URL, token: AdminToken): AdminConversationScope {\n const requested = (url.searchParams.get(\"conversationId\") ?? \"\").trim();\n const platform = (url.searchParams.get(\"platform\") ?? \"\").trim();\n return resolveConversationScope(requested, platform, token);\n}\n\ninterface SafePathResult {\n absolute: string;\n error?: string;\n}\n\nfunction safeJoinUnderRoot(rootDir: string, relative: string): SafePathResult {\n if (relative.startsWith(\"/\") || relative.includes(\"\\0\")) {\n return { absolute: \"\", error: \"Invalid path\" };\n }\n if (relative.split(/[\\\\/]+/).some((part) => part === \"..\" || part === \"\")) {\n if (relative !== \"\") return { absolute: \"\", error: \"Invalid path\" };\n }\n const target = pathResolve(rootDir, relative);\n const rootAbs = pathResolve(rootDir);\n if (target !== rootAbs && !target.startsWith(rootAbs + pathSep)) {\n return { absolute: \"\", error: \"Path escapes conversation directory\" };\n }\n return { absolute: target };\n}\n\ninterface TreeNode {\n name: string;\n path: string;\n type: \"dir\" | \"file\";\n size?: number;\n mtimeMs?: number;\n children?: TreeNode[];\n truncated?: boolean;\n}\n\nfunction buildTree(startDir: string, relPrefix: string): TreeNode | null {\n let counter = { value: 0 };\n const walk = (dir: string, rel: string, depth: number): TreeNode | null => {\n if (counter.value >= WORKSPACE_TREE_MAX_ENTRIES) return null;\n let stats;\n try {\n stats = statSync(dir);\n } catch {\n return null;\n }\n const name = rel === \"\" ? \".\" : basename(rel);\n if (!stats.isDirectory()) {\n counter.value += 1;\n return {\n name,\n path: rel,\n type: \"file\",\n size: stats.size,\n mtimeMs: stats.mtimeMs,\n };\n }\n counter.value += 1;\n if (depth >= WORKSPACE_TREE_MAX_DEPTH) {\n return { name, path: rel, type: \"dir\", truncated: true };\n }\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return { name, path: rel, type: \"dir\" };\n }\n const children: TreeNode[] = [];\n let truncated = false;\n for (const entry of entries.toSorted((a, b) => {\n if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;\n return a.name.localeCompare(b.name);\n })) {\n const childRel = rel === \"\" ? entry.name : `${rel}/${entry.name}`;\n if (!isWorkspacePathAllowed(childRel)) continue;\n if (counter.value >= WORKSPACE_TREE_MAX_ENTRIES) {\n truncated = true;\n break;\n }\n const node = walk(join(dir, entry.name), childRel, depth + 1);\n if (node) children.push(node);\n }\n return {\n name,\n path: rel,\n type: \"dir\",\n children,\n ...(truncated ? { truncated: true } : {}),\n };\n };\n const node = walk(startDir, relPrefix, 0);\n return node;\n}\n\nfunction serveWorkspaceTree(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const convDir = workspace.office(scope.address).dir;\n if (!existsSync(convDir)) {\n jsonRes(res, 200, { conversationId: scope.conversationId, tree: null });\n return;\n }\n const requestedSub = (url.searchParams.get(\"path\") ?? \"\").trim();\n if (!isWorkspacePathAllowed(requestedSub)) {\n jsonRes(res, 403, { error: \"Workspace path is not exposed\" });\n return;\n }\n const startSafe = safeJoinUnderRoot(convDir, requestedSub);\n if (startSafe.error) {\n jsonRes(res, 400, { error: startSafe.error });\n return;\n }\n const tree = buildTree(startSafe.absolute, requestedSub);\n jsonRes(res, 200, {\n conversationId: scope.conversationId,\n root: requestedSub || \".\",\n tree,\n });\n}\n\nconst BINARY_PROBE_BYTES = 4096;\n\nfunction looksTextual(buf: Buffer): boolean {\n const limit = Math.min(buf.length, BINARY_PROBE_BYTES);\n for (let i = 0; i < limit; i++) {\n const byte = buf[i];\n if (byte === undefined || byte === 0) return false;\n if (byte < 9) return false;\n if (byte === 11 || byte === 12) return false;\n if (byte > 13 && byte < 32) return false;\n }\n return true;\n}\n\nfunction servePreviewFile(\n res: ServerResponse,\n absolutePath: string,\n metadata: Record<string, unknown>,\n notFoundMessage: string,\n): void {\n let stats;\n try {\n stats = statSync(absolutePath);\n } catch {\n jsonRes(res, 404, { error: notFoundMessage });\n return;\n }\n if (!stats.isFile()) {\n jsonRes(res, 400, { error: \"Not a file\" });\n return;\n }\n if (stats.size > PREVIEW_FILE_MAX_BYTES) {\n jsonRes(res, 413, {\n error: \"File too large to preview\",\n size: stats.size,\n limit: PREVIEW_FILE_MAX_BYTES,\n });\n return;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(absolutePath);\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n return;\n }\n if (!looksTextual(buf)) {\n jsonRes(res, 200, {\n ...metadata,\n size: stats.size,\n mtimeMs: stats.mtimeMs,\n binary: true,\n content: null,\n });\n return;\n }\n jsonRes(res, 200, {\n ...metadata,\n size: stats.size,\n mtimeMs: stats.mtimeMs,\n binary: false,\n content: buf.toString(\"utf-8\"),\n });\n}\n\nfunction serveWorkspaceFile(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const requestedPath = (url.searchParams.get(\"path\") ?? \"\").trim();\n if (!requestedPath) {\n jsonRes(res, 400, { error: \"Missing path\" });\n return;\n }\n if (!isWorkspacePathAllowed(requestedPath)) {\n jsonRes(res, 403, { error: \"Workspace path is not exposed\" });\n return;\n }\n const convDir = workspace.office(scope.address).dir;\n const safe = safeJoinUnderRoot(convDir, requestedPath);\n if (safe.error) {\n jsonRes(res, 400, { error: safe.error });\n return;\n }\n servePreviewFile(res, safe.absolute, { path: requestedPath }, \"File not found\");\n}\n\n// ── Skills ─────────────────────────────────────────────────────────────────────\n\ninterface SkillEntry {\n name: string;\n description: string;\n source: \"global\" | \"conversation\";\n path: string;\n directory: string;\n}\n\n/**\n * Read skill metadata via the harness frontmatter parser (the owning module).\n * The portal historically accepted frontmatter keys case-insensitively, so\n * the lookup — not the parser — preserves that leniency.\n */\nfunction readSkillMeta(filePath: string): { name?: string; description?: string } {\n let text: string;\n try {\n text = readFileSync(filePath, \"utf-8\");\n } catch {\n return {};\n }\n const { values } = parseFrontmatter(text);\n const out: { name?: string; description?: string } = {};\n for (const [key, value] of Object.entries(values)) {\n const normalized = key.toLowerCase();\n if (normalized === \"name\") out.name = value;\n if (normalized === \"description\") out.description = value;\n }\n return out;\n}\n\nexport function readSkillsFromDir(skillsDir: string, source: SkillEntry[\"source\"]): SkillEntry[] {\n if (!existsSync(skillsDir)) return [];\n const out: SkillEntry[] = [];\n let entries;\n try {\n entries = readdirSync(skillsDir, { withFileTypes: true });\n } catch {\n return [];\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith(\".\")) continue;\n const skillMd = join(skillsDir, entry.name, \"SKILL.md\");\n if (!existsSync(skillMd)) continue;\n const meta = readSkillMeta(skillMd);\n out.push({\n name: meta.name ?? entry.name,\n description: meta.description ?? \"\",\n source,\n path: skillMd,\n directory: entry.name,\n });\n }\n return out.toSorted((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * List MCP servers for both scopes, with env/header VALUES redacted to key\n * names: they carry API keys, and this response renders in a browser. The\n * panel edits full entries but only ever needs to show which keys exist.\n */\nfunction serveMcpServersList(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const servers = loadScopeMcpServers(workspace.office(scope.address));\n jsonRes(res, 200, {\n conversationId: scope.conversationId,\n presets: listMcpPresets(),\n global: redactMcpServers(servers.global),\n conversation: redactMcpServers(servers.conversation),\n });\n}\n\nfunction redactMcpServers(map: Record<string, McpServerConfig>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(map).map(([name, config]) => [\n name,\n {\n ...(config.command !== undefined ? { command: config.command } : {}),\n ...(config.args !== undefined ? { args: config.args } : {}),\n ...(config.url !== undefined ? { url: redactMcpUrl(config.url) } : {}),\n ...(config.disabled !== undefined ? { disabled: config.disabled } : {}),\n envKeys: Object.keys(config.env ?? {}),\n headerKeys: Object.keys(config.headers ?? {}),\n },\n ]),\n );\n}\n\nfunction mcpStringMap(value: unknown): Record<string, string> | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(value)) {\n if (typeof v === \"string\" && k.trim()) out[k.trim()] = v;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nconst MCP_VERIFY_TIMEOUT_MS = 20_000;\n\ntype McpVerifyResult = { name: string; tools: number } | { name: string; error: string };\n\n/**\n * Connect to the given servers once, the way the runner will, and report per\n * server either the tool count or the server's own error text. This is the\n * difference between \"settings.json was written\" and \"it works\": the runner\n * only logs load failures host-side, so without this the portal is the only\n * place an operator can learn that a pasted token is wrong.\n *\n * For stdio entries this runs the configured command on the host now, at save\n * time, rather than at the next message.\n */\nasync function verifyMcpServers(\n servers: Record<string, McpServerConfig>,\n): Promise<McpVerifyResult[]> {\n const names = Object.keys(servers);\n if (names.length === 0) return [];\n const loaded = await loadMcpTools(servers, AbortSignal.timeout(MCP_VERIFY_TIMEOUT_MS));\n try {\n return names.map((name) => {\n const failure = loaded.errors.find((e) => e.server === name);\n if (failure) {\n const url = servers[name]?.url;\n const error = url ? failure.error.split(url).join(redactMcpUrl(url)) : failure.error;\n return { name, error };\n }\n const prefix = `mcp__${name}__`;\n return { name, tools: loaded.tools.filter((t) => t.name.startsWith(prefix)).length };\n });\n } finally {\n await loaded.dispose();\n }\n}\n\ntype McpMutationPlan =\n | { next: Record<string, McpServerConfig>; touched: Record<string, McpServerConfig> }\n | { status: number; error: string };\n\n/**\n * Compute the scope's next server map for one mutation. `import` takes the\n * cross-client `mcpServers` JSON verbatim (see `parseStandardMcpServers`) and\n * replaces every named entry outright — a pasted declaration is the whole\n * truth for that server, so no env/header carry-over from the previous entry.\n * `touched` lists the entries to connection-check after the write.\n */\nfunction planMcpMutation(\n action: \"import\" | \"install\" | \"remove\" | \"toggle\",\n body: Record<string, unknown>,\n current: Record<string, McpServerConfig>,\n): McpMutationPlan {\n const next: Record<string, McpServerConfig> = { ...current };\n if (action === \"import\") {\n const parsed = parseStandardMcpServers(typeof body.json === \"string\" ? body.json : \"\");\n if (!parsed.servers) return { status: 400, error: parsed.error };\n Object.assign(next, parsed.servers);\n return { next, touched: parsed.servers };\n }\n const preset =\n action === \"install\" && typeof body.presetId === \"string\"\n ? findMcpPreset(body.presetId)\n : undefined;\n if (action === \"install\" && !preset) return { status: 400, error: \"unknown MCP preset\" };\n const name = preset?.serverName ?? (typeof body.name === \"string\" ? body.name.trim() : \"\");\n if (!isValidMcpServerName(name)) {\n return {\n status: 400,\n error: \"invalid server name (letters, digits, '_' or '-', starting with a letter)\",\n };\n }\n if (action === \"install\") {\n try {\n next[name] = materializeMcpPreset(preset!, mcpStringMap(body.credentials) ?? {});\n } catch (err) {\n return { status: 400, error: err instanceof Error ? err.message : String(err) };\n }\n return { next, touched: { [name]: next[name] } };\n }\n if (!(name in next)) return { status: 404, error: \"Not declared here.\" };\n if (action === \"remove\") {\n delete next[name];\n return { next, touched: {} };\n }\n const entry = next[name]!;\n next[name] = entry.disabled ? { ...entry, disabled: undefined } : { ...entry, disabled: true };\n return { next, touched: {} };\n}\n\n/**\n * Import / install / remove / toggle / test MCP servers in one scope. Reads\n * the scope's raw map, applies the change, and writes the full map back\n * wholesale because merging would make removal impossible. Runner\n * caches refresh via applyGlobalSettings/applyConversationSettings.\n *\n * Writes persist even when the follow-up connection check fails: the\n * operator edits the entry, not retypes it. `test` re-checks an existing\n * entry without writing.\n */\nasync function serveMcpServerMutation(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): Promise<void> {\n const action = body.action;\n if (\n action !== \"import\" &&\n action !== \"install\" &&\n action !== \"remove\" &&\n action !== \"toggle\" &&\n action !== \"test\"\n ) {\n jsonRes(res, 400, {\n error: \"action must be 'import', 'install', 'remove', 'toggle', or 'test'\",\n });\n return;\n }\n const mutationScope = body.scope === \"global\" ? \"global\" : \"conversation\";\n const scope = resolveTargetConversation(body, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const office = workspace.office(scope.address);\n const maps = loadScopeMcpServers(office);\n const current = mutationScope === \"global\" ? maps.global : maps.conversation;\n\n if (action === \"test\") {\n const name = typeof body.name === \"string\" ? body.name.trim() : \"\";\n const entry = current[name];\n if (!entry) {\n jsonRes(res, 404, { error: \"Not declared here.\" });\n return;\n }\n const results = await verifyMcpServers({ [name]: { ...entry, disabled: undefined } });\n jsonRes(res, 200, { ok: true, results });\n return;\n }\n\n const plan = planMcpMutation(action, body, current);\n if (\"error\" in plan) {\n jsonRes(res, plan.status, { error: plan.error });\n return;\n }\n const result =\n mutationScope === \"global\"\n ? applyGlobalSettings(services.runtime, { mcpServers: plan.next })\n : applyConversationSettings(services.runtime, office, { mcpServers: plan.next });\n if (!result.ok) {\n jsonRes(res, 409, { error: \"Conversation is busy; try again shortly.\" });\n return;\n }\n const results = await verifyMcpServers(plan.touched);\n jsonRes(res, 200, { ok: true, results });\n}\n\nfunction serveSkillsList(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const global = readSkillsFromDir(workspace.skillsDir, \"global\");\n const conversation = readSkillsFromDir(workspace.office(scope.address).skillsDir, \"conversation\");\n jsonRes(res, 200, {\n conversationId: scope.conversationId,\n skills: [...global, ...conversation],\n });\n}\n\nfunction serveSkillFile(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const source = (url.searchParams.get(\"source\") ?? \"\").trim();\n const directory = (url.searchParams.get(\"directory\") ?? \"\").trim();\n if (source !== \"global\" && source !== \"conversation\") {\n jsonRes(res, 400, { error: \"Invalid skill source\" });\n return;\n }\n if (\n !directory ||\n directory.includes(\"/\") ||\n directory.includes(\"\\\\\") ||\n directory.includes(\"..\")\n ) {\n jsonRes(res, 400, { error: \"Invalid skill directory\" });\n return;\n }\n\n const skillsRoot =\n source === \"global\" ? workspace.skillsDir : workspace.office(scope.address).skillsDir;\n const safe = safeJoinUnderRoot(skillsRoot, join(directory, \"SKILL.md\"));\n if (safe.error) {\n jsonRes(res, 400, { error: safe.error });\n return;\n }\n\n servePreviewFile(res, safe.absolute, { source, directory }, \"Skill file not found\");\n}\n\n// ── Events ─────────────────────────────────────────────────────────────────────\n\n/**\n * List events through the owning store, whose payloads are validated by the\n * event-format module (files that fail validation stay visible with a null\n * payload so operators can delete them).\n */\nexport async function listOfficeEvents(store: EventStore): Promise<EventSummary[]> {\n const entries = await store.list();\n return entries.map((entry) => {\n const payload = entry.payload;\n return {\n name: entry.filename,\n officePlatform: store.address.platform,\n officeConversationId: store.address.conversationId,\n size: entry.size,\n mtimeMs: entry.mtimeMs,\n type: payload?.type ?? null,\n platform: payload?.platform ?? null,\n conversationId: payload?.conversationId ?? null,\n text: payload?.text ?? null,\n at: payload?.type === \"one-shot\" ? payload.at : null,\n schedule: payload?.type === \"periodic\" ? payload.schedule : null,\n timezone: payload?.type === \"periodic\" ? payload.timezone : null,\n };\n });\n}\n\nfunction requireAdminEventStore(\n res: ServerResponse,\n services: AdminServices,\n office: Office,\n): EventStore | null {\n if (!services.eventStore) {\n jsonRes(res, 503, { error: \"Working directory not available\" });\n return null;\n }\n return services.eventStore(office);\n}\n\n/** Every registered office's events, each read through its own store. */\nasync function serveEventsList(res: ServerResponse, services: AdminServices): Promise<void> {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n if (!services.eventStore) {\n jsonRes(res, 503, { error: \"Working directory not available\" });\n return;\n }\n const events: EventSummary[] = [];\n for (const record of listRegisteredOffices(workspace.stateDir)) {\n events.push(...(await listOfficeEvents(services.eventStore(workspace.office(record)))));\n }\n jsonRes(res, 200, { events });\n}\n\n/** Per-conversation listing through that office's confined store. */\nasync function serveConversationEventsList(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): Promise<void> {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const store = requireAdminEventStore(res, services, workspace.office(scope.address));\n if (!store) return;\n jsonRes(res, 200, {\n conversationId: scope.conversationId,\n events: await listOfficeEvents(store),\n });\n}\n\n/** Delete a single event file scoped to the caller's conversation. */\nasync function serveConversationEventDelete(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): Promise<void> {\n const scope = resolveTargetConversation(body, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n let name: string;\n try {\n name = validateEventFilename(typeof body.name === \"string\" ? body.name : \"\");\n } catch {\n jsonRes(res, 400, { error: \"Invalid name\" });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const store = requireAdminEventStore(res, services, workspace.office(scope.address));\n if (!store) return;\n // The store is confined to this office: another office's filename is \"not found\".\n try {\n await store.delete(name);\n jsonRes(res, 200, { ok: true });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n jsonRes(res, /not found/.test(message) ? 404 : 500, { error: message });\n }\n}\n\n// ── Utilities ──────────────────────────────────────────────────────────────────\n\nconst esc = escapeHtml;\n\n// ── HTML ───────────────────────────────────────────────────────────────────────\n\nconst adminViewBody = `<nav class=\"tab-nav\" role=\"tablist\" aria-label=\"Admin sections\">\n <button class=\"tab-btn active\" role=\"tab\" aria-selected=\"true\" aria-controls=\"panel-conversation\" data-tab=\"conversation\">Conversation</button>\n <button class=\"tab-btn\" role=\"tab\" aria-selected=\"false\" aria-controls=\"panel-global\" data-tab=\"global\">Global</button>\n </nav>\n\n <div class=\"tab-panel active\" id=\"panel-conversation\">\n <details class=\"card sect\" id=\"sect-settings\" data-section=\"settings\" open>\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Settings</p>\n <h2 class=\"card-title\">模型 / Thinking / Auto-reply / Workspace mount</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadSettings()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"settings-content\"><div class=\"loading-msg\">Loading…</div></div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-workspace\" data-section=\"workspace\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Workspace</p>\n <h2 class=\"card-title\">檔案瀏覽 (只讀)</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadWorkspace()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div class=\"workspace-split\">\n <div id=\"workspace-tree\" class=\"workspace-tree\"><div class=\"loading-msg\">Loading…</div></div>\n <div id=\"workspace-preview\" class=\"workspace-preview\"><div class=\"placeholder-msg\">Click a file to preview</div></div>\n </div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-skills\" data-section=\"skills\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Skills</p>\n <h2 class=\"card-title\">可用的 skills</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadSkills()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div class=\"workspace-split\">\n <div id=\"skills-content\" class=\"workspace-tree\"><div class=\"loading-msg\">Loading…</div></div>\n <div id=\"skills-preview\" class=\"workspace-preview\"><div class=\"placeholder-msg\">Click a skill to preview SKILL.md</div></div>\n </div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-mcp\" data-section=\"mcp\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">MCP Servers</p>\n <h2 class=\"card-title\">此對話的 MCP servers</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadMcpServers()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"mcp-conv-msg\" class=\"status-msg\" style=\"display:none\"></div>\n <div id=\"mcp-conv-content\"><div class=\"loading-msg\">Loading…</div></div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-vault\" data-section=\"vault\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Vault</p>\n <h2 class=\"card-title\">該對話的憑證</h2>\n </div></div>\n <button class=\"primary-action-btn\" onclick=\"event.stopPropagation(); openLogin()\">Open in new tab ↗</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"vault-link-result\" class=\"link-result\" style=\"display:none\"></div>\n <p class=\"card-desc\">Opens the one-time credential form for this conversation's vault in a new tab.</p>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-events\" data-section=\"events\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Events</p>\n <h2 class=\"card-title\">關聯此對話的 events</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadConversationEvents()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"events-content\"><div class=\"loading-msg\">Loading…</div></div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-session\" data-section=\"session\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Session View</p>\n <h2 class=\"card-title\">對話歷史檢視</h2>\n </div></div>\n <button class=\"primary-action-btn\" onclick=\"event.stopPropagation(); openSessionView()\">Open in new tab ↗</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"session-link-result\" class=\"link-result\" style=\"display:none\"></div>\n <p class=\"card-desc\">Opens the session timeline for this conversation in a new tab.</p>\n </div>\n </details>\n </div>\n\n <div class=\"tab-panel\" id=\"panel-global\">\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">All Conversations</p>\n <h2 class=\"card-title\">所有對話</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadAllConversations()\">↻</button>\n </header>\n <div id=\"all-conv-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Token Usage</p>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadTokenUsage()\">↻</button>\n </header>\n <h2 class=\"card-subtitle\" style=\"margin-bottom:10px\">Top 20 sessions</h3>\n <div id=\"session-usage-content\"><div class=\"loading-msg\">Loading…</div></div>\n <h2 class=\"card-subtitle\" style=\"margin:24px 0 10px\">Usage timeline</h3>\n <div class=\"timeline-controls\">\n <label>Conversation\n <select id=\"timeline-conv\" onchange=\"loadUsageTimeline()\"></select>\n </label>\n </div>\n <div id=\"usage-timeline-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Global Settings</p>\n <h2 class=\"card-title\">全域預設</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadGlobalSettings()\">↻</button>\n </header>\n <div id=\"global-settings-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Global MCP Servers</p>\n <h2 class=\"card-title\">所有對話都可用的 MCP servers</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadMcpServers()\">↻</button>\n </header>\n <div id=\"mcp-global-msg\" class=\"status-msg\" style=\"display:none\"></div>\n <div id=\"mcp-global-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Global Skills</p>\n <h2 class=\"card-title\">全域 skills</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadGlobalSkills()\">↻</button>\n </header>\n <div id=\"global-skills-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Global Events</p>\n <h2 class=\"card-title\">全域 events.json</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadEvents()\">↻</button>\n </header>\n <div id=\"global-events-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n </div>\n\n <dialog id=\"mcp-install-dialog\" class=\"mcp-dialog\" aria-labelledby=\"mcp-dialog-title\">\n <div class=\"mcp-dialog-head\">\n <div>\n <p class=\"eyebrow\">Install MCP preset</p>\n <h2 id=\"mcp-dialog-title\" class=\"card-title\"></h2>\n </div>\n <button class=\"mcp-dialog-close\" type=\"button\" aria-label=\"Close\" onclick=\"closeMcpInstall()\">×</button>\n </div>\n <div id=\"mcp-dialog-content\"></div>\n <div id=\"mcp-dialog-error\" class=\"inline-result err\" style=\"display:none\"></div>\n <div class=\"mcp-dialog-actions\">\n <button class=\"mcp-btn\" type=\"button\" onclick=\"closeMcpInstall()\">Cancel</button>\n <button id=\"mcp-dialog-install\" class=\"primary-action-btn\" type=\"button\" onclick=\"installMcpPreset(this)\">Install preset</button>\n </div>\n </dialog>\n\n <dialog id=\"mcp-custom-dialog\" class=\"mcp-dialog\" aria-labelledby=\"mcp-custom-dialog-title\">\n <div class=\"mcp-dialog-head\">\n <div>\n <p class=\"eyebrow\">Add MCP server</p>\n <h2 id=\"mcp-custom-dialog-title\" class=\"card-title\">新增自訂 server</h2>\n </div>\n <button class=\"mcp-dialog-close\" type=\"button\" aria-label=\"Close\" onclick=\"closeMcpCustomDialog()\">×</button>\n </div>\n <div id=\"mcp-custom-dialog-content\"></div>\n <div id=\"mcp-custom-dialog-error\" class=\"inline-result err\" style=\"display:none\"></div>\n <div class=\"mcp-dialog-actions\">\n <button class=\"mcp-btn\" type=\"button\" onclick=\"closeMcpCustomDialog()\">Cancel</button>\n <button id=\"mcp-custom-dialog-submit\" class=\"primary-action-btn\" type=\"button\" onclick=\"submitMcpCustomDialog(this)\">新增並測試連線</button>\n </div>\n </dialog>`;\n\nconst adminViewScript = ` let activeConversationKey = defaultConversationKey;\n function scopeOf(key) {\n const sep = key.indexOf(':');\n return { platform: key.slice(0, sep), conversationId: key.slice(sep + 1) };\n }\n function scopeQuery() {\n const scope = scopeOf(activeConversationKey);\n return 'conversationId=' + encodeURIComponent(scope.conversationId) +\n '&platform=' + encodeURIComponent(scope.platform);\n }\n function scopeBody() {\n const scope = scopeOf(activeConversationKey);\n return { conversationId: scope.conversationId, platform: scope.platform };\n }\n let availableModels = [];\n let modelsLoaded = false;\n let mcpPresets = [];\n let mcpServersByScope = { conversation: {}, global: {} };\n let pendingMcpInstall = null;\n\n // ── Helpers ──────────────────────────────────────────────────────────────────\n\n function escHtml(str) {\n return String(str).replace(/[&<>\"']/g, (c) => (\n {'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]\n ));\n }\n const escAttr = escHtml;\n async function copyToClipboard(text) {\n try { await navigator.clipboard.writeText(text); } catch { prompt('Copy this link:', text); }\n }\n document.addEventListener('click', (event) => {\n const target = event.target instanceof Element\n ? event.target.closest('[data-admin-action]')\n : null;\n if (!target) return;\n switch (target.dataset.adminAction) {\n case 'preview-file':\n void previewFile(target.dataset.filePath || '');\n break;\n case 'copy-link':\n void copyToClipboard(target.dataset.copyText || '');\n break;\n case 'delete-event':\n void deleteEvent(target.dataset.eventName || '', target);\n break;\n case 'select-conversation':\n setActiveConversation(target.dataset.conversationId || '');\n switchTab('conversation');\n break;\n case 'toggle-timeline-filter':\n toggleTimelineFilter(target.dataset.filterKey || '');\n break;\n }\n });\n async function apiGet(path) {\n const url = path + (path.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(adminToken);\n const r = await fetch(url);\n const data = await r.json();\n if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));\n return data;\n }\n async function apiPost(path, body) {\n const r = await fetch(path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token: adminToken, ...body }),\n });\n const data = await r.json();\n if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));\n return data;\n }\n async function loadModels() {\n try {\n const data = await apiGet('/admin/api/models');\n availableModels = Array.isArray(data.models) ? data.models : [];\n } catch (err) {\n availableModels = [];\n } finally {\n modelsLoaded = true;\n }\n }\n function modelRef(provider, model) {\n return provider && model ? provider + '/' + model : '';\n }\n function parseModelRef(value) {\n const slash = value.indexOf('/');\n if (slash <= 0 || slash === value.length - 1) return { provider: '', model: '' };\n return { provider: value.slice(0, slash), model: value.slice(slash + 1) };\n }\n function renderModelOptions(currentProvider, currentModel) {\n const current = modelRef(currentProvider, currentModel);\n const seen = new Set();\n const groups = {\n available: [],\n unverified: [],\n };\n if (current) {\n seen.add(current);\n groups.available.push('<option value=\"' + escAttr(current) + '\">' + escHtml(current + ' (current)') + '</option>');\n }\n for (const model of availableModels) {\n const ref = modelRef(model.provider, model.id);\n if (!ref || seen.has(ref)) continue;\n seen.add(ref);\n const details = [model.name && model.name !== model.id ? model.name : '', model.reasoning ? 'thinking' : '', Array.isArray(model.input) && model.input.includes('image') ? 'image' : '']\n .filter(Boolean)\n .join(' · ');\n const option = '<option value=\"' + escAttr(ref) + '\">' + escHtml(details ? ref + ' — ' + details : ref) + '</option>';\n if (model.status === 'unverified') groups.unverified.push(option);\n else groups.available.push(option);\n }\n const sections = [];\n if (groups.available.length > 0) sections.push('<optgroup label=\"Available\">' + groups.available.join('') + '</optgroup>');\n if (groups.unverified.length > 0) sections.push('<optgroup label=\"Configured but unverified\">' + groups.unverified.join('') + '</optgroup>');\n if (sections.length === 0) {\n return '<option value=\"\">No available models</option>';\n }\n return sections.join('');\n }\n\n // ── Tab switching ────────────────────────────────────────────────────────────\n\n const tabBtns = document.querySelectorAll('.tab-btn');\n const tabPanels = document.querySelectorAll('.tab-panel');\n\n function switchTab(tabId) {\n tabBtns.forEach((btn) => {\n const active = btn.dataset.tab === tabId;\n btn.classList.toggle('active', active);\n btn.setAttribute('aria-selected', active ? 'true' : 'false');\n });\n tabPanels.forEach((panel) => panel.classList.toggle('active', panel.id === 'panel-' + tabId));\n if (tabId === 'global') initGlobal();\n }\n tabBtns.forEach((btn) => btn.addEventListener('click', () => switchTab(btn.dataset.tab)));\n\n // ── Conversation switcher ───────────────────────────────────────────────────\n\n async function initConvSwitcher() {\n const sel = document.getElementById('conv-switcher');\n try {\n const data = await apiGet('/admin/api/conversations');\n sel.innerHTML = data.conversations.map((c) => {\n const key = c.platform + ':' + c.conversationId;\n const label = (c.label || c.conversationId) + (c.running ? ' (running)' : '');\n const selected = key === defaultConversationKey ? ' selected' : '';\n return '<option value=\"' + escAttr(key) + '\"' + selected + '>' + escHtml(label) + '</option>';\n }).join('');\n sel.addEventListener('change', () => setActiveConversation(sel.value));\n } catch (err) {\n // ignore; conversation selector stays empty\n }\n }\n\n // Sections load lazily: only when their <details> is (or becomes) open,\n // so switching conversations or opening the admin page doesn't fire every\n // API at once. sectionLoaded is cleared on conversation switch so an\n // already-open section refetches for the new scope.\n //\n // Looked up by name (not collected into an object up front) because the\n // loaders below are const declarations further down this same script;\n // referencing them before their own line runs would hit the temporal\n // dead zone. By the time this function is actually called (after a\n // <details> toggle, always after the whole script has executed), they're\n // all initialized.\n function sectionLoader(key) {\n switch (key) {\n case 'settings': return loadSettings;\n case 'workspace': return loadWorkspace;\n case 'skills': return loadSkills;\n case 'mcp': return loadMcpServers;\n case 'events': return loadConversationEvents;\n default: return undefined;\n }\n }\n const sectionLoaded = new Set();\n\n function conversationSectionEls() {\n return document.querySelectorAll('#panel-conversation > details[data-section]');\n }\n\n function ensureSectionLoaded(key) {\n if (sectionLoaded.has(key)) return;\n const loader = sectionLoader(key);\n if (!loader) return;\n sectionLoaded.add(key);\n loader();\n }\n\n function initSections() {\n conversationSectionEls().forEach((el) => {\n const key = el.dataset.section;\n const stored = localStorage.getItem('admin-sect-' + key);\n if (stored !== null) el.open = stored === '1';\n if (el.open) ensureSectionLoaded(key);\n el.addEventListener('toggle', () => {\n localStorage.setItem('admin-sect-' + key, el.open ? '1' : '0');\n if (el.open) ensureSectionLoaded(key);\n });\n });\n }\n\n function setActiveConversation(key) {\n activeConversationKey = key;\n const sel = document.getElementById('conv-switcher');\n if (sel && sel.value !== key) sel.value = key;\n // Data is scoped to the previous conversation; drop it so any open\n // section refetches, and closed sections load fresh on next expand.\n sectionLoaded.clear();\n conversationSectionEls().forEach((el) => {\n if (el.open) ensureSectionLoaded(el.dataset.section);\n });\n openLogin(true);\n openSessionView(true);\n }\n\n // ── Settings ─────────────────────────────────────────────────────────────────\n\n const loadSettings = () => loadSettingsPanel('settings-content', () => '/admin/api/conversation-state?' + scopeQuery(), renderSettings);\n const loadGlobalSettings = () => loadSettingsPanel('global-settings-content', () => '/admin/api/settings/global', renderGlobalSettings);\n\n async function loadSettingsPanel(id, path, render) {\n const container = document.getElementById(id);\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n if (!modelsLoaded) await loadModels();\n try {\n const data = await apiGet(path());\n container.innerHTML = render(data);\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function renderConfigCard(title, content, action, buttonLabel, resultId) {\n return '<div class=\"config-block\"><h3 class=\"card-subtitle\">' + title + '</h3>' + content +\n '<button class=\"primary-action-btn\" onclick=\"' + action + '(this)\">' + buttonLabel + '</button>' +\n '<div id=\"' + resultId + '\" class=\"inline-result\" style=\"display:none\"></div></div>';\n }\n\n function renderOptions(choices, selected) {\n return choices.map(([value, label]) =>\n '<option value=\"' + escAttr(value) + '\"' + (selected === value ? ' selected' : '') + '>' + escHtml(label) + '</option>'\n ).join('');\n }\n function renderThinkingOptions(value) {\n return renderOptions(['off','minimal','low','medium','high','xhigh','max'].map((t) => [t, t]), value);\n }\n function renderReplyOptions(slack) {\n return renderOptions(['top-level','thread'].map((m) => [m, m]), (slack && slack.replyMode) || 'top-level');\n }\n function renderSettings(data) {\n const thinkingOpts = renderThinkingOptions(data.thinkingLevel);\n const replyModeOpts = renderReplyOptions(data.slack);\n const globalReplyMode = (data.slack && data.slack.globalReplyMode) || 'top-level';\n const globalModel = [data.globalProvider, data.globalModel].filter(Boolean).join('/');\n const globalModelLabel = globalModel + (data.globalThinkingLevel ? ':' + data.globalThinkingLevel : '');\n return [\n '<div class=\"config-grid\">',\n renderConfigCard('Model', [\n '<div class=\"config-row config-row-stack\"><label>Model</label><select id=\"m-model-ref\">' + renderModelOptions(data.provider, data.model) + '</select></div>',\n '<div class=\"config-row\"><label>Thinking</label><select id=\"m-thinking\">' + thinkingOpts + '</select></div>',\n '<p class=\"muted-note\">Global default: ' + escHtml(globalModelLabel) + '</p>',\n ].join(''), 'saveModel', 'Save model', 'model-save-result'),\n\n renderConfigCard('Office visibility', [\n '<div class=\"config-row\"><label>Visibility</label><select id=\"m-visibility\">' +\n renderOptions([\n ['default', 'Follow the Slack conversation type (default)'],\n ['private', 'Private — treat this public channel as private'],\n ], data.officeVisibilityOverride === 'private' ? 'private' : 'default') + '</select></div>',\n '<p class=\"muted-note\">Effective: <strong>' + escHtml(data.officeVisibility) + '</strong> · ' + escHtml(\n data.officeVisibilitySource === 'override' ? 'set here'\n : data.officeVisibilitySource === 'platform' ? 'from the Slack conversation type'\n : 'conversation type unknown; private until observed') + '</p>',\n '<p class=\"muted-note\">Public offices can be read by every other office and may write shared MEMORY.md and skills. Private offices (private channels, DMs) are visible only to themselves and read shared knowledge without writing it. Nothing can be made more visible than Slack allows.</p>',\n (data.officeLegacyFull ? '<p class=\"muted-note\">This office still declares the retired <code>full</code> door policy; it no longer widens access.</p>' : ''),\n ].join(''), 'saveVisibility', 'Save visibility', 'mount-save-result'),\n renderConfigCard('Slack', [\n '<div class=\"config-row\"><label>Reply mode</label><select id=\"m-slack-reply-mode\">' + replyModeOpts + '</select></div>',\n '<p class=\"muted-note\">Global default: ' + escHtml(globalReplyMode) + '</p>',\n ].join(''), 'saveSlack', 'Save Slack', 'slack-save-result'),\n '</div>',\n ].join('');\n }\n\n async function saveModel(btn) {\n const selectedModel = parseModelRef(document.getElementById('m-model-ref').value.trim());\n const provider = selectedModel.provider;\n const model = selectedModel.model;\n const thinkingLevel = document.getElementById('m-thinking').value;\n const result = document.getElementById('model-save-result');\n if (!provider || !model) {\n result.style.display = 'block'; result.className = 'inline-result err';\n result.textContent = 'Provider and model are required';\n return;\n }\n await saveConversationSetting(btn, result, 'model', { provider, model, thinkingLevel }, 'Save model');\n }\n\n async function saveSlack(btn) {\n const replyMode = document.getElementById('m-slack-reply-mode').value;\n const result = document.getElementById('slack-save-result');\n await saveConversationSetting(btn, result, 'slack', { replyMode }, 'Save Slack');\n }\n\n async function saveVisibility(btn) {\n const visibility = document.getElementById('m-visibility').value;\n const result = document.getElementById('mount-save-result');\n await saveConversationSetting(btn, result, 'visibility', { visibility }, 'Save visibility', loadSettings);\n }\n\n function saveConversationSetting(btn, result, setting, values, label, onSaved) {\n return saveSetting(btn, result, 'conversations/' + setting, { ...scopeBody(), ...values }, label, onSaved);\n }\n\n async function saveSetting(btn, result, path, values, label, onSaved) {\n btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';\n try {\n await apiPost('/admin/api/' + path, values);\n result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';\n if (onSaved) onSaved();\n } catch (err) {\n result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;\n } finally {\n btn.disabled = false; btn.textContent = label;\n }\n }\n\n // ── Workspace ────────────────────────────────────────────────────────────────\n\n async function loadWorkspace() {\n const treeEl = document.getElementById('workspace-tree');\n const previewEl = document.getElementById('workspace-preview');\n treeEl.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n previewEl.innerHTML = '<div class=\"placeholder-msg\">Click a file to preview</div>';\n try {\n const data = await apiGet('/admin/api/workspace/tree?' + scopeQuery());\n if (!data.tree) {\n treeEl.innerHTML = '<div class=\"empty-state\">No files</div>';\n return;\n }\n treeEl.innerHTML = '<ul class=\"tree-root\">' + renderTreeChildren(data.tree) + '</ul>';\n } catch (err) {\n treeEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function renderTreeChildren(node) {\n if (node.type === 'file') {\n return '<li><button class=\"tree-file\" data-admin-action=\"preview-file\" data-file-path=\"' + escAttr(node.path) + '\">' + escHtml(node.name) + '</button></li>';\n }\n if (!node.children || node.children.length === 0) {\n return '<li><span class=\"tree-dir empty\">' + escHtml(node.name || '.') + '/</span></li>';\n }\n const inner = node.children.map((c) =>\n c.type === 'file'\n ? '<li><button class=\"tree-file\" data-admin-action=\"preview-file\" data-file-path=\"' + escAttr(c.path) + '\">' + escHtml(c.name) + '</button></li>'\n : '<li><details open><summary class=\"tree-dir\">' + escHtml(c.name) + '/</summary><ul>' + renderTreeChildren(c) + '</ul></details></li>'\n ).join('');\n return inner;\n }\n\n function renderPreviewFileResult(previewEl, label, data) {\n if (data.binary) {\n previewEl.innerHTML = '<div class=\"preview-meta\">' + escHtml(label) + ' · ' + data.size + ' bytes · binary</div><div class=\"placeholder-msg\">Binary file — preview not available</div>';\n return;\n }\n previewEl.innerHTML =\n '<div class=\"preview-meta\">' + escHtml(label) + ' · ' + data.size + ' bytes</div>' +\n '<pre class=\"preview-body\">' + escHtml(data.content || '') + '</pre>';\n }\n\n async function previewFile(path) {\n const previewEl = document.getElementById('workspace-preview');\n previewEl.innerHTML = '<div class=\"loading-msg\">Loading ' + escHtml(path) + '…</div>';\n try {\n const data = await apiGet('/admin/api/workspace/file?' + scopeQuery() + '&path=' + encodeURIComponent(path));\n renderPreviewFileResult(previewEl, path, data);\n } catch (err) {\n previewEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n // ── Skills ───────────────────────────────────────────────────────────────────\n\n async function loadScopePanels(prefix, endpoint, render) {\n const convEl = document.getElementById(prefix + '-conv-content');\n const globalEl = document.getElementById(prefix + '-global-content');\n if (convEl) convEl.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n if (globalEl) globalEl.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n const data = await apiGet('/admin/api/' + endpoint + '?' + scopeQuery());\n render(data, convEl, globalEl);\n } catch (err) {\n if (convEl) convEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n if (globalEl) globalEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n // ── MCP servers ───────────────────────────────────────────────────────────────────────\n\n function mcpMessage(scope, text, kind) {\n const el = document.getElementById(scope === 'global' ? 'mcp-global-msg' : 'mcp-conv-msg');\n if (!el) return;\n el.style.display = 'block';\n el.className = 'status-msg status-msg-' + kind;\n el.textContent = text;\n }\n\n function mcpTransport(server) {\n return server.command\n ? server.command + (server.args && server.args.length ? ' ' + server.args.join(' ') : '')\n : (server.url || '');\n }\n\n function renderMcpServer(scope, name, server) {\n const transport = (server.command ? 'stdio: ' : 'http: ') + mcpTransport(server);\n const keys = [];\n if (server.envKeys && server.envKeys.length) keys.push('env: ' + server.envKeys.join(', '));\n if (server.headerKeys && server.headerKeys.length) keys.push('headers: ' + server.headerKeys.join(', '));\n return '<article class=\"mcp-preset mcp-installed-card\">' +\n '<div class=\"mcp-preset-top\">' +\n '<span class=\"mcp-preset-category\">' + (server.command ? 'STDIO' : 'HTTP') + '</span>' +\n (server.disabled ? '<span class=\"mcp-badge mcp-badge-warn\">disabled</span>' : '<span class=\"mcp-badge mcp-badge-ok\">enabled</span>') +\n '</div>' +\n '<h3>' + escHtml(name) + '</h3>' +\n '<p class=\"mcp-preset-meta mcp-installed-transport\">' + escHtml(transport) + '</p>' +\n (keys.length ? '<div class=\"mcp-preset-meta\">' + escHtml(keys.join(' · ')) + '</div>' : '') +\n '<div class=\"mcp-preset-actions\">' +\n '<button class=\"mcp-btn\" data-mcp-action=\"test\" data-mcp-scope=\"' + scope + '\" data-mcp-name=\"' + escAttr(name) + '\">Test</button>' +\n '<button class=\"mcp-btn\" data-mcp-action=\"toggle\" data-mcp-scope=\"' + scope + '\" data-mcp-name=\"' + escAttr(name) + '\">' + (server.disabled ? 'Enable' : 'Disable') + '</button>' +\n '<button class=\"mcp-btn mcp-btn-danger\" data-mcp-action=\"remove\" data-mcp-scope=\"' + scope + '\" data-mcp-name=\"' + escAttr(name) + '\">Remove</button>' +\n '</div>' +\n '</article>';\n }\n\n function renderMcpPreset(scope, preset, servers) {\n const installed = Boolean(servers[preset.serverName]);\n const transport = preset.server.command ? 'Runs on host' : 'Remote service';\n return '<article class=\"mcp-preset\">' +\n '<div class=\"mcp-preset-top\">' +\n '<span class=\"mcp-preset-category\">' + escHtml(preset.category) + '</span>' +\n (installed ? '<span class=\"mcp-badge mcp-badge-ok\">Installed here</span>' : '') +\n '</div>' +\n '<h3>' + escHtml(preset.name) + '</h3>' +\n '<p>' + escHtml(preset.description) + '</p>' +\n '<div class=\"mcp-preset-meta\">' + escHtml(transport) + ' · <code>' + escHtml(preset.serverName) + '</code></div>' +\n '<div class=\"mcp-preset-actions\">' +\n '<a href=\"' + escAttr(preset.sourceUrl) + '\" target=\"_blank\" rel=\"noopener\">Source ↗</a>' +\n '<button class=\"primary-action-btn\" data-mcp-action=\"preset\" data-mcp-scope=\"' + scope + '\" data-mcp-preset=\"' + escAttr(preset.id) + '\">' + (installed ? 'Review' : 'Install') + '</button>' +\n '</div>' +\n '</article>';\n }\n\n function renderMcpScope(el, scope, servers) {\n const presets = mcpPresets.map((preset) => renderMcpPreset(scope, preset, servers)).join('');\n const rows = Object.entries(servers).map(([name, server]) => renderMcpServer(scope, name, server)).join('');\n el.innerHTML =\n '<div class=\"mcp-market-head\"><div><h3>Explore presets</h3><p>Reviewed recipes that install into this scope.</p></div><span>' + mcpPresets.length + ' available</span></div>' +\n '<div class=\"mcp-preset-grid\">' + presets + '</div>' +\n '<details class=\"mcp-installed\" open><summary>Installed in this scope</summary>' +\n '<div class=\"mcp-preset-grid\">' +\n rows +\n '<button type=\"button\" class=\"mcp-add-card\" data-mcp-action=\"open-custom\" data-mcp-scope=\"' + scope + '\">' +\n '<span class=\"mcp-add-card-plus\">+</span><span>新增 server</span>' +\n '</button>' +\n '</div>' +\n '</details>';\n }\n\n function renderMcpGuidedForm(scope) {\n return '<div class=\"mcp-guided-grid\">' +\n '<label class=\"mcp-field\"><span>Server 名稱</span><input id=\"mcp-' + scope + '-g-name\" class=\"form-input mcp-json\" placeholder=\"例如 github\" autocomplete=\"off\" /></label>' +\n '<div class=\"mcp-transport-toggle\">' +\n '<label><input type=\"radio\" name=\"mcp-' + scope + '-g-transport\" value=\"stdio\" checked /> 本機指令 (stdio)</label>' +\n '<label><input type=\"radio\" name=\"mcp-' + scope + '-g-transport\" value=\"http\" /> 遠端服務 (HTTP)</label>' +\n '</div>' +\n '<div id=\"mcp-' + scope + '-g-stdio\" class=\"mcp-transport-fields\">' +\n '<label class=\"mcp-field\"><span>指令</span><input id=\"mcp-' + scope + '-g-command\" class=\"form-input mcp-json\" placeholder=\"npx\" autocomplete=\"off\" /></label>' +\n '<label class=\"mcp-field\"><span>參數(以逗號分隔)</span><input id=\"mcp-' + scope + '-g-args\" class=\"form-input mcp-json\" placeholder=\"-y, @modelcontextprotocol/server-github\" autocomplete=\"off\" /></label>' +\n '</div>' +\n '<div id=\"mcp-' + scope + '-g-http\" class=\"mcp-transport-fields\" style=\"display:none\">' +\n '<label class=\"mcp-field\"><span>URL</span><input id=\"mcp-' + scope + '-g-url\" class=\"form-input mcp-json\" placeholder=\"https://mcp.example.com/mcp\" autocomplete=\"off\" /></label>' +\n '</div>' +\n '<div class=\"mcp-kv-block\">' +\n '<div class=\"mcp-kv-head\"><span id=\"mcp-' + scope + '-g-kv-label\">環境變數 (env)</span><button type=\"button\" class=\"mcp-btn\" data-mcp-kv-add=\"' + scope + '\">+ 新增一列</button></div>' +\n '<div id=\"mcp-' + scope + '-g-kv\" class=\"mcp-kv-rows\"></div>' +\n '</div>' +\n '</div>';\n }\n\n function renderMcpKvRow() {\n return '<div class=\"mcp-kv-row\">' +\n '<input class=\"form-input mcp-json\" placeholder=\"KEY\" data-kv-key autocomplete=\"off\" />' +\n '<input class=\"form-input mcp-json\" type=\"password\" placeholder=\"value\" data-kv-value autocomplete=\"off\" />' +\n '<button type=\"button\" class=\"mcp-btn mcp-btn-danger\" data-mcp-kv-remove>移除</button>' +\n '</div>';\n }\n\n function addMcpGuidedRow(scope) {\n const container = document.getElementById('mcp-' + scope + '-g-kv');\n if (container) container.insertAdjacentHTML('beforeend', renderMcpKvRow());\n }\n\n function updateMcpGuidedTransport(scope) {\n const checked = document.querySelector('input[name=\"mcp-' + scope + '-g-transport\"]:checked');\n const transport = checked ? checked.value : 'stdio';\n const stdioEl = document.getElementById('mcp-' + scope + '-g-stdio');\n const httpEl = document.getElementById('mcp-' + scope + '-g-http');\n const kvLabel = document.getElementById('mcp-' + scope + '-g-kv-label');\n if (stdioEl) stdioEl.style.display = transport === 'stdio' ? '' : 'none';\n if (httpEl) httpEl.style.display = transport === 'http' ? '' : 'none';\n if (kvLabel) kvLabel.textContent = transport === 'stdio' ? '環境變數 (env)' : 'HTTP Headers';\n }\n\n let pendingMcpCustomScope = null;\n\n function openMcpCustomDialog(scope) {\n pendingMcpCustomScope = scope;\n const content = document.getElementById('mcp-custom-dialog-content');\n content.innerHTML =\n '<div class=\"mcp-add-mode\">' +\n '<button type=\"button\" class=\"mcp-mode-btn active\" data-mcp-mode=\"guided\">引導式表單</button>' +\n '<button type=\"button\" class=\"mcp-mode-btn\" data-mcp-mode=\"json\">貼上 JSON</button>' +\n '</div>' +\n '<div id=\"mcp-custom-guided-panel\" class=\"mcp-panel\">' + renderMcpGuidedForm('custom') + '</div>' +\n '<div id=\"mcp-custom-json-panel\" class=\"mcp-panel\" style=\"display:none\">' +\n '<p class=\"mcp-manual-hint\">貼上 MCP server 文件給的 <code>mcpServers</code> JSON,原樣保存到這個 scope。remote 走 Streamable HTTP,local 走 stdio。</p>' +\n '<textarea id=\"mcp-custom-json\" class=\"form-input mcp-json\" spellcheck=\"false\" rows=\"9\" placeholder=\"' + escAttr(MCP_JSON_PLACEHOLDER) + '\"></textarea>' +\n '</div>';\n const dialogError = document.getElementById('mcp-custom-dialog-error');\n dialogError.style.display = 'none';\n dialogError.textContent = '';\n addMcpGuidedRow('custom');\n document.getElementById('mcp-custom-dialog').showModal();\n }\n\n function closeMcpCustomDialog() {\n pendingMcpCustomScope = null;\n document.getElementById('mcp-custom-dialog').close();\n }\n\n function switchMcpCustomMode(mode) {\n const guidedEl = document.getElementById('mcp-custom-guided-panel');\n const jsonEl = document.getElementById('mcp-custom-json-panel');\n if (guidedEl) guidedEl.style.display = mode === 'guided' ? '' : 'none';\n if (jsonEl) jsonEl.style.display = mode === 'json' ? '' : 'none';\n document.querySelectorAll('#mcp-custom-dialog-content [data-mcp-mode]').forEach((b) => {\n b.classList.toggle('active', b.dataset.mcpMode === mode);\n });\n }\n\n async function submitMcpCustomDialog(btn) {\n const scope = pendingMcpCustomScope;\n if (!scope) return;\n const dialogError = document.getElementById('mcp-custom-dialog-error');\n dialogError.style.display = 'none';\n dialogError.textContent = '';\n const jsonMode = document.getElementById('mcp-custom-json-panel').style.display !== 'none';\n let json;\n if (jsonMode) {\n const raw = document.getElementById('mcp-custom-json').value.trim();\n if (!raw) { dialogError.textContent = '請貼上 mcpServers JSON'; dialogError.style.display = 'block'; return; }\n json = raw;\n } else {\n const nameEl = document.getElementById('mcp-custom-g-name');\n const name = nameEl ? nameEl.value.trim() : '';\n if (!name) { dialogError.textContent = '請輸入 server 名稱'; dialogError.style.display = 'block'; return; }\n const checked = document.querySelector('input[name=\"mcp-custom-g-transport\"]:checked');\n const transport = checked ? checked.value : 'stdio';\n const kv = {};\n document.querySelectorAll('#mcp-custom-g-kv .mcp-kv-row').forEach((row) => {\n const keyInput = row.querySelector('[data-kv-key]');\n const valueInput = row.querySelector('[data-kv-value]');\n const key = keyInput ? keyInput.value.trim() : '';\n if (key) kv[key] = valueInput ? valueInput.value : '';\n });\n let entry;\n if (transport === 'stdio') {\n const commandEl = document.getElementById('mcp-custom-g-command');\n const command = commandEl ? commandEl.value.trim() : '';\n if (!command) { dialogError.textContent = '請輸入指令'; dialogError.style.display = 'block'; return; }\n const argsEl = document.getElementById('mcp-custom-g-args');\n const argsRaw = argsEl ? argsEl.value.trim() : '';\n const args = argsRaw ? argsRaw.split(',').map((s) => s.trim()).filter(Boolean) : [];\n entry = Object.assign({ command: command }, args.length ? { args: args } : {}, Object.keys(kv).length ? { env: kv } : {});\n } else {\n const urlEl = document.getElementById('mcp-custom-g-url');\n const url = urlEl ? urlEl.value.trim() : '';\n if (!url) { dialogError.textContent = '請輸入 URL'; dialogError.style.display = 'block'; return; }\n entry = Object.assign({ url: url }, Object.keys(kv).length ? { headers: kv } : {});\n }\n json = JSON.stringify({ mcpServers: { [name]: entry } });\n }\n btn.disabled = true;\n btn.textContent = '儲存中…';\n try {\n const data = await apiPost('/admin/api/mcp-servers/mutate', {\n action: 'import', scope: scope, json: json, ...scopeBody(),\n });\n const results = Array.isArray(data.results) ? data.results : [];\n const failed = results.filter((r) => r.error).length;\n closeMcpCustomDialog();\n mcpMessage(scope, failed ? '已儲存,但 ' + failed + ' 個 server 連線失敗,見上方選項維護。' : '已儲存並連線成功。', failed ? 'err' : 'ok');\n await loadMcpServers();\n } catch (err) {\n dialogError.textContent = err.message;\n dialogError.style.display = 'block';\n } finally {\n btn.disabled = false;\n btn.textContent = '新增並測試連線';\n }\n }\n\n const MCP_JSON_PLACEHOLDER = JSON.stringify({\n mcpServers: {\n browserless: {\n type: 'http',\n url: 'https://mcp.browserless.io/mcp',\n headers: { Authorization: 'Bearer YOUR_API_TOKEN' },\n },\n },\n }, null, 2);\n\n const loadMcpServers = () => loadScopePanels('mcp', 'mcp-servers', (data, convEl, globalEl) => {\n mcpPresets = Array.isArray(data.presets) ? data.presets : [];\n mcpServersByScope = { conversation: data.conversation || {}, global: data.global || {} };\n if (convEl) renderMcpScope(convEl, 'conversation', mcpServersByScope.conversation);\n if (globalEl) renderMcpScope(globalEl, 'global', mcpServersByScope.global);\n });\n\n async function mutateMcpServer(scope, action, name, extra) {\n mcpMessage(scope, action === 'test' ? '連線測試中…' : '儲存並測試連線中…', 'busy');\n try {\n const data = await apiPost('/admin/api/mcp-servers/mutate', {\n action: action,\n scope: scope,\n ...(name ? { name: name } : {}),\n ...(extra || {}),\n ...scopeBody(),\n });\n const results = Array.isArray(data.results) ? data.results : [];\n const failed = results.filter((r) => r.error).length;\n if (action === 'test') {\n mcpMessage(scope, failed ? '✗ ' + name + ': ' + results[0].error : '✓ ' + name + ': ' + results[0].tools + ' tool(s)', failed ? 'err' : 'ok');\n return;\n }\n if (action === 'remove' || action === 'toggle') {\n mcpMessage(scope, '完成。新設定在下一次回應生效。', 'ok');\n } else {\n mcpMessage(scope, failed ? '已儲存,但 ' + failed + ' 個 server 連線失敗,見下方錯誤。' : '已儲存並連線成功。', failed ? 'err' : 'ok');\n }\n await loadMcpServers();\n } catch (err) {\n mcpMessage(scope, err.message, 'err');\n }\n }\n\n function openMcpPreset(scope, presetId) {\n const preset = mcpPresets.find((item) => item.id === presetId);\n if (!preset) return;\n pendingMcpInstall = { scope: scope, preset: preset };\n const local = Boolean(preset.server.command);\n const installed = Boolean(mcpServersByScope[scope][preset.serverName]);\n const credentials = preset.credentials.map((credential, index) =>\n '<label class=\"mcp-credential\"><span>' + escHtml(credential.label) + (credential.required ? ' *' : '') + '</span>' +\n '<input data-mcp-credential=\"' + index + '\" type=\"' + (credential.secret ? 'password' : 'text') + '\" autocomplete=\"off\" />' +\n '<small>' + escHtml(credential.description) + '</small></label>'\n ).join('');\n document.getElementById('mcp-dialog-title').textContent = preset.name;\n const dialogError = document.getElementById('mcp-dialog-error');\n dialogError.style.display = 'none';\n dialogError.textContent = '';\n document.getElementById('mcp-dialog-content').innerHTML =\n '<p class=\"mcp-dialog-desc\">' + escHtml(preset.description) + '</p>' +\n '<div class=\"mcp-install-preview\"><span>' + (local ? 'Host command' : 'Remote endpoint') + '</span><code>' + escHtml(mcpTransport(preset.server)) + '</code></div>' +\n '<div class=\"mcp-security-note ' + (local ? 'local' : 'remote') + '\">' +\n (local\n ? '<strong>Host code execution.</strong> This command runs outside the conversation sandbox with the mikan process user permissions.'\n : '<strong>External service.</strong> Tool calls and selected data will be sent to this remote origin.') +\n '</div>' +\n (installed ? '<div class=\"mcp-replace-note\">Installing again replaces the existing <code>' + escHtml(preset.serverName) + '</code> entry in this scope.</div>' : '') +\n (credentials || '<p class=\"mcp-no-credentials\">No credentials required.</p>') +\n '<p class=\"mcp-secret-note\">These values are stored in host-private settings and are not shown to the model or sandbox.</p>' +\n '<a class=\"mcp-setup-link\" href=\"' + escAttr(preset.setupUrl) + '\" target=\"_blank\" rel=\"noopener\">Setup documentation ↗</a>';\n document.getElementById('mcp-install-dialog').showModal();\n }\n\n function closeMcpInstall() {\n pendingMcpInstall = null;\n document.getElementById('mcp-install-dialog').close();\n }\n\n async function installMcpPreset(btn) {\n if (!pendingMcpInstall) return;\n const { scope, preset } = pendingMcpInstall;\n const credentials = {};\n document.querySelectorAll('[data-mcp-credential]').forEach((input) => {\n const descriptor = preset.credentials[Number(input.dataset.mcpCredential)];\n if (descriptor) credentials[descriptor.key] = input.value;\n });\n btn.disabled = true;\n btn.textContent = 'Installing…';\n try {\n const data = await apiPost('/admin/api/mcp-servers/mutate', {\n action: 'install', scope: scope, presetId: preset.id, credentials: credentials, ...scopeBody(),\n });\n closeMcpInstall();\n const result = (Array.isArray(data.results) ? data.results : [])[0];\n if (result && result.error) mcpMessage(scope, '✗ ' + preset.name + ' saved but failed to connect: ' + result.error, 'err');\n else mcpMessage(scope, '✓ ' + preset.name + ' installed, ' + (result ? result.tools : 0) + ' tool(s).', 'ok');\n await loadMcpServers();\n } catch (err) {\n const dialogError = document.getElementById('mcp-dialog-error');\n dialogError.textContent = err.message;\n dialogError.style.display = 'block';\n } finally {\n btn.disabled = false;\n btn.textContent = 'Install preset';\n }\n }\n\n document.addEventListener('click', (event) => {\n const modeBtn = event.target.closest('#mcp-custom-dialog-content [data-mcp-mode]');\n if (modeBtn) { switchMcpCustomMode(modeBtn.dataset.mcpMode); return; }\n const kvAddBtn = event.target.closest('[data-mcp-kv-add]');\n if (kvAddBtn) { addMcpGuidedRow(kvAddBtn.dataset.mcpKvAdd); return; }\n const kvRemoveBtn = event.target.closest('[data-mcp-kv-remove]');\n if (kvRemoveBtn) { kvRemoveBtn.closest('.mcp-kv-row').remove(); return; }\n const transportRadio = event.target.closest('input[name^=\"mcp-\"][name$=\"-g-transport\"]');\n if (transportRadio) {\n const match = /^mcp-(.+)-g-transport$/.exec(transportRadio.name);\n if (match) updateMcpGuidedTransport(match[1]);\n return;\n }\n const btn = event.target.closest('[data-mcp-action]');\n if (!btn) return;\n if (btn.dataset.mcpAction === 'open-custom') { openMcpCustomDialog(btn.dataset.mcpScope); return; }\n if (btn.dataset.mcpAction === 'preset') { openMcpPreset(btn.dataset.mcpScope, btn.dataset.mcpPreset); return; }\n void mutateMcpServer(btn.dataset.mcpScope, btn.dataset.mcpAction, btn.dataset.mcpName);\n });\n\n async function loadSkills() {\n const container = document.getElementById('skills-content');\n const previewEl = document.getElementById('skills-preview');\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n if (previewEl) previewEl.innerHTML = '<div class=\"placeholder-msg\">Click a skill to preview SKILL.md</div>';\n try {\n const data = await apiGet('/admin/api/skills?' + scopeQuery());\n if (data.skills.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">No skills available</div>';\n return;\n }\n container.innerHTML = '<div class=\"skills-list\">' +\n data.skills.map((s) =>\n '<button class=\"skill-row skill-row-btn\" data-skill-source=\"' + escAttr(s.source) + '\" data-skill-directory=\"' + escAttr(s.directory) + '\" data-skill-name=\"' + escAttr(s.name) + '\">' +\n '<div class=\"skill-name\">' + escHtml(s.name) + '<span class=\"skill-source skill-source-' + s.source + '\">' + s.source + '</span></div>' +\n (s.description ? '<div class=\"skill-desc\">' + escHtml(s.description) + '</div>' : '') +\n '</button>'\n ).join('') + '</div>';\n\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n async function previewSkill(source, directory, name) {\n const previewEl = document.getElementById('skills-preview');\n if (!source || !directory) {\n previewEl.innerHTML = '<div class=\"err-msg\">Missing skill source or directory</div>';\n return;\n }\n previewEl.innerHTML = '<div class=\"loading-msg\">Loading ' + escHtml(name || directory) + '…</div>';\n try {\n const data = await apiGet('/admin/api/skills/file?' + scopeQuery() + '&source=' + encodeURIComponent(source) + '&directory=' + encodeURIComponent(directory));\n renderPreviewFileResult(previewEl, source + '/' + directory + '/SKILL.md', data);\n } catch (err) {\n previewEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n document.getElementById('skills-content').addEventListener('click', (event) => {\n const btn = event.target.closest('[data-skill-source]');\n if (!btn) return;\n previewSkill(btn.dataset.skillSource, btn.dataset.skillDirectory, btn.dataset.skillName);\n });\n\n // ── Vault (Login link) ───────────────────────────────────────────────────────\n\n const openLogin = (silent) => openPortalLink('vault', 'login', silent);\n const openSessionView = (silent) => openPortalLink('session', 'session', silent);\n\n // Generates a fresh one-time/short-lived link for the other portal and\n // opens it in a new tab — the login and session-view tokens are separate\n // capabilities from the admin token (see docs/portal-auth-model.md), so\n // this always mints a new link rather than reusing anything cached here.\n async function openPortalLink(resultId, kind, silent) {\n const result = document.getElementById(resultId + '-link-result');\n if (silent) { result.style.display = 'none'; return; }\n result.style.display = 'block'; result.className = 'link-result loading'; result.textContent = 'Generating link…';\n try {\n const data = await apiPost('/admin/api/conversations/' + kind + '-link', scopeBody());\n result.className = 'link-result ok';\n result.innerHTML =\n (kind === 'login' ? '<span class=\"link-vault\">vault: <code>' + escHtml(data.vaultId) + '</code></span>' : '') +\n '<a href=\"' + escAttr(data.url) + '\" target=\"_blank\" rel=\"noopener\">' + escHtml(data.url) + '</a>' +\n '<button class=\"copy-link-btn\" data-admin-action=\"copy-link\" data-copy-text=\"' + escAttr(data.url) + '\">Copy</button>';\n window.open(data.url, '_blank', 'noopener');\n } catch (err) {\n result.className = 'link-result err'; result.textContent = err.message;\n }\n }\n\n // ── Events ───────────────────────────────────────────────────────────────────\n\n const loadConversationEvents = () => loadEventList(true);\n const loadEvents = () => loadEventList(false);\n\n async function loadEventList(conversation) {\n const container = document.getElementById(conversation ? 'events-content' : 'global-events-content');\n if (!container) return;\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n const data = await apiGet(conversation ? '/admin/api/conversations/events?' + scopeQuery() : '/admin/api/events');\n if (data.events.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">' + (conversation ? '沒有關聯此對話的 event' : 'No events scheduled') + '</div>';\n return;\n }\n container.innerHTML = '<div class=\"events-list\">' +\n data.events.map((e) => renderEventRow(e, conversation)).join('') + '</div>';\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function renderEventRow(e, allowDelete) {\n const meta = [e.type, e.platform, e.conversationId, e.schedule || e.at]\n .filter(Boolean).map(escHtml).join(' · ');\n const preview = e.text ? '<div class=\"event-text\">' + escHtml(e.text.length > 240 ? e.text.slice(0, 237) + '…' : e.text) + '</div>' : '';\n const deleteBtn = allowDelete\n ? '<button class=\"event-delete-btn\" data-admin-action=\"delete-event\" data-event-name=\"' + escAttr(e.name) + '\">Delete</button>'\n : '';\n return '<div class=\"event-row\">' +\n '<div class=\"event-row-top\">' +\n '<div class=\"event-name\"><code>' + escHtml(e.name) + '</code></div>' +\n deleteBtn +\n '</div>' +\n '<div class=\"event-meta\">' + meta + '</div>' +\n preview +\n '</div>';\n }\n\n async function deleteEvent(name, btn) {\n if (!confirm('Delete event \"' + name + '\"?')) return;\n btn.disabled = true; btn.textContent = 'Deleting…';\n try {\n await apiPost('/admin/api/conversations/events/delete', {\n ...scopeBody(), name,\n });\n await loadConversationEvents();\n } catch (err) {\n btn.disabled = false; btn.textContent = 'Delete';\n alert(err.message);\n }\n }\n\n // ── Global section ──────────────────────────────────────────────────────────\n\n let globalLoaded = false;\n function initGlobal() {\n if (globalLoaded) return;\n globalLoaded = true;\n loadAllConversations();\n loadTokenUsage();\n loadGlobalSettings();\n loadGlobalSkills();\n loadEvents();\n loadMcpServers();\n }\n\n async function loadAllConversations() {\n const container = document.getElementById('all-conv-content');\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n const data = await apiGet('/admin/api/conversations');\n if (data.conversations.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">No conversations found</div>';\n return;\n }\n container.innerHTML = '<div class=\"conv-list\">' + data.conversations.map((c) => {\n const last = c.lastActivityAt ? new Date(c.lastActivityAt).toLocaleString() : '—';\n return '<button class=\"conv-row-btn\" data-admin-action=\"select-conversation\" data-conversation-id=\"' + escAttr(c.conversationId) + '\">' +\n '<span class=\"conv-id\">' + escHtml(c.label || c.conversationId) + '</span>' +\n (c.running ? '<span class=\"status-pill running\">running</span>' : '') +\n '<span class=\"conv-last\">' + escHtml(last) + '</span>' +\n '</button>';\n }).join('') + '</div>';\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n async function loadSessionUsage() {\n const container = document.getElementById('session-usage-content');\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n const data = await apiGet('/admin/api/session-usage');\n if (data.sessions.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">No token usage found</div>';\n return;\n }\n container.innerHTML = '<div class=\"usage-table-wrap\"><table class=\"usage-table\"><thead><tr><th>#</th><th>Channel</th><th>Session</th><th>Updated</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cache Write</th><th>Total</th><th>Cost</th></tr></thead><tbody>' +\n data.sessions.map((s, i) => '<tr>' +\n '<td>' + (i + 1) + '</td>' +\n '<td>' + escHtml(s.label || s.conversationId) + '</td>' +\n '<td><code>' + escHtml(s.fileName) + '</code></td>' +\n '<td>' + escHtml(new Date(s.updatedAt).toLocaleString()) + '</td>' +\n '<td>' + fmtNum(s.input) + '</td>' +\n '<td>' + fmtNum(s.output) + '</td>' +\n '<td>' + fmtNum(s.cacheRead) + '</td>' +\n '<td>' + fmtNum(s.cacheWrite) + '</td>' +\n '<td><strong>' + fmtNum(s.total) + '</strong></td>' +\n '<td>' + (s.cost > 0 ? '$' + Number(s.cost).toFixed(4) : '—') + '</td>' +\n '</tr>').join('') + '</tbody></table></div>';\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function fmtNum(value) {\n return Number(value || 0).toLocaleString('en-US');\n }\n\n let timelineConvLoaded = false;\n let timelineData = null;\n let timelineFilter = null;\n async function ensureTimelineConvOptions() {\n if (timelineConvLoaded) return;\n const sel = document.getElementById('timeline-conv');\n const prev = sel.value;\n const data = await apiGet('/admin/api/conversations');\n if (!data.conversations.length) {\n sel.innerHTML = '<option value=\"\">No conversations</option>';\n timelineConvLoaded = true;\n return;\n }\n const want = prev || defaultConversationKey;\n sel.innerHTML = data.conversations.map((c) => {\n const key = c.platform + ':' + c.conversationId;\n return '<option value=\"' + escAttr(key) + '\"' +\n (key === want ? ' selected' : '') + '>' +\n escHtml(c.label || c.conversationId) + '</option>';\n }).join('');\n timelineConvLoaded = true;\n }\n\n // Shared refresh for the merged Token Usage section: reloads the session\n // ranking and re-fetches the conversation list (so new sessions appear),\n // preserving the currently selected conversation.\n async function loadTokenUsage() {\n loadSessionUsage();\n timelineConvLoaded = false;\n await loadUsageTimeline();\n }\n\n async function loadUsageTimeline() {\n const container = document.getElementById('usage-timeline-content');\n try {\n await ensureTimelineConvOptions();\n const conv = document.getElementById('timeline-conv').value;\n if (!conv) {\n container.innerHTML = '<div class=\"empty-state\">No conversations found</div>';\n return;\n }\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n const convScope = scopeOf(conv);\n const data = await apiGet('/admin/api/conversation-usage?conversationId=' +\n encodeURIComponent(convScope.conversationId) +\n '&platform=' + encodeURIComponent(convScope.platform));\n timelineData = data;\n container.innerHTML = renderUsageTimeline(data);\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function tlCard(label, value) {\n return '<div class=\"tl-card\"><div class=\"tl-card-label\">' + label +\n '</div><div class=\"tl-card-value\">' + value + '</div></div>';\n }\n\n const TL_SERIES = [\n { key: 'cacheRead', seg: 'tl-cache-read', sw: 'sw-cache-read', label: 'Cache read' },\n { key: 'cacheWrite', seg: 'tl-cache-write', sw: 'sw-cache-write', label: 'Cache write' },\n { key: 'input', seg: 'tl-input', sw: 'sw-input', label: 'Input' },\n { key: 'output', seg: 'tl-output', sw: 'sw-output', label: 'Output' },\n ];\n\n // Click a legend item to show only that series; click it again for all.\n function toggleTimelineFilter(key) {\n timelineFilter = timelineFilter === key ? null : key;\n if (timelineData) {\n document.getElementById('usage-timeline-content').innerHTML = renderUsageTimeline(timelineData);\n }\n }\n\n function renderUsageTimeline(data) {\n const buckets = data.buckets || [];\n const totals = data.totals || { total: 0, cost: 0, cacheRead: 0 };\n const cacheHit = totals.total > 0 ? Math.round((totals.cacheRead / totals.total) * 100) : 0;\n const cards = '<div class=\"tl-cards\">' +\n tlCard('Total cost', totals.cost > 0 ? '$' + Number(totals.cost).toFixed(4) : '—') +\n tlCard('Total tokens', fmtNum(totals.total)) +\n tlCard('Cache hit', cacheHit + '%') +\n '</div>';\n\n if (totals.total === 0) {\n const emptyNote = data.hasOlder\n ? '<div class=\"tl-note\">No usage in the last 14 days · earlier activity exists</div>'\n : '<div class=\"empty-state\">No token usage in the last 14 days</div>';\n return cards + emptyNote;\n }\n\n const active = TL_SERIES.find((s) => s.key === timelineFilter) || null;\n const valueOf = (b) => active ? (b[active.key] || 0) : b.total;\n const max = Math.max(1, ...buckets.map(valueOf));\n const px = (v) => Math.round((v / max) * 180);\n\n const legend = '<div class=\"tl-legend\">' + TL_SERIES.map((s) => {\n const cls = 'tl-legend-item' +\n (active && active.key === s.key ? ' active' : (active ? ' dim' : ''));\n return '<span class=\"' + cls + '\" data-admin-action=\"toggle-timeline-filter\" data-filter-key=\"' + escAttr(s.key) + '\">' +\n '<i class=\"sw ' + s.sw + '\"></i>' + s.label + '</span>';\n }).join('') + '</div>';\n\n const bars = buckets.map((b) => {\n const val = valueOf(b);\n const tip = active\n ? b.date + ' · ' + active.label + ': ' + fmtNum(b[active.key] || 0) + ' tokens'\n : b.date + ' · ' + fmtNum(b.total) + ' tokens' +\n (b.cost > 0 ? ' · $' + Number(b.cost).toFixed(4) : '');\n let inner;\n if (val <= 0) {\n inner = '<span class=\"tl-empty\"></span>';\n } else if (active) {\n inner = '<span class=\"tl-seg ' + active.seg + '\" style=\"height:' + px(val) + 'px\"></span>';\n } else {\n inner = '<span class=\"tl-seg tl-output\" style=\"height:' + px(b.output) + 'px\"></span>' +\n '<span class=\"tl-seg tl-input\" style=\"height:' + px(b.input) + 'px\"></span>' +\n '<span class=\"tl-seg tl-cache-write\" style=\"height:' + px(b.cacheWrite) + 'px\"></span>' +\n '<span class=\"tl-seg tl-cache-read\" style=\"height:' + px(b.cacheRead) + 'px\"></span>';\n }\n return '<div class=\"tl-bar\">' +\n '<span class=\"tl-tip\">' + escHtml(tip) + '</span>' +\n '<div class=\"tl-fill\">' + inner + '</div>' +\n '</div>';\n }).join('');\n\n const axis = buckets.length\n ? '<div class=\"tl-axis\"><span>' + escHtml(buckets[0].date.slice(5)) +\n '</span><span>' + escHtml(buckets[buckets.length - 1].date.slice(5)) + '</span></div>'\n : '';\n const note = data.hasOlder\n ? '<div class=\"tl-note\">Showing last 14 days · earlier activity not shown</div>'\n : '<div class=\"tl-note\">Showing last 14 days</div>';\n const peak = '<div class=\"tl-peak\" style=\"bottom:180px\"><span class=\"tl-peak-label\">' +\n fmtNum(max) + ' tokens</span></div>';\n return cards + legend + '<div class=\"tl-chart\">' + peak + bars + '</div>' + axis + note;\n }\n\n function renderGlobalSettings(data) {\n const thinkingOpts = renderThinkingOptions(data.thinkingLevel);\n const replyModeOpts = renderReplyOptions(data.slack);\n return [\n '<div class=\"config-grid\">',\n renderConfigCard('Default model', [\n '<div class=\"config-row config-row-stack\"><label>Model</label><select id=\"g-model-ref\">' + renderModelOptions(data.provider, data.model) + '</select></div>',\n '<div class=\"config-row\"><label>Thinking</label><select id=\"g-thinking\">' + thinkingOpts + '</select></div>',\n ].join(''), 'saveGlobalModel', 'Save model', 'g-model-result'),\n renderConfigCard('Sandbox limits', [\n '<div class=\"config-row\"><label>CPUs</label><input id=\"g-cpus\" placeholder=\"0.5\" value=\"' + escAttr(data.sandboxCpus || '') + '\"></div>',\n '<div class=\"config-row\"><label>Memory</label><input id=\"g-mem\" placeholder=\"1g\" value=\"' + escAttr(data.sandboxMemory || '') + '\"></div>',\n '<div class=\"config-row\"><label>Boost CPUs</label><input id=\"g-bcpus\" placeholder=\"2\" value=\"' + escAttr(data.sandboxBoostCpus || '') + '\"></div>',\n '<div class=\"config-row\"><label>Boost Mem</label><input id=\"g-bmem\" placeholder=\"4g\" value=\"' + escAttr(data.sandboxBoostMemory || '') + '\"></div>',\n ].join(''), 'saveGlobalSandbox', 'Save sandbox', 'g-sandbox-result'),\n renderConfigCard('Slack', [\n '<div class=\"config-row\"><label>Reply mode</label><select id=\"g-slack-reply-mode\">' + replyModeOpts + '</select></div>',\n ].join(''), 'saveGlobalSlack', 'Save Slack', 'g-slack-result'),\n '</div>',\n ].join('');\n }\n\n async function saveGlobalModel(btn) {\n const selectedModel = parseModelRef(document.getElementById('g-model-ref').value.trim());\n const provider = selectedModel.provider;\n const model = selectedModel.model;\n const thinkingLevel = document.getElementById('g-thinking').value;\n const result = document.getElementById('g-model-result');\n if (!provider || !model) {\n result.style.display = 'block'; result.className = 'inline-result err';\n result.textContent = 'Provider and model are required'; return;\n }\n btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';\n try {\n await apiPost('/admin/api/settings/model', { provider, model, thinkingLevel });\n result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';\n } catch (err) {\n result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;\n } finally {\n btn.disabled = false; btn.textContent = 'Save model';\n }\n }\n\n async function saveGlobalSandbox(btn) {\n const cpus = document.getElementById('g-cpus').value.trim();\n const memory = document.getElementById('g-mem').value.trim();\n const boostCpus = document.getElementById('g-bcpus').value.trim();\n const boostMemory = document.getElementById('g-bmem').value.trim();\n const result = document.getElementById('g-sandbox-result');\n await saveSetting(btn, result, 'settings/sandbox', { cpus, memory, boostCpus, boostMemory }, 'Save sandbox');\n }\n\n async function saveGlobalSlack(btn) {\n const replyMode = document.getElementById('g-slack-reply-mode').value;\n const result = document.getElementById('g-slack-result');\n await saveSetting(btn, result, 'settings/slack', { replyMode }, 'Save Slack');\n }\n\n async function loadGlobalSkills() {\n const container = document.getElementById('global-skills-content');\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n // Reuse skills endpoint scoped to a conversation that doesn't have any of its own; the global half is what we want.\n const data = await apiGet('/admin/api/skills?' + scopeQuery());\n const globals = data.skills.filter((s) => s.source === 'global');\n if (globals.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">No global skills</div>';\n return;\n }\n container.innerHTML = '<div class=\"skills-list\">' + globals.map((s) =>\n '<div class=\"skill-row\"><div class=\"skill-name\">' + escHtml(s.name) + '</div>' +\n (s.description ? '<div class=\"skill-desc\">' + escHtml(s.description) + '</div>' : '') + '</div>'\n ).join('') + '</div>';\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n // ── Init ─────────────────────────────────────────────────────────────────────\n\n // Sections load on demand (see initSections); loadSettingsPanel already\n // calls loadModels() itself the first time it's needed.\n initConvSwitcher();\n initSections();\n `;\n\nfunction renderAdminPage(token: AdminToken): string {\n const userLabel = token.platformUserName ?? token.platformUserId;\n const script = `\n const adminToken = ${JSON.stringify(token.token)};\n // Conversation ids never contain \":\" (session-key grammar), so\n // \"platform:id\" is a safe composite scope key for the UI.\n const defaultConversationKey = ${JSON.stringify(`${token.platform}:${token.conversationId}`)};\n${adminViewScript}`;\n\n return renderPortalShell({\n activeView: \"admin\",\n pageTitle: \"Admin\",\n identity: { primary: token.platform, secondary: userLabel },\n conversationSwitcher: { currentId: token.conversationId },\n body: adminViewBody,\n extraStyles: adminViewStyles,\n inlineScript: script,\n });\n}\n\nfunction renderAdminErrorPage(message: string): string {\n return renderPortalShell({\n activeView: \"admin\",\n pageTitle: \"Admin\",\n body: `<section class=\"card\" style=\"text-align:center;padding:40px 32px\">\n <p class=\"eyebrow\">${PRODUCT_NAME} admin</p>\n <h1 class=\"page-title\" style=\"margin:12px 0 16px\">Access Denied</h1>\n <div class=\"err-msg\">${esc(message)}</div>\n </section>`,\n });\n}\n\n// ── Styles ─────────────────────────────────────────────────────────────────────\n\nconst adminViewStyles = `\n .tab-nav {\n display: flex; gap: 6px; padding: 6px;\n border: 1px solid var(--border); border-radius: 16px;\n background: rgba(255,255,255,0.72); backdrop-filter: blur(8px);\n overflow-x: auto; scrollbar-width: none;\n }\n .tab-nav::-webkit-scrollbar { display: none; }\n .tab-btn {\n flex: 1; min-width: 80px; padding: 10px 16px;\n border: none; border-radius: 10px; background: transparent;\n color: var(--muted);\n font: 500 0.88rem/1.2 'DM Sans', sans-serif;\n cursor: pointer; white-space: nowrap;\n transition: background 140ms, color 140ms;\n }\n .tab-btn:hover { background: rgba(0,0,0,0.04); color: var(--text); }\n .tab-btn.active { background: var(--text); color: #fafafa; font-weight: 600; }\n .tab-btn:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }\n\n .tab-panel { display: none; flex-direction: column; gap: 14px; }\n .tab-panel.active { display: flex; }\n\n .card-desc { color: var(--muted); font-size: 0.9rem; line-height: 1.55; margin-bottom: 12px; }\n\n .link-result {\n margin-top: 12px; padding: 10px 14px; border-radius: 10px;\n display: flex; gap: 10px; align-items: center; flex-wrap: wrap;\n font-size: 0.84rem;\n }\n .link-result.ok { background: var(--ok-bg); border: 1px solid var(--ok-border); }\n .link-result.err { background: var(--err-bg); border: 1px solid var(--err-border); color: var(--err-text); }\n .link-result.loading { background: rgba(0,0,0,0.025); border: 1px solid var(--border); color: var(--muted); }\n .link-result a {\n color: var(--ok-text);\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.78rem; word-break: break-all; flex: 1; min-width: 0;\n }\n .link-vault { color: var(--muted); font-size: 0.78rem; flex-shrink: 0; }\n .copy-link-btn {\n padding: 5px 12px; border: 1px solid var(--ok-border); border-radius: 7px;\n background: rgba(255,255,255,0.7); color: var(--ok-text);\n font: 500 0.78rem/1.2 'DM Sans', sans-serif;\n cursor: pointer; flex-shrink: 0;\n }\n\n .config-grid {\n display: grid; grid-template-columns: 1fr 1fr; gap: 18px;\n }\n .config-block { display: flex; flex-direction: column; gap: 10px; }\n .config-row { display: grid; grid-template-columns: 110px 1fr; gap: 10px; align-items: center; }\n .config-row.config-row-stack { grid-template-columns: 1fr; }\n .config-row label { font-size: 0.82rem; color: var(--muted); }\n .config-row input, .config-row select, .config-row textarea {\n padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px;\n font-family: inherit; font-size: 0.84rem; width: 100%;\n }\n .config-row textarea {\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n resize: vertical;\n }\n .toggle { display: inline-flex; align-items: center; gap: 8px; font-size: 0.84rem; }\n\n .inline-result {\n padding: 8px 12px; border-radius: 8px; font-size: 0.82rem; margin-top: 4px;\n }\n .inline-result.ok { background: var(--ok-bg); color: var(--ok-text); border: 1px solid var(--ok-border); }\n .inline-result.err { background: var(--err-bg); color: var(--err-text); border: 1px solid var(--err-border); }\n\n /* ── Sections (Conversation page stack) ─────────────────────────────── */\n\n /* Each section is a <details>; the card's own padding sits on the\n <summary>/<div class=\"sect-body\"> instead of the <details> element so a\n collapsed section is just its header row. */\n details.sect { padding: 0; overflow: hidden; }\n details.sect > summary.sect-head {\n padding: 24px 28px; margin-bottom: 0; cursor: pointer; list-style: none;\n }\n details.sect > summary.sect-head::-webkit-details-marker { display: none; }\n details.sect > .sect-body { padding: 0 28px 24px; }\n\n .sect-head {\n display: flex; align-items: flex-start; justify-content: space-between;\n gap: 12px; flex-wrap: wrap;\n }\n .sect-head .card-title { margin-bottom: 0; }\n .sect-title { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }\n .sect-caret {\n flex-shrink: 0; margin-top: 3px; color: var(--subtle);\n transition: transform 140ms; display: inline-block;\n }\n details.sect[open] > summary.sect-head .sect-caret { transform: rotate(90deg); }\n .sect-disabled { opacity: 0.7; }\n\n .refresh-btn {\n flex-shrink: 0; padding: 6px 12px;\n border: 1px solid var(--border); border-radius: 10px;\n background: rgba(0,0,0,0.025); color: var(--muted);\n font: 500 0.84rem/1.2 'DM Sans', sans-serif; cursor: pointer;\n }\n .refresh-btn:hover { background: rgba(0,0,0,0.06); color: var(--text); }\n\n /* ── Workspace ──────────────────────────────────────────────────────── */\n\n .workspace-split {\n display: grid; grid-template-columns: 260px 1fr; gap: 14px;\n min-height: 360px;\n }\n .workspace-tree {\n border: 1px solid var(--border); border-radius: 12px; padding: 10px;\n background: rgba(0,0,0,0.02); overflow: auto; max-height: 480px;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.78rem;\n }\n .workspace-tree ul { list-style: none; padding-left: 12px; margin: 0; }\n .workspace-tree .tree-root { padding-left: 0; }\n .workspace-tree details { margin: 1px 0; }\n .workspace-tree summary { cursor: pointer; padding: 2px 4px; border-radius: 4px; }\n .workspace-tree summary:hover { background: rgba(0,0,0,0.05); }\n .tree-dir { color: var(--text); font-weight: 600; }\n .tree-dir.empty { color: var(--subtle); font-weight: 400; }\n .tree-file {\n display: block; width: 100%; text-align: left;\n background: transparent; border: none; cursor: pointer;\n padding: 2px 4px; border-radius: 4px;\n font-family: inherit; font-size: inherit; color: var(--muted);\n }\n .tree-file:hover { background: rgba(0,0,0,0.05); color: var(--text); }\n\n .workspace-preview {\n border: 1px solid var(--border); border-radius: 12px;\n background: #fff; padding: 12px; overflow: auto; max-height: 480px;\n }\n .preview-meta {\n font-size: 0.74rem; color: var(--subtle);\n margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--border);\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n }\n .preview-body {\n margin: 0; white-space: pre-wrap; word-break: break-word;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.78rem; color: var(--text);\n }\n .placeholder-msg { color: var(--subtle); font-size: 0.86rem; padding: 24px 8px; text-align: center; }\n\n /* ── Skills ─────────────────────────────────────────────────────────── */\n\n .skills-list { display: flex; flex-direction: column; gap: 8px; }\n .skill-row {\n padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;\n background: rgba(0,0,0,0.02);\n }\n .skill-row-btn {\n width: 100%; text-align: left; cursor: pointer; font-family: inherit;\n }\n .skill-row-btn:hover { background: rgba(0,0,0,0.05); }\n .skill-name {\n font-weight: 650; font-size: 0.9rem; color: var(--text);\n display: flex; align-items: center; gap: 8px; flex-wrap: wrap;\n }\n .skill-source {\n padding: 1px 8px; border-radius: 999px; font-size: 0.7rem;\n font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;\n }\n .skill-source-global { background: rgba(59,130,246,0.1); color: #1d4ed8; }\n .skill-source-conversation { background: rgba(217,119,6,0.1); color: var(--accent); }\n .skill-desc { color: var(--muted); font-size: 0.82rem; margin-top: 4px; line-height: 1.5; }\n\n /* ── MCP marketplace ───────────────────────────────────────────────── */\n\n .form-input {\n flex: 1 1 240px; min-width: 0; padding: 8px 10px;\n border: 1px solid var(--border); border-radius: 8px; background: var(--card);\n font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.8rem; color: var(--text);\n }\n .status-msg {\n padding: 8px 12px; border-radius: 8px; margin-bottom: 12px;\n font-size: 0.82rem; line-height: 1.5; word-break: break-word;\n }\n .status-msg-ok { background: rgba(22,163,74,0.1); color: #15803d; }\n .status-msg-err { background: rgba(220,38,38,0.1); color: #b91c1c; }\n .status-msg-busy { background: rgba(0,0,0,0.05); color: var(--muted); }\n .mcp-btn {\n padding: 4px 10px; border: 1px solid var(--border); border-radius: 7px;\n background: var(--card); color: var(--text); font-size: 0.76rem; cursor: pointer;\n }\n .mcp-btn:hover { background: rgba(0,0,0,0.05); }\n .mcp-btn-danger { color: #b91c1c; }\n .mcp-badge {\n padding: 1px 8px; border-radius: 999px; font-size: 0.7rem;\n font-weight: 600; letter-spacing: 0.03em;\n }\n .mcp-badge-ok { background: rgba(22,163,74,0.1); color: #15803d; }\n .mcp-badge-warn { background: rgba(217,119,6,0.12); color: var(--accent); }\n\n .mcp-market-head {\n display: flex; align-items: end; justify-content: space-between; gap: 16px;\n margin-bottom: 12px;\n }\n .mcp-market-head h3 { margin: 0; font-size: 1rem; }\n .mcp-market-head p { margin: 3px 0 0; color: var(--muted); font-size: 0.8rem; }\n .mcp-market-head > span {\n color: var(--subtle); font: 500 0.72rem/1.2 'JetBrains Mono', ui-monospace, monospace;\n }\n .mcp-preset-grid {\n display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px;\n }\n .mcp-preset {\n min-width: 0; padding: 14px; border: 1px solid var(--border); border-radius: 12px;\n background:\n linear-gradient(145deg, rgba(255,255,255,0.85), rgba(247,245,238,0.72)),\n var(--card);\n display: flex; flex-direction: column; min-height: 190px;\n }\n .mcp-preset-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n .mcp-preset-category {\n color: var(--accent); font-size: 0.66rem; font-weight: 700;\n letter-spacing: 0.09em; text-transform: uppercase;\n }\n .mcp-preset h3 { margin: 14px 0 5px; font-size: 1.06rem; letter-spacing: -0.02em; }\n .mcp-preset p { margin: 0; color: var(--muted); font-size: 0.8rem; line-height: 1.5; }\n .mcp-preset-meta {\n margin-top: 12px; color: var(--subtle); font-size: 0.72rem;\n }\n .mcp-preset-meta code { color: var(--text); background: transparent; }\n .mcp-preset-actions {\n margin-top: auto; padding-top: 16px; display: flex; align-items: center;\n justify-content: space-between; gap: 10px;\n }\n .mcp-preset-actions a, .mcp-setup-link {\n color: var(--muted); font-size: 0.76rem; text-decoration: none;\n }\n .mcp-preset-actions a:hover, .mcp-setup-link:hover { color: var(--text); text-decoration: underline; }\n .mcp-preset-actions .primary-action-btn { padding: 6px 12px; }\n .mcp-installed {\n margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px;\n }\n .mcp-installed > summary {\n cursor: pointer; color: var(--muted); font-size: 0.8rem; font-weight: 600;\n margin-bottom: 10px;\n }\n .mcp-installed-card { min-height: 150px; }\n .mcp-installed-transport {\n font-family: 'JetBrains Mono', ui-monospace, monospace; word-break: break-all;\n }\n .mcp-add-card {\n min-width: 0; min-height: 190px; border: 1.5px dashed var(--border); border-radius: 12px;\n background: transparent; color: var(--muted); font-size: 0.86rem; font-weight: 600;\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n gap: 8px; cursor: pointer;\n }\n .mcp-add-card:hover { background: rgba(0,0,0,0.03); color: var(--text); border-color: var(--text); }\n .mcp-add-card-plus { font-size: 1.8rem; line-height: 1; font-weight: 400; }\n .mcp-manual-hint { color: var(--muted); font-size: 0.8rem; margin: 0 0 10px; line-height: 1.5; }\n .mcp-add-mode { display: flex; gap: 8px; margin: 0 0 12px; }\n .mcp-mode-btn {\n padding: 6px 12px; border: 1px solid var(--border); border-radius: 999px;\n background: var(--card); font-size: 0.8rem; cursor: pointer; color: var(--muted);\n }\n .mcp-mode-btn.active { background: var(--text); color: #fafafa; border-color: var(--text); }\n .mcp-guided-grid { display: grid; gap: 12px; }\n .mcp-field { display: grid; gap: 5px; }\n .mcp-field span { font-size: 0.78rem; font-weight: 650; color: var(--muted); }\n .mcp-transport-toggle { display: flex; gap: 16px; font-size: 0.85rem; align-items: center; }\n .mcp-transport-toggle label { display: flex; align-items: center; gap: 6px; cursor: pointer; }\n .mcp-transport-fields { display: grid; gap: 12px; }\n .mcp-kv-block { display: grid; gap: 8px; }\n .mcp-kv-head { display: flex; align-items: center; justify-content: space-between; }\n .mcp-kv-head span { font-size: 0.78rem; font-weight: 650; color: var(--muted); }\n .mcp-kv-rows { display: grid; gap: 6px; }\n .mcp-kv-row { display: flex; gap: 8px; }\n .mcp-kv-row .form-input { flex: 1 1 auto; }\n .mcp-json {\n width: 100%; box-sizing: border-box; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.8rem; line-height: 1.45; resize: vertical;\n }\n .mcp-verify { margin-top: 10px; display: grid; gap: 6px; }\n .mcp-verify .inline-result { word-break: break-word; }\n\n .mcp-dialog {\n width: min(620px, calc(100vw - 28px)); max-height: calc(100vh - 40px);\n border: 1px solid var(--border); border-radius: 18px; padding: 22px;\n color: var(--text); background: #fbfaf6; box-shadow: 0 28px 90px rgba(18,18,16,0.24);\n /* The shell's global \"* { margin: 0 }\" reset overrides the UA stylesheet's\n dialog[open] { margin: auto } centering rule, so pin it back explicitly. */\n position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0;\n }\n .mcp-dialog::backdrop { background: rgba(20,20,18,0.5); backdrop-filter: blur(3px); }\n .mcp-dialog-head {\n display: flex; align-items: flex-start; justify-content: space-between; gap: 18px;\n padding-bottom: 14px; border-bottom: 1px solid var(--border);\n }\n .mcp-dialog-head .card-title { margin: 4px 0 0; }\n .mcp-dialog-close {\n border: 0; background: transparent; color: var(--muted); cursor: pointer;\n font-size: 1.6rem; line-height: 1; padding: 2px 5px;\n }\n .mcp-dialog-desc { color: var(--muted); line-height: 1.55; font-size: 0.88rem; }\n .mcp-install-preview {\n display: grid; gap: 6px; padding: 12px; border-radius: 10px;\n background: #1d211f; color: #f5f2e9;\n }\n .mcp-install-preview span {\n color: #aeb8b1; font-size: 0.66rem; font-weight: 700;\n letter-spacing: 0.08em; text-transform: uppercase;\n }\n .mcp-install-preview code {\n color: #f5f2e9; background: transparent; word-break: break-all; font-size: 0.78rem;\n }\n .mcp-security-note, .mcp-replace-note {\n margin-top: 12px; padding: 10px 12px; border-radius: 9px;\n font-size: 0.78rem; line-height: 1.5;\n }\n .mcp-security-note.local {\n background: rgba(220,38,38,0.08); border: 1px solid rgba(185,28,28,0.18); color: #991b1b;\n }\n .mcp-security-note.remote {\n background: rgba(59,130,246,0.08); border: 1px solid rgba(29,78,216,0.16); color: #1e40af;\n }\n .mcp-replace-note { background: rgba(217,119,6,0.1); color: #92400e; }\n .mcp-credential { display: grid; gap: 5px; margin-top: 14px; }\n .mcp-credential span { font-size: 0.78rem; font-weight: 650; }\n .mcp-credential input {\n width: 100%; padding: 9px 10px; border: 1px solid var(--border); border-radius: 8px;\n background: #fff; color: var(--text); font: 0.8rem/1.3 'JetBrains Mono', ui-monospace, monospace;\n }\n .mcp-credential small, .mcp-secret-note, .mcp-no-credentials {\n color: var(--subtle); font-size: 0.72rem; line-height: 1.45;\n }\n .mcp-secret-note { margin: 14px 0 8px; }\n .mcp-no-credentials { margin: 14px 0 0; }\n #mcp-dialog-error, #mcp-custom-dialog-error { margin-top: 14px; }\n .mcp-dialog-actions {\n display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px;\n padding-top: 14px; border-top: 1px solid var(--border);\n }\n\n /* ── Events ─────────────────────────────────────────────────────────── */\n\n .events-list { display: flex; flex-direction: column; gap: 8px; }\n .event-row {\n padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;\n background: rgba(0,0,0,0.02);\n }\n .event-row-top {\n display: flex; align-items: center; justify-content: space-between;\n gap: 10px;\n }\n .event-name { min-width: 0; flex: 1; word-break: break-all; }\n .event-name code { font-size: 0.82rem; background: transparent; padding: 0; }\n .event-meta { font-size: 0.74rem; color: var(--muted); margin-top: 3px; }\n .event-text {\n font-size: 0.82rem; color: var(--text); margin-top: 6px;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n white-space: pre-wrap; word-break: break-word;\n }\n .event-delete-btn {\n flex-shrink: 0; padding: 4px 10px;\n border-radius: 7px; border: 1px solid rgba(185, 28, 28, 0.18);\n background: rgba(0,0,0,0.03); color: var(--err-text);\n font: 500 0.76rem/1.2 'DM Sans', sans-serif; cursor: pointer;\n }\n .event-delete-btn:hover:not(:disabled) {\n background: var(--err-bg); border-color: rgba(185, 28, 28, 0.28);\n }\n .event-delete-btn:disabled { opacity: 0.5; cursor: wait; }\n\n /* ── All Conversations list ─────────────────────────────────────────── */\n\n .conv-list { display: flex; flex-direction: column; gap: 6px; }\n .conv-row-btn {\n display: flex; align-items: center; gap: 12px;\n padding: 10px 14px; border: 1px solid var(--border); border-radius: 10px;\n background: rgba(0,0,0,0.02); cursor: pointer; text-align: left;\n transition: background 120ms, border-color 120ms;\n }\n .conv-row-btn:hover { background: rgba(0,0,0,0.05); border-color: rgba(0,0,0,0.14); }\n .conv-id { flex: 1; font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.84rem; }\n .conv-last { color: var(--subtle); font-size: 0.78rem; }\n\n .usage-table-wrap { overflow-x: auto; }\n .usage-table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }\n .usage-table th, .usage-table td {\n padding: 8px 10px; border-bottom: 1px solid var(--border);\n text-align: left; white-space: nowrap;\n }\n .usage-table th {\n color: var(--subtle); font-size: 0.68rem;\n text-transform: uppercase; letter-spacing: 0.08em;\n }\n .usage-table code { font-size: 0.72rem; }\n\n .timeline-controls {\n display: flex; flex-wrap: wrap; gap: 18px; align-items: center; margin-bottom: 16px;\n }\n .timeline-controls label {\n display: flex; align-items: center; gap: 8px;\n font-size: 0.76rem; color: var(--muted);\n }\n .timeline-controls select {\n padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px;\n background: #fff; color: var(--text); font-size: 0.8rem; max-width: 240px;\n }\n .tl-note { margin-top: 8px; font-size: 0.72rem; color: var(--subtle); }\n .tl-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 16px; }\n .tl-card { background: rgba(0,0,0,0.025); border-radius: 10px; padding: 10px 12px; }\n .tl-card-label { font-size: 0.73rem; color: var(--muted); margin-bottom: 4px; }\n .tl-card-value { font-size: 1.35rem; font-weight: 600; color: var(--text); }\n .tl-legend { display: flex; gap: 16px; font-size: 0.73rem; color: var(--muted); margin-bottom: 10px; }\n .tl-legend span { display: inline-flex; align-items: center; gap: 6px; }\n .tl-legend-item { cursor: pointer; transition: opacity 100ms; }\n .tl-legend-item:hover { text-decoration: underline; }\n .tl-legend-item.active { color: var(--text); font-weight: 600; text-decoration: underline; }\n .tl-legend-item.dim { opacity: 0.4; }\n .sw { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }\n .sw-cache-read { background: rgba(0,0,0,0.18); }\n .sw-cache-write { background: #3b6fb0; }\n .sw-input { background: var(--accent); }\n .sw-output { background: var(--ok-text); }\n .tl-chart {\n position: relative;\n display: flex; align-items: flex-end; gap: 6px;\n height: 216px; border-bottom: 1px solid var(--border);\n }\n .tl-peak {\n position: absolute; left: 0; right: 0; height: 0;\n border-top: 1px dashed var(--accent); pointer-events: none;\n }\n .tl-peak-label {\n position: absolute; left: 0; top: -15px;\n font-size: 0.7rem; color: var(--accent); white-space: nowrap;\n }\n .tl-bar {\n position: relative; flex: 1; min-width: 0; height: 180px;\n display: flex; flex-direction: column; justify-content: flex-end;\n border-radius: 6px 6px 0 0; transition: background 80ms ease;\n }\n .tl-bar:hover { background: rgba(0,0,0,0.06); }\n .tl-bar:hover .tl-seg { opacity: 0.55; }\n .tl-fill {\n display: flex; flex-direction: column; justify-content: flex-end;\n border-radius: 3px 3px 0 0; overflow: hidden;\n }\n .tl-tip {\n position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);\n margin-bottom: 6px; padding: 5px 9px; border-radius: 6px;\n background: var(--text); color: #fafafa; font-size: 0.7rem; line-height: 1.35;\n white-space: nowrap; opacity: 0; pointer-events: none; z-index: 5;\n }\n .tl-bar:hover .tl-tip { opacity: 1; }\n .tl-seg { display: block; width: 100%; }\n .tl-seg.tl-output { background: var(--ok-text); }\n .tl-seg.tl-input { background: var(--accent); }\n .tl-seg.tl-cache-write { background: #3b6fb0; }\n .tl-seg.tl-cache-read { background: rgba(0,0,0,0.18); }\n .tl-empty { display: block; width: 100%; height: 3px; background: var(--border); }\n .tl-axis { display: flex; justify-content: space-between; margin-top: 6px; font-size: 0.7rem; color: var(--subtle); }\n\n .status-pill {\n display: inline-flex; padding: 2px 9px; border-radius: 999px;\n font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;\n }\n .status-pill.running { background: var(--ok-bg); color: var(--ok-text); border: 1px solid var(--ok-border); }\n\n @media (max-width: 640px) {\n .tab-btn { padding: 9px 12px; font-size: 0.82rem; min-width: 60px; }\n .config-grid { grid-template-columns: 1fr; }\n .config-row { grid-template-columns: 1fr; gap: 4px; }\n .workspace-split { grid-template-columns: 1fr; }\n .workspace-tree, .workspace-preview { max-height: 260px; }\n .mcp-preset-grid { grid-template-columns: 1fr; }\n .mcp-dialog { padding: 18px; }\n }\n`;\n"]}
|
|
1
|
+
{"version":3,"file":"portal.js","sourceRoot":"","sources":["../../../../src/adapters/web/admin/portal.ts"],"names":[],"mappings":"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","sourcesContent":["import { validateEventFilename } from \"../../../events/index.js\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { basename, join, resolve as pathResolve, sep as pathSep } from \"node:path\";\nimport { MikanModels, parseFrontmatter } from \"../../../harness/index.js\";\nimport { SessionStore } from \"../../../sessions/session-store.js\";\nimport type { EventStore } from \"../../../events/index.js\";\nimport type { PlatformName } from \"../../../adapter.js\";\nimport { InMemoryTokenStore } from \"../token-store.js\";\nimport type { AdminToken } from \"./types.js\";\nexport type { AdminToken } from \"./types.js\";\n\nconst ADMIN_TOKEN_TTL_MS = 30 * 60 * 1000;\n\nexport class InMemoryAdminTokenStore extends InMemoryTokenStore<AdminToken> {\n create(args: {\n platform: PlatformName;\n platformUserId: string;\n conversationId: string;\n platformUserName?: string;\n }): AdminToken {\n this.deleteWhere(\n (token) => token.platform === args.platform && token.platformUserId === args.platformUserId,\n );\n return this.createRecord(ADMIN_TOKEN_TTL_MS, {\n platform: args.platform,\n platformUserId: args.platformUserId,\n ...(args.platformUserName ? { platformUserName: args.platformUserName } : {}),\n conversationId: args.conversationId,\n });\n }\n}\n\nimport {\n loadOfficeVisibilityOverride,\n loadGlobalSettings,\n loadScopeMcpServers,\n resolveConversationSettings,\n type AgentConfig,\n type SandboxSettings,\n} from \"../../../config.js\";\nimport { findMcpPreset, listMcpPresets, materializeMcpPreset } from \"../../../harness/mcp.js\";\nimport { loadMcpTools } from \"../../../harness/mcp.js\";\nimport {\n isValidMcpServerName,\n parseStandardMcpServers,\n redactMcpUrl,\n} from \"../../../harness/mcp.js\";\nimport type { McpServerConfig } from \"../../../harness/types.js\";\nimport {\n applyConversationSettings,\n applyOfficeVisibility,\n applyGlobalSettings,\n} from \"../../../settings-mutation.js\";\nimport {\n escapeHtml,\n jsonResponse as jsonRes,\n readJsonBody,\n renderPortalShell,\n} from \"../portal-shell.js\";\nimport { resolveExistingSessionFile } from \"../session-view/portal.js\";\nimport { PRODUCT_NAME } from \"../../../platform-messages.js\";\nimport { credentialAuthorizationKey } from \"../../../sandbox/identity.js\";\nimport { resolveWorkspaceProjection } from \"../../../office/projection.js\";\nimport { sharedVaultKey } from \"../../../vault/index.js\";\nimport { modelKey, resolveAdminModelAccessStatuses } from \"./provider-models.js\";\n\nexport type { AdminRuntimeBridge, AdminServices, EventSummary } from \"./types.js\";\nimport type { AdminServices, EventSummary } from \"./types.js\";\nimport type { OfficeAddress } from \"../../../adapter.js\";\nimport {\n assertPlatformName,\n createOfficeAddress,\n listRegisteredOffices,\n sameOffice,\n type Office,\n type Workspace,\n} from \"../../../office/index.js\";\n\n// ── Handler ────────────────────────────────────────────────────────────────────\n\nexport async function handleAdminRequest(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<boolean> {\n if (!url.pathname.startsWith(\"/admin\")) return false;\n\n if (req.method === \"GET\" && url.pathname === \"/admin\") {\n const provided = url.searchParams.get(\"token\") ?? \"\";\n const token = services.adminTokenStore.peek(provided);\n if (!token) {\n res.writeHead(403, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderAdminErrorPage(\n \"Admin link is missing, invalid, or expired. Send `/admin` to the bot to get a fresh link.\",\n ),\n );\n return true;\n }\n res.writeHead(200, {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n });\n res.end(renderAdminPage(token));\n return true;\n }\n\n if (url.pathname.startsWith(\"/admin/api/\")) {\n await routeApiRequest(req, res, url, services);\n return true;\n }\n\n return false;\n}\n\n// ── API routing ────────────────────────────────────────────────────────────────\n\nasync function routeApiRequest(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<void> {\n if (req.method === \"GET\") {\n await routeGetApiRequest(res, url, services);\n return;\n }\n if (req.method !== \"POST\") {\n jsonRes(res, 405, { error: \"Method not allowed\" });\n return;\n }\n await routePostApiRequest(req, res, url, services);\n}\n\nasync function routeGetApiRequest(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<void> {\n const token = services.adminTokenStore.peek(url.searchParams.get(\"token\") ?? \"\");\n if (!token) {\n jsonRes(res, 403, { error: \"Unauthorized\" });\n return;\n }\n switch (url.pathname) {\n case \"/admin/api/conversations\":\n return serveConversationsList(res, services);\n case \"/admin/api/session-usage\":\n return serveSessionUsage(res, services);\n case \"/admin/api/conversation-usage\":\n return serveConversationUsage(res, url, services);\n case \"/admin/api/conversation-state\":\n return serveConversationState(res, url, services, token);\n case \"/admin/api/settings/global\":\n return serveGlobalSettings(res);\n case \"/admin/api/models\":\n return serveModelsList(res);\n case \"/admin/api/workspace/tree\":\n return serveWorkspaceTree(res, url, services, token);\n case \"/admin/api/workspace/file\":\n return serveWorkspaceFile(res, url, services, token);\n case \"/admin/api/skills\":\n return serveSkillsList(res, url, services, token);\n case \"/admin/api/skills/file\":\n return serveSkillFile(res, url, services, token);\n case \"/admin/api/mcp-servers\":\n return serveMcpServersList(res, url, services, token);\n case \"/admin/api/events\":\n return serveEventsList(res, services);\n case \"/admin/api/conversations/events\":\n return serveConversationEventsList(res, url, services, token);\n default:\n jsonRes(res, 404, { error: \"Not found\" });\n }\n}\n\nasync function routePostApiRequest(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<void> {\n const body = await readJsonBody(req, res, 32 * 1024);\n if (!body) return;\n const token = services.adminTokenStore.peek(typeof body.token === \"string\" ? body.token : \"\");\n if (!token) {\n jsonRes(res, 403, { error: \"Unauthorized\" });\n return;\n }\n switch (url.pathname) {\n case \"/admin/api/conversations/model\":\n return serveConversationModelUpdate(res, body, services, token);\n case \"/admin/api/conversations/visibility\":\n return serveConversationVisibilityUpdate(res, body, services, token);\n case \"/admin/api/conversations/slack\":\n return serveConversationSlackUpdate(res, body, services, token);\n case \"/admin/api/conversations/session-link\":\n return serveConversationSessionLink(res, body, services, token);\n case \"/admin/api/conversations/login-link\":\n return serveConversationLoginLink(res, body, services, token);\n case \"/admin/api/conversations/events/delete\":\n return serveConversationEventDelete(res, body, services, token);\n case \"/admin/api/mcp-servers/mutate\":\n return serveMcpServerMutation(res, body, services, token);\n case \"/admin/api/settings/model\":\n return serveGlobalModelUpdate(res, body, services);\n case \"/admin/api/settings/sandbox\":\n return serveGlobalSandboxUpdate(res, body, services);\n case \"/admin/api/settings/slack\":\n return serveGlobalSlackUpdate(res, body, services);\n default:\n jsonRes(res, 404, { error: \"Not found\" });\n }\n}\n\n// ── Scope helpers ──────────────────────────────────────────────────────────────\n\ninterface AdminConversationScope {\n address: OfficeAddress;\n conversationId: string;\n error?: string;\n}\n\n/**\n * Admin scope is a full office address. The platform defaults to the token's\n * (an admin invoked from Slack browses Slack offices); cross-platform targets\n * name theirs explicitly. Identity validation is the address factory's.\n */\nexport function resolveConversationScope(\n requestedId: string,\n requestedPlatform: string,\n token: AdminToken,\n): AdminConversationScope {\n const fallback = createOfficeAddress(token.platform, token.conversationId);\n try {\n const address = createOfficeAddress(\n requestedPlatform ? assertPlatformName(requestedPlatform) : token.platform,\n requestedId || token.conversationId,\n );\n return { address, conversationId: address.conversationId };\n } catch {\n return {\n address: fallback,\n conversationId: fallback.conversationId,\n error: \"Invalid conversation scope.\",\n };\n }\n}\n\nfunction resolveTargetConversation(\n body: Record<string, unknown>,\n token: AdminToken,\n): AdminConversationScope {\n const requested = typeof body.conversationId === \"string\" ? body.conversationId.trim() : \"\";\n const platform = typeof body.platform === \"string\" ? body.platform.trim() : \"\";\n return resolveConversationScope(requested, platform, token);\n}\n\nfunction requireAdminWorkspace(res: ServerResponse, services: AdminServices): Workspace | null {\n if (!services.workspace) {\n jsonRes(res, 503, { error: \"Working directory not available\" });\n return null;\n }\n return services.workspace;\n}\n\nfunction requireConversationWorkspace(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): { scope: AdminConversationScope; workspace: Workspace } | undefined {\n const scope = resolveTargetConversation(body, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return undefined;\n }\n const workspace = requireAdminWorkspace(res, services);\n return workspace ? { scope, workspace } : undefined;\n}\n\n// ── API handlers ───────────────────────────────────────────────────────────────\n\n/**\n * Office directories are office-key named and not reversible to raw ids, so\n * enumeration reads the office registry — the durable raw-id ↔ office\n * mapping — instead of scanning the workspace. Offices whose directory\n * disappeared are filtered out.\n */\nfunction listAdminOffices(workspace: Workspace): OfficeAddress[] {\n return listRegisteredOffices(workspace.stateDir)\n .filter((office) => existsSync(workspace.office(office).dir))\n .map((office) => createOfficeAddress(office.platform, office.conversationId))\n .toSorted(\n (a, b) =>\n a.platform.localeCompare(b.platform) || a.conversationId.localeCompare(b.conversationId),\n );\n}\n\nfunction conversationLastActivity(workspace: Workspace, office: OfficeAddress): number | null {\n const dir = workspace.office(office).dir;\n if (!existsSync(dir)) return null;\n let latest = 0;\n const visit = (path: string, depth: number): void => {\n if (depth > 3) return;\n let entries;\n try {\n entries = readdirSync(path, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = join(path, entry.name);\n if (entry.isDirectory()) {\n visit(full, depth + 1);\n continue;\n }\n try {\n const stats = statSync(full);\n if (stats.mtimeMs > latest) latest = stats.mtimeMs;\n } catch {\n // ignore\n }\n }\n };\n visit(dir, 0);\n return latest > 0 ? latest : null;\n}\n\nfunction conversationDisplayLabel(services: AdminServices, office: OfficeAddress): string {\n const bot = services.botsByPlatform?.[office.platform];\n const channel = bot?.getMessagingInfo().channels.find((c) => c.id === office.conversationId);\n if (channel) return `${office.platform}:#${channel.name}:${office.conversationId}`;\n return `${office.platform}:${office.conversationId}`;\n}\n\nfunction serveConversationsList(res: ServerResponse, services: AdminServices): void {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const running = services.runtime?.getRunningSessions() ?? [];\n\n const conversations = listAdminOffices(workspace).map((office) => ({\n platform: office.platform,\n conversationId: office.conversationId,\n label: conversationDisplayLabel(services, office),\n running: running.some((session) => sameOffice(session.address, office)),\n lastActivityAt: conversationLastActivity(workspace, office),\n }));\n\n jsonRes(res, 200, { conversations });\n}\n\ninterface SessionUsageRow {\n conversationId: string;\n label: string;\n fileName: string;\n sessionId: string;\n updatedAt: string;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n total: number;\n cost: number;\n}\n\nasync function serveSessionUsage(res: ServerResponse, services: AdminServices): Promise<void> {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const usageLists: SessionUsageRow[][] = [];\n for (const office of listAdminOffices(workspace)) {\n usageLists.push(\n await listConversationSessionUsage(\n workspace,\n office,\n conversationDisplayLabel(services, office),\n ),\n );\n }\n const rows = usageLists\n .flat()\n .toSorted((a, b) => b.total - a.total)\n .slice(0, 20);\n\n jsonRes(res, 200, { sessions: rows });\n}\n\nasync function listConversationSessionUsage(\n workspace: Workspace,\n office: OfficeAddress,\n label: string,\n): Promise<SessionUsageRow[]> {\n const sessionDir = workspace.office(office).sessionsDir;\n let files: string[];\n try {\n files = readdirSync(sessionDir, { withFileTypes: true })\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".jsonl\"))\n .map((entry) => entry.name);\n } catch {\n return [];\n }\n const rows: SessionUsageRow[] = [];\n for (const name of files) {\n rows.push(...(await readSessionUsage(join(sessionDir, name), office.conversationId, label)));\n }\n return rows;\n}\n\nasync function readSessionUsage(\n sessionFile: string,\n conversationId: string,\n label: string,\n): Promise<SessionUsageRow[]> {\n try {\n const manager = await SessionStore.inspect(sessionFile);\n const header = manager.getHeader();\n\n const entries = await manager.getEntries();\n const usage = entries.reduce(\n (sum, entry) => {\n if (entry.type !== \"message\" || entry.message.role !== \"assistant\") return sum;\n const message = entry.message as unknown as AssistantUsageMessage;\n const item = message.usage;\n if (!item) return sum;\n sum.input += numberOrZero(item.input);\n sum.output += numberOrZero(item.output);\n sum.cacheRead += numberOrZero(item.cacheRead);\n sum.cacheWrite += numberOrZero(item.cacheWrite);\n sum.cost += numberOrZero(item.cost?.total);\n return sum;\n },\n { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 },\n );\n const total = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;\n if (total <= 0) return [];\n\n return [\n {\n conversationId,\n label,\n fileName: basename(sessionFile),\n sessionId: header.id,\n updatedAt:\n entries.length > 0 ? new Date(entries.at(-1)!.timestamp).toISOString() : header.timestamp,\n ...usage,\n total,\n },\n ];\n } catch {\n return [];\n }\n}\n\ninterface AssistantUsageMessage {\n usage?: {\n input?: unknown;\n output?: unknown;\n cacheRead?: unknown;\n cacheWrite?: unknown;\n cost?: { total?: unknown };\n };\n}\n\nfunction numberOrZero(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\ninterface UsageBucket {\n date: string;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n total: number;\n cost: number;\n}\n\nfunction localDayKey(d: Date): string {\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, \"0\");\n const day = String(d.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\nfunction emptyBucket(date: string): UsageBucket {\n return { date, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0, cost: 0 };\n}\n\n/** Per-conversation daily token usage over the last N days (N clamped to 1..7). */\nasync function serveConversationUsage(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n): Promise<void> {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const conversationId = (url.searchParams.get(\"conversationId\") ?? \"\").trim();\n const platformParam = (url.searchParams.get(\"platform\") ?? \"\").trim();\n const office = listAdminOffices(workspace).find(\n (candidate) =>\n candidate.conversationId === conversationId &&\n (!platformParam || candidate.platform === platformParam),\n );\n if (!office) {\n jsonRes(res, 400, { error: \"Unknown conversationId\" });\n return;\n }\n\n const days = 14;\n\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n const buckets = new Map<string, UsageBucket>();\n const order: string[] = [];\n for (let i = days - 1; i >= 0; i--) {\n const d = new Date(today);\n d.setDate(today.getDate() - i);\n const key = localDayKey(d);\n order.push(key);\n buckets.set(key, emptyBucket(key));\n }\n const cutoff = new Date(today);\n cutoff.setDate(today.getDate() - (days - 1));\n\n const flags = { hasOlder: false };\n const sessionDir = workspace.office(office).sessionsDir;\n try {\n for (const entry of readdirSync(sessionDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".jsonl\")) continue;\n await accumulateSessionUsageByDay(join(sessionDir, entry.name), cutoff, buckets, flags);\n }\n } catch {\n // No sessions directory yet — return empty buckets.\n }\n\n const series = order.map((key) => buckets.get(key) ?? emptyBucket(key));\n const totals = series.reduce((sum, b) => {\n sum.input += b.input;\n sum.output += b.output;\n sum.cacheRead += b.cacheRead;\n sum.cacheWrite += b.cacheWrite;\n sum.total += b.total;\n sum.cost += b.cost;\n return sum;\n }, emptyBucket(\"\"));\n\n jsonRes(res, 200, {\n platform: office.platform,\n conversationId,\n label: conversationDisplayLabel(services, office),\n days,\n hasOlder: flags.hasOlder,\n buckets: series,\n totals: {\n input: totals.input,\n output: totals.output,\n cacheRead: totals.cacheRead,\n cacheWrite: totals.cacheWrite,\n total: totals.total,\n cost: totals.cost,\n },\n });\n}\n\nasync function accumulateSessionUsageByDay(\n sessionFile: string,\n cutoff: Date,\n buckets: Map<string, UsageBucket>,\n flags: { hasOlder: boolean },\n): Promise<void> {\n try {\n const manager = await SessionStore.inspect(sessionFile);\n\n for (const entry of await manager.getEntries()) {\n if (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n const usage = (entry.message as unknown as AssistantUsageMessage).usage;\n if (!usage || !entry.timestamp) continue;\n\n const when = new Date(entry.timestamp);\n if (Number.isNaN(when.getTime())) continue;\n if (when < cutoff) {\n flags.hasOlder = true;\n continue;\n }\n\n const bucket = buckets.get(localDayKey(when));\n if (!bucket) continue;\n\n const input = numberOrZero(usage.input);\n const output = numberOrZero(usage.output);\n const cacheRead = numberOrZero(usage.cacheRead);\n const cacheWrite = numberOrZero(usage.cacheWrite);\n bucket.input += input;\n bucket.output += output;\n bucket.cacheRead += cacheRead;\n bucket.cacheWrite += cacheWrite;\n bucket.total += input + output + cacheRead + cacheWrite;\n bucket.cost += numberOrZero(usage.cost?.total);\n }\n } catch {\n // Skip unreadable session files.\n }\n}\n\nfunction serveConversationState(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 400, { error: scope.error });\n return;\n }\n const conversationId = scope.conversationId;\n\n const office = workspace.office(scope.address);\n const globalConfig = loadGlobalSettings();\n const conversationConfig = resolveConversationSettings(office);\n const conversationWorkspace = resolveWorkspaceProjection(office);\n\n jsonRes(res, 200, {\n conversationId,\n provider: conversationConfig.provider,\n model: conversationConfig.model,\n thinkingLevel: conversationConfig.thinkingLevel,\n globalProvider: globalConfig.provider,\n globalModel: globalConfig.model,\n globalThinkingLevel: globalConfig.thinkingLevel,\n officeVisibility: conversationWorkspace.visibility,\n officeVisibilitySource: conversationWorkspace.source,\n officeVisibilityOverride: loadOfficeVisibilityOverride(office),\n slack: {\n replyMode:\n conversationConfig.slack?.replyMode ?? globalConfig.slack?.replyMode ?? \"top-level\",\n globalReplyMode: globalConfig.slack?.replyMode ?? \"top-level\",\n },\n });\n}\n\nfunction serveGlobalSettings(res: ServerResponse): void {\n try {\n const config = loadGlobalSettings();\n jsonRes(res, 200, {\n provider: config.provider,\n model: config.model,\n thinkingLevel: config.thinkingLevel,\n sandboxCpus: config.sandbox?.cpus ?? null,\n sandboxMemory: config.sandbox?.memory ?? null,\n sandboxBoostCpus: config.sandbox?.boost?.cpus ?? null,\n sandboxBoostMemory: config.sandbox?.boost?.memory ?? null,\n defaultSharedVault: config.sandbox?.defaultSharedVault ?? null,\n slack: {\n replyMode: config.slack?.replyMode ?? \"top-level\",\n },\n });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nasync function serveModelsList(res: ServerResponse): Promise<void> {\n try {\n const registry = MikanModels.create();\n const availableModels = await registry.getAvailable();\n const statuses = await resolveAdminModelAccessStatuses(registry, availableModels);\n const models = availableModels.map((model) => ({\n provider: model.provider,\n id: model.id,\n name: model.name ?? model.id,\n reasoning: model.reasoning,\n input: model.input,\n contextWindow: model.contextWindow,\n maxTokens: model.maxTokens,\n status: statuses.get(modelKey(model.provider, model.id))?.status ?? \"available\",\n }));\n jsonRes(res, 200, { models });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nconst VALID_THINKING_LEVELS = new Set([\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nfunction serveConversationModelUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n const provider = typeof body.provider === \"string\" ? body.provider.trim() : \"\";\n const model = typeof body.model === \"string\" ? body.model.trim() : \"\";\n const thinkingLevel =\n typeof body.thinkingLevel === \"string\" && VALID_THINKING_LEVELS.has(body.thinkingLevel)\n ? (body.thinkingLevel as AgentConfig[\"thinkingLevel\"])\n : undefined;\n\n if (!provider || !model) {\n jsonRes(res, 400, { error: \"Missing provider or model\" });\n return;\n }\n const target = requireConversationWorkspace(res, body, services, token);\n if (!target) return;\n const { scope, workspace } = target;\n\n try {\n const result = applyConversationSettings(services.runtime, workspace.office(scope.address), {\n provider,\n model,\n ...(thinkingLevel ? { thinkingLevel } : {}),\n });\n if (!result.ok) {\n jsonRes(res, 409, {\n error: \"Conversation has a running job; retry after it finishes (or /stop it).\",\n });\n return;\n }\n jsonRes(res, 200, { ok: true, runtimeSwitched: result.runtimeSwitched });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction serveConversationVisibilityUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n if (body.visibility !== \"private\" && body.visibility !== \"default\") {\n jsonRes(res, 400, { error: \"visibility must be 'private' or 'default'\" });\n return;\n }\n const target = requireConversationWorkspace(res, body, services, token);\n if (!target) return;\n const { scope, workspace } = target;\n try {\n const result = applyOfficeVisibility(\n services.runtime,\n workspace.office(scope.address),\n body.visibility === \"private\" ? \"private\" : null,\n );\n if (!result.ok) {\n jsonRes(res, 409, { error: \"Conversation is busy; retry when the current run finishes\" });\n return;\n }\n jsonRes(res, 200, { ok: true });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction serveConversationSlackUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n const replyMode = body.replyMode;\n if (replyMode !== \"top-level\" && replyMode !== \"thread\") {\n jsonRes(res, 400, { error: \"replyMode must be 'top-level' or 'thread'\" });\n return;\n }\n const target = requireConversationWorkspace(res, body, services, token);\n if (!target) return;\n const { scope, workspace } = target;\n respondWithSettingsUpdate(res, () => {\n applyConversationSettings(services.runtime, workspace.office(scope.address), {\n slack: { replyMode },\n });\n return { ok: true };\n });\n}\n\nfunction serveConversationSessionLink(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n const target = requireConversationWorkspace(res, body, services, token);\n if (!target) return;\n const { scope, workspace } = target;\n if (!services.sessionViewTokenStore) {\n jsonRes(res, 503, { error: \"Session view token store not available\" });\n return;\n }\n if (!services.portalBaseUrl) {\n jsonRes(res, 503, {\n error: \"Portal URL not configured. Set MIKAN_LINK_URL to enable link generation.\",\n });\n return;\n }\n\n const sessionFile = resolveExistingSessionFile(\n workspace.office(scope.address).dir,\n scope.conversationId,\n );\n if (!sessionFile) {\n jsonRes(res, 404, { error: \"No session file found for this conversation\" });\n return;\n }\n\n try {\n const { token: viewToken } = services.sessionViewTokenStore.create({\n platform: token.platform,\n platformUserId: token.platformUserId,\n conversationId: scope.conversationId,\n sessionKey: scope.conversationId,\n sessionFile,\n platformUserName: token.platformUserName,\n });\n const url = `${services.portalBaseUrl}/session?token=${encodeURIComponent(viewToken)}`;\n jsonRes(res, 200, { ok: true, url });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction serveConversationLoginLink(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveTargetConversation(body, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n if (!services.portalBaseUrl) {\n jsonRes(res, 503, { error: \"Portal URL not configured.\" });\n return;\n }\n if (!services.sandbox) {\n jsonRes(res, 503, { error: \"Sandbox config not available.\" });\n return;\n }\n const sharedName = typeof body.sharedVault === \"string\" ? body.sharedVault.trim() : \"\";\n let vaultId: string;\n if (sharedName) {\n const key = sharedVaultKey(sharedName);\n if (!key) {\n jsonRes(res, 400, { error: \"Invalid shared vault name\" });\n return;\n }\n vaultId = key;\n } else {\n try {\n vaultId = credentialAuthorizationKey(services.sandbox, {\n userId: token.platformUserId,\n address: scope.address,\n });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n return;\n }\n }\n try {\n const { token: linkToken } = services.linkTokenStore.create(\n token.platform,\n token.platformUserId,\n scope.conversationId,\n vaultId,\n \"\",\n );\n const url = `${services.portalBaseUrl}/link?token=${encodeURIComponent(linkToken)}`;\n jsonRes(res, 200, { ok: true, url, vaultId });\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction serveGlobalModelUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n): void {\n const provider = typeof body.provider === \"string\" ? body.provider.trim() : \"\";\n const model = typeof body.model === \"string\" ? body.model.trim() : \"\";\n const thinkingLevel =\n typeof body.thinkingLevel === \"string\" && VALID_THINKING_LEVELS.has(body.thinkingLevel)\n ? (body.thinkingLevel as AgentConfig[\"thinkingLevel\"])\n : undefined;\n\n if (!provider || !model) {\n jsonRes(res, 400, { error: \"Missing provider or model\" });\n return;\n }\n\n respondWithSettingsUpdate(res, () => {\n const result = applyGlobalSettings(services.runtime, {\n provider,\n model,\n ...(thinkingLevel ? { thinkingLevel } : {}),\n });\n return {\n ok: true,\n staleConversations: result.staleConversations.map(\n (office) => `${office.platform}:${office.conversationId}`,\n ),\n };\n });\n}\n\nfunction serveGlobalSlackUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n): void {\n const replyMode = body.replyMode;\n if (replyMode !== \"top-level\" && replyMode !== \"thread\") {\n jsonRes(res, 400, { error: \"replyMode must be 'top-level' or 'thread'\" });\n return;\n }\n\n respondWithSettingsUpdate(res, () => {\n applyGlobalSettings(services.runtime, { slack: { replyMode } });\n return { ok: true };\n });\n}\n\nfunction serveGlobalSandboxUpdate(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n): void {\n const cpus = typeof body.cpus === \"string\" ? body.cpus.trim() : \"\";\n const memory = typeof body.memory === \"string\" ? body.memory.trim() : \"\";\n const boostCpus = typeof body.boostCpus === \"string\" ? body.boostCpus.trim() : \"\";\n const boostMemory = typeof body.boostMemory === \"string\" ? body.boostMemory.trim() : \"\";\n const update: SandboxSettings = {\n ...(cpus ? { cpus } : {}),\n ...(memory ? { memory } : {}),\n ...(boostCpus || boostMemory\n ? {\n boost: {\n ...(boostCpus ? { cpus: boostCpus } : {}),\n ...(boostMemory ? { memory: boostMemory } : {}),\n },\n }\n : {}),\n };\n\n if (Object.keys(update).length === 0) {\n jsonRes(res, 400, { error: \"No valid sandbox fields provided\" });\n return;\n }\n\n respondWithSettingsUpdate(res, () => {\n applyGlobalSettings(services.runtime, { sandbox: update });\n return { ok: true };\n });\n}\n\nfunction respondWithSettingsUpdate(res: ServerResponse, update: () => object): void {\n try {\n jsonRes(res, 200, update());\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n// ── Workspace ──────────────────────────────────────────────────────────────────\n\nconst WORKSPACE_TREE_MAX_DEPTH = 4;\nconst WORKSPACE_TREE_MAX_ENTRIES = 800;\nconst PREVIEW_FILE_MAX_BYTES = 256 * 1024;\n\nconst WORKSPACE_TOP_DIRS = new Set([\"scratch\"]);\n\n/**\n * Limit what the admin UI can browse under a conversation directory.\n * Allowed: top-level \"scratch/\" subtree.\n */\nfunction isWorkspacePathAllowed(rel: string): boolean {\n if (rel === \"\") return true;\n const first = rel.split(\"/\").find(Boolean);\n if (first === undefined) return true;\n return WORKSPACE_TOP_DIRS.has(first);\n}\n\nfunction resolveConversationFromQuery(url: URL, token: AdminToken): AdminConversationScope {\n const requested = (url.searchParams.get(\"conversationId\") ?? \"\").trim();\n const platform = (url.searchParams.get(\"platform\") ?? \"\").trim();\n return resolveConversationScope(requested, platform, token);\n}\n\ninterface SafePathResult {\n absolute: string;\n error?: string;\n}\n\nfunction safeJoinUnderRoot(rootDir: string, relative: string): SafePathResult {\n if (relative.startsWith(\"/\") || relative.includes(\"\\0\")) {\n return { absolute: \"\", error: \"Invalid path\" };\n }\n if (relative.split(/[\\\\/]+/).some((part) => part === \"..\" || part === \"\")) {\n if (relative !== \"\") return { absolute: \"\", error: \"Invalid path\" };\n }\n const target = pathResolve(rootDir, relative);\n const rootAbs = pathResolve(rootDir);\n if (target !== rootAbs && !target.startsWith(rootAbs + pathSep)) {\n return { absolute: \"\", error: \"Path escapes conversation directory\" };\n }\n return { absolute: target };\n}\n\ninterface TreeNode {\n name: string;\n path: string;\n type: \"dir\" | \"file\";\n size?: number;\n mtimeMs?: number;\n children?: TreeNode[];\n truncated?: boolean;\n}\n\nfunction buildTree(startDir: string, relPrefix: string): TreeNode | null {\n let counter = { value: 0 };\n const walk = (dir: string, rel: string, depth: number): TreeNode | null => {\n if (counter.value >= WORKSPACE_TREE_MAX_ENTRIES) return null;\n let stats;\n try {\n stats = statSync(dir);\n } catch {\n return null;\n }\n const name = rel === \"\" ? \".\" : basename(rel);\n if (!stats.isDirectory()) {\n counter.value += 1;\n return {\n name,\n path: rel,\n type: \"file\",\n size: stats.size,\n mtimeMs: stats.mtimeMs,\n };\n }\n counter.value += 1;\n if (depth >= WORKSPACE_TREE_MAX_DEPTH) {\n return { name, path: rel, type: \"dir\", truncated: true };\n }\n let entries;\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return { name, path: rel, type: \"dir\" };\n }\n const children: TreeNode[] = [];\n let truncated = false;\n for (const entry of entries.toSorted((a, b) => {\n if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;\n return a.name.localeCompare(b.name);\n })) {\n const childRel = rel === \"\" ? entry.name : `${rel}/${entry.name}`;\n if (!isWorkspacePathAllowed(childRel)) continue;\n if (counter.value >= WORKSPACE_TREE_MAX_ENTRIES) {\n truncated = true;\n break;\n }\n const node = walk(join(dir, entry.name), childRel, depth + 1);\n if (node) children.push(node);\n }\n return {\n name,\n path: rel,\n type: \"dir\",\n children,\n ...(truncated ? { truncated: true } : {}),\n };\n };\n const node = walk(startDir, relPrefix, 0);\n return node;\n}\n\nfunction serveWorkspaceTree(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const convDir = workspace.office(scope.address).dir;\n if (!existsSync(convDir)) {\n jsonRes(res, 200, { conversationId: scope.conversationId, tree: null });\n return;\n }\n const requestedSub = (url.searchParams.get(\"path\") ?? \"\").trim();\n if (!isWorkspacePathAllowed(requestedSub)) {\n jsonRes(res, 403, { error: \"Workspace path is not exposed\" });\n return;\n }\n const startSafe = safeJoinUnderRoot(convDir, requestedSub);\n if (startSafe.error) {\n jsonRes(res, 400, { error: startSafe.error });\n return;\n }\n const tree = buildTree(startSafe.absolute, requestedSub);\n jsonRes(res, 200, {\n conversationId: scope.conversationId,\n root: requestedSub || \".\",\n tree,\n });\n}\n\nconst BINARY_PROBE_BYTES = 4096;\n\nfunction looksTextual(buf: Buffer): boolean {\n const limit = Math.min(buf.length, BINARY_PROBE_BYTES);\n for (let i = 0; i < limit; i++) {\n const byte = buf[i];\n if (byte === undefined || byte === 0) return false;\n if (byte < 9) return false;\n if (byte === 11 || byte === 12) return false;\n if (byte > 13 && byte < 32) return false;\n }\n return true;\n}\n\nfunction servePreviewFile(\n res: ServerResponse,\n absolutePath: string,\n metadata: Record<string, unknown>,\n notFoundMessage: string,\n): void {\n let stats;\n try {\n stats = statSync(absolutePath);\n } catch {\n jsonRes(res, 404, { error: notFoundMessage });\n return;\n }\n if (!stats.isFile()) {\n jsonRes(res, 400, { error: \"Not a file\" });\n return;\n }\n if (stats.size > PREVIEW_FILE_MAX_BYTES) {\n jsonRes(res, 413, {\n error: \"File too large to preview\",\n size: stats.size,\n limit: PREVIEW_FILE_MAX_BYTES,\n });\n return;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(absolutePath);\n } catch (err) {\n jsonRes(res, 500, { error: err instanceof Error ? err.message : String(err) });\n return;\n }\n if (!looksTextual(buf)) {\n jsonRes(res, 200, {\n ...metadata,\n size: stats.size,\n mtimeMs: stats.mtimeMs,\n binary: true,\n content: null,\n });\n return;\n }\n jsonRes(res, 200, {\n ...metadata,\n size: stats.size,\n mtimeMs: stats.mtimeMs,\n binary: false,\n content: buf.toString(\"utf-8\"),\n });\n}\n\nfunction serveWorkspaceFile(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const requestedPath = (url.searchParams.get(\"path\") ?? \"\").trim();\n if (!requestedPath) {\n jsonRes(res, 400, { error: \"Missing path\" });\n return;\n }\n if (!isWorkspacePathAllowed(requestedPath)) {\n jsonRes(res, 403, { error: \"Workspace path is not exposed\" });\n return;\n }\n const convDir = workspace.office(scope.address).dir;\n const safe = safeJoinUnderRoot(convDir, requestedPath);\n if (safe.error) {\n jsonRes(res, 400, { error: safe.error });\n return;\n }\n servePreviewFile(res, safe.absolute, { path: requestedPath }, \"File not found\");\n}\n\n// ── Skills ─────────────────────────────────────────────────────────────────────\n\ninterface SkillEntry {\n name: string;\n description: string;\n source: \"global\" | \"conversation\";\n path: string;\n directory: string;\n}\n\n/**\n * Read skill metadata via the harness frontmatter parser (the owning module).\n * The portal historically accepted frontmatter keys case-insensitively, so\n * the lookup — not the parser — preserves that leniency.\n */\nfunction readSkillMeta(filePath: string): { name?: string; description?: string } {\n let text: string;\n try {\n text = readFileSync(filePath, \"utf-8\");\n } catch {\n return {};\n }\n const { values } = parseFrontmatter(text);\n const out: { name?: string; description?: string } = {};\n for (const [key, value] of Object.entries(values)) {\n const normalized = key.toLowerCase();\n if (normalized === \"name\") out.name = value;\n if (normalized === \"description\") out.description = value;\n }\n return out;\n}\n\nexport function readSkillsFromDir(skillsDir: string, source: SkillEntry[\"source\"]): SkillEntry[] {\n if (!existsSync(skillsDir)) return [];\n const out: SkillEntry[] = [];\n let entries;\n try {\n entries = readdirSync(skillsDir, { withFileTypes: true });\n } catch {\n return [];\n }\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith(\".\")) continue;\n const skillMd = join(skillsDir, entry.name, \"SKILL.md\");\n if (!existsSync(skillMd)) continue;\n const meta = readSkillMeta(skillMd);\n out.push({\n name: meta.name ?? entry.name,\n description: meta.description ?? \"\",\n source,\n path: skillMd,\n directory: entry.name,\n });\n }\n return out.toSorted((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * List MCP servers for both scopes, with env/header VALUES redacted to key\n * names: they carry API keys, and this response renders in a browser. The\n * panel edits full entries but only ever needs to show which keys exist.\n */\nfunction serveMcpServersList(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const servers = loadScopeMcpServers(workspace.office(scope.address));\n jsonRes(res, 200, {\n conversationId: scope.conversationId,\n presets: listMcpPresets(),\n global: redactMcpServers(servers.global),\n conversation: redactMcpServers(servers.conversation),\n });\n}\n\nfunction redactMcpServers(map: Record<string, McpServerConfig>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(map).map(([name, config]) => [\n name,\n {\n ...(config.command !== undefined ? { command: config.command } : {}),\n ...(config.args !== undefined ? { args: config.args } : {}),\n ...(config.url !== undefined ? { url: redactMcpUrl(config.url) } : {}),\n ...(config.disabled !== undefined ? { disabled: config.disabled } : {}),\n envKeys: Object.keys(config.env ?? {}),\n headerKeys: Object.keys(config.headers ?? {}),\n },\n ]),\n );\n}\n\nfunction mcpStringMap(value: unknown): Record<string, string> | undefined {\n if (typeof value !== \"object\" || value === null) return undefined;\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(value)) {\n if (typeof v === \"string\" && k.trim()) out[k.trim()] = v;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nconst MCP_VERIFY_TIMEOUT_MS = 20_000;\n\ntype McpVerifyResult = { name: string; tools: number } | { name: string; error: string };\n\n/**\n * Connect to the given servers once, the way the runner will, and report per\n * server either the tool count or the server's own error text. This is the\n * difference between \"settings.json was written\" and \"it works\": the runner\n * only logs load failures host-side, so without this the portal is the only\n * place an operator can learn that a pasted token is wrong.\n *\n * For stdio entries this runs the configured command on the host now, at save\n * time, rather than at the next message.\n */\nasync function verifyMcpServers(\n servers: Record<string, McpServerConfig>,\n): Promise<McpVerifyResult[]> {\n const names = Object.keys(servers);\n if (names.length === 0) return [];\n const loaded = await loadMcpTools(servers, AbortSignal.timeout(MCP_VERIFY_TIMEOUT_MS));\n try {\n return names.map((name) => {\n const failure = loaded.errors.find((e) => e.server === name);\n if (failure) {\n const url = servers[name]?.url;\n const error = url ? failure.error.split(url).join(redactMcpUrl(url)) : failure.error;\n return { name, error };\n }\n const prefix = `mcp__${name}__`;\n return { name, tools: loaded.tools.filter((t) => t.name.startsWith(prefix)).length };\n });\n } finally {\n await loaded.dispose();\n }\n}\n\ntype McpMutationPlan =\n | { next: Record<string, McpServerConfig>; touched: Record<string, McpServerConfig> }\n | { status: number; error: string };\n\n/**\n * Compute the scope's next server map for one mutation. `import` takes the\n * cross-client `mcpServers` JSON verbatim (see `parseStandardMcpServers`) and\n * replaces every named entry outright — a pasted declaration is the whole\n * truth for that server, so no env/header carry-over from the previous entry.\n * `touched` lists the entries to connection-check after the write.\n */\nfunction planMcpMutation(\n action: \"import\" | \"install\" | \"remove\" | \"toggle\",\n body: Record<string, unknown>,\n current: Record<string, McpServerConfig>,\n): McpMutationPlan {\n const next: Record<string, McpServerConfig> = { ...current };\n if (action === \"import\") {\n const parsed = parseStandardMcpServers(typeof body.json === \"string\" ? body.json : \"\");\n if (!parsed.servers) return { status: 400, error: parsed.error };\n Object.assign(next, parsed.servers);\n return { next, touched: parsed.servers };\n }\n const preset =\n action === \"install\" && typeof body.presetId === \"string\"\n ? findMcpPreset(body.presetId)\n : undefined;\n if (action === \"install\" && !preset) return { status: 400, error: \"unknown MCP preset\" };\n const name = preset?.serverName ?? (typeof body.name === \"string\" ? body.name.trim() : \"\");\n if (!isValidMcpServerName(name)) {\n return {\n status: 400,\n error: \"invalid server name (letters, digits, '_' or '-', starting with a letter)\",\n };\n }\n if (action === \"install\") {\n try {\n next[name] = materializeMcpPreset(preset!, mcpStringMap(body.credentials) ?? {});\n } catch (err) {\n return { status: 400, error: err instanceof Error ? err.message : String(err) };\n }\n return { next, touched: { [name]: next[name] } };\n }\n if (!(name in next)) return { status: 404, error: \"Not declared here.\" };\n if (action === \"remove\") {\n delete next[name];\n return { next, touched: {} };\n }\n const entry = next[name]!;\n next[name] = entry.disabled ? { ...entry, disabled: undefined } : { ...entry, disabled: true };\n return { next, touched: {} };\n}\n\n/**\n * Import / install / remove / toggle / test MCP servers in one scope. Reads\n * the scope's raw map, applies the change, and writes the full map back\n * wholesale because merging would make removal impossible. Runner\n * caches refresh via applyGlobalSettings/applyConversationSettings.\n *\n * Writes persist even when the follow-up connection check fails: the\n * operator edits the entry, not retypes it. `test` re-checks an existing\n * entry without writing.\n */\nasync function serveMcpServerMutation(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): Promise<void> {\n const action = body.action;\n if (\n action !== \"import\" &&\n action !== \"install\" &&\n action !== \"remove\" &&\n action !== \"toggle\" &&\n action !== \"test\"\n ) {\n jsonRes(res, 400, {\n error: \"action must be 'import', 'install', 'remove', 'toggle', or 'test'\",\n });\n return;\n }\n const mutationScope = body.scope === \"global\" ? \"global\" : \"conversation\";\n const scope = resolveTargetConversation(body, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const office = workspace.office(scope.address);\n const maps = loadScopeMcpServers(office);\n const current = mutationScope === \"global\" ? maps.global : maps.conversation;\n\n if (action === \"test\") {\n const name = typeof body.name === \"string\" ? body.name.trim() : \"\";\n const entry = current[name];\n if (!entry) {\n jsonRes(res, 404, { error: \"Not declared here.\" });\n return;\n }\n const results = await verifyMcpServers({ [name]: { ...entry, disabled: undefined } });\n jsonRes(res, 200, { ok: true, results });\n return;\n }\n\n const plan = planMcpMutation(action, body, current);\n if (\"error\" in plan) {\n jsonRes(res, plan.status, { error: plan.error });\n return;\n }\n const result =\n mutationScope === \"global\"\n ? applyGlobalSettings(services.runtime, { mcpServers: plan.next })\n : applyConversationSettings(services.runtime, office, { mcpServers: plan.next });\n if (!result.ok) {\n jsonRes(res, 409, { error: \"Conversation is busy; try again shortly.\" });\n return;\n }\n const results = await verifyMcpServers(plan.touched);\n jsonRes(res, 200, { ok: true, results });\n}\n\nfunction serveSkillsList(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const global = readSkillsFromDir(workspace.skillsDir, \"global\");\n const conversation = readSkillsFromDir(workspace.office(scope.address).skillsDir, \"conversation\");\n jsonRes(res, 200, {\n conversationId: scope.conversationId,\n skills: [...global, ...conversation],\n });\n}\n\nfunction serveSkillFile(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): void {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n\n const source = (url.searchParams.get(\"source\") ?? \"\").trim();\n const directory = (url.searchParams.get(\"directory\") ?? \"\").trim();\n if (source !== \"global\" && source !== \"conversation\") {\n jsonRes(res, 400, { error: \"Invalid skill source\" });\n return;\n }\n if (\n !directory ||\n directory.includes(\"/\") ||\n directory.includes(\"\\\\\") ||\n directory.includes(\"..\")\n ) {\n jsonRes(res, 400, { error: \"Invalid skill directory\" });\n return;\n }\n\n const skillsRoot =\n source === \"global\" ? workspace.skillsDir : workspace.office(scope.address).skillsDir;\n const safe = safeJoinUnderRoot(skillsRoot, join(directory, \"SKILL.md\"));\n if (safe.error) {\n jsonRes(res, 400, { error: safe.error });\n return;\n }\n\n servePreviewFile(res, safe.absolute, { source, directory }, \"Skill file not found\");\n}\n\n// ── Events ─────────────────────────────────────────────────────────────────────\n\n/**\n * List events through the owning store, whose payloads are validated by the\n * event-format module (files that fail validation stay visible with a null\n * payload so operators can delete them).\n */\nexport async function listOfficeEvents(store: EventStore): Promise<EventSummary[]> {\n const entries = await store.list();\n return entries.map((entry) => {\n const payload = entry.payload;\n return {\n name: entry.filename,\n officePlatform: store.address.platform,\n officeConversationId: store.address.conversationId,\n size: entry.size,\n mtimeMs: entry.mtimeMs,\n type: payload?.type ?? null,\n platform: payload?.platform ?? null,\n conversationId: payload?.conversationId ?? null,\n text: payload?.text ?? null,\n at: payload?.type === \"one-shot\" ? payload.at : null,\n schedule: payload?.type === \"periodic\" ? payload.schedule : null,\n timezone: payload?.type === \"periodic\" ? payload.timezone : null,\n };\n });\n}\n\nfunction requireAdminEventStore(\n res: ServerResponse,\n services: AdminServices,\n office: Office,\n): EventStore | null {\n if (!services.eventStore) {\n jsonRes(res, 503, { error: \"Working directory not available\" });\n return null;\n }\n return services.eventStore(office);\n}\n\n/** Every registered office's events, each read through its own store. */\nasync function serveEventsList(res: ServerResponse, services: AdminServices): Promise<void> {\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n if (!services.eventStore) {\n jsonRes(res, 503, { error: \"Working directory not available\" });\n return;\n }\n const events: EventSummary[] = [];\n for (const record of listRegisteredOffices(workspace.stateDir)) {\n events.push(...(await listOfficeEvents(services.eventStore(workspace.office(record)))));\n }\n jsonRes(res, 200, { events });\n}\n\n/** Per-conversation listing through that office's confined store. */\nasync function serveConversationEventsList(\n res: ServerResponse,\n url: URL,\n services: AdminServices,\n token: AdminToken,\n): Promise<void> {\n const scope = resolveConversationFromQuery(url, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const store = requireAdminEventStore(res, services, workspace.office(scope.address));\n if (!store) return;\n jsonRes(res, 200, {\n conversationId: scope.conversationId,\n events: await listOfficeEvents(store),\n });\n}\n\n/** Delete a single event file scoped to the caller's conversation. */\nasync function serveConversationEventDelete(\n res: ServerResponse,\n body: Record<string, unknown>,\n services: AdminServices,\n token: AdminToken,\n): Promise<void> {\n const scope = resolveTargetConversation(body, token);\n if (scope.error) {\n jsonRes(res, 403, { error: scope.error });\n return;\n }\n let name: string;\n try {\n name = validateEventFilename(typeof body.name === \"string\" ? body.name : \"\");\n } catch {\n jsonRes(res, 400, { error: \"Invalid name\" });\n return;\n }\n const workspace = requireAdminWorkspace(res, services);\n if (!workspace) return;\n const store = requireAdminEventStore(res, services, workspace.office(scope.address));\n if (!store) return;\n // The store is confined to this office: another office's filename is \"not found\".\n try {\n await store.delete(name);\n jsonRes(res, 200, { ok: true });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n jsonRes(res, /not found/.test(message) ? 404 : 500, { error: message });\n }\n}\n\n// ── Utilities ──────────────────────────────────────────────────────────────────\n\nconst esc = escapeHtml;\n\n// ── HTML ───────────────────────────────────────────────────────────────────────\n\nconst adminViewBody = `<nav class=\"tab-nav\" role=\"tablist\" aria-label=\"Admin sections\">\n <button class=\"tab-btn active\" role=\"tab\" aria-selected=\"true\" aria-controls=\"panel-conversation\" data-tab=\"conversation\">Conversation</button>\n <button class=\"tab-btn\" role=\"tab\" aria-selected=\"false\" aria-controls=\"panel-global\" data-tab=\"global\">Global</button>\n </nav>\n\n <div class=\"tab-panel active\" id=\"panel-conversation\">\n <details class=\"card sect\" id=\"sect-settings\" data-section=\"settings\" open>\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Settings</p>\n <h2 class=\"card-title\">模型 / Thinking / Auto-reply / Workspace mount</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadSettings()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"settings-content\"><div class=\"loading-msg\">Loading…</div></div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-workspace\" data-section=\"workspace\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Workspace</p>\n <h2 class=\"card-title\">檔案瀏覽 (只讀)</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadWorkspace()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div class=\"workspace-split\">\n <div id=\"workspace-tree\" class=\"workspace-tree\"><div class=\"loading-msg\">Loading…</div></div>\n <div id=\"workspace-preview\" class=\"workspace-preview\"><div class=\"placeholder-msg\">Click a file to preview</div></div>\n </div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-skills\" data-section=\"skills\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Skills</p>\n <h2 class=\"card-title\">可用的 skills</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadSkills()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div class=\"workspace-split\">\n <div id=\"skills-content\" class=\"workspace-tree\"><div class=\"loading-msg\">Loading…</div></div>\n <div id=\"skills-preview\" class=\"workspace-preview\"><div class=\"placeholder-msg\">Click a skill to preview SKILL.md</div></div>\n </div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-mcp\" data-section=\"mcp\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">MCP Servers</p>\n <h2 class=\"card-title\">此對話的 MCP servers</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadMcpServers()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"mcp-conv-msg\" class=\"status-msg\" style=\"display:none\"></div>\n <div id=\"mcp-conv-content\"><div class=\"loading-msg\">Loading…</div></div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-vault\" data-section=\"vault\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Vault</p>\n <h2 class=\"card-title\">該對話的憑證</h2>\n </div></div>\n <button class=\"primary-action-btn\" onclick=\"event.stopPropagation(); openLogin()\">Open in new tab ↗</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"vault-link-result\" class=\"link-result\" style=\"display:none\"></div>\n <p class=\"card-desc\">Opens the one-time credential form for this conversation's vault in a new tab.</p>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-events\" data-section=\"events\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Events</p>\n <h2 class=\"card-title\">關聯此對話的 events</h2>\n </div></div>\n <button class=\"refresh-btn\" onclick=\"event.stopPropagation(); loadConversationEvents()\">↻</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"events-content\"><div class=\"loading-msg\">Loading…</div></div>\n </div>\n </details>\n\n <details class=\"card sect\" id=\"sect-session\" data-section=\"session\">\n <summary class=\"sect-head\">\n <div class=\"sect-title\"><span class=\"sect-caret\" aria-hidden=\"true\">▸</span><div>\n <p class=\"eyebrow\">Session View</p>\n <h2 class=\"card-title\">對話歷史檢視</h2>\n </div></div>\n <button class=\"primary-action-btn\" onclick=\"event.stopPropagation(); openSessionView()\">Open in new tab ↗</button>\n </summary>\n <div class=\"sect-body\">\n <div id=\"session-link-result\" class=\"link-result\" style=\"display:none\"></div>\n <p class=\"card-desc\">Opens the session timeline for this conversation in a new tab.</p>\n </div>\n </details>\n </div>\n\n <div class=\"tab-panel\" id=\"panel-global\">\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">All Conversations</p>\n <h2 class=\"card-title\">所有對話</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadAllConversations()\">↻</button>\n </header>\n <div id=\"all-conv-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Token Usage</p>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadTokenUsage()\">↻</button>\n </header>\n <h2 class=\"card-subtitle\" style=\"margin-bottom:10px\">Top 20 sessions</h3>\n <div id=\"session-usage-content\"><div class=\"loading-msg\">Loading…</div></div>\n <h2 class=\"card-subtitle\" style=\"margin:24px 0 10px\">Usage timeline</h3>\n <div class=\"timeline-controls\">\n <label>Conversation\n <select id=\"timeline-conv\" onchange=\"loadUsageTimeline()\"></select>\n </label>\n </div>\n <div id=\"usage-timeline-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Global Settings</p>\n <h2 class=\"card-title\">全域預設</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadGlobalSettings()\">↻</button>\n </header>\n <div id=\"global-settings-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Global MCP Servers</p>\n <h2 class=\"card-title\">所有對話都可用的 MCP servers</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadMcpServers()\">↻</button>\n </header>\n <div id=\"mcp-global-msg\" class=\"status-msg\" style=\"display:none\"></div>\n <div id=\"mcp-global-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Global Skills</p>\n <h2 class=\"card-title\">全域 skills</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadGlobalSkills()\">↻</button>\n </header>\n <div id=\"global-skills-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n\n <section class=\"card sect\">\n <header class=\"sect-head\">\n <div>\n <p class=\"eyebrow\">Global Events</p>\n <h2 class=\"card-title\">全域 events.json</h2>\n </div>\n <button class=\"refresh-btn\" onclick=\"loadEvents()\">↻</button>\n </header>\n <div id=\"global-events-content\"><div class=\"loading-msg\">Loading…</div></div>\n </section>\n </div>\n\n <dialog id=\"mcp-install-dialog\" class=\"mcp-dialog\" aria-labelledby=\"mcp-dialog-title\">\n <div class=\"mcp-dialog-head\">\n <div>\n <p class=\"eyebrow\">Install MCP preset</p>\n <h2 id=\"mcp-dialog-title\" class=\"card-title\"></h2>\n </div>\n <button class=\"mcp-dialog-close\" type=\"button\" aria-label=\"Close\" onclick=\"closeMcpInstall()\">×</button>\n </div>\n <div id=\"mcp-dialog-content\"></div>\n <div id=\"mcp-dialog-error\" class=\"inline-result err\" style=\"display:none\"></div>\n <div class=\"mcp-dialog-actions\">\n <button class=\"mcp-btn\" type=\"button\" onclick=\"closeMcpInstall()\">Cancel</button>\n <button id=\"mcp-dialog-install\" class=\"primary-action-btn\" type=\"button\" onclick=\"installMcpPreset(this)\">Install preset</button>\n </div>\n </dialog>\n\n <dialog id=\"mcp-custom-dialog\" class=\"mcp-dialog\" aria-labelledby=\"mcp-custom-dialog-title\">\n <div class=\"mcp-dialog-head\">\n <div>\n <p class=\"eyebrow\">Add MCP server</p>\n <h2 id=\"mcp-custom-dialog-title\" class=\"card-title\">新增自訂 server</h2>\n </div>\n <button class=\"mcp-dialog-close\" type=\"button\" aria-label=\"Close\" onclick=\"closeMcpCustomDialog()\">×</button>\n </div>\n <div id=\"mcp-custom-dialog-content\"></div>\n <div id=\"mcp-custom-dialog-error\" class=\"inline-result err\" style=\"display:none\"></div>\n <div class=\"mcp-dialog-actions\">\n <button class=\"mcp-btn\" type=\"button\" onclick=\"closeMcpCustomDialog()\">Cancel</button>\n <button id=\"mcp-custom-dialog-submit\" class=\"primary-action-btn\" type=\"button\" onclick=\"submitMcpCustomDialog(this)\">新增並測試連線</button>\n </div>\n </dialog>`;\n\nconst adminViewScript = ` let activeConversationKey = defaultConversationKey;\n function scopeOf(key) {\n const sep = key.indexOf(':');\n return { platform: key.slice(0, sep), conversationId: key.slice(sep + 1) };\n }\n function scopeQuery() {\n const scope = scopeOf(activeConversationKey);\n return 'conversationId=' + encodeURIComponent(scope.conversationId) +\n '&platform=' + encodeURIComponent(scope.platform);\n }\n function scopeBody() {\n const scope = scopeOf(activeConversationKey);\n return { conversationId: scope.conversationId, platform: scope.platform };\n }\n let availableModels = [];\n let modelsLoaded = false;\n let mcpPresets = [];\n let mcpServersByScope = { conversation: {}, global: {} };\n let pendingMcpInstall = null;\n\n // ── Helpers ──────────────────────────────────────────────────────────────────\n\n function escHtml(str) {\n return String(str).replace(/[&<>\"']/g, (c) => (\n {'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]\n ));\n }\n const escAttr = escHtml;\n async function copyToClipboard(text) {\n try { await navigator.clipboard.writeText(text); } catch { prompt('Copy this link:', text); }\n }\n document.addEventListener('click', (event) => {\n const target = event.target instanceof Element\n ? event.target.closest('[data-admin-action]')\n : null;\n if (!target) return;\n switch (target.dataset.adminAction) {\n case 'preview-file':\n void previewFile(target.dataset.filePath || '');\n break;\n case 'copy-link':\n void copyToClipboard(target.dataset.copyText || '');\n break;\n case 'delete-event':\n void deleteEvent(target.dataset.eventName || '', target);\n break;\n case 'select-conversation':\n setActiveConversation(target.dataset.conversationId || '');\n switchTab('conversation');\n break;\n case 'toggle-timeline-filter':\n toggleTimelineFilter(target.dataset.filterKey || '');\n break;\n }\n });\n async function apiGet(path) {\n const url = path + (path.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(adminToken);\n const r = await fetch(url);\n const data = await r.json();\n if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));\n return data;\n }\n async function apiPost(path, body) {\n const r = await fetch(path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token: adminToken, ...body }),\n });\n const data = await r.json();\n if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));\n return data;\n }\n async function loadModels() {\n try {\n const data = await apiGet('/admin/api/models');\n availableModels = Array.isArray(data.models) ? data.models : [];\n } catch (err) {\n availableModels = [];\n } finally {\n modelsLoaded = true;\n }\n }\n function modelRef(provider, model) {\n return provider && model ? provider + '/' + model : '';\n }\n function parseModelRef(value) {\n const slash = value.indexOf('/');\n if (slash <= 0 || slash === value.length - 1) return { provider: '', model: '' };\n return { provider: value.slice(0, slash), model: value.slice(slash + 1) };\n }\n function renderModelOptions(currentProvider, currentModel) {\n const current = modelRef(currentProvider, currentModel);\n const seen = new Set();\n const groups = {\n available: [],\n unverified: [],\n };\n if (current) {\n seen.add(current);\n groups.available.push('<option value=\"' + escAttr(current) + '\">' + escHtml(current + ' (current)') + '</option>');\n }\n for (const model of availableModels) {\n const ref = modelRef(model.provider, model.id);\n if (!ref || seen.has(ref)) continue;\n seen.add(ref);\n const details = [model.name && model.name !== model.id ? model.name : '', model.reasoning ? 'thinking' : '', Array.isArray(model.input) && model.input.includes('image') ? 'image' : '']\n .filter(Boolean)\n .join(' · ');\n const option = '<option value=\"' + escAttr(ref) + '\">' + escHtml(details ? ref + ' — ' + details : ref) + '</option>';\n if (model.status === 'unverified') groups.unverified.push(option);\n else groups.available.push(option);\n }\n const sections = [];\n if (groups.available.length > 0) sections.push('<optgroup label=\"Available\">' + groups.available.join('') + '</optgroup>');\n if (groups.unverified.length > 0) sections.push('<optgroup label=\"Configured but unverified\">' + groups.unverified.join('') + '</optgroup>');\n if (sections.length === 0) {\n return '<option value=\"\">No available models</option>';\n }\n return sections.join('');\n }\n\n // ── Tab switching ────────────────────────────────────────────────────────────\n\n const tabBtns = document.querySelectorAll('.tab-btn');\n const tabPanels = document.querySelectorAll('.tab-panel');\n\n function switchTab(tabId) {\n tabBtns.forEach((btn) => {\n const active = btn.dataset.tab === tabId;\n btn.classList.toggle('active', active);\n btn.setAttribute('aria-selected', active ? 'true' : 'false');\n });\n tabPanels.forEach((panel) => panel.classList.toggle('active', panel.id === 'panel-' + tabId));\n if (tabId === 'global') initGlobal();\n }\n tabBtns.forEach((btn) => btn.addEventListener('click', () => switchTab(btn.dataset.tab)));\n\n // ── Conversation switcher ───────────────────────────────────────────────────\n\n async function initConvSwitcher() {\n const sel = document.getElementById('conv-switcher');\n try {\n const data = await apiGet('/admin/api/conversations');\n sel.innerHTML = data.conversations.map((c) => {\n const key = c.platform + ':' + c.conversationId;\n const label = (c.label || c.conversationId) + (c.running ? ' (running)' : '');\n const selected = key === defaultConversationKey ? ' selected' : '';\n return '<option value=\"' + escAttr(key) + '\"' + selected + '>' + escHtml(label) + '</option>';\n }).join('');\n sel.addEventListener('change', () => setActiveConversation(sel.value));\n } catch (err) {\n // ignore; conversation selector stays empty\n }\n }\n\n // Sections load lazily: only when their <details> is (or becomes) open,\n // so switching conversations or opening the admin page doesn't fire every\n // API at once. sectionLoaded is cleared on conversation switch so an\n // already-open section refetches for the new scope.\n //\n // Looked up by name (not collected into an object up front) because the\n // loaders below are const declarations further down this same script;\n // referencing them before their own line runs would hit the temporal\n // dead zone. By the time this function is actually called (after a\n // <details> toggle, always after the whole script has executed), they're\n // all initialized.\n function sectionLoader(key) {\n switch (key) {\n case 'settings': return loadSettings;\n case 'workspace': return loadWorkspace;\n case 'skills': return loadSkills;\n case 'mcp': return loadMcpServers;\n case 'events': return loadConversationEvents;\n default: return undefined;\n }\n }\n const sectionLoaded = new Set();\n\n function conversationSectionEls() {\n return document.querySelectorAll('#panel-conversation > details[data-section]');\n }\n\n function ensureSectionLoaded(key) {\n if (sectionLoaded.has(key)) return;\n const loader = sectionLoader(key);\n if (!loader) return;\n sectionLoaded.add(key);\n loader();\n }\n\n function initSections() {\n conversationSectionEls().forEach((el) => {\n const key = el.dataset.section;\n const stored = localStorage.getItem('admin-sect-' + key);\n if (stored !== null) el.open = stored === '1';\n if (el.open) ensureSectionLoaded(key);\n el.addEventListener('toggle', () => {\n localStorage.setItem('admin-sect-' + key, el.open ? '1' : '0');\n if (el.open) ensureSectionLoaded(key);\n });\n });\n }\n\n function setActiveConversation(key) {\n activeConversationKey = key;\n const sel = document.getElementById('conv-switcher');\n if (sel && sel.value !== key) sel.value = key;\n // Data is scoped to the previous conversation; drop it so any open\n // section refetches, and closed sections load fresh on next expand.\n sectionLoaded.clear();\n conversationSectionEls().forEach((el) => {\n if (el.open) ensureSectionLoaded(el.dataset.section);\n });\n openLogin(true);\n openSessionView(true);\n }\n\n // ── Settings ─────────────────────────────────────────────────────────────────\n\n const loadSettings = () => loadSettingsPanel('settings-content', () => '/admin/api/conversation-state?' + scopeQuery(), renderSettings);\n const loadGlobalSettings = () => loadSettingsPanel('global-settings-content', () => '/admin/api/settings/global', renderGlobalSettings);\n\n async function loadSettingsPanel(id, path, render) {\n const container = document.getElementById(id);\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n if (!modelsLoaded) await loadModels();\n try {\n const data = await apiGet(path());\n container.innerHTML = render(data);\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function renderConfigCard(title, content, action, buttonLabel, resultId) {\n return '<div class=\"config-block\"><h3 class=\"card-subtitle\">' + title + '</h3>' + content +\n '<button class=\"primary-action-btn\" onclick=\"' + action + '(this)\">' + buttonLabel + '</button>' +\n '<div id=\"' + resultId + '\" class=\"inline-result\" style=\"display:none\"></div></div>';\n }\n\n function renderOptions(choices, selected) {\n return choices.map(([value, label]) =>\n '<option value=\"' + escAttr(value) + '\"' + (selected === value ? ' selected' : '') + '>' + escHtml(label) + '</option>'\n ).join('');\n }\n function renderThinkingOptions(value) {\n return renderOptions(['off','minimal','low','medium','high','xhigh','max'].map((t) => [t, t]), value);\n }\n function renderReplyOptions(slack) {\n return renderOptions(['top-level','thread'].map((m) => [m, m]), (slack && slack.replyMode) || 'top-level');\n }\n function renderSettings(data) {\n const thinkingOpts = renderThinkingOptions(data.thinkingLevel);\n const replyModeOpts = renderReplyOptions(data.slack);\n const globalReplyMode = (data.slack && data.slack.globalReplyMode) || 'top-level';\n const globalModel = [data.globalProvider, data.globalModel].filter(Boolean).join('/');\n const globalModelLabel = globalModel + (data.globalThinkingLevel ? ':' + data.globalThinkingLevel : '');\n return [\n '<div class=\"config-grid\">',\n renderConfigCard('Model', [\n '<div class=\"config-row config-row-stack\"><label>Model</label><select id=\"m-model-ref\">' + renderModelOptions(data.provider, data.model) + '</select></div>',\n '<div class=\"config-row\"><label>Thinking</label><select id=\"m-thinking\">' + thinkingOpts + '</select></div>',\n '<p class=\"muted-note\">Global default: ' + escHtml(globalModelLabel) + '</p>',\n ].join(''), 'saveModel', 'Save model', 'model-save-result'),\n\n renderConfigCard('Office visibility', [\n '<div class=\"config-row\"><label>Visibility</label><select id=\"m-visibility\">' +\n renderOptions([\n ['default', 'Follow the Slack conversation type (default)'],\n ['private', 'Private — treat this public channel as private'],\n ], data.officeVisibilityOverride === 'private' ? 'private' : 'default') + '</select></div>',\n '<p class=\"muted-note\">Effective: <strong>' + escHtml(data.officeVisibility) + '</strong> · ' + escHtml(\n data.officeVisibilitySource === 'override' ? 'set here'\n : data.officeVisibilitySource === 'platform' ? 'from the Slack conversation type'\n : 'conversation type unknown; private until observed') + '</p>',\n '<p class=\"muted-note\">Public offices can be read by every other office and may write shared MEMORY.md and skills. Private offices (private channels, DMs) are visible only to themselves and read shared knowledge without writing it. Nothing can be made more visible than Slack allows.</p>',\n ].join(''), 'saveVisibility', 'Save visibility', 'mount-save-result'),\n renderConfigCard('Slack', [\n '<div class=\"config-row\"><label>Reply mode</label><select id=\"m-slack-reply-mode\">' + replyModeOpts + '</select></div>',\n '<p class=\"muted-note\">Global default: ' + escHtml(globalReplyMode) + '</p>',\n ].join(''), 'saveSlack', 'Save Slack', 'slack-save-result'),\n '</div>',\n ].join('');\n }\n\n async function saveModel(btn) {\n const selectedModel = parseModelRef(document.getElementById('m-model-ref').value.trim());\n const provider = selectedModel.provider;\n const model = selectedModel.model;\n const thinkingLevel = document.getElementById('m-thinking').value;\n const result = document.getElementById('model-save-result');\n if (!provider || !model) {\n result.style.display = 'block'; result.className = 'inline-result err';\n result.textContent = 'Provider and model are required';\n return;\n }\n await saveConversationSetting(btn, result, 'model', { provider, model, thinkingLevel }, 'Save model');\n }\n\n async function saveSlack(btn) {\n const replyMode = document.getElementById('m-slack-reply-mode').value;\n const result = document.getElementById('slack-save-result');\n await saveConversationSetting(btn, result, 'slack', { replyMode }, 'Save Slack');\n }\n\n async function saveVisibility(btn) {\n const visibility = document.getElementById('m-visibility').value;\n const result = document.getElementById('mount-save-result');\n await saveConversationSetting(btn, result, 'visibility', { visibility }, 'Save visibility', loadSettings);\n }\n\n function saveConversationSetting(btn, result, setting, values, label, onSaved) {\n return saveSetting(btn, result, 'conversations/' + setting, { ...scopeBody(), ...values }, label, onSaved);\n }\n\n async function saveSetting(btn, result, path, values, label, onSaved) {\n btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';\n try {\n await apiPost('/admin/api/' + path, values);\n result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';\n if (onSaved) onSaved();\n } catch (err) {\n result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;\n } finally {\n btn.disabled = false; btn.textContent = label;\n }\n }\n\n // ── Workspace ────────────────────────────────────────────────────────────────\n\n async function loadWorkspace() {\n const treeEl = document.getElementById('workspace-tree');\n const previewEl = document.getElementById('workspace-preview');\n treeEl.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n previewEl.innerHTML = '<div class=\"placeholder-msg\">Click a file to preview</div>';\n try {\n const data = await apiGet('/admin/api/workspace/tree?' + scopeQuery());\n if (!data.tree) {\n treeEl.innerHTML = '<div class=\"empty-state\">No files</div>';\n return;\n }\n treeEl.innerHTML = '<ul class=\"tree-root\">' + renderTreeChildren(data.tree) + '</ul>';\n } catch (err) {\n treeEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function renderTreeChildren(node) {\n if (node.type === 'file') {\n return '<li><button class=\"tree-file\" data-admin-action=\"preview-file\" data-file-path=\"' + escAttr(node.path) + '\">' + escHtml(node.name) + '</button></li>';\n }\n if (!node.children || node.children.length === 0) {\n return '<li><span class=\"tree-dir empty\">' + escHtml(node.name || '.') + '/</span></li>';\n }\n const inner = node.children.map((c) =>\n c.type === 'file'\n ? '<li><button class=\"tree-file\" data-admin-action=\"preview-file\" data-file-path=\"' + escAttr(c.path) + '\">' + escHtml(c.name) + '</button></li>'\n : '<li><details open><summary class=\"tree-dir\">' + escHtml(c.name) + '/</summary><ul>' + renderTreeChildren(c) + '</ul></details></li>'\n ).join('');\n return inner;\n }\n\n function renderPreviewFileResult(previewEl, label, data) {\n if (data.binary) {\n previewEl.innerHTML = '<div class=\"preview-meta\">' + escHtml(label) + ' · ' + data.size + ' bytes · binary</div><div class=\"placeholder-msg\">Binary file — preview not available</div>';\n return;\n }\n previewEl.innerHTML =\n '<div class=\"preview-meta\">' + escHtml(label) + ' · ' + data.size + ' bytes</div>' +\n '<pre class=\"preview-body\">' + escHtml(data.content || '') + '</pre>';\n }\n\n async function previewFile(path) {\n const previewEl = document.getElementById('workspace-preview');\n previewEl.innerHTML = '<div class=\"loading-msg\">Loading ' + escHtml(path) + '…</div>';\n try {\n const data = await apiGet('/admin/api/workspace/file?' + scopeQuery() + '&path=' + encodeURIComponent(path));\n renderPreviewFileResult(previewEl, path, data);\n } catch (err) {\n previewEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n // ── Skills ───────────────────────────────────────────────────────────────────\n\n async function loadScopePanels(prefix, endpoint, render) {\n const convEl = document.getElementById(prefix + '-conv-content');\n const globalEl = document.getElementById(prefix + '-global-content');\n if (convEl) convEl.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n if (globalEl) globalEl.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n const data = await apiGet('/admin/api/' + endpoint + '?' + scopeQuery());\n render(data, convEl, globalEl);\n } catch (err) {\n if (convEl) convEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n if (globalEl) globalEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n // ── MCP servers ───────────────────────────────────────────────────────────────────────\n\n function mcpMessage(scope, text, kind) {\n const el = document.getElementById(scope === 'global' ? 'mcp-global-msg' : 'mcp-conv-msg');\n if (!el) return;\n el.style.display = 'block';\n el.className = 'status-msg status-msg-' + kind;\n el.textContent = text;\n }\n\n function mcpTransport(server) {\n return server.command\n ? server.command + (server.args && server.args.length ? ' ' + server.args.join(' ') : '')\n : (server.url || '');\n }\n\n function renderMcpServer(scope, name, server) {\n const transport = (server.command ? 'stdio: ' : 'http: ') + mcpTransport(server);\n const keys = [];\n if (server.envKeys && server.envKeys.length) keys.push('env: ' + server.envKeys.join(', '));\n if (server.headerKeys && server.headerKeys.length) keys.push('headers: ' + server.headerKeys.join(', '));\n return '<article class=\"mcp-preset mcp-installed-card\">' +\n '<div class=\"mcp-preset-top\">' +\n '<span class=\"mcp-preset-category\">' + (server.command ? 'STDIO' : 'HTTP') + '</span>' +\n (server.disabled ? '<span class=\"mcp-badge mcp-badge-warn\">disabled</span>' : '<span class=\"mcp-badge mcp-badge-ok\">enabled</span>') +\n '</div>' +\n '<h3>' + escHtml(name) + '</h3>' +\n '<p class=\"mcp-preset-meta mcp-installed-transport\">' + escHtml(transport) + '</p>' +\n (keys.length ? '<div class=\"mcp-preset-meta\">' + escHtml(keys.join(' · ')) + '</div>' : '') +\n '<div class=\"mcp-preset-actions\">' +\n '<button class=\"mcp-btn\" data-mcp-action=\"test\" data-mcp-scope=\"' + scope + '\" data-mcp-name=\"' + escAttr(name) + '\">Test</button>' +\n '<button class=\"mcp-btn\" data-mcp-action=\"toggle\" data-mcp-scope=\"' + scope + '\" data-mcp-name=\"' + escAttr(name) + '\">' + (server.disabled ? 'Enable' : 'Disable') + '</button>' +\n '<button class=\"mcp-btn mcp-btn-danger\" data-mcp-action=\"remove\" data-mcp-scope=\"' + scope + '\" data-mcp-name=\"' + escAttr(name) + '\">Remove</button>' +\n '</div>' +\n '</article>';\n }\n\n function renderMcpPreset(scope, preset, servers) {\n const installed = Boolean(servers[preset.serverName]);\n const transport = preset.server.command ? 'Runs on host' : 'Remote service';\n return '<article class=\"mcp-preset\">' +\n '<div class=\"mcp-preset-top\">' +\n '<span class=\"mcp-preset-category\">' + escHtml(preset.category) + '</span>' +\n (installed ? '<span class=\"mcp-badge mcp-badge-ok\">Installed here</span>' : '') +\n '</div>' +\n '<h3>' + escHtml(preset.name) + '</h3>' +\n '<p>' + escHtml(preset.description) + '</p>' +\n '<div class=\"mcp-preset-meta\">' + escHtml(transport) + ' · <code>' + escHtml(preset.serverName) + '</code></div>' +\n '<div class=\"mcp-preset-actions\">' +\n '<a href=\"' + escAttr(preset.sourceUrl) + '\" target=\"_blank\" rel=\"noopener\">Source ↗</a>' +\n '<button class=\"primary-action-btn\" data-mcp-action=\"preset\" data-mcp-scope=\"' + scope + '\" data-mcp-preset=\"' + escAttr(preset.id) + '\">' + (installed ? 'Review' : 'Install') + '</button>' +\n '</div>' +\n '</article>';\n }\n\n function renderMcpScope(el, scope, servers) {\n const presets = mcpPresets.map((preset) => renderMcpPreset(scope, preset, servers)).join('');\n const rows = Object.entries(servers).map(([name, server]) => renderMcpServer(scope, name, server)).join('');\n el.innerHTML =\n '<div class=\"mcp-market-head\"><div><h3>Explore presets</h3><p>Reviewed recipes that install into this scope.</p></div><span>' + mcpPresets.length + ' available</span></div>' +\n '<div class=\"mcp-preset-grid\">' + presets + '</div>' +\n '<details class=\"mcp-installed\" open><summary>Installed in this scope</summary>' +\n '<div class=\"mcp-preset-grid\">' +\n rows +\n '<button type=\"button\" class=\"mcp-add-card\" data-mcp-action=\"open-custom\" data-mcp-scope=\"' + scope + '\">' +\n '<span class=\"mcp-add-card-plus\">+</span><span>新增 server</span>' +\n '</button>' +\n '</div>' +\n '</details>';\n }\n\n function renderMcpGuidedForm(scope) {\n return '<div class=\"mcp-guided-grid\">' +\n '<label class=\"mcp-field\"><span>Server 名稱</span><input id=\"mcp-' + scope + '-g-name\" class=\"form-input mcp-json\" placeholder=\"例如 github\" autocomplete=\"off\" /></label>' +\n '<div class=\"mcp-transport-toggle\">' +\n '<label><input type=\"radio\" name=\"mcp-' + scope + '-g-transport\" value=\"stdio\" checked /> 本機指令 (stdio)</label>' +\n '<label><input type=\"radio\" name=\"mcp-' + scope + '-g-transport\" value=\"http\" /> 遠端服務 (HTTP)</label>' +\n '</div>' +\n '<div id=\"mcp-' + scope + '-g-stdio\" class=\"mcp-transport-fields\">' +\n '<label class=\"mcp-field\"><span>指令</span><input id=\"mcp-' + scope + '-g-command\" class=\"form-input mcp-json\" placeholder=\"npx\" autocomplete=\"off\" /></label>' +\n '<label class=\"mcp-field\"><span>參數(以逗號分隔)</span><input id=\"mcp-' + scope + '-g-args\" class=\"form-input mcp-json\" placeholder=\"-y, @modelcontextprotocol/server-github\" autocomplete=\"off\" /></label>' +\n '</div>' +\n '<div id=\"mcp-' + scope + '-g-http\" class=\"mcp-transport-fields\" style=\"display:none\">' +\n '<label class=\"mcp-field\"><span>URL</span><input id=\"mcp-' + scope + '-g-url\" class=\"form-input mcp-json\" placeholder=\"https://mcp.example.com/mcp\" autocomplete=\"off\" /></label>' +\n '</div>' +\n '<div class=\"mcp-kv-block\">' +\n '<div class=\"mcp-kv-head\"><span id=\"mcp-' + scope + '-g-kv-label\">環境變數 (env)</span><button type=\"button\" class=\"mcp-btn\" data-mcp-kv-add=\"' + scope + '\">+ 新增一列</button></div>' +\n '<div id=\"mcp-' + scope + '-g-kv\" class=\"mcp-kv-rows\"></div>' +\n '</div>' +\n '</div>';\n }\n\n function renderMcpKvRow() {\n return '<div class=\"mcp-kv-row\">' +\n '<input class=\"form-input mcp-json\" placeholder=\"KEY\" data-kv-key autocomplete=\"off\" />' +\n '<input class=\"form-input mcp-json\" type=\"password\" placeholder=\"value\" data-kv-value autocomplete=\"off\" />' +\n '<button type=\"button\" class=\"mcp-btn mcp-btn-danger\" data-mcp-kv-remove>移除</button>' +\n '</div>';\n }\n\n function addMcpGuidedRow(scope) {\n const container = document.getElementById('mcp-' + scope + '-g-kv');\n if (container) container.insertAdjacentHTML('beforeend', renderMcpKvRow());\n }\n\n function updateMcpGuidedTransport(scope) {\n const checked = document.querySelector('input[name=\"mcp-' + scope + '-g-transport\"]:checked');\n const transport = checked ? checked.value : 'stdio';\n const stdioEl = document.getElementById('mcp-' + scope + '-g-stdio');\n const httpEl = document.getElementById('mcp-' + scope + '-g-http');\n const kvLabel = document.getElementById('mcp-' + scope + '-g-kv-label');\n if (stdioEl) stdioEl.style.display = transport === 'stdio' ? '' : 'none';\n if (httpEl) httpEl.style.display = transport === 'http' ? '' : 'none';\n if (kvLabel) kvLabel.textContent = transport === 'stdio' ? '環境變數 (env)' : 'HTTP Headers';\n }\n\n let pendingMcpCustomScope = null;\n\n function openMcpCustomDialog(scope) {\n pendingMcpCustomScope = scope;\n const content = document.getElementById('mcp-custom-dialog-content');\n content.innerHTML =\n '<div class=\"mcp-add-mode\">' +\n '<button type=\"button\" class=\"mcp-mode-btn active\" data-mcp-mode=\"guided\">引導式表單</button>' +\n '<button type=\"button\" class=\"mcp-mode-btn\" data-mcp-mode=\"json\">貼上 JSON</button>' +\n '</div>' +\n '<div id=\"mcp-custom-guided-panel\" class=\"mcp-panel\">' + renderMcpGuidedForm('custom') + '</div>' +\n '<div id=\"mcp-custom-json-panel\" class=\"mcp-panel\" style=\"display:none\">' +\n '<p class=\"mcp-manual-hint\">貼上 MCP server 文件給的 <code>mcpServers</code> JSON,原樣保存到這個 scope。remote 走 Streamable HTTP,local 走 stdio。</p>' +\n '<textarea id=\"mcp-custom-json\" class=\"form-input mcp-json\" spellcheck=\"false\" rows=\"9\" placeholder=\"' + escAttr(MCP_JSON_PLACEHOLDER) + '\"></textarea>' +\n '</div>';\n const dialogError = document.getElementById('mcp-custom-dialog-error');\n dialogError.style.display = 'none';\n dialogError.textContent = '';\n addMcpGuidedRow('custom');\n document.getElementById('mcp-custom-dialog').showModal();\n }\n\n function closeMcpCustomDialog() {\n pendingMcpCustomScope = null;\n document.getElementById('mcp-custom-dialog').close();\n }\n\n function switchMcpCustomMode(mode) {\n const guidedEl = document.getElementById('mcp-custom-guided-panel');\n const jsonEl = document.getElementById('mcp-custom-json-panel');\n if (guidedEl) guidedEl.style.display = mode === 'guided' ? '' : 'none';\n if (jsonEl) jsonEl.style.display = mode === 'json' ? '' : 'none';\n document.querySelectorAll('#mcp-custom-dialog-content [data-mcp-mode]').forEach((b) => {\n b.classList.toggle('active', b.dataset.mcpMode === mode);\n });\n }\n\n async function submitMcpCustomDialog(btn) {\n const scope = pendingMcpCustomScope;\n if (!scope) return;\n const dialogError = document.getElementById('mcp-custom-dialog-error');\n dialogError.style.display = 'none';\n dialogError.textContent = '';\n const jsonMode = document.getElementById('mcp-custom-json-panel').style.display !== 'none';\n let json;\n if (jsonMode) {\n const raw = document.getElementById('mcp-custom-json').value.trim();\n if (!raw) { dialogError.textContent = '請貼上 mcpServers JSON'; dialogError.style.display = 'block'; return; }\n json = raw;\n } else {\n const nameEl = document.getElementById('mcp-custom-g-name');\n const name = nameEl ? nameEl.value.trim() : '';\n if (!name) { dialogError.textContent = '請輸入 server 名稱'; dialogError.style.display = 'block'; return; }\n const checked = document.querySelector('input[name=\"mcp-custom-g-transport\"]:checked');\n const transport = checked ? checked.value : 'stdio';\n const kv = {};\n document.querySelectorAll('#mcp-custom-g-kv .mcp-kv-row').forEach((row) => {\n const keyInput = row.querySelector('[data-kv-key]');\n const valueInput = row.querySelector('[data-kv-value]');\n const key = keyInput ? keyInput.value.trim() : '';\n if (key) kv[key] = valueInput ? valueInput.value : '';\n });\n let entry;\n if (transport === 'stdio') {\n const commandEl = document.getElementById('mcp-custom-g-command');\n const command = commandEl ? commandEl.value.trim() : '';\n if (!command) { dialogError.textContent = '請輸入指令'; dialogError.style.display = 'block'; return; }\n const argsEl = document.getElementById('mcp-custom-g-args');\n const argsRaw = argsEl ? argsEl.value.trim() : '';\n const args = argsRaw ? argsRaw.split(',').map((s) => s.trim()).filter(Boolean) : [];\n entry = Object.assign({ command: command }, args.length ? { args: args } : {}, Object.keys(kv).length ? { env: kv } : {});\n } else {\n const urlEl = document.getElementById('mcp-custom-g-url');\n const url = urlEl ? urlEl.value.trim() : '';\n if (!url) { dialogError.textContent = '請輸入 URL'; dialogError.style.display = 'block'; return; }\n entry = Object.assign({ url: url }, Object.keys(kv).length ? { headers: kv } : {});\n }\n json = JSON.stringify({ mcpServers: { [name]: entry } });\n }\n btn.disabled = true;\n btn.textContent = '儲存中…';\n try {\n const data = await apiPost('/admin/api/mcp-servers/mutate', {\n action: 'import', scope: scope, json: json, ...scopeBody(),\n });\n const results = Array.isArray(data.results) ? data.results : [];\n const failed = results.filter((r) => r.error).length;\n closeMcpCustomDialog();\n mcpMessage(scope, failed ? '已儲存,但 ' + failed + ' 個 server 連線失敗,見上方選項維護。' : '已儲存並連線成功。', failed ? 'err' : 'ok');\n await loadMcpServers();\n } catch (err) {\n dialogError.textContent = err.message;\n dialogError.style.display = 'block';\n } finally {\n btn.disabled = false;\n btn.textContent = '新增並測試連線';\n }\n }\n\n const MCP_JSON_PLACEHOLDER = JSON.stringify({\n mcpServers: {\n browserless: {\n type: 'http',\n url: 'https://mcp.browserless.io/mcp',\n headers: { Authorization: 'Bearer YOUR_API_TOKEN' },\n },\n },\n }, null, 2);\n\n const loadMcpServers = () => loadScopePanels('mcp', 'mcp-servers', (data, convEl, globalEl) => {\n mcpPresets = Array.isArray(data.presets) ? data.presets : [];\n mcpServersByScope = { conversation: data.conversation || {}, global: data.global || {} };\n if (convEl) renderMcpScope(convEl, 'conversation', mcpServersByScope.conversation);\n if (globalEl) renderMcpScope(globalEl, 'global', mcpServersByScope.global);\n });\n\n async function mutateMcpServer(scope, action, name, extra) {\n mcpMessage(scope, action === 'test' ? '連線測試中…' : '儲存並測試連線中…', 'busy');\n try {\n const data = await apiPost('/admin/api/mcp-servers/mutate', {\n action: action,\n scope: scope,\n ...(name ? { name: name } : {}),\n ...(extra || {}),\n ...scopeBody(),\n });\n const results = Array.isArray(data.results) ? data.results : [];\n const failed = results.filter((r) => r.error).length;\n if (action === 'test') {\n mcpMessage(scope, failed ? '✗ ' + name + ': ' + results[0].error : '✓ ' + name + ': ' + results[0].tools + ' tool(s)', failed ? 'err' : 'ok');\n return;\n }\n if (action === 'remove' || action === 'toggle') {\n mcpMessage(scope, '完成。新設定在下一次回應生效。', 'ok');\n } else {\n mcpMessage(scope, failed ? '已儲存,但 ' + failed + ' 個 server 連線失敗,見下方錯誤。' : '已儲存並連線成功。', failed ? 'err' : 'ok');\n }\n await loadMcpServers();\n } catch (err) {\n mcpMessage(scope, err.message, 'err');\n }\n }\n\n function openMcpPreset(scope, presetId) {\n const preset = mcpPresets.find((item) => item.id === presetId);\n if (!preset) return;\n pendingMcpInstall = { scope: scope, preset: preset };\n const local = Boolean(preset.server.command);\n const installed = Boolean(mcpServersByScope[scope][preset.serverName]);\n const credentials = preset.credentials.map((credential, index) =>\n '<label class=\"mcp-credential\"><span>' + escHtml(credential.label) + (credential.required ? ' *' : '') + '</span>' +\n '<input data-mcp-credential=\"' + index + '\" type=\"' + (credential.secret ? 'password' : 'text') + '\" autocomplete=\"off\" />' +\n '<small>' + escHtml(credential.description) + '</small></label>'\n ).join('');\n document.getElementById('mcp-dialog-title').textContent = preset.name;\n const dialogError = document.getElementById('mcp-dialog-error');\n dialogError.style.display = 'none';\n dialogError.textContent = '';\n document.getElementById('mcp-dialog-content').innerHTML =\n '<p class=\"mcp-dialog-desc\">' + escHtml(preset.description) + '</p>' +\n '<div class=\"mcp-install-preview\"><span>' + (local ? 'Host command' : 'Remote endpoint') + '</span><code>' + escHtml(mcpTransport(preset.server)) + '</code></div>' +\n '<div class=\"mcp-security-note ' + (local ? 'local' : 'remote') + '\">' +\n (local\n ? '<strong>Host code execution.</strong> This command runs outside the conversation sandbox with the mikan process user permissions.'\n : '<strong>External service.</strong> Tool calls and selected data will be sent to this remote origin.') +\n '</div>' +\n (installed ? '<div class=\"mcp-replace-note\">Installing again replaces the existing <code>' + escHtml(preset.serverName) + '</code> entry in this scope.</div>' : '') +\n (credentials || '<p class=\"mcp-no-credentials\">No credentials required.</p>') +\n '<p class=\"mcp-secret-note\">These values are stored in host-private settings and are not shown to the model or sandbox.</p>' +\n '<a class=\"mcp-setup-link\" href=\"' + escAttr(preset.setupUrl) + '\" target=\"_blank\" rel=\"noopener\">Setup documentation ↗</a>';\n document.getElementById('mcp-install-dialog').showModal();\n }\n\n function closeMcpInstall() {\n pendingMcpInstall = null;\n document.getElementById('mcp-install-dialog').close();\n }\n\n async function installMcpPreset(btn) {\n if (!pendingMcpInstall) return;\n const { scope, preset } = pendingMcpInstall;\n const credentials = {};\n document.querySelectorAll('[data-mcp-credential]').forEach((input) => {\n const descriptor = preset.credentials[Number(input.dataset.mcpCredential)];\n if (descriptor) credentials[descriptor.key] = input.value;\n });\n btn.disabled = true;\n btn.textContent = 'Installing…';\n try {\n const data = await apiPost('/admin/api/mcp-servers/mutate', {\n action: 'install', scope: scope, presetId: preset.id, credentials: credentials, ...scopeBody(),\n });\n closeMcpInstall();\n const result = (Array.isArray(data.results) ? data.results : [])[0];\n if (result && result.error) mcpMessage(scope, '✗ ' + preset.name + ' saved but failed to connect: ' + result.error, 'err');\n else mcpMessage(scope, '✓ ' + preset.name + ' installed, ' + (result ? result.tools : 0) + ' tool(s).', 'ok');\n await loadMcpServers();\n } catch (err) {\n const dialogError = document.getElementById('mcp-dialog-error');\n dialogError.textContent = err.message;\n dialogError.style.display = 'block';\n } finally {\n btn.disabled = false;\n btn.textContent = 'Install preset';\n }\n }\n\n document.addEventListener('click', (event) => {\n const modeBtn = event.target.closest('#mcp-custom-dialog-content [data-mcp-mode]');\n if (modeBtn) { switchMcpCustomMode(modeBtn.dataset.mcpMode); return; }\n const kvAddBtn = event.target.closest('[data-mcp-kv-add]');\n if (kvAddBtn) { addMcpGuidedRow(kvAddBtn.dataset.mcpKvAdd); return; }\n const kvRemoveBtn = event.target.closest('[data-mcp-kv-remove]');\n if (kvRemoveBtn) { kvRemoveBtn.closest('.mcp-kv-row').remove(); return; }\n const transportRadio = event.target.closest('input[name^=\"mcp-\"][name$=\"-g-transport\"]');\n if (transportRadio) {\n const match = /^mcp-(.+)-g-transport$/.exec(transportRadio.name);\n if (match) updateMcpGuidedTransport(match[1]);\n return;\n }\n const btn = event.target.closest('[data-mcp-action]');\n if (!btn) return;\n if (btn.dataset.mcpAction === 'open-custom') { openMcpCustomDialog(btn.dataset.mcpScope); return; }\n if (btn.dataset.mcpAction === 'preset') { openMcpPreset(btn.dataset.mcpScope, btn.dataset.mcpPreset); return; }\n void mutateMcpServer(btn.dataset.mcpScope, btn.dataset.mcpAction, btn.dataset.mcpName);\n });\n\n async function loadSkills() {\n const container = document.getElementById('skills-content');\n const previewEl = document.getElementById('skills-preview');\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n if (previewEl) previewEl.innerHTML = '<div class=\"placeholder-msg\">Click a skill to preview SKILL.md</div>';\n try {\n const data = await apiGet('/admin/api/skills?' + scopeQuery());\n if (data.skills.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">No skills available</div>';\n return;\n }\n container.innerHTML = '<div class=\"skills-list\">' +\n data.skills.map((s) =>\n '<button class=\"skill-row skill-row-btn\" data-skill-source=\"' + escAttr(s.source) + '\" data-skill-directory=\"' + escAttr(s.directory) + '\" data-skill-name=\"' + escAttr(s.name) + '\">' +\n '<div class=\"skill-name\">' + escHtml(s.name) + '<span class=\"skill-source skill-source-' + s.source + '\">' + s.source + '</span></div>' +\n (s.description ? '<div class=\"skill-desc\">' + escHtml(s.description) + '</div>' : '') +\n '</button>'\n ).join('') + '</div>';\n\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n async function previewSkill(source, directory, name) {\n const previewEl = document.getElementById('skills-preview');\n if (!source || !directory) {\n previewEl.innerHTML = '<div class=\"err-msg\">Missing skill source or directory</div>';\n return;\n }\n previewEl.innerHTML = '<div class=\"loading-msg\">Loading ' + escHtml(name || directory) + '…</div>';\n try {\n const data = await apiGet('/admin/api/skills/file?' + scopeQuery() + '&source=' + encodeURIComponent(source) + '&directory=' + encodeURIComponent(directory));\n renderPreviewFileResult(previewEl, source + '/' + directory + '/SKILL.md', data);\n } catch (err) {\n previewEl.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n document.getElementById('skills-content').addEventListener('click', (event) => {\n const btn = event.target.closest('[data-skill-source]');\n if (!btn) return;\n previewSkill(btn.dataset.skillSource, btn.dataset.skillDirectory, btn.dataset.skillName);\n });\n\n // ── Vault (Login link) ───────────────────────────────────────────────────────\n\n const openLogin = (silent) => openPortalLink('vault', 'login', silent);\n const openSessionView = (silent) => openPortalLink('session', 'session', silent);\n\n // Generates a fresh one-time/short-lived link for the other portal and\n // opens it in a new tab — the login and session-view tokens are separate\n // capabilities from the admin token (see docs/portal-auth-model.md), so\n // this always mints a new link rather than reusing anything cached here.\n async function openPortalLink(resultId, kind, silent) {\n const result = document.getElementById(resultId + '-link-result');\n if (silent) { result.style.display = 'none'; return; }\n result.style.display = 'block'; result.className = 'link-result loading'; result.textContent = 'Generating link…';\n try {\n const data = await apiPost('/admin/api/conversations/' + kind + '-link', scopeBody());\n result.className = 'link-result ok';\n result.innerHTML =\n (kind === 'login' ? '<span class=\"link-vault\">vault: <code>' + escHtml(data.vaultId) + '</code></span>' : '') +\n '<a href=\"' + escAttr(data.url) + '\" target=\"_blank\" rel=\"noopener\">' + escHtml(data.url) + '</a>' +\n '<button class=\"copy-link-btn\" data-admin-action=\"copy-link\" data-copy-text=\"' + escAttr(data.url) + '\">Copy</button>';\n window.open(data.url, '_blank', 'noopener');\n } catch (err) {\n result.className = 'link-result err'; result.textContent = err.message;\n }\n }\n\n // ── Events ───────────────────────────────────────────────────────────────────\n\n const loadConversationEvents = () => loadEventList(true);\n const loadEvents = () => loadEventList(false);\n\n async function loadEventList(conversation) {\n const container = document.getElementById(conversation ? 'events-content' : 'global-events-content');\n if (!container) return;\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n const data = await apiGet(conversation ? '/admin/api/conversations/events?' + scopeQuery() : '/admin/api/events');\n if (data.events.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">' + (conversation ? '沒有關聯此對話的 event' : 'No events scheduled') + '</div>';\n return;\n }\n container.innerHTML = '<div class=\"events-list\">' +\n data.events.map((e) => renderEventRow(e, conversation)).join('') + '</div>';\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function renderEventRow(e, allowDelete) {\n const meta = [e.type, e.platform, e.conversationId, e.schedule || e.at]\n .filter(Boolean).map(escHtml).join(' · ');\n const preview = e.text ? '<div class=\"event-text\">' + escHtml(e.text.length > 240 ? e.text.slice(0, 237) + '…' : e.text) + '</div>' : '';\n const deleteBtn = allowDelete\n ? '<button class=\"event-delete-btn\" data-admin-action=\"delete-event\" data-event-name=\"' + escAttr(e.name) + '\">Delete</button>'\n : '';\n return '<div class=\"event-row\">' +\n '<div class=\"event-row-top\">' +\n '<div class=\"event-name\"><code>' + escHtml(e.name) + '</code></div>' +\n deleteBtn +\n '</div>' +\n '<div class=\"event-meta\">' + meta + '</div>' +\n preview +\n '</div>';\n }\n\n async function deleteEvent(name, btn) {\n if (!confirm('Delete event \"' + name + '\"?')) return;\n btn.disabled = true; btn.textContent = 'Deleting…';\n try {\n await apiPost('/admin/api/conversations/events/delete', {\n ...scopeBody(), name,\n });\n await loadConversationEvents();\n } catch (err) {\n btn.disabled = false; btn.textContent = 'Delete';\n alert(err.message);\n }\n }\n\n // ── Global section ──────────────────────────────────────────────────────────\n\n let globalLoaded = false;\n function initGlobal() {\n if (globalLoaded) return;\n globalLoaded = true;\n loadAllConversations();\n loadTokenUsage();\n loadGlobalSettings();\n loadGlobalSkills();\n loadEvents();\n loadMcpServers();\n }\n\n async function loadAllConversations() {\n const container = document.getElementById('all-conv-content');\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n const data = await apiGet('/admin/api/conversations');\n if (data.conversations.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">No conversations found</div>';\n return;\n }\n container.innerHTML = '<div class=\"conv-list\">' + data.conversations.map((c) => {\n const last = c.lastActivityAt ? new Date(c.lastActivityAt).toLocaleString() : '—';\n return '<button class=\"conv-row-btn\" data-admin-action=\"select-conversation\" data-conversation-id=\"' + escAttr(c.conversationId) + '\">' +\n '<span class=\"conv-id\">' + escHtml(c.label || c.conversationId) + '</span>' +\n (c.running ? '<span class=\"status-pill running\">running</span>' : '') +\n '<span class=\"conv-last\">' + escHtml(last) + '</span>' +\n '</button>';\n }).join('') + '</div>';\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n async function loadSessionUsage() {\n const container = document.getElementById('session-usage-content');\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n const data = await apiGet('/admin/api/session-usage');\n if (data.sessions.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">No token usage found</div>';\n return;\n }\n container.innerHTML = '<div class=\"usage-table-wrap\"><table class=\"usage-table\"><thead><tr><th>#</th><th>Channel</th><th>Session</th><th>Updated</th><th>Input</th><th>Output</th><th>Cache Read</th><th>Cache Write</th><th>Total</th><th>Cost</th></tr></thead><tbody>' +\n data.sessions.map((s, i) => '<tr>' +\n '<td>' + (i + 1) + '</td>' +\n '<td>' + escHtml(s.label || s.conversationId) + '</td>' +\n '<td><code>' + escHtml(s.fileName) + '</code></td>' +\n '<td>' + escHtml(new Date(s.updatedAt).toLocaleString()) + '</td>' +\n '<td>' + fmtNum(s.input) + '</td>' +\n '<td>' + fmtNum(s.output) + '</td>' +\n '<td>' + fmtNum(s.cacheRead) + '</td>' +\n '<td>' + fmtNum(s.cacheWrite) + '</td>' +\n '<td><strong>' + fmtNum(s.total) + '</strong></td>' +\n '<td>' + (s.cost > 0 ? '$' + Number(s.cost).toFixed(4) : '—') + '</td>' +\n '</tr>').join('') + '</tbody></table></div>';\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function fmtNum(value) {\n return Number(value || 0).toLocaleString('en-US');\n }\n\n let timelineConvLoaded = false;\n let timelineData = null;\n let timelineFilter = null;\n async function ensureTimelineConvOptions() {\n if (timelineConvLoaded) return;\n const sel = document.getElementById('timeline-conv');\n const prev = sel.value;\n const data = await apiGet('/admin/api/conversations');\n if (!data.conversations.length) {\n sel.innerHTML = '<option value=\"\">No conversations</option>';\n timelineConvLoaded = true;\n return;\n }\n const want = prev || defaultConversationKey;\n sel.innerHTML = data.conversations.map((c) => {\n const key = c.platform + ':' + c.conversationId;\n return '<option value=\"' + escAttr(key) + '\"' +\n (key === want ? ' selected' : '') + '>' +\n escHtml(c.label || c.conversationId) + '</option>';\n }).join('');\n timelineConvLoaded = true;\n }\n\n // Shared refresh for the merged Token Usage section: reloads the session\n // ranking and re-fetches the conversation list (so new sessions appear),\n // preserving the currently selected conversation.\n async function loadTokenUsage() {\n loadSessionUsage();\n timelineConvLoaded = false;\n await loadUsageTimeline();\n }\n\n async function loadUsageTimeline() {\n const container = document.getElementById('usage-timeline-content');\n try {\n await ensureTimelineConvOptions();\n const conv = document.getElementById('timeline-conv').value;\n if (!conv) {\n container.innerHTML = '<div class=\"empty-state\">No conversations found</div>';\n return;\n }\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n const convScope = scopeOf(conv);\n const data = await apiGet('/admin/api/conversation-usage?conversationId=' +\n encodeURIComponent(convScope.conversationId) +\n '&platform=' + encodeURIComponent(convScope.platform));\n timelineData = data;\n container.innerHTML = renderUsageTimeline(data);\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n function tlCard(label, value) {\n return '<div class=\"tl-card\"><div class=\"tl-card-label\">' + label +\n '</div><div class=\"tl-card-value\">' + value + '</div></div>';\n }\n\n const TL_SERIES = [\n { key: 'cacheRead', seg: 'tl-cache-read', sw: 'sw-cache-read', label: 'Cache read' },\n { key: 'cacheWrite', seg: 'tl-cache-write', sw: 'sw-cache-write', label: 'Cache write' },\n { key: 'input', seg: 'tl-input', sw: 'sw-input', label: 'Input' },\n { key: 'output', seg: 'tl-output', sw: 'sw-output', label: 'Output' },\n ];\n\n // Click a legend item to show only that series; click it again for all.\n function toggleTimelineFilter(key) {\n timelineFilter = timelineFilter === key ? null : key;\n if (timelineData) {\n document.getElementById('usage-timeline-content').innerHTML = renderUsageTimeline(timelineData);\n }\n }\n\n function renderUsageTimeline(data) {\n const buckets = data.buckets || [];\n const totals = data.totals || { total: 0, cost: 0, cacheRead: 0 };\n const cacheHit = totals.total > 0 ? Math.round((totals.cacheRead / totals.total) * 100) : 0;\n const cards = '<div class=\"tl-cards\">' +\n tlCard('Total cost', totals.cost > 0 ? '$' + Number(totals.cost).toFixed(4) : '—') +\n tlCard('Total tokens', fmtNum(totals.total)) +\n tlCard('Cache hit', cacheHit + '%') +\n '</div>';\n\n if (totals.total === 0) {\n const emptyNote = data.hasOlder\n ? '<div class=\"tl-note\">No usage in the last 14 days · earlier activity exists</div>'\n : '<div class=\"empty-state\">No token usage in the last 14 days</div>';\n return cards + emptyNote;\n }\n\n const active = TL_SERIES.find((s) => s.key === timelineFilter) || null;\n const valueOf = (b) => active ? (b[active.key] || 0) : b.total;\n const max = Math.max(1, ...buckets.map(valueOf));\n const px = (v) => Math.round((v / max) * 180);\n\n const legend = '<div class=\"tl-legend\">' + TL_SERIES.map((s) => {\n const cls = 'tl-legend-item' +\n (active && active.key === s.key ? ' active' : (active ? ' dim' : ''));\n return '<span class=\"' + cls + '\" data-admin-action=\"toggle-timeline-filter\" data-filter-key=\"' + escAttr(s.key) + '\">' +\n '<i class=\"sw ' + s.sw + '\"></i>' + s.label + '</span>';\n }).join('') + '</div>';\n\n const bars = buckets.map((b) => {\n const val = valueOf(b);\n const tip = active\n ? b.date + ' · ' + active.label + ': ' + fmtNum(b[active.key] || 0) + ' tokens'\n : b.date + ' · ' + fmtNum(b.total) + ' tokens' +\n (b.cost > 0 ? ' · $' + Number(b.cost).toFixed(4) : '');\n let inner;\n if (val <= 0) {\n inner = '<span class=\"tl-empty\"></span>';\n } else if (active) {\n inner = '<span class=\"tl-seg ' + active.seg + '\" style=\"height:' + px(val) + 'px\"></span>';\n } else {\n inner = '<span class=\"tl-seg tl-output\" style=\"height:' + px(b.output) + 'px\"></span>' +\n '<span class=\"tl-seg tl-input\" style=\"height:' + px(b.input) + 'px\"></span>' +\n '<span class=\"tl-seg tl-cache-write\" style=\"height:' + px(b.cacheWrite) + 'px\"></span>' +\n '<span class=\"tl-seg tl-cache-read\" style=\"height:' + px(b.cacheRead) + 'px\"></span>';\n }\n return '<div class=\"tl-bar\">' +\n '<span class=\"tl-tip\">' + escHtml(tip) + '</span>' +\n '<div class=\"tl-fill\">' + inner + '</div>' +\n '</div>';\n }).join('');\n\n const axis = buckets.length\n ? '<div class=\"tl-axis\"><span>' + escHtml(buckets[0].date.slice(5)) +\n '</span><span>' + escHtml(buckets[buckets.length - 1].date.slice(5)) + '</span></div>'\n : '';\n const note = data.hasOlder\n ? '<div class=\"tl-note\">Showing last 14 days · earlier activity not shown</div>'\n : '<div class=\"tl-note\">Showing last 14 days</div>';\n const peak = '<div class=\"tl-peak\" style=\"bottom:180px\"><span class=\"tl-peak-label\">' +\n fmtNum(max) + ' tokens</span></div>';\n return cards + legend + '<div class=\"tl-chart\">' + peak + bars + '</div>' + axis + note;\n }\n\n function renderGlobalSettings(data) {\n const thinkingOpts = renderThinkingOptions(data.thinkingLevel);\n const replyModeOpts = renderReplyOptions(data.slack);\n return [\n '<div class=\"config-grid\">',\n renderConfigCard('Default model', [\n '<div class=\"config-row config-row-stack\"><label>Model</label><select id=\"g-model-ref\">' + renderModelOptions(data.provider, data.model) + '</select></div>',\n '<div class=\"config-row\"><label>Thinking</label><select id=\"g-thinking\">' + thinkingOpts + '</select></div>',\n ].join(''), 'saveGlobalModel', 'Save model', 'g-model-result'),\n renderConfigCard('Sandbox limits', [\n '<div class=\"config-row\"><label>CPUs</label><input id=\"g-cpus\" placeholder=\"0.5\" value=\"' + escAttr(data.sandboxCpus || '') + '\"></div>',\n '<div class=\"config-row\"><label>Memory</label><input id=\"g-mem\" placeholder=\"1g\" value=\"' + escAttr(data.sandboxMemory || '') + '\"></div>',\n '<div class=\"config-row\"><label>Boost CPUs</label><input id=\"g-bcpus\" placeholder=\"2\" value=\"' + escAttr(data.sandboxBoostCpus || '') + '\"></div>',\n '<div class=\"config-row\"><label>Boost Mem</label><input id=\"g-bmem\" placeholder=\"4g\" value=\"' + escAttr(data.sandboxBoostMemory || '') + '\"></div>',\n ].join(''), 'saveGlobalSandbox', 'Save sandbox', 'g-sandbox-result'),\n renderConfigCard('Slack', [\n '<div class=\"config-row\"><label>Reply mode</label><select id=\"g-slack-reply-mode\">' + replyModeOpts + '</select></div>',\n ].join(''), 'saveGlobalSlack', 'Save Slack', 'g-slack-result'),\n '</div>',\n ].join('');\n }\n\n async function saveGlobalModel(btn) {\n const selectedModel = parseModelRef(document.getElementById('g-model-ref').value.trim());\n const provider = selectedModel.provider;\n const model = selectedModel.model;\n const thinkingLevel = document.getElementById('g-thinking').value;\n const result = document.getElementById('g-model-result');\n if (!provider || !model) {\n result.style.display = 'block'; result.className = 'inline-result err';\n result.textContent = 'Provider and model are required'; return;\n }\n btn.disabled = true; btn.textContent = 'Saving…'; result.style.display = 'none';\n try {\n await apiPost('/admin/api/settings/model', { provider, model, thinkingLevel });\n result.style.display = 'block'; result.className = 'inline-result ok'; result.textContent = 'Saved ✓';\n } catch (err) {\n result.style.display = 'block'; result.className = 'inline-result err'; result.textContent = err.message;\n } finally {\n btn.disabled = false; btn.textContent = 'Save model';\n }\n }\n\n async function saveGlobalSandbox(btn) {\n const cpus = document.getElementById('g-cpus').value.trim();\n const memory = document.getElementById('g-mem').value.trim();\n const boostCpus = document.getElementById('g-bcpus').value.trim();\n const boostMemory = document.getElementById('g-bmem').value.trim();\n const result = document.getElementById('g-sandbox-result');\n await saveSetting(btn, result, 'settings/sandbox', { cpus, memory, boostCpus, boostMemory }, 'Save sandbox');\n }\n\n async function saveGlobalSlack(btn) {\n const replyMode = document.getElementById('g-slack-reply-mode').value;\n const result = document.getElementById('g-slack-result');\n await saveSetting(btn, result, 'settings/slack', { replyMode }, 'Save Slack');\n }\n\n async function loadGlobalSkills() {\n const container = document.getElementById('global-skills-content');\n container.innerHTML = '<div class=\"loading-msg\">Loading…</div>';\n try {\n // Reuse skills endpoint scoped to a conversation that doesn't have any of its own; the global half is what we want.\n const data = await apiGet('/admin/api/skills?' + scopeQuery());\n const globals = data.skills.filter((s) => s.source === 'global');\n if (globals.length === 0) {\n container.innerHTML = '<div class=\"empty-state\">No global skills</div>';\n return;\n }\n container.innerHTML = '<div class=\"skills-list\">' + globals.map((s) =>\n '<div class=\"skill-row\"><div class=\"skill-name\">' + escHtml(s.name) + '</div>' +\n (s.description ? '<div class=\"skill-desc\">' + escHtml(s.description) + '</div>' : '') + '</div>'\n ).join('') + '</div>';\n } catch (err) {\n container.innerHTML = '<div class=\"err-msg\">' + escHtml(err.message) + '</div>';\n }\n }\n\n // ── Init ─────────────────────────────────────────────────────────────────────\n\n // Sections load on demand (see initSections); loadSettingsPanel already\n // calls loadModels() itself the first time it's needed.\n initConvSwitcher();\n initSections();\n `;\n\nfunction renderAdminPage(token: AdminToken): string {\n const userLabel = token.platformUserName ?? token.platformUserId;\n const script = `\n const adminToken = ${JSON.stringify(token.token)};\n // Conversation ids never contain \":\" (session-key grammar), so\n // \"platform:id\" is a safe composite scope key for the UI.\n const defaultConversationKey = ${JSON.stringify(`${token.platform}:${token.conversationId}`)};\n${adminViewScript}`;\n\n return renderPortalShell({\n activeView: \"admin\",\n pageTitle: \"Admin\",\n identity: { primary: token.platform, secondary: userLabel },\n conversationSwitcher: { currentId: token.conversationId },\n body: adminViewBody,\n extraStyles: adminViewStyles,\n inlineScript: script,\n });\n}\n\nfunction renderAdminErrorPage(message: string): string {\n return renderPortalShell({\n activeView: \"admin\",\n pageTitle: \"Admin\",\n body: `<section class=\"card\" style=\"text-align:center;padding:40px 32px\">\n <p class=\"eyebrow\">${PRODUCT_NAME} admin</p>\n <h1 class=\"page-title\" style=\"margin:12px 0 16px\">Access Denied</h1>\n <div class=\"err-msg\">${esc(message)}</div>\n </section>`,\n });\n}\n\n// ── Styles ─────────────────────────────────────────────────────────────────────\n\nconst adminViewStyles = `\n .tab-nav {\n display: flex; gap: 6px; padding: 6px;\n border: 1px solid var(--border); border-radius: 16px;\n background: rgba(255,255,255,0.72); backdrop-filter: blur(8px);\n overflow-x: auto; scrollbar-width: none;\n }\n .tab-nav::-webkit-scrollbar { display: none; }\n .tab-btn {\n flex: 1; min-width: 80px; padding: 10px 16px;\n border: none; border-radius: 10px; background: transparent;\n color: var(--muted);\n font: 500 0.88rem/1.2 'DM Sans', sans-serif;\n cursor: pointer; white-space: nowrap;\n transition: background 140ms, color 140ms;\n }\n .tab-btn:hover { background: rgba(0,0,0,0.04); color: var(--text); }\n .tab-btn.active { background: var(--text); color: #fafafa; font-weight: 600; }\n .tab-btn:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }\n\n .tab-panel { display: none; flex-direction: column; gap: 14px; }\n .tab-panel.active { display: flex; }\n\n .card-desc { color: var(--muted); font-size: 0.9rem; line-height: 1.55; margin-bottom: 12px; }\n\n .link-result {\n margin-top: 12px; padding: 10px 14px; border-radius: 10px;\n display: flex; gap: 10px; align-items: center; flex-wrap: wrap;\n font-size: 0.84rem;\n }\n .link-result.ok { background: var(--ok-bg); border: 1px solid var(--ok-border); }\n .link-result.err { background: var(--err-bg); border: 1px solid var(--err-border); color: var(--err-text); }\n .link-result.loading { background: rgba(0,0,0,0.025); border: 1px solid var(--border); color: var(--muted); }\n .link-result a {\n color: var(--ok-text);\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.78rem; word-break: break-all; flex: 1; min-width: 0;\n }\n .link-vault { color: var(--muted); font-size: 0.78rem; flex-shrink: 0; }\n .copy-link-btn {\n padding: 5px 12px; border: 1px solid var(--ok-border); border-radius: 7px;\n background: rgba(255,255,255,0.7); color: var(--ok-text);\n font: 500 0.78rem/1.2 'DM Sans', sans-serif;\n cursor: pointer; flex-shrink: 0;\n }\n\n .config-grid {\n display: grid; grid-template-columns: 1fr 1fr; gap: 18px;\n }\n .config-block { display: flex; flex-direction: column; gap: 10px; }\n .config-row { display: grid; grid-template-columns: 110px 1fr; gap: 10px; align-items: center; }\n .config-row.config-row-stack { grid-template-columns: 1fr; }\n .config-row label { font-size: 0.82rem; color: var(--muted); }\n .config-row input, .config-row select, .config-row textarea {\n padding: 7px 10px; border: 1px solid var(--border); border-radius: 8px;\n font-family: inherit; font-size: 0.84rem; width: 100%;\n }\n .config-row textarea {\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n resize: vertical;\n }\n .toggle { display: inline-flex; align-items: center; gap: 8px; font-size: 0.84rem; }\n\n .inline-result {\n padding: 8px 12px; border-radius: 8px; font-size: 0.82rem; margin-top: 4px;\n }\n .inline-result.ok { background: var(--ok-bg); color: var(--ok-text); border: 1px solid var(--ok-border); }\n .inline-result.err { background: var(--err-bg); color: var(--err-text); border: 1px solid var(--err-border); }\n\n /* ── Sections (Conversation page stack) ─────────────────────────────── */\n\n /* Each section is a <details>; the card's own padding sits on the\n <summary>/<div class=\"sect-body\"> instead of the <details> element so a\n collapsed section is just its header row. */\n details.sect { padding: 0; overflow: hidden; }\n details.sect > summary.sect-head {\n padding: 24px 28px; margin-bottom: 0; cursor: pointer; list-style: none;\n }\n details.sect > summary.sect-head::-webkit-details-marker { display: none; }\n details.sect > .sect-body { padding: 0 28px 24px; }\n\n .sect-head {\n display: flex; align-items: flex-start; justify-content: space-between;\n gap: 12px; flex-wrap: wrap;\n }\n .sect-head .card-title { margin-bottom: 0; }\n .sect-title { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }\n .sect-caret {\n flex-shrink: 0; margin-top: 3px; color: var(--subtle);\n transition: transform 140ms; display: inline-block;\n }\n details.sect[open] > summary.sect-head .sect-caret { transform: rotate(90deg); }\n .sect-disabled { opacity: 0.7; }\n\n .refresh-btn {\n flex-shrink: 0; padding: 6px 12px;\n border: 1px solid var(--border); border-radius: 10px;\n background: rgba(0,0,0,0.025); color: var(--muted);\n font: 500 0.84rem/1.2 'DM Sans', sans-serif; cursor: pointer;\n }\n .refresh-btn:hover { background: rgba(0,0,0,0.06); color: var(--text); }\n\n /* ── Workspace ──────────────────────────────────────────────────────── */\n\n .workspace-split {\n display: grid; grid-template-columns: 260px 1fr; gap: 14px;\n min-height: 360px;\n }\n .workspace-tree {\n border: 1px solid var(--border); border-radius: 12px; padding: 10px;\n background: rgba(0,0,0,0.02); overflow: auto; max-height: 480px;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.78rem;\n }\n .workspace-tree ul { list-style: none; padding-left: 12px; margin: 0; }\n .workspace-tree .tree-root { padding-left: 0; }\n .workspace-tree details { margin: 1px 0; }\n .workspace-tree summary { cursor: pointer; padding: 2px 4px; border-radius: 4px; }\n .workspace-tree summary:hover { background: rgba(0,0,0,0.05); }\n .tree-dir { color: var(--text); font-weight: 600; }\n .tree-dir.empty { color: var(--subtle); font-weight: 400; }\n .tree-file {\n display: block; width: 100%; text-align: left;\n background: transparent; border: none; cursor: pointer;\n padding: 2px 4px; border-radius: 4px;\n font-family: inherit; font-size: inherit; color: var(--muted);\n }\n .tree-file:hover { background: rgba(0,0,0,0.05); color: var(--text); }\n\n .workspace-preview {\n border: 1px solid var(--border); border-radius: 12px;\n background: #fff; padding: 12px; overflow: auto; max-height: 480px;\n }\n .preview-meta {\n font-size: 0.74rem; color: var(--subtle);\n margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--border);\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n }\n .preview-body {\n margin: 0; white-space: pre-wrap; word-break: break-word;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.78rem; color: var(--text);\n }\n .placeholder-msg { color: var(--subtle); font-size: 0.86rem; padding: 24px 8px; text-align: center; }\n\n /* ── Skills ─────────────────────────────────────────────────────────── */\n\n .skills-list { display: flex; flex-direction: column; gap: 8px; }\n .skill-row {\n padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;\n background: rgba(0,0,0,0.02);\n }\n .skill-row-btn {\n width: 100%; text-align: left; cursor: pointer; font-family: inherit;\n }\n .skill-row-btn:hover { background: rgba(0,0,0,0.05); }\n .skill-name {\n font-weight: 650; font-size: 0.9rem; color: var(--text);\n display: flex; align-items: center; gap: 8px; flex-wrap: wrap;\n }\n .skill-source {\n padding: 1px 8px; border-radius: 999px; font-size: 0.7rem;\n font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;\n }\n .skill-source-global { background: rgba(59,130,246,0.1); color: #1d4ed8; }\n .skill-source-conversation { background: rgba(217,119,6,0.1); color: var(--accent); }\n .skill-desc { color: var(--muted); font-size: 0.82rem; margin-top: 4px; line-height: 1.5; }\n\n /* ── MCP marketplace ───────────────────────────────────────────────── */\n\n .form-input {\n flex: 1 1 240px; min-width: 0; padding: 8px 10px;\n border: 1px solid var(--border); border-radius: 8px; background: var(--card);\n font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.8rem; color: var(--text);\n }\n .status-msg {\n padding: 8px 12px; border-radius: 8px; margin-bottom: 12px;\n font-size: 0.82rem; line-height: 1.5; word-break: break-word;\n }\n .status-msg-ok { background: rgba(22,163,74,0.1); color: #15803d; }\n .status-msg-err { background: rgba(220,38,38,0.1); color: #b91c1c; }\n .status-msg-busy { background: rgba(0,0,0,0.05); color: var(--muted); }\n .mcp-btn {\n padding: 4px 10px; border: 1px solid var(--border); border-radius: 7px;\n background: var(--card); color: var(--text); font-size: 0.76rem; cursor: pointer;\n }\n .mcp-btn:hover { background: rgba(0,0,0,0.05); }\n .mcp-btn-danger { color: #b91c1c; }\n .mcp-badge {\n padding: 1px 8px; border-radius: 999px; font-size: 0.7rem;\n font-weight: 600; letter-spacing: 0.03em;\n }\n .mcp-badge-ok { background: rgba(22,163,74,0.1); color: #15803d; }\n .mcp-badge-warn { background: rgba(217,119,6,0.12); color: var(--accent); }\n\n .mcp-market-head {\n display: flex; align-items: end; justify-content: space-between; gap: 16px;\n margin-bottom: 12px;\n }\n .mcp-market-head h3 { margin: 0; font-size: 1rem; }\n .mcp-market-head p { margin: 3px 0 0; color: var(--muted); font-size: 0.8rem; }\n .mcp-market-head > span {\n color: var(--subtle); font: 500 0.72rem/1.2 'JetBrains Mono', ui-monospace, monospace;\n }\n .mcp-preset-grid {\n display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px;\n }\n .mcp-preset {\n min-width: 0; padding: 14px; border: 1px solid var(--border); border-radius: 12px;\n background:\n linear-gradient(145deg, rgba(255,255,255,0.85), rgba(247,245,238,0.72)),\n var(--card);\n display: flex; flex-direction: column; min-height: 190px;\n }\n .mcp-preset-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n .mcp-preset-category {\n color: var(--accent); font-size: 0.66rem; font-weight: 700;\n letter-spacing: 0.09em; text-transform: uppercase;\n }\n .mcp-preset h3 { margin: 14px 0 5px; font-size: 1.06rem; letter-spacing: -0.02em; }\n .mcp-preset p { margin: 0; color: var(--muted); font-size: 0.8rem; line-height: 1.5; }\n .mcp-preset-meta {\n margin-top: 12px; color: var(--subtle); font-size: 0.72rem;\n }\n .mcp-preset-meta code { color: var(--text); background: transparent; }\n .mcp-preset-actions {\n margin-top: auto; padding-top: 16px; display: flex; align-items: center;\n justify-content: space-between; gap: 10px;\n }\n .mcp-preset-actions a, .mcp-setup-link {\n color: var(--muted); font-size: 0.76rem; text-decoration: none;\n }\n .mcp-preset-actions a:hover, .mcp-setup-link:hover { color: var(--text); text-decoration: underline; }\n .mcp-preset-actions .primary-action-btn { padding: 6px 12px; }\n .mcp-installed {\n margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px;\n }\n .mcp-installed > summary {\n cursor: pointer; color: var(--muted); font-size: 0.8rem; font-weight: 600;\n margin-bottom: 10px;\n }\n .mcp-installed-card { min-height: 150px; }\n .mcp-installed-transport {\n font-family: 'JetBrains Mono', ui-monospace, monospace; word-break: break-all;\n }\n .mcp-add-card {\n min-width: 0; min-height: 190px; border: 1.5px dashed var(--border); border-radius: 12px;\n background: transparent; color: var(--muted); font-size: 0.86rem; font-weight: 600;\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n gap: 8px; cursor: pointer;\n }\n .mcp-add-card:hover { background: rgba(0,0,0,0.03); color: var(--text); border-color: var(--text); }\n .mcp-add-card-plus { font-size: 1.8rem; line-height: 1; font-weight: 400; }\n .mcp-manual-hint { color: var(--muted); font-size: 0.8rem; margin: 0 0 10px; line-height: 1.5; }\n .mcp-add-mode { display: flex; gap: 8px; margin: 0 0 12px; }\n .mcp-mode-btn {\n padding: 6px 12px; border: 1px solid var(--border); border-radius: 999px;\n background: var(--card); font-size: 0.8rem; cursor: pointer; color: var(--muted);\n }\n .mcp-mode-btn.active { background: var(--text); color: #fafafa; border-color: var(--text); }\n .mcp-guided-grid { display: grid; gap: 12px; }\n .mcp-field { display: grid; gap: 5px; }\n .mcp-field span { font-size: 0.78rem; font-weight: 650; color: var(--muted); }\n .mcp-transport-toggle { display: flex; gap: 16px; font-size: 0.85rem; align-items: center; }\n .mcp-transport-toggle label { display: flex; align-items: center; gap: 6px; cursor: pointer; }\n .mcp-transport-fields { display: grid; gap: 12px; }\n .mcp-kv-block { display: grid; gap: 8px; }\n .mcp-kv-head { display: flex; align-items: center; justify-content: space-between; }\n .mcp-kv-head span { font-size: 0.78rem; font-weight: 650; color: var(--muted); }\n .mcp-kv-rows { display: grid; gap: 6px; }\n .mcp-kv-row { display: flex; gap: 8px; }\n .mcp-kv-row .form-input { flex: 1 1 auto; }\n .mcp-json {\n width: 100%; box-sizing: border-box; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.8rem; line-height: 1.45; resize: vertical;\n }\n .mcp-verify { margin-top: 10px; display: grid; gap: 6px; }\n .mcp-verify .inline-result { word-break: break-word; }\n\n .mcp-dialog {\n width: min(620px, calc(100vw - 28px)); max-height: calc(100vh - 40px);\n border: 1px solid var(--border); border-radius: 18px; padding: 22px;\n color: var(--text); background: #fbfaf6; box-shadow: 0 28px 90px rgba(18,18,16,0.24);\n /* The shell's global \"* { margin: 0 }\" reset overrides the UA stylesheet's\n dialog[open] { margin: auto } centering rule, so pin it back explicitly. */\n position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0;\n }\n .mcp-dialog::backdrop { background: rgba(20,20,18,0.5); backdrop-filter: blur(3px); }\n .mcp-dialog-head {\n display: flex; align-items: flex-start; justify-content: space-between; gap: 18px;\n padding-bottom: 14px; border-bottom: 1px solid var(--border);\n }\n .mcp-dialog-head .card-title { margin: 4px 0 0; }\n .mcp-dialog-close {\n border: 0; background: transparent; color: var(--muted); cursor: pointer;\n font-size: 1.6rem; line-height: 1; padding: 2px 5px;\n }\n .mcp-dialog-desc { color: var(--muted); line-height: 1.55; font-size: 0.88rem; }\n .mcp-install-preview {\n display: grid; gap: 6px; padding: 12px; border-radius: 10px;\n background: #1d211f; color: #f5f2e9;\n }\n .mcp-install-preview span {\n color: #aeb8b1; font-size: 0.66rem; font-weight: 700;\n letter-spacing: 0.08em; text-transform: uppercase;\n }\n .mcp-install-preview code {\n color: #f5f2e9; background: transparent; word-break: break-all; font-size: 0.78rem;\n }\n .mcp-security-note, .mcp-replace-note {\n margin-top: 12px; padding: 10px 12px; border-radius: 9px;\n font-size: 0.78rem; line-height: 1.5;\n }\n .mcp-security-note.local {\n background: rgba(220,38,38,0.08); border: 1px solid rgba(185,28,28,0.18); color: #991b1b;\n }\n .mcp-security-note.remote {\n background: rgba(59,130,246,0.08); border: 1px solid rgba(29,78,216,0.16); color: #1e40af;\n }\n .mcp-replace-note { background: rgba(217,119,6,0.1); color: #92400e; }\n .mcp-credential { display: grid; gap: 5px; margin-top: 14px; }\n .mcp-credential span { font-size: 0.78rem; font-weight: 650; }\n .mcp-credential input {\n width: 100%; padding: 9px 10px; border: 1px solid var(--border); border-radius: 8px;\n background: #fff; color: var(--text); font: 0.8rem/1.3 'JetBrains Mono', ui-monospace, monospace;\n }\n .mcp-credential small, .mcp-secret-note, .mcp-no-credentials {\n color: var(--subtle); font-size: 0.72rem; line-height: 1.45;\n }\n .mcp-secret-note { margin: 14px 0 8px; }\n .mcp-no-credentials { margin: 14px 0 0; }\n #mcp-dialog-error, #mcp-custom-dialog-error { margin-top: 14px; }\n .mcp-dialog-actions {\n display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px;\n padding-top: 14px; border-top: 1px solid var(--border);\n }\n\n /* ── Events ─────────────────────────────────────────────────────────── */\n\n .events-list { display: flex; flex-direction: column; gap: 8px; }\n .event-row {\n padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;\n background: rgba(0,0,0,0.02);\n }\n .event-row-top {\n display: flex; align-items: center; justify-content: space-between;\n gap: 10px;\n }\n .event-name { min-width: 0; flex: 1; word-break: break-all; }\n .event-name code { font-size: 0.82rem; background: transparent; padding: 0; }\n .event-meta { font-size: 0.74rem; color: var(--muted); margin-top: 3px; }\n .event-text {\n font-size: 0.82rem; color: var(--text); margin-top: 6px;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n white-space: pre-wrap; word-break: break-word;\n }\n .event-delete-btn {\n flex-shrink: 0; padding: 4px 10px;\n border-radius: 7px; border: 1px solid rgba(185, 28, 28, 0.18);\n background: rgba(0,0,0,0.03); color: var(--err-text);\n font: 500 0.76rem/1.2 'DM Sans', sans-serif; cursor: pointer;\n }\n .event-delete-btn:hover:not(:disabled) {\n background: var(--err-bg); border-color: rgba(185, 28, 28, 0.28);\n }\n .event-delete-btn:disabled { opacity: 0.5; cursor: wait; }\n\n /* ── All Conversations list ─────────────────────────────────────────── */\n\n .conv-list { display: flex; flex-direction: column; gap: 6px; }\n .conv-row-btn {\n display: flex; align-items: center; gap: 12px;\n padding: 10px 14px; border: 1px solid var(--border); border-radius: 10px;\n background: rgba(0,0,0,0.02); cursor: pointer; text-align: left;\n transition: background 120ms, border-color 120ms;\n }\n .conv-row-btn:hover { background: rgba(0,0,0,0.05); border-color: rgba(0,0,0,0.14); }\n .conv-id { flex: 1; font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.84rem; }\n .conv-last { color: var(--subtle); font-size: 0.78rem; }\n\n .usage-table-wrap { overflow-x: auto; }\n .usage-table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }\n .usage-table th, .usage-table td {\n padding: 8px 10px; border-bottom: 1px solid var(--border);\n text-align: left; white-space: nowrap;\n }\n .usage-table th {\n color: var(--subtle); font-size: 0.68rem;\n text-transform: uppercase; letter-spacing: 0.08em;\n }\n .usage-table code { font-size: 0.72rem; }\n\n .timeline-controls {\n display: flex; flex-wrap: wrap; gap: 18px; align-items: center; margin-bottom: 16px;\n }\n .timeline-controls label {\n display: flex; align-items: center; gap: 8px;\n font-size: 0.76rem; color: var(--muted);\n }\n .timeline-controls select {\n padding: 6px 10px; border: 1px solid var(--border); border-radius: 8px;\n background: #fff; color: var(--text); font-size: 0.8rem; max-width: 240px;\n }\n .tl-note { margin-top: 8px; font-size: 0.72rem; color: var(--subtle); }\n .tl-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 16px; }\n .tl-card { background: rgba(0,0,0,0.025); border-radius: 10px; padding: 10px 12px; }\n .tl-card-label { font-size: 0.73rem; color: var(--muted); margin-bottom: 4px; }\n .tl-card-value { font-size: 1.35rem; font-weight: 600; color: var(--text); }\n .tl-legend { display: flex; gap: 16px; font-size: 0.73rem; color: var(--muted); margin-bottom: 10px; }\n .tl-legend span { display: inline-flex; align-items: center; gap: 6px; }\n .tl-legend-item { cursor: pointer; transition: opacity 100ms; }\n .tl-legend-item:hover { text-decoration: underline; }\n .tl-legend-item.active { color: var(--text); font-weight: 600; text-decoration: underline; }\n .tl-legend-item.dim { opacity: 0.4; }\n .sw { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }\n .sw-cache-read { background: rgba(0,0,0,0.18); }\n .sw-cache-write { background: #3b6fb0; }\n .sw-input { background: var(--accent); }\n .sw-output { background: var(--ok-text); }\n .tl-chart {\n position: relative;\n display: flex; align-items: flex-end; gap: 6px;\n height: 216px; border-bottom: 1px solid var(--border);\n }\n .tl-peak {\n position: absolute; left: 0; right: 0; height: 0;\n border-top: 1px dashed var(--accent); pointer-events: none;\n }\n .tl-peak-label {\n position: absolute; left: 0; top: -15px;\n font-size: 0.7rem; color: var(--accent); white-space: nowrap;\n }\n .tl-bar {\n position: relative; flex: 1; min-width: 0; height: 180px;\n display: flex; flex-direction: column; justify-content: flex-end;\n border-radius: 6px 6px 0 0; transition: background 80ms ease;\n }\n .tl-bar:hover { background: rgba(0,0,0,0.06); }\n .tl-bar:hover .tl-seg { opacity: 0.55; }\n .tl-fill {\n display: flex; flex-direction: column; justify-content: flex-end;\n border-radius: 3px 3px 0 0; overflow: hidden;\n }\n .tl-tip {\n position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%);\n margin-bottom: 6px; padding: 5px 9px; border-radius: 6px;\n background: var(--text); color: #fafafa; font-size: 0.7rem; line-height: 1.35;\n white-space: nowrap; opacity: 0; pointer-events: none; z-index: 5;\n }\n .tl-bar:hover .tl-tip { opacity: 1; }\n .tl-seg { display: block; width: 100%; }\n .tl-seg.tl-output { background: var(--ok-text); }\n .tl-seg.tl-input { background: var(--accent); }\n .tl-seg.tl-cache-write { background: #3b6fb0; }\n .tl-seg.tl-cache-read { background: rgba(0,0,0,0.18); }\n .tl-empty { display: block; width: 100%; height: 3px; background: var(--border); }\n .tl-axis { display: flex; justify-content: space-between; margin-top: 6px; font-size: 0.7rem; color: var(--subtle); }\n\n .status-pill {\n display: inline-flex; padding: 2px 9px; border-radius: 999px;\n font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;\n }\n .status-pill.running { background: var(--ok-bg); color: var(--ok-text); border: 1px solid var(--ok-border); }\n\n @media (max-width: 640px) {\n .tab-btn { padding: 9px 12px; font-size: 0.82rem; min-width: 60px; }\n .config-grid { grid-template-columns: 1fr; }\n .config-row { grid-template-columns: 1fr; gap: 4px; }\n .workspace-split { grid-template-columns: 1fr; }\n .workspace-tree, .workspace-preview { max-height: 260px; }\n .mcp-preset-grid { grid-template-columns: 1fr; }\n .mcp-dialog { padding: 18px; }\n }\n`;\n"]}
|