@geminixiang/mikan 0.3.2 → 0.4.0
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 +36 -0
- package/dist/adapter.d.ts +1 -138
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +1 -4
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +25 -33
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +28 -0
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/discord/types.d.ts +6 -0
- package/dist/adapters/discord/types.d.ts.map +1 -0
- package/dist/adapters/discord/types.js +2 -0
- package/dist/adapters/discord/types.js.map +1 -0
- package/dist/adapters/intake.d.ts +11 -0
- package/dist/adapters/intake.d.ts.map +1 -0
- package/dist/adapters/intake.js +42 -0
- package/dist/adapters/intake.js.map +1 -0
- package/dist/adapters/shared.d.ts +7 -31
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +18 -2
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +14 -33
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +148 -116
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts +3 -4
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +97 -14
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +5 -20
- package/dist/adapters/slack/session.d.ts.map +1 -1
- package/dist/adapters/slack/session.js.map +1 -1
- package/dist/adapters/slack/types.d.ts +84 -0
- package/dist/adapters/slack/types.d.ts.map +1 -0
- package/dist/adapters/slack/types.js +2 -0
- package/dist/adapters/slack/types.js.map +1 -0
- package/dist/adapters/streaming.d.ts +18 -0
- package/dist/adapters/streaming.d.ts.map +1 -0
- package/dist/adapters/streaming.js +44 -0
- package/dist/adapters/streaming.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts +1 -4
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +32 -39
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +33 -0
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/telegram/types.d.ts +6 -0
- package/dist/adapters/telegram/types.d.ts.map +1 -0
- package/dist/adapters/telegram/types.js +2 -0
- package/dist/adapters/telegram/types.js.map +1 -0
- package/dist/adapters/types.d.ts +58 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/agent.d.ts +4 -16
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +33 -20
- package/dist/agent.js.map +1 -1
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +1 -1
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/auto-reply.d.ts.map +1 -1
- package/dist/commands/auto-reply.js +1 -8
- package/dist/commands/auto-reply.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/model.d.ts +5 -8
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +15 -20
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +5 -10
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/parse.d.ts.map +1 -1
- package/dist/commands/parse.js +1 -4
- package/dist/commands/parse.js.map +1 -1
- package/dist/commands/registry.d.ts +1 -0
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +23 -0
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/sandbox.d.ts +2 -5
- package/dist/commands/sandbox.d.ts.map +1 -1
- package/dist/commands/sandbox.js +11 -16
- package/dist/commands/sandbox.js.map +1 -1
- package/dist/commands/session-view.d.ts.map +1 -1
- package/dist/commands/session-view.js +10 -15
- package/dist/commands/session-view.js.map +1 -1
- package/dist/commands/types.d.ts +11 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/config.d.ts +6 -28
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -41
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +1 -15
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +4 -44
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +24 -43
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts +3 -7
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +8 -8
- package/dist/execution-resolver.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +2 -6
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +1 -37
- package/dist/log.js.map +1 -1
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +16 -16
- package/dist/main.js.map +1 -1
- package/dist/observability/instrument.d.ts.map +1 -0
- package/dist/{instrument.js → observability/instrument.js} +2 -2
- package/dist/observability/instrument.js.map +1 -0
- package/dist/{sentry.d.ts → observability/sentry.d.ts} +10 -30
- package/dist/observability/sentry.d.ts.map +1 -0
- package/dist/{sentry.js → observability/sentry.js} +70 -6
- package/dist/observability/sentry.js.map +1 -0
- package/dist/observability/types.d.ts +47 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/observability/types.js +2 -0
- package/dist/observability/types.js.map +1 -0
- package/dist/{ui-copy.d.ts → platform-messages.d.ts} +1 -1
- package/dist/platform-messages.d.ts.map +1 -0
- package/dist/{ui-copy.js → platform-messages.js} +1 -1
- package/dist/platform-messages.js.map +1 -0
- package/dist/portal-shell.d.ts +2 -28
- package/dist/portal-shell.d.ts.map +1 -1
- package/dist/portal-shell.js +2 -2
- package/dist/portal-shell.js.map +1 -1
- package/dist/provisioner.d.ts +2 -23
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +1 -1
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/conversation-orchestrator.d.ts +4 -19
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -1
- package/dist/runtime/conversation-orchestrator.js +17 -8
- package/dist/runtime/conversation-orchestrator.js.map +1 -1
- package/dist/runtime/session-runtime.d.ts +2 -23
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/session-runtime.js +7 -9
- package/dist/runtime/session-runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +35 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -1
- package/dist/sandbox/cloudflare.js +1 -1
- package/dist/sandbox/cloudflare.js.map +1 -1
- package/dist/sandbox/container.d.ts.map +1 -1
- package/dist/sandbox/container.js +1 -4
- package/dist/sandbox/container.js.map +1 -1
- package/dist/sessions/chat-session-manager.d.ts +2 -46
- package/dist/sessions/chat-session-manager.d.ts.map +1 -1
- package/dist/sessions/chat-session-manager.js +12 -40
- package/dist/sessions/chat-session-manager.js.map +1 -1
- package/dist/sessions/metadata.d.ts +1 -13
- package/dist/sessions/metadata.d.ts.map +1 -1
- package/dist/sessions/metadata.js.map +1 -1
- package/dist/sessions/policy.d.ts +3 -10
- package/dist/sessions/policy.d.ts.map +1 -1
- package/dist/sessions/policy.js.map +1 -1
- package/dist/sessions/store.d.ts +1 -12
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +4 -7
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +76 -0
- package/dist/sessions/types.d.ts.map +1 -0
- package/dist/sessions/types.js +2 -0
- package/dist/sessions/types.js.map +1 -0
- package/dist/store.d.ts +2 -19
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +1 -1
- package/dist/store.js.map +1 -1
- package/dist/tools/event.d.ts +30 -36
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +207 -26
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +1 -1
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/truncate.d.ts +2 -26
- package/dist/tools/truncate.d.ts.map +1 -1
- package/dist/tools/truncate.js.map +1 -1
- package/dist/tools/types.d.ts +54 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/trigger.d.ts +2 -13
- package/dist/trigger.d.ts.map +1 -1
- package/dist/trigger.js.map +1 -1
- package/dist/types.d.ts +307 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/date.d.ts +10 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +23 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/file-guards.d.ts.map +1 -0
- package/dist/utils/file-guards.js.map +1 -0
- package/dist/utils/fs-atomic.d.ts.map +1 -0
- package/dist/utils/fs-atomic.js.map +1 -0
- package/dist/utils/html.d.ts.map +1 -0
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/http-body.d.ts +10 -0
- package/dist/utils/http-body.d.ts.map +1 -0
- package/dist/utils/http-body.js +34 -0
- package/dist/utils/http-body.js.map +1 -0
- package/dist/vault/index.d.ts +34 -0
- package/dist/vault/index.d.ts.map +1 -0
- package/dist/{vault.js → vault/index.js} +4 -4
- package/dist/vault/index.js.map +1 -0
- package/dist/{vault-routing.d.ts → vault/routing.d.ts} +2 -2
- package/dist/vault/routing.d.ts.map +1 -0
- package/dist/{vault-routing.js → vault/routing.js} +2 -2
- package/dist/vault/routing.js.map +1 -0
- package/dist/{vault.d.ts → vault/types.d.ts} +3 -34
- package/dist/vault/types.d.ts.map +1 -0
- package/dist/vault/types.js +2 -0
- package/dist/vault/types.js.map +1 -0
- package/dist/web/admin/portal.d.ts +5 -0
- package/dist/web/admin/portal.d.ts.map +1 -0
- package/dist/{admin → web/admin}/portal.js +140 -52
- package/dist/web/admin/portal.js.map +1 -0
- package/dist/web/admin/store.d.ts +13 -0
- package/dist/web/admin/store.d.ts.map +1 -0
- package/dist/web/admin/store.js +23 -0
- package/dist/web/admin/store.js.map +1 -0
- package/dist/web/admin/types.d.ts +28 -0
- package/dist/web/admin/types.d.ts.map +1 -0
- package/dist/web/admin/types.js +2 -0
- package/dist/web/admin/types.js.map +1 -0
- package/dist/web/login/oauth.d.ts +6 -0
- package/dist/web/login/oauth.d.ts.map +1 -0
- package/dist/{login/index.js → web/login/oauth.js} +107 -106
- package/dist/web/login/oauth.js.map +1 -0
- package/dist/{login → web/login}/portal.d.ts +5 -5
- package/dist/web/login/portal.d.ts.map +1 -0
- package/dist/{login → web/login}/portal.js +16 -35
- package/dist/web/login/portal.js.map +1 -0
- package/dist/web/login/store.d.ts +12 -0
- package/dist/web/login/store.d.ts.map +1 -0
- package/dist/web/login/store.js +28 -0
- package/dist/web/login/store.js.map +1 -0
- package/dist/web/login/types.d.ts +50 -0
- package/dist/web/login/types.d.ts.map +1 -0
- package/dist/web/login/types.js +2 -0
- package/dist/web/login/types.js.map +1 -0
- package/dist/web/session-view/command.d.ts +4 -0
- package/dist/web/session-view/command.d.ts.map +1 -0
- package/dist/{session-view → web/session-view}/command.js +1 -1
- package/dist/web/session-view/command.js.map +1 -0
- package/dist/{session-view → web/session-view}/portal.d.ts +2 -5
- package/dist/web/session-view/portal.d.ts.map +1 -0
- package/dist/{session-view → web/session-view}/portal.js +5 -5
- package/dist/web/session-view/portal.js.map +1 -0
- package/dist/web/session-view/service.d.ts +6 -0
- package/dist/web/session-view/service.d.ts.map +1 -0
- package/dist/{session-view → web/session-view}/service.js +6 -36
- package/dist/web/session-view/service.js.map +1 -0
- package/dist/web/session-view/store.d.ts +8 -0
- package/dist/web/session-view/store.d.ts.map +1 -0
- package/dist/web/session-view/store.js +20 -0
- package/dist/web/session-view/store.js.map +1 -0
- package/dist/{session-view/service.d.ts → web/session-view/types.d.ts} +20 -4
- package/dist/web/session-view/types.d.ts.map +1 -0
- package/dist/web/session-view/types.js +2 -0
- package/dist/web/session-view/types.js.map +1 -0
- package/dist/web/token-store.d.ts +19 -0
- package/dist/web/token-store.d.ts.map +1 -0
- package/dist/web/token-store.js +45 -0
- package/dist/web/token-store.js.map +1 -0
- package/dist/web/types.d.ts +5 -0
- package/dist/web/types.d.ts.map +1 -0
- package/dist/web/types.js +2 -0
- package/dist/web/types.js.map +1 -0
- package/package.json +1 -1
- package/dist/adapters/discord/index.d.ts +0 -3
- package/dist/adapters/discord/index.d.ts.map +0 -1
- package/dist/adapters/discord/index.js +0 -3
- package/dist/adapters/discord/index.js.map +0 -1
- package/dist/adapters/slack/index.d.ts +0 -3
- package/dist/adapters/slack/index.d.ts.map +0 -1
- package/dist/adapters/slack/index.js +0 -3
- package/dist/adapters/slack/index.js.map +0 -1
- package/dist/adapters/slack/thread-manager.d.ts +0 -19
- package/dist/adapters/slack/thread-manager.d.ts.map +0 -1
- package/dist/adapters/slack/thread-manager.js +0 -11
- package/dist/adapters/slack/thread-manager.js.map +0 -1
- package/dist/adapters/telegram/index.d.ts +0 -3
- package/dist/adapters/telegram/index.d.ts.map +0 -1
- package/dist/adapters/telegram/index.js +0 -3
- package/dist/adapters/telegram/index.js.map +0 -1
- package/dist/admin/portal.d.ts +0 -27
- package/dist/admin/portal.d.ts.map +0 -1
- package/dist/admin/portal.js.map +0 -1
- package/dist/admin/store.d.ts +0 -22
- package/dist/admin/store.d.ts.map +0 -1
- package/dist/admin/store.js +0 -39
- package/dist/admin/store.js.map +0 -1
- package/dist/commands/index.d.ts +0 -5
- package/dist/commands/index.d.ts.map +0 -1
- package/dist/commands/index.js +0 -20
- package/dist/commands/index.js.map +0 -1
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js.map +0 -1
- package/dist/file-guards.d.ts.map +0 -1
- package/dist/file-guards.js.map +0 -1
- package/dist/fs-atomic.d.ts.map +0 -1
- package/dist/fs-atomic.js.map +0 -1
- package/dist/html.d.ts.map +0 -1
- package/dist/html.js.map +0 -1
- package/dist/instrument.d.ts.map +0 -1
- package/dist/instrument.js.map +0 -1
- package/dist/login/index.d.ts +0 -43
- package/dist/login/index.d.ts.map +0 -1
- package/dist/login/index.js.map +0 -1
- package/dist/login/portal.d.ts.map +0 -1
- package/dist/login/portal.js.map +0 -1
- package/dist/login/store.d.ts +0 -26
- package/dist/login/store.d.ts.map +0 -1
- package/dist/login/store.js +0 -56
- package/dist/login/store.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -2
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js +0 -2
- package/dist/runtime/index.js.map +0 -1
- package/dist/sentry.d.ts.map +0 -1
- package/dist/sentry.js.map +0 -1
- package/dist/session-view/command.d.ts +0 -5
- package/dist/session-view/command.d.ts.map +0 -1
- package/dist/session-view/command.js.map +0 -1
- package/dist/session-view/portal.d.ts.map +0 -1
- package/dist/session-view/portal.js.map +0 -1
- package/dist/session-view/service.d.ts.map +0 -1
- package/dist/session-view/service.js.map +0 -1
- package/dist/session-view/store.d.ts +0 -18
- package/dist/session-view/store.d.ts.map +0 -1
- package/dist/session-view/store.js +0 -36
- package/dist/session-view/store.js.map +0 -1
- package/dist/ui-copy.d.ts.map +0 -1
- package/dist/ui-copy.js.map +0 -1
- package/dist/vault-routing.d.ts.map +0 -1
- package/dist/vault-routing.js.map +0 -1
- package/dist/vault.d.ts.map +0 -1
- package/dist/vault.js.map +0 -1
- /package/dist/{instrument.d.ts → observability/instrument.d.ts} +0 -0
- /package/dist/{env.d.ts → utils/env.d.ts} +0 -0
- /package/dist/{env.js → utils/env.js} +0 -0
- /package/dist/{file-guards.d.ts → utils/file-guards.d.ts} +0 -0
- /package/dist/{file-guards.js → utils/file-guards.js} +0 -0
- /package/dist/{fs-atomic.d.ts → utils/fs-atomic.d.ts} +0 -0
- /package/dist/{fs-atomic.js → utils/fs-atomic.js} +0 -0
- /package/dist/{html.d.ts → utils/html.d.ts} +0 -0
- /package/dist/{html.js → utils/html.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.js","sourceRoot":"","sources":["../../../src/web/login/portal.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from \"crypto\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"http\";\nimport type { Bot, PlatformName } from \"../../adapter.js\";\nimport { handleAdminRequest, type AdminRuntimeBridge } from \"../admin/portal.js\";\nimport type { InMemoryAdminTokenStore } from \"../admin/store.js\";\nimport { escapeHtml } from \"../../utils/html.js\";\nimport { readRawBody } from \"../../utils/http-body.js\";\nimport { renderPortalShell } from \"../../portal-shell.js\";\nimport type { SandboxConfig } from \"../../sandbox/index.js\";\nimport { resolveLinkBaseUrl } from \"../../config.js\";\nimport {\n handleSessionViewRequest,\n type SessionViewInteractiveOptions,\n} from \"../session-view/portal.js\";\nimport type { InMemorySessionViewTokenStore } from \"../session-view/store.js\";\nimport type { InMemoryLinkTokenStore } from \"./store.js\";\nimport {\n getOAuthServices,\n resolveOAuthService,\n type LoginCredentialKind,\n type OAuthService,\n} from \"./oauth.js\";\nimport * as log from \"../../log.js\";\nimport { reportUserFacingError } from \"../../observability/sentry.js\";\nimport { PRODUCT_NAME } from \"../../platform-messages.js\";\nimport { defaultVaultTargetPath, type VaultManager } from \"../../vault/index.js\";\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type { NotifyFn } from \"./types.js\";\nimport type { NotifyFn } from \"./types.js\";\n\ninterface LinkCompleteBody {\n token: string;\n mode?: LoginCredentialKind;\n envKey?: string;\n credential?: string;\n env?: Record<string, string>;\n}\n\ninterface OAuthStartBody {\n token: string;\n serviceId: string;\n}\n\ninterface PendingOAuthState {\n linkToken: string;\n serviceId: string;\n codeVerifier: string;\n expiresAt: number;\n}\n\ninterface SecretPresetField {\n envKey: string;\n envKeys?: string[];\n label: string;\n type: \"text\" | \"password\";\n placeholder: string;\n helpText: string;\n optional?: boolean;\n pattern?: string;\n patternMessage?: string;\n}\n\ninterface SecretPreset {\n id: string;\n label: string;\n description: string;\n note?: string;\n fields: SecretPresetField[];\n}\n\nconst OAUTH_STATE_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_SECRET_CONFIG_ID = \"manual\";\nconst SECRET_PRESETS: SecretPreset[] = [\n {\n id: \"cloudflare_wrangler\",\n label: \"Cloudflare / Wrangler\",\n description:\n \"Store a Cloudflare API token and account ID for Wrangler, Workers, Pages, D1, and KV.\",\n note: \"Create a scoped API Token from Cloudflare Dashboard → My Profile → API Tokens. Do not use the Global API Key.\",\n fields: [\n {\n envKey: \"CLOUDFLARE_API_TOKEN\",\n label: \"Cloudflare API Token\",\n type: \"password\",\n placeholder: \"cfut_...\",\n helpText: \"Recommended for Wrangler, CI, and sandbox use.\",\n },\n {\n envKey: \"CLOUDFLARE_ACCOUNT_ID\",\n label: \"Cloudflare Account ID\",\n type: \"text\",\n placeholder: \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\n helpText: \"Find this via wrangler whoami or in the Cloudflare dashboard account page.\",\n pattern: \"^[A-Fa-f0-9]{32}$\",\n patternMessage: \"Account ID must be a 32-character hexadecimal string.\",\n },\n ],\n },\n {\n id: \"openai\",\n label: \"OpenAI\",\n description: \"Store an OpenAI API key for tools and SDKs that use OPENAI_API_KEY.\",\n note: \"Create a standard API key from the OpenAI dashboard. Paste the key exactly as issued.\",\n fields: [\n {\n envKey: \"OPENAI_API_KEY\",\n label: \"OpenAI API Key\",\n type: \"password\",\n placeholder: \"sk-...\",\n helpText: \"Used by the OpenAI SDK, CLI wrappers, and many coding tools.\",\n },\n ],\n },\n {\n id: \"anthropic\",\n label: \"Anthropic\",\n description: \"Store an Anthropic API key for Claude and tools that use ANTHROPIC_API_KEY.\",\n note: \"Create this key from the Anthropic Console. Use a workspace-scoped key when possible.\",\n fields: [\n {\n envKey: \"ANTHROPIC_API_KEY\",\n label: \"Anthropic API Key\",\n type: \"password\",\n placeholder: \"sk-ant-...\",\n helpText: \"Used by Claude integrations and Anthropic-compatible tooling.\",\n },\n ],\n },\n {\n id: \"gemini\",\n label: \"Gemini\",\n description:\n \"Store one Google AI Studio key and expose it as both GEMINI_API_KEY and GOOGLE_API_KEY.\",\n note: \"Create a Gemini / Google AI Studio API key, then paste it once here for compatibility with both env names.\",\n fields: [\n {\n envKey: \"GEMINI_API_KEY\",\n envKeys: [\"GEMINI_API_KEY\", \"GOOGLE_API_KEY\"],\n label: \"Gemini API Key\",\n type: \"password\",\n placeholder: \"AIza...\",\n helpText: \"One value will be written to both GEMINI_API_KEY and GOOGLE_API_KEY.\",\n },\n ],\n },\n {\n id: \"openrouter\",\n label: \"OpenRouter\",\n description: \"Store an OpenRouter API key for tools that route models through OpenRouter.\",\n note: \"Create a key from the OpenRouter dashboard and paste it here.\",\n fields: [\n {\n envKey: \"OPENROUTER_API_KEY\",\n label: \"OpenRouter API Key\",\n type: \"password\",\n placeholder: \"sk-or-v1-...\",\n helpText: \"Used by OpenRouter SDKs and compatible model gateways.\",\n },\n ],\n },\n {\n id: \"github_pat\",\n label: \"GitHub PAT\",\n description:\n \"Store one GitHub personal access token and expose it as both GH_TOKEN and GITHUB_TOKEN.\",\n note: \"Create a fine-grained or classic personal access token from GitHub Settings → Developer settings.\",\n fields: [\n {\n envKey: \"GH_TOKEN\",\n envKeys: [\"GH_TOKEN\", \"GITHUB_TOKEN\"],\n label: \"GitHub Personal Access Token\",\n type: \"password\",\n placeholder: \"github_pat_...\",\n helpText: \"One value will be written to both GH_TOKEN and GITHUB_TOKEN.\",\n },\n ],\n },\n {\n id: \"vercel\",\n label: \"Vercel\",\n description: \"Store a Vercel token plus optional org and project IDs for deployment tooling.\",\n note: \"Create a token from the Vercel dashboard. Org ID and Project ID are optional but useful for scripted deploys.\",\n fields: [\n {\n envKey: \"VERCEL_TOKEN\",\n label: \"Vercel Token\",\n type: \"password\",\n placeholder: \"vercel_...\",\n helpText: \"Required for Vercel CLI and API access.\",\n },\n {\n envKey: \"VERCEL_ORG_ID\",\n label: \"Vercel Org ID\",\n type: \"text\",\n placeholder: \"team_...\",\n helpText: \"Optional. Set this when you want to target a specific team or account.\",\n optional: true,\n },\n {\n envKey: \"VERCEL_PROJECT_ID\",\n label: \"Vercel Project ID\",\n type: \"text\",\n placeholder: \"prj_...\",\n helpText: \"Optional. Set this when deploy scripts need a fixed project reference.\",\n optional: true,\n },\n ],\n },\n {\n id: \"sentry\",\n label: \"Sentry\",\n description:\n \"Store a Sentry auth token plus optional org/project, and mount ~/.sentryclirc for sentry-cli.\",\n note: \"Create an auth token from Sentry Settings → Account → API → Auth Tokens. Saving this preset also writes /root/.sentryclirc for sentry-cli.\",\n fields: [\n {\n envKey: \"SENTRY_AUTH_TOKEN\",\n label: \"Sentry Auth Token\",\n type: \"password\",\n placeholder: \"sntrys_...\",\n helpText:\n \"Required for Sentry CLI, releases, and sourcemap uploads. Also written to /root/.sentryclirc.\",\n },\n {\n envKey: \"SENTRY_ORG\",\n label: \"Sentry Org Slug\",\n type: \"text\",\n placeholder: \"my-org\",\n helpText: \"Optional. Helpful for Sentry CLI commands and CI automation.\",\n optional: true,\n },\n {\n envKey: \"SENTRY_PROJECT\",\n label: \"Sentry Project Slug\",\n type: \"text\",\n placeholder: \"my-project\",\n helpText: \"Optional. Helpful for release and sourcemap commands.\",\n optional: true,\n },\n ],\n },\n];\n\n// ── startLinkServer ────────────────────────────────────────────────────────────\n\n/**\n * Start a small HTTP server that receives credential onboarding callbacks from the web portal.\n *\n * Routes:\n * GET /health — health check\n * GET /link?token=xxx — credential onboarding page\n * POST /api/link/complete — API key completion endpoint\n * POST /api/oauth/start — creates provider OAuth redirect URL\n * GET /oauth/callback — OAuth callback endpoint\n */\nexport function startLinkServer(\n port: number,\n linkTokenStore: InMemoryLinkTokenStore,\n vaultManager: VaultManager,\n notify: NotifyFn,\n sessionViewTokenStore?: InMemorySessionViewTokenStore,\n sessionViewInteractive?: SessionViewInteractiveOptions,\n adminOptions?: {\n adminTokenStore: InMemoryAdminTokenStore;\n workingDir?: string;\n runtime?: AdminRuntimeBridge;\n sandbox?: SandboxConfig;\n botsByPlatform?: Partial<Record<PlatformName, Bot>>;\n },\n): Server {\n const oauthStates = new Map<string, PendingOAuthState>();\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const url = new URL(req.url ?? \"/\", requestBaseUrl(req));\n\n if (req.method === \"GET\" && url.pathname === \"/health\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n return;\n }\n\n if (\n adminOptions?.adminTokenStore &&\n handleAdminRequest(req, res, url, {\n vaultManager,\n linkTokenStore,\n sessionViewTokenStore,\n adminTokenStore: adminOptions.adminTokenStore,\n portalBaseUrl: resolveLinkBaseUrl() ?? undefined,\n workingDir: adminOptions.workingDir,\n runtime: adminOptions.runtime,\n sandbox: adminOptions.sandbox,\n botsByPlatform: adminOptions.botsByPlatform,\n })\n ) {\n return;\n }\n\n if (\n await handleSessionViewRequest(req, res, url, sessionViewTokenStore, sessionViewInteractive)\n ) {\n return;\n }\n\n if (req.method === \"GET\" && url.pathname === \"/link\") {\n const rawToken = url.searchParams.get(\"token\") ?? \"\";\n const linkToken = linkTokenStore.peek(rawToken);\n\n if (!linkToken) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderErrorPage(\n \"This link is invalid or has expired. Ask the bot for a new /login link.\",\n ),\n );\n return;\n }\n\n const oauthServiceHint = linkToken.providerId\n ? resolveOAuthService(linkToken.providerId)\n : undefined;\n const oauthServices = getOAuthServices();\n const defaultMode: LoginCredentialKind = oauthServiceHint ? \"oauth\" : \"api_key\";\n const existingSecrets = describeVaultSecrets(vaultManager, linkToken.vaultId);\n\n const title = oauthServiceHint ? `${oauthServiceHint.label} OAuth` : \"Store Secret\";\n const helpText = oauthServiceHint\n ? `Authorize ${oauthServiceHint.label} and store tokens in your vault.`\n : \"Set any environment variable key/value pair in your vault.\";\n const secretLabel = \"Secret value\";\n const placeholder = \"sk-...\";\n const initialEnvKey = \"\";\n\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderCredentialPage(\n rawToken,\n title,\n defaultMode,\n initialEnvKey,\n secretLabel,\n placeholder,\n helpText,\n oauthServices,\n oauthServiceHint?.id,\n existingSecrets,\n ),\n );\n return;\n }\n\n if (req.method === \"POST\" && url.pathname === \"/api/link/complete\") {\n if (!enforceCsrf(req, res)) return;\n void readJsonBody(req, res, async (body) => {\n await handleLinkComplete(body, linkTokenStore, vaultManager, notify, res);\n });\n return;\n }\n\n if (req.method === \"POST\" && url.pathname === \"/api/oauth/start\") {\n if (!enforceCsrf(req, res)) return;\n void readJsonBody(req, res, async (body) => {\n await handleOAuthStart(body, req, linkTokenStore, oauthStates, res);\n });\n return;\n }\n\n if (req.method === \"GET\" && url.pathname === \"/oauth/callback\") {\n void handleOAuthCallback(\n url,\n req,\n linkTokenStore,\n vaultManager,\n notify,\n oauthStates,\n res,\n ).catch((err: Error) => {\n log.logWarning(\"OAuth callback failed\", err.message);\n reportUserFacingError(err, {\n domain: \"login\",\n surface: \"oauth\",\n operation: \"oauth_callback\",\n severity: \"error\",\n context: { route: \"/oauth/callback\" },\n });\n res.writeHead(500, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"OAuth callback failed. Please retry /login.\"));\n });\n return;\n }\n\n res.writeHead(404);\n res.end();\n } catch (err) {\n log.logWarning(\"Link server request error\", err instanceof Error ? err.message : String(err));\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n }\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n }\n });\n\n // Bind to loopback when MIKAN_LINK_URL is unset so the credential UI and OAuth\n // callbacks are not exposed on public interfaces by default. Production\n // deployments set MIKAN_LINK_URL and are expected to front this server with a\n // reverse proxy, which can still reach it via 0.0.0.0.\n const bindHost = resolveLinkBaseUrl() ? undefined : \"127.0.0.1\";\n server.listen(port, bindHost, () => {\n log.logInfo(`Link callback server listening on ${bindHost ?? \"0.0.0.0\"}:${port}`);\n if (!resolveLinkBaseUrl()) {\n log.logWarning(\n \"MIKAN_LINK_URL is not set — bound to 127.0.0.1 and OAuth redirect_uri will be \" +\n \"derived from request headers (Host / X-Forwarded-*). Set \" +\n \"MIKAN_LINK_URL=https://your-host.example.com for production.\",\n );\n }\n });\n\n server.on(\"error\", (err) => {\n log.logWarning(\"Link server error\", err.message);\n });\n\n return server;\n}\n\n/**\n * Resolve the externally-visible base URL of this server.\n *\n * Prefers MIKAN_LINK_URL (see config.ts) so the OAuth `redirect_uri` is\n * deterministic and not influenced by attacker-controlled request headers.\n * Falls back to Host / X-Forwarded-* only when no base URL is configured\n * — intended for local development.\n */\nfunction requestBaseUrl(req: IncomingMessage): string {\n const configured = resolveLinkBaseUrl();\n if (configured) return configured;\n\n const protoRaw = (req.headers[\"x-forwarded-proto\"] as string | undefined)?.split(\",\")[0]?.trim();\n const proto = protoRaw || \"http\";\n const host =\n ((req.headers[\"x-forwarded-host\"] as string | undefined)?.split(\",\")[0]?.trim() ??\n req.headers.host ??\n `localhost`) ||\n `localhost`;\n return `${proto}://${host}`;\n}\n\n/**\n * Block cross-site POSTs to the credential endpoints. Two defenses:\n * 1. Require Content-Type: application/json, which forces a CORS preflight\n * for any cross-origin fetch and rules out `<form enctype=\"text/plain\">`\n * tricks that could otherwise smuggle a JSON body.\n * 2. When MIKAN_LINK_URL is configured, require that the Origin (or Referer,\n * as a fallback for browsers that strip Origin) matches that base URL.\n * This stops an attacker-controlled page — even one that somehow stole a\n * victim's link token — from completing the flow.\n */\nfunction enforceCsrf(req: IncomingMessage, res: ServerResponse): boolean {\n const contentType = (req.headers[\"content-type\"] as string | undefined)\n ?.split(\";\")[0]\n ?.trim()\n .toLowerCase();\n if (contentType !== \"application/json\") {\n res.writeHead(415, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Content-Type must be application/json\" }));\n return false;\n }\n\n const configured = resolveLinkBaseUrl();\n if (!configured) {\n // No trusted origin to compare against in local/dev mode; the loopback\n // bind already prevents cross-host access.\n return true;\n }\n\n let configuredOrigin: string;\n try {\n configuredOrigin = new URL(configured).origin;\n } catch {\n // Misconfigured MIKAN_LINK_URL — fail closed.\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Server misconfiguration\" }));\n return false;\n }\n\n if (requestOrigin(req) !== configuredOrigin) {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Cross-origin request rejected\" }));\n return false;\n }\n\n return true;\n}\n\n/** Best-effort origin of the request, derived from Origin or Referer. */\nfunction requestOrigin(req: IncomingMessage): string | undefined {\n const origin = (req.headers.origin as string | undefined)?.trim();\n if (origin && origin !== \"null\") return origin;\n\n const referer = (req.headers.referer as string | undefined)?.trim();\n if (!referer) return undefined;\n try {\n return new URL(referer).origin;\n } catch {\n return undefined;\n }\n}\n\nasync function readJsonBody(\n req: IncomingMessage,\n res: ServerResponse,\n onBody: (body: string) => Promise<void>,\n): Promise<void> {\n const body = await readRawBody(req, res, 16 * 1024);\n if (body === null) return;\n await onBody(body);\n}\n\n// ── HTML helpers ───────────────────────────────────────────────────────────────\n\nconst esc = escapeHtml;\n\nconst loginViewStyles = `\n /* Login portal inherits all base chrome from the shared shell.\n This module only adds preset cards, service logos, help popovers,\n and the mode toggle. */\n\n p { margin: 0; color: var(--muted); font-size: 0.92rem; line-height: 1.55; }\n\n .stack > * + * { margin-top: 14px; }\n\n label {\n display: block;\n margin-bottom: 6px;\n font-size: 0.86rem;\n font-weight: 600;\n color: var(--text);\n }\n\n input, select {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--border);\n border-radius: 10px;\n background: #fff;\n color: var(--text);\n font: inherit;\n }\n\n input:focus-visible,\n select:focus-visible {\n outline: 2px solid var(--text);\n outline-offset: 1px;\n }\n\n .primary-button {\n width: 100%;\n padding: 12px 18px;\n border: none;\n border-radius: 12px;\n background: var(--text);\n color: #fff;\n cursor: pointer;\n font: 500 0.95rem/1.2 'DM Sans', sans-serif;\n transition: opacity 120ms;\n }\n\n .primary-button:hover:not(:disabled) { opacity: 0.85; }\n .primary-button:disabled { opacity: 0.5; cursor: default; }\n\n .service-logo {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 10px;\n flex: 0 0 36px;\n background: #1c1e21;\n color: #fff;\n }\n\n .service-logo svg {\n display: block;\n width: 20px;\n height: 20px;\n }\n\n .service-logo-text {\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n }\n\n .service-logo.cloudflare {\n background: linear-gradient(180deg, #ffb66d 0%, #f48120 100%);\n }\n\n .service-logo.openai {\n background: linear-gradient(180deg, #3e4045 0%, #111315 100%);\n }\n\n .service-logo.anthropic {\n background: linear-gradient(180deg, #d6b48c 0%, #9a6d3a 100%);\n }\n\n .service-logo.gemini {\n background: linear-gradient(180deg, #8ab4ff 0%, #5b6cff 100%);\n }\n\n .service-logo.openrouter {\n background: linear-gradient(180deg, #8c8cff 0%, #4f46e5 100%);\n }\n\n .service-logo.github {\n background: linear-gradient(180deg, #4a4f57 0%, #1b1f23 100%);\n }\n\n .service-logo.vercel {\n background: linear-gradient(180deg, #4a4f57 0%, #000 100%);\n }\n\n .service-logo.sentry {\n background: linear-gradient(180deg, #7c5cff 0%, #3f2e8c 100%);\n }\n\n .service-logo.manual {\n background: linear-gradient(180deg, #43474d 0%, #1c1e21 100%);\n }\n\n .provider-card > * + * {\n margin-top: 14px;\n }\n\n .provider-header {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .provider-title {\n flex: 1;\n margin: 0;\n font-size: 1rem;\n font-weight: 650;\n line-height: 1.3;\n }\n\n .provider-field label {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n\n .help {\n position: relative;\n display: inline-flex;\n align-items: center;\n }\n\n .help-trigger {\n width: 18px;\n height: 18px;\n padding: 0;\n border: 1px solid var(--border);\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.9);\n color: var(--muted);\n font-size: 11px;\n font-weight: 700;\n line-height: 1;\n cursor: pointer;\n }\n\n .help-trigger:hover {\n color: var(--text);\n border-color: var(--text);\n }\n\n .help-content {\n display: none;\n position: absolute;\n top: calc(100% + 6px);\n left: 0;\n z-index: 10;\n width: max-content;\n max-width: 280px;\n padding: 10px 12px;\n border: 1px solid var(--border);\n border-radius: 10px;\n background: #fff;\n color: var(--text);\n font-size: 0.85rem;\n font-weight: 400;\n line-height: 1.45;\n box-shadow: 0 8px 24px rgba(28, 30, 33, 0.12);\n white-space: normal;\n }\n\n .help-trigger[aria-expanded=\"true\"] + .help-content {\n display: block;\n }\n\n .help-trigger[aria-expanded=\"true\"] {\n color: var(--text);\n border-color: var(--text);\n }\n\n .mode {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-top: 22px;\n }\n\n .mode label {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n padding: 10px 12px;\n border: 1px solid var(--border);\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.85);\n font-weight: 500;\n }\n\n .mode input {\n width: auto;\n margin: 0;\n }\n\n .panel {\n display: none;\n }\n\n .panel.active {\n display: block;\n }\n\n #api-panel.active {\n display: grid;\n gap: 16px;\n }\n\n .panel-note {\n margin-top: 10px;\n font-size: 0.92rem;\n }\n\n .result,\n .status {\n margin-top: 20px;\n padding: 14px 16px;\n border-radius: 14px;\n font-size: 0.95rem;\n }\n\n .result {\n display: none;\n }\n\n .result.ok,\n .status.ok {\n background: var(--ok-bg);\n color: var(--ok-text);\n }\n\n .result.err,\n .status.err {\n background: var(--err-bg);\n color: var(--err-text);\n }\n\n .secrets-summary {\n margin-top: 18px;\n padding: 14px 16px;\n border: 1px solid var(--border);\n border-radius: 14px;\n background: rgba(255, 255, 255, 0.72);\n }\n\n .secrets-summary h2 {\n margin: 0 0 8px;\n font-size: 0.98rem;\n }\n\n .secrets-summary p {\n font-size: 0.92rem;\n }\n\n .secrets-summary ul {\n margin: 10px 0 0;\n padding-left: 18px;\n color: var(--text);\n }\n\n .secrets-summary li + li {\n margin-top: 6px;\n }\n\n .close-note {\n margin-top: 14px;\n font-size: 0.92rem;\n }\n\n @media (max-width: 640px) {\n .mode label { flex: 1; justify-content: center; }\n input, select { padding: 12px; }\n .primary-button { padding: 14px 18px; }\n .help-content { max-width: min(260px, calc(100vw - 40px)); }\n .provider-header .help-content { left: auto; right: 0; }\n }\n`;\n\nfunction renderHtmlDocument(title: string, shellContent: string): string {\n return renderPortalShell({\n activeView: \"vault\",\n pageTitle: \"Vault\",\n body: shellContent,\n extraStyles: loginViewStyles,\n });\n}\n\nfunction renderStatusPage(\n title: string,\n message: string,\n tone: \"ok\" | \"err\",\n options?: { closeNote?: boolean },\n): string {\n const closeNote = options?.closeNote ? '<p class=\"close-note\">You can close this tab.</p>' : \"\";\n return renderHtmlDocument(\n title,\n `<section class=\"card\"><div class=\"stack\">\n <p class=\"eyebrow\">${PRODUCT_NAME}</p>\n <h1 class=\"page-title\">${esc(title)}</h1>\n <div class=\"status ${tone}\">${esc(message)}</div>\n ${closeNote}\n </div></section>`,\n );\n}\n\ninterface ExistingSecretsSummary {\n envKeys: string[];\n mountTargets: string[];\n}\n\nfunction describeVaultSecrets(vaultManager: VaultManager, vaultId: string): ExistingSecretsSummary {\n const vault = vaultManager.resolve(vaultId);\n if (!vault) {\n return { envKeys: [], mountTargets: [] };\n }\n\n return {\n envKeys: Object.keys(vault.env).toSorted((left, right) => left.localeCompare(right)),\n mountTargets: [...new Set(vault.mounts.map((mount) => mount.target))].toSorted((left, right) =>\n left.localeCompare(right),\n ),\n };\n}\n\nfunction renderSecretsSummary(summary: ExistingSecretsSummary): string {\n if (summary.envKeys.length === 0 && summary.mountTargets.length === 0) {\n return `\n <section class=\"secrets-summary\">\n <h2>Currently stored</h2>\n <p>No secrets are stored in this vault yet.</p>\n </section>`;\n }\n\n const envItems = summary.envKeys.map((envKey) => `<li><code>${esc(envKey)}</code></li>`).join(\"\");\n const mountItems = summary.mountTargets\n .map((target) => `<li><code>${esc(target)}</code></li>`)\n .join(\"\");\n\n return `\n <section class=\"secrets-summary\">\n <h2>Currently stored</h2>\n <p>Only secret names and mounted paths are shown here. Secret values are never displayed.</p>\n ${summary.envKeys.length > 0 ? `<p><strong>Environment keys</strong></p><ul>${envItems}</ul>` : \"\"}\n ${summary.mountTargets.length > 0 ? `<p><strong>Mounted secret files</strong></p><ul>${mountItems}</ul>` : \"\"}\n </section>`;\n}\n\nfunction renderServiceLogo(kind: string): string {\n if (kind === \"cloudflare_wrangler\") {\n return `<span class=\"service-logo cloudflare\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M8.5 17.5h8.2a2.9 2.9 0 0 0 .4-5.78A4.45 4.45 0 0 0 8.9 10.4a3.7 3.7 0 0 0-.4 7.1Z\" fill=\"white\" fill-opacity=\"0.98\"/>\n <path d=\"M6.6 17.5h5.1a2.3 2.3 0 0 0 0-4.6 3.1 3.1 0 0 0-3-2.2 3.23 3.23 0 0 0-3.18 3.64A2.67 2.67 0 0 0 6.6 17.5Z\" fill=\"white\"/>\n </svg>\n </span>`;\n }\n\n const textLogos: Record<string, { className: string; text: string }> = {\n openai: { className: \"openai\", text: \"OA\" },\n anthropic: { className: \"anthropic\", text: \"AI\" },\n gemini: { className: \"gemini\", text: \"G\" },\n openrouter: { className: \"openrouter\", text: \"OR\" },\n github_pat: { className: \"github\", text: \"GH\" },\n vercel: { className: \"vercel\", text: \"V\" },\n sentry: { className: \"sentry\", text: \"S\" },\n manual: { className: \"manual\", text: \">_\" },\n };\n const logo = textLogos[kind] ?? textLogos.manual;\n return `<span class=\"service-logo ${logo.className}\" aria-hidden=\"true\"><span class=\"service-logo-text\">${logo.text}</span></span>`;\n}\n\nfunction resolveFieldEnvKeys(field: SecretPresetField): string[] {\n return field.envKeys && field.envKeys.length > 0 ? field.envKeys : [field.envKey];\n}\n\nfunction renderStoredEnvKeysInline(field: SecretPresetField): string {\n return resolveFieldEnvKeys(field)\n .map((envKey) => `<code>${esc(envKey)}</code>`)\n .join(\", \");\n}\n\nfunction renderHelpIcon(html: string): string {\n return `<span class=\"help\">\n <button type=\"button\" class=\"help-trigger\" aria-label=\"More info\" aria-expanded=\"false\">?</button>\n <span class=\"help-content\" role=\"tooltip\">${html}</span>\n </span>`;\n}\n\nfunction renderPresetProviderCard(preset: SecretPreset): string {\n const headerHelp = preset.note ? renderHelpIcon(esc(preset.note)) : \"\";\n const fields = preset.fields\n .map((field) => {\n const storedKeys = renderStoredEnvKeysInline(field);\n const helpText = `${esc(field.helpText)} Stored as ${storedKeys}.${field.optional ? \" Optional.\" : \"\"}`;\n return `<div class=\"provider-field\">\n <label for=\"preset-${esc(preset.id)}-${esc(field.envKey)}\">\n ${esc(field.label)}\n ${renderHelpIcon(helpText)}\n </label>\n <input\n id=\"preset-${esc(preset.id)}-${esc(field.envKey)}\"\n type=\"${field.type}\"\n autocomplete=\"off\"\n placeholder=\"${esc(field.placeholder)}\"\n data-env-key=\"${esc(field.envKey)}\"\n data-env-keys=\"${esc(resolveFieldEnvKeys(field).join(\",\"))}\"\n data-field-label=\"${esc(field.label)}\"\n ${field.optional ? 'data-optional=\"true\"' : \"\"}\n ${field.pattern ? `data-pattern=\"${esc(field.pattern)}\"` : \"\"}\n ${field.patternMessage ? `data-pattern-message=\"${esc(field.patternMessage)}\"` : \"\"}\n >\n </div>`;\n })\n .join(\"\\n\");\n\n return `<section class=\"card provider-card\" data-provider-kind=\"preset\" data-provider-id=\"${esc(preset.id)}\">\n <div class=\"provider-header\">\n ${renderServiceLogo(preset.id)}\n <h2 class=\"provider-title\">${esc(preset.label)}</h2>\n ${headerHelp}\n </div>\n ${fields}\n </section>`;\n}\n\nfunction renderManualProviderCard(\n initialEnvKey: string,\n secretLabel: string,\n placeholder: string,\n): string {\n const headerHelp = renderHelpIcon(\n esc(\n \"Set any environment variable key/value pair manually. Use this when no provider preset fits.\",\n ),\n );\n return `<section class=\"card provider-card\" data-provider-kind=\"manual\" data-provider-id=\"${esc(DEFAULT_SECRET_CONFIG_ID)}\">\n <div class=\"provider-header\">\n ${renderServiceLogo(\"manual\")}\n <h2 class=\"provider-title\">Manual entry</h2>\n ${headerHelp}\n </div>\n <div class=\"provider-field\">\n <label for=\"envKey\">Environment key</label>\n <input id=\"envKey\" type=\"text\" name=\"envKey\" placeholder=\"OPENAI_API_KEY\" value=\"${esc(initialEnvKey)}\" autocomplete=\"off\">\n </div>\n <div class=\"provider-field\">\n <label for=\"credential\">${esc(secretLabel)}</label>\n <input id=\"credential\" type=\"password\" name=\"credential\" placeholder=\"${esc(placeholder)}\" autocomplete=\"off\">\n </div>\n </section>`;\n}\n\nfunction renderCredentialPage(\n token: string,\n title: string,\n defaultMode: LoginCredentialKind,\n initialEnvKey: string,\n secretLabel: string,\n placeholder: string,\n helpText: string,\n oauthServices: OAuthService[],\n oauthServiceIdHint: string | undefined,\n existingSecrets: ExistingSecretsSummary,\n): string {\n const oauthOptions = oauthServices\n .map((service) => {\n const selected = service.id === oauthServiceIdHint ? ' selected=\"selected\"' : \"\";\n return `<option value=\"${esc(service.id)}\"${selected}>${esc(service.label)}</option>`;\n })\n .join(\"\\n\");\n const presetCards = SECRET_PRESETS.map(renderPresetProviderCard).join(\"\\n\");\n\n return renderHtmlDocument(\n \"Login\",\n `<section class=\"card stack\">\n <p class=\"eyebrow\">${PRODUCT_NAME}</p>\n <h1 class=\"page-title\">${esc(title)}</h1>\n <p>Your personal sandbox is already provisioned automatically.</p>\n <p>${esc(helpText)}</p>\n ${renderSecretsSummary(existingSecrets)}\n <div class=\"mode\">\n <label><input type=\"radio\" name=\"mode\" value=\"api_key\" ${defaultMode === \"api_key\" ? \"checked\" : \"\"}> Secrets / API tokens</label>\n <label><input type=\"radio\" name=\"mode\" value=\"oauth\" ${defaultMode === \"oauth\" ? \"checked\" : \"\"}> OAuth login</label>\n </div>\n</section>\n\n<div id=\"api-panel\" class=\"panel\">\n ${presetCards}\n ${renderManualProviderCard(initialEnvKey, secretLabel, placeholder)}\n</div>\n\n<div id=\"oauth-panel\" class=\"panel card stack\">\n <label for=\"oauthService\">OAuth service</label>\n <select id=\"oauthService\" name=\"oauthService\">${oauthOptions}</select>\n <p class=\"panel-note\">You'll be redirected to the selected service's authorization page.</p>\n</div>\n\n<div>\n <button id=\"btn\" class=\"primary-button\" onclick=\"connect()\">Continue</button>\n <div id=\"result\" class=\"result\" aria-live=\"polite\"></div>\n</div>\n <script>\n const envKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n function selectedMode() {\n return document.querySelector('input[name=\"mode\"]:checked').value;\n }\n\n function showResult(message, ok) {\n const result = document.getElementById('result');\n result.style.display = 'block';\n result.className = ok ? 'result ok' : 'result err';\n result.textContent = message;\n }\n\n function resetContinueButton() {\n const btn = document.getElementById('btn');\n btn.disabled = false;\n btn.textContent = 'Continue';\n }\n\n function syncPanels() {\n const mode = selectedMode();\n document.getElementById('api-panel').classList.toggle('active', mode === 'api_key');\n document.getElementById('oauth-panel').classList.toggle('active', mode === 'oauth');\n }\n\n function collectManualCard(card) {\n const envKey = card.querySelector('#envKey').value.trim();\n const credential = card.querySelector('#credential').value.trim();\n if (!envKey && !credential) return { skip: true };\n if (!envKeyPattern.test(envKey)) return { error: 'Manual entry: please enter a valid environment key.' };\n if (!credential) return { error: 'Manual entry: please enter a secret value.' };\n return { env: { [envKey]: credential } };\n }\n\n function collectPresetCard(card) {\n const inputs = card.querySelectorAll('input[data-env-key]');\n const filled = Array.from(inputs).some((input) => input.value.trim() !== '');\n if (!filled) return { skip: true };\n\n const env = {};\n for (const input of inputs) {\n const value = input.value.trim();\n const label = input.dataset.fieldLabel || input.dataset.envKey || 'a value';\n const optional = input.dataset.optional === 'true';\n if (!value) {\n if (optional) continue;\n return { error: 'Please enter ' + label + '.' };\n }\n if (input.dataset.pattern && !(new RegExp(input.dataset.pattern).test(value))) {\n return { error: input.dataset.patternMessage || ('Invalid ' + label + '.') };\n }\n const envKeys = (input.dataset.envKeys || input.dataset.envKey || '')\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean);\n for (const envKey of envKeys) {\n env[envKey] = value;\n }\n }\n return { env };\n }\n\n function collectApiEnv() {\n const env = {};\n let any = false;\n for (const card of document.querySelectorAll('.provider-card')) {\n const result = card.dataset.providerKind === 'manual'\n ? collectManualCard(card)\n : collectPresetCard(card);\n if (result.skip) continue;\n if (result.error) return { error: result.error };\n Object.assign(env, result.env);\n any = true;\n }\n if (!any) return { error: 'Fill in at least one provider before continuing.' };\n return { env };\n }\n\n async function startOAuthFlow() {\n const serviceId = document.getElementById('oauthService').value;\n const r = await fetch('/api/oauth/start', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token: '${esc(token)}', serviceId }),\n });\n const data = await r.json();\n if (!r.ok) {\n showResult('Error: ' + (data.error ?? r.status), false);\n resetContinueButton();\n return;\n }\n window.location.href = data.redirectUrl;\n }\n\n async function saveApiSecrets() {\n const payload = collectApiEnv();\n if (payload.error) {\n showResult(payload.error, false);\n resetContinueButton();\n return;\n }\n\n const r = await fetch('/api/link/complete', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token: '${esc(token)}', mode: 'api_key', env: payload.env }),\n });\n const data = await r.json();\n if (r.ok) {\n showResult(data.message ?? 'Credential stored. You can close this tab.', true);\n document.getElementById('btn').style.display = 'none';\n for (const input of document.querySelectorAll('input,select,button')) input.disabled = true;\n } else {\n showResult('Error: ' + (data.error ?? r.status), false);\n resetContinueButton();\n }\n }\n\n let openHelp = null;\n function closeOpenHelp() {\n if (openHelp) {\n openHelp.setAttribute('aria-expanded', 'false');\n openHelp = null;\n }\n }\n\n for (const trigger of document.querySelectorAll('.help-trigger')) {\n trigger.addEventListener('click', (event) => {\n event.stopPropagation();\n const wasOpen = trigger.getAttribute('aria-expanded') === 'true';\n closeOpenHelp();\n if (!wasOpen) {\n trigger.setAttribute('aria-expanded', 'true');\n openHelp = trigger;\n }\n });\n }\n\n document.addEventListener('click', closeOpenHelp);\n document.addEventListener('keydown', (event) => {\n if (event.key === 'Escape') closeOpenHelp();\n });\n\n for (const radio of document.querySelectorAll('input[name=\"mode\"]')) {\n radio.addEventListener('change', syncPanels);\n }\n\n syncPanels();\n\n async function connect() {\n const btn = document.getElementById('btn');\n const mode = selectedMode();\n btn.disabled = true;\n btn.textContent = mode === 'oauth' ? 'Redirecting…' : 'Saving…';\n\n try {\n if (mode === 'oauth') {\n await startOAuthFlow();\n return;\n }\n await saveApiSecrets();\n } catch (err) {\n showResult('Network error: ' + (err?.message ?? err), false);\n resetContinueButton();\n }\n }\n </script>`,\n );\n}\n\nfunction renderErrorPage(message: string): string {\n return renderStatusPage(\"Login Error\", message, \"err\");\n}\n\nfunction renderSuccessPage(message: string): string {\n return renderStatusPage(\"Connected\", message, \"ok\", { closeNote: true });\n}\n\nfunction isValidEnvKey(value: string): boolean {\n return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);\n}\n\nfunction extractEnvUpdates(data: Partial<LinkCompleteBody>): {\n updates?: Record<string, string>;\n error?: string;\n} {\n if (data.env && typeof data.env === \"object\" && !Array.isArray(data.env)) {\n const rawEntries = Object.entries(data.env);\n if (rawEntries.length === 0) return { error: \"Missing required field: env\" };\n\n const updates: Record<string, string> = {};\n for (const [rawKey, rawValue] of rawEntries) {\n const envKey = rawKey.trim();\n const credential = typeof rawValue === \"string\" ? rawValue.trim() : \"\";\n if (!isValidEnvKey(envKey)) return { error: `Invalid envKey format: ${rawKey}` };\n if (!credential) return { error: `Missing value for envKey: ${envKey}` };\n updates[envKey] = credential;\n }\n\n return { updates };\n }\n\n const envKey = data.envKey?.trim() ?? \"\";\n const credential = data.credential?.trim() ?? \"\";\n if (!isValidEnvKey(envKey)) return { error: \"Invalid envKey format\" };\n if (!credential) return { error: \"Missing required field: credential\" };\n return { updates: { [envKey]: credential } };\n}\n\nfunction renderStoredEnvMessage(envKeys: string[], fileTargets: string[] = []): string {\n const fileSuffix = fileTargets.length > 0 ? ` Mounted file(s): ${fileTargets.join(\", \")}.` : \"\";\n if (envKeys.length === 1) {\n return `${envKeys[0]} stored successfully in vault.${fileSuffix}`;\n }\n\n return `${envKeys.length} secrets stored successfully in vault: ${envKeys.join(\", \")}.${fileSuffix}`;\n}\n\nfunction renderSentryCliConfig(updates: Record<string, string>): string | undefined {\n const token = updates.SENTRY_AUTH_TOKEN?.trim();\n if (!token) return undefined;\n\n const lines = [\"[auth]\", `token=${token}`, \"\"];\n const defaults: string[] = [];\n const org = updates.SENTRY_ORG?.trim();\n const project = updates.SENTRY_PROJECT?.trim();\n if (org) defaults.push(`org = ${org}`);\n if (project) defaults.push(`project = ${project}`);\n if (defaults.length > 0) {\n lines.push(\"[defaults]\", ...defaults, \"\");\n }\n return lines.join(\"\\n\");\n}\n\n// ── API-key completion ────────────────────────────────────────────────────────\n\nasync function handleLinkComplete(\n body: string,\n linkTokenStore: InMemoryLinkTokenStore,\n vaultManager: VaultManager,\n notify: NotifyFn,\n res: ServerResponse,\n): Promise<void> {\n let data: Partial<LinkCompleteBody>;\n try {\n data = JSON.parse(body) as Partial<LinkCompleteBody>;\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON\" }));\n return;\n }\n\n if (!data.token) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing required field: token\" }));\n return;\n }\n\n const { updates, error } = extractEnvUpdates(data);\n if (!updates || error) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: error ?? \"Invalid env payload\" }));\n return;\n }\n\n const envKeys = Object.keys(updates).toSorted((left, right) => left.localeCompare(right));\n\n // Atomic consume prevents two concurrent requests from both passing the\n // validity check before either deletes the token.\n const linkToken = linkTokenStore.consume(data.token);\n if (!linkToken) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid or expired token\" }));\n return;\n }\n\n const fileTargets: string[] = [];\n const sentryCliConfig = renderSentryCliConfig(updates);\n\n try {\n vaultManager.upsertEnv(linkToken.vaultId, updates);\n if (sentryCliConfig) {\n vaultManager.upsertFile(\n linkToken.vaultId,\n \".sentryclirc\",\n sentryCliConfig,\n \"/root/.sentryclirc\",\n );\n fileTargets.push(\"/root/.sentryclirc\");\n }\n } catch (persistError) {\n log.logWarning(\n `Failed to persist [${envKeys.join(\", \")}] for ${linkToken.platform}/${linkToken.platformUserId}`,\n persistError instanceof Error ? persistError.message : String(persistError),\n );\n reportUserFacingError(persistError, {\n domain: \"login\",\n surface: \"credential_login\",\n operation: \"vault_persist\",\n severity: \"error\",\n platform: linkToken.platform,\n context: {\n conversationId: linkToken.conversationId,\n vaultId: linkToken.vaultId,\n credentialMode: data.mode ?? \"manual\",\n envKeyCount: envKeys.length,\n fileTargetCount: fileTargets.length,\n },\n });\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error:\n \"Failed to store credential on server. Please fix the server issue and run /login again.\",\n }),\n );\n return;\n }\n\n log.logInfo(\n `Stored [${envKeys.join(\", \")}] for ${linkToken.platform}/${linkToken.platformUserId} in vault:${linkToken.vaultId}`,\n );\n\n const message = renderStoredEnvMessage(envKeys, fileTargets);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true, message }));\n\n notify(\n linkToken.platform,\n linkToken.conversationId,\n `${message} Vault: \\`${linkToken.vaultId}\\`.`,\n ).catch((err: Error) => {\n log.logWarning(\"Failed to notify user after credential login\", err.message);\n reportUserFacingError(err, {\n domain: \"login\",\n surface: \"credential_login\",\n operation: \"notify_user\",\n severity: \"warning\",\n platform: linkToken.platform,\n context: {\n conversationId: linkToken.conversationId,\n vaultId: linkToken.vaultId,\n credentialMode: data.mode ?? \"manual\",\n envKeyCount: envKeys.length,\n fileTargetCount: fileTargets.length,\n },\n });\n });\n}\n\n// ── OAuth flow ────────────────────────────────────────────────────────────────\n\nasync function handleOAuthStart(\n body: string,\n req: IncomingMessage,\n linkTokenStore: InMemoryLinkTokenStore,\n oauthStates: Map<string, PendingOAuthState>,\n res: ServerResponse,\n): Promise<void> {\n let data: Partial<OAuthStartBody>;\n try {\n data = JSON.parse(body) as Partial<OAuthStartBody>;\n } catch {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid JSON\" }));\n return;\n }\n\n if (!data.token || !data.serviceId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing required fields: token/serviceId\" }));\n return;\n }\n\n const linkToken = linkTokenStore.peek(data.token);\n if (!linkToken) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid or expired token\" }));\n return;\n }\n\n const service = resolveOAuthService(data.serviceId);\n if (!service) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Unsupported OAuth service: ${data.serviceId}` }));\n return;\n }\n\n const clientId = process.env[service.clientIdEnvKey];\n const clientSecret = process.env[service.clientSecretEnvKey];\n if (!clientId || !clientSecret) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error:\n `OAuth service ${service.label} is not configured. ` +\n `Missing ${service.clientIdEnvKey}/${service.clientSecretEnvKey}.`,\n }),\n );\n return;\n }\n\n const state = randomBytes(16).toString(\"hex\");\n const codeVerifier = randomBytes(32).toString(\"base64url\");\n oauthStates.set(state, {\n linkToken: data.token,\n serviceId: service.id,\n codeVerifier,\n expiresAt: Date.now() + OAUTH_STATE_TTL_MS,\n });\n\n for (const [k, v] of oauthStates) {\n if (Date.now() > v.expiresAt) oauthStates.delete(k);\n }\n\n const redirectUri = `${requestBaseUrl(req)}/oauth/callback`;\n const authorizeUrl = new URL(service.authorizationUrl);\n authorizeUrl.searchParams.set(\"response_type\", \"code\");\n authorizeUrl.searchParams.set(\"client_id\", clientId);\n authorizeUrl.searchParams.set(\"redirect_uri\", redirectUri);\n authorizeUrl.searchParams.set(\"state\", state);\n if (service.scopes.length > 0) {\n authorizeUrl.searchParams.set(\"scope\", service.scopes.join(\" \"));\n }\n for (const [key, value] of Object.entries(service.authorizationParams ?? {})) {\n authorizeUrl.searchParams.set(key, value);\n }\n\n const codeChallenge = createHash(\"sha256\").update(codeVerifier).digest(\"base64url\");\n authorizeUrl.searchParams.set(\"code_challenge\", codeChallenge);\n authorizeUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true, redirectUrl: authorizeUrl.toString() }));\n}\n\nasync function handleOAuthCallback(\n url: URL,\n req: IncomingMessage,\n linkTokenStore: InMemoryLinkTokenStore,\n vaultManager: VaultManager,\n notify: NotifyFn,\n oauthStates: Map<string, PendingOAuthState>,\n res: ServerResponse,\n): Promise<void> {\n const state = url.searchParams.get(\"state\") ?? \"\";\n const code = url.searchParams.get(\"code\") ?? \"\";\n const oauthError = url.searchParams.get(\"error\");\n\n // Atomic pop: whatever path we take from here, this state is spent.\n // Done before any `await` to close the TOCTOU window between the state\n // lookup and the final delete.\n const pending = oauthStates.get(state);\n if (pending) oauthStates.delete(state);\n\n if (oauthError) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(`OAuth authorization failed: ${oauthError}`));\n return;\n }\n\n if (!pending || Date.now() > pending.expiresAt) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"OAuth state is invalid or expired. Please run /login again.\"));\n return;\n }\n\n if (!code) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"Missing OAuth authorization code.\"));\n return;\n }\n\n const service = resolveOAuthService(pending.serviceId);\n if (!service) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"Unsupported OAuth service.\"));\n return;\n }\n\n const clientId = process.env[service.clientIdEnvKey];\n const clientSecret = process.env[service.clientSecretEnvKey];\n if (!clientId || !clientSecret) {\n res.writeHead(500, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"OAuth service is not configured on server.\"));\n return;\n }\n\n // Atomic consume: pairs with the callback being one-shot. Two concurrent\n // callbacks for the same state would previously both pass `peek` and both\n // run `exchangeOAuthCode` across the await; only one reaches `consume`.\n const linkToken = linkTokenStore.consume(pending.linkToken);\n if (!linkToken) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"Login link is invalid or expired. Please run /login again.\"));\n return;\n }\n\n const redirectUri = `${requestBaseUrl(req)}/oauth/callback`;\n const tokenResp = await exchangeOAuthCode(\n service,\n code,\n clientId,\n clientSecret,\n redirectUri,\n pending.codeVerifier,\n );\n\n const accessToken = tokenResp.access_token?.trim();\n const refreshToken = tokenResp.refresh_token?.trim();\n\n if (!accessToken) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderErrorPage(\"OAuth token exchange did not return an access_token.\"));\n return;\n }\n\n const updates: Record<string, string> = {};\n for (const key of service.accessTokenEnvKeys ?? []) {\n updates[key] = accessToken;\n }\n if (refreshToken && service.refreshTokenEnvKey) {\n updates[service.refreshTokenEnvKey] = refreshToken;\n }\n\n const fileOutput = service.fileOutput;\n let mountedPath: string | undefined;\n if (fileOutput?.type === \"authorized_user\") {\n if (!refreshToken) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderErrorPage(\n \"OAuth token exchange did not return a refresh_token. \" +\n \"Retry after revoking prior consent or ensure prompt=consent is applied.\",\n ),\n );\n return;\n }\n\n mountedPath = fileOutput.targetPath ?? defaultVaultTargetPath(fileOutput.relativePath);\n if (fileOutput.envKey) {\n updates[fileOutput.envKey] = mountedPath;\n }\n for (const key of fileOutput.additionalEnvKeys ?? []) {\n updates[key] = mountedPath;\n }\n }\n\n const storedTargets: string[] = [];\n try {\n if (Object.keys(updates).length > 0) {\n vaultManager.upsertEnv(linkToken.vaultId, updates);\n storedTargets.push(...Object.keys(updates).toSorted());\n }\n if (fileOutput?.type === \"authorized_user\" && refreshToken) {\n vaultManager.upsertFile(\n linkToken.vaultId,\n fileOutput.relativePath,\n renderAuthorizedUserCredential(clientId, clientSecret, refreshToken),\n fileOutput.targetPath,\n );\n if (mountedPath) storedTargets.push(mountedPath);\n }\n } catch (persistError) {\n log.logWarning(\n `Failed to persist OAuth credentials for ${linkToken.platform}/${linkToken.platformUserId}`,\n persistError instanceof Error ? persistError.message : String(persistError),\n );\n reportUserFacingError(persistError, {\n domain: \"login\",\n surface: \"oauth\",\n operation: \"vault_persist\",\n severity: \"error\",\n platform: linkToken.platform,\n context: {\n conversationId: linkToken.conversationId,\n vaultId: linkToken.vaultId,\n serviceId: service.id,\n credentialMode: \"oauth\",\n storedTargetCount: storedTargets.length,\n },\n });\n res.writeHead(500, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderErrorPage(\n \"OAuth tokens were received but could not be stored on the server. Fix the server issue and run /login again.\",\n ),\n );\n return;\n }\n\n log.logInfo(\n `Stored [${storedTargets.join(\", \")}] for ${linkToken.platform}/${linkToken.platformUserId} in vault:${linkToken.vaultId}`,\n );\n\n notify(\n linkToken.platform,\n linkToken.conversationId,\n `${service.label} OAuth stored (${storedTargets.join(\", \")}) in vault \\`${linkToken.vaultId}\\`.`,\n ).catch((err: Error) => {\n log.logWarning(\"Failed to notify user after OAuth login\", err.message);\n reportUserFacingError(err, {\n domain: \"login\",\n surface: \"oauth\",\n operation: \"notify_user\",\n severity: \"warning\",\n platform: linkToken.platform,\n context: {\n conversationId: linkToken.conversationId,\n vaultId: linkToken.vaultId,\n serviceId: service.id,\n credentialMode: \"oauth\",\n storedTargetCount: storedTargets.length,\n },\n });\n });\n\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderSuccessPage(`${service.label} OAuth connected successfully.`));\n}\n\nasync function exchangeOAuthCode(\n service: OAuthService,\n code: string,\n clientId: string,\n clientSecret: string,\n redirectUri: string,\n codeVerifier: string,\n): Promise<Record<string, string>> {\n const params = new URLSearchParams();\n params.set(\"grant_type\", \"authorization_code\");\n params.set(\"code\", code);\n params.set(\"client_id\", clientId);\n params.set(\"client_secret\", clientSecret);\n params.set(\"redirect_uri\", redirectUri);\n params.set(\"code_verifier\", codeVerifier);\n\n const response = await fetch(service.tokenUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n });\n\n const text = await response.text();\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n let parsed: Record<string, string> = {};\n\n if (contentType.includes(\"application/json\")) {\n parsed = JSON.parse(text) as Record<string, string>;\n } else {\n const form = new URLSearchParams(text);\n parsed = Object.fromEntries(form.entries());\n }\n\n if (!response.ok) {\n const message = parsed.error_description ?? parsed.error ?? `${response.status}`;\n throw new Error(`OAuth token exchange failed for ${service.id}: ${message}`);\n }\n\n return parsed;\n}\n\nfunction renderAuthorizedUserCredential(\n clientId: string,\n clientSecret: string,\n refreshToken: string,\n): string {\n return (\n JSON.stringify(\n {\n client_id: clientId,\n client_secret: clientSecret,\n refresh_token: refreshToken,\n type: \"authorized_user\",\n },\n null,\n 2,\n ) + \"\\n\"\n );\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PlatformName } from "../../adapter.js";
|
|
2
|
+
import { InMemoryTokenStore } from "../token-store.js";
|
|
3
|
+
export type { LinkToken } from "./types.js";
|
|
4
|
+
import type { LinkToken } from "./types.js";
|
|
5
|
+
export declare class InMemoryLinkTokenStore extends InMemoryTokenStore<LinkToken> {
|
|
6
|
+
/**
|
|
7
|
+
* Create a link token for a platform user.
|
|
8
|
+
* Invalidates any existing token for the same user before creating a new one.
|
|
9
|
+
*/
|
|
10
|
+
create(platform: PlatformName, platformUserId: string, conversationId: string, vaultId: string, providerId: string): LinkToken;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/web/login/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,qBAAa,sBAAuB,SAAQ,kBAAkB,CAAC,SAAS,CAAC;IACvE;;;OAGG;IACH,MAAM,CACJ,QAAQ,EAAE,YAAY,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,SAAS,CAmBX;CACF","sourcesContent":["import type { PlatformName } from \"../../adapter.js\";\nimport { InMemoryTokenStore } from \"../token-store.js\";\nexport type { LinkToken } from \"./types.js\";\nimport type { LinkToken } from \"./types.js\";\n\nconst TTL_MS = 15 * 60 * 1000;\n\nexport class InMemoryLinkTokenStore extends InMemoryTokenStore<LinkToken> {\n /**\n * Create a link token for a platform user.\n * Invalidates any existing token for the same user before creating a new one.\n */\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n vaultId: string,\n providerId: string,\n ): LinkToken {\n for (const [key, t] of this.tokens) {\n if (t.platform === platform && t.platformUserId === platformUserId) {\n this.tokens.delete(key);\n }\n }\n\n const { token, expiresAt } = this.mintToken(TTL_MS);\n const record: LinkToken = {\n token,\n platform,\n platformUserId,\n vaultId,\n providerId,\n conversationId,\n expiresAt,\n };\n this.tokens.set(token, record);\n return record;\n }\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { InMemoryTokenStore } from "../token-store.js";
|
|
2
|
+
const TTL_MS = 15 * 60 * 1000;
|
|
3
|
+
export class InMemoryLinkTokenStore extends InMemoryTokenStore {
|
|
4
|
+
/**
|
|
5
|
+
* Create a link token for a platform user.
|
|
6
|
+
* Invalidates any existing token for the same user before creating a new one.
|
|
7
|
+
*/
|
|
8
|
+
create(platform, platformUserId, conversationId, vaultId, providerId) {
|
|
9
|
+
for (const [key, t] of this.tokens) {
|
|
10
|
+
if (t.platform === platform && t.platformUserId === platformUserId) {
|
|
11
|
+
this.tokens.delete(key);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const { token, expiresAt } = this.mintToken(TTL_MS);
|
|
15
|
+
const record = {
|
|
16
|
+
token,
|
|
17
|
+
platform,
|
|
18
|
+
platformUserId,
|
|
19
|
+
vaultId,
|
|
20
|
+
providerId,
|
|
21
|
+
conversationId,
|
|
22
|
+
expiresAt,
|
|
23
|
+
};
|
|
24
|
+
this.tokens.set(token, record);
|
|
25
|
+
return record;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/web/login/store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAIvD,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9B,MAAM,OAAO,sBAAuB,SAAQ,kBAA6B;IACvE;;;OAGG;IACH,MAAM,CACJ,QAAsB,EACtB,cAAsB,EACtB,cAAsB,EACtB,OAAe,EACf,UAAkB;QAElB,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACnC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,cAAc,KAAK,cAAc,EAAE,CAAC;gBACnE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;QAED,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACpD,MAAM,MAAM,GAAc;YACxB,KAAK;YACL,QAAQ;YACR,cAAc;YACd,OAAO;YACP,UAAU;YACV,cAAc;YACd,SAAS;SACV,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC/B,OAAO,MAAM,CAAC;IAChB,CAAC;CACF","sourcesContent":["import type { PlatformName } from \"../../adapter.js\";\nimport { InMemoryTokenStore } from \"../token-store.js\";\nexport type { LinkToken } from \"./types.js\";\nimport type { LinkToken } from \"./types.js\";\n\nconst TTL_MS = 15 * 60 * 1000;\n\nexport class InMemoryLinkTokenStore extends InMemoryTokenStore<LinkToken> {\n /**\n * Create a link token for a platform user.\n * Invalidates any existing token for the same user before creating a new one.\n */\n create(\n platform: PlatformName,\n platformUserId: string,\n conversationId: string,\n vaultId: string,\n providerId: string,\n ): LinkToken {\n for (const [key, t] of this.tokens) {\n if (t.platform === platform && t.platformUserId === platformUserId) {\n this.tokens.delete(key);\n }\n }\n\n const { token, expiresAt } = this.mintToken(TTL_MS);\n const record: LinkToken = {\n token,\n platform,\n platformUserId,\n vaultId,\n providerId,\n conversationId,\n expiresAt,\n };\n this.tokens.set(token, record);\n return record;\n }\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PlatformName } from "../../adapter.js";
|
|
2
|
+
import type { TokenRecord } from "../types.js";
|
|
3
|
+
export type LoginCredentialKind = "api_key" | "oauth";
|
|
4
|
+
interface OAuthAuthorizedUserFileOutput {
|
|
5
|
+
type: "authorized_user";
|
|
6
|
+
relativePath: string;
|
|
7
|
+
targetPath?: string;
|
|
8
|
+
envKey?: string;
|
|
9
|
+
additionalEnvKeys?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface OAuthService {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
aliases: string[];
|
|
15
|
+
authorizationUrl: string;
|
|
16
|
+
tokenUrl: string;
|
|
17
|
+
scopes: string[];
|
|
18
|
+
/** Env var names for reading the OAuth client credentials (input). */
|
|
19
|
+
clientIdEnvKey: string;
|
|
20
|
+
clientSecretEnvKey: string;
|
|
21
|
+
/** Env var names to write the access token into after a successful exchange (output). */
|
|
22
|
+
accessTokenEnvKeys?: string[];
|
|
23
|
+
/** Env var name to write the refresh token into after a successful exchange (output). */
|
|
24
|
+
refreshTokenEnvKey?: string;
|
|
25
|
+
authorizationParams?: Record<string, string>;
|
|
26
|
+
fileOutput?: OAuthAuthorizedUserFileOutput;
|
|
27
|
+
}
|
|
28
|
+
export type ParsedLoginCommand = {
|
|
29
|
+
action: "setup";
|
|
30
|
+
} | {
|
|
31
|
+
action: "shared_create" | "shared_update" | "shared_delete";
|
|
32
|
+
name: string;
|
|
33
|
+
} | {
|
|
34
|
+
action: "shared_list";
|
|
35
|
+
} | {
|
|
36
|
+
action: "copy_shared";
|
|
37
|
+
name: string;
|
|
38
|
+
};
|
|
39
|
+
export interface LinkToken extends TokenRecord {
|
|
40
|
+
platform: PlatformName;
|
|
41
|
+
platformUserId: string;
|
|
42
|
+
vaultId: string;
|
|
43
|
+
providerId: string;
|
|
44
|
+
/** Conversation to notify when binding completes */
|
|
45
|
+
conversationId: string;
|
|
46
|
+
}
|
|
47
|
+
/** Called after a binding is written, to notify the user in chat */
|
|
48
|
+
export type NotifyFn = (platform: string, conversationId: string, message: string) => Promise<void>;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/web/login/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEtD,UAAU,6BAA6B;IACrC,IAAI,EAAE,iBAAiB,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,UAAU,CAAC,EAAE,6BAA6B,CAAC;CAC5C;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACnB;IAAE,MAAM,EAAE,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7E;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,GACzB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C,MAAM,WAAW,SAAU,SAAQ,WAAW;IAC5C,QAAQ,EAAE,YAAY,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,oEAAoE;AACpE,MAAM,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC","sourcesContent":["import type { PlatformName } from \"../../adapter.js\";\nimport type { TokenRecord } from \"../types.js\";\n\nexport type LoginCredentialKind = \"api_key\" | \"oauth\";\n\ninterface OAuthAuthorizedUserFileOutput {\n type: \"authorized_user\";\n relativePath: string;\n targetPath?: string;\n envKey?: string;\n additionalEnvKeys?: string[];\n}\n\nexport interface OAuthService {\n id: string;\n label: string;\n aliases: string[];\n authorizationUrl: string;\n tokenUrl: string;\n scopes: string[];\n /** Env var names for reading the OAuth client credentials (input). */\n clientIdEnvKey: string;\n clientSecretEnvKey: string;\n /** Env var names to write the access token into after a successful exchange (output). */\n accessTokenEnvKeys?: string[];\n /** Env var name to write the refresh token into after a successful exchange (output). */\n refreshTokenEnvKey?: string;\n authorizationParams?: Record<string, string>;\n fileOutput?: OAuthAuthorizedUserFileOutput;\n}\n\nexport type ParsedLoginCommand =\n | { action: \"setup\" }\n | { action: \"shared_create\" | \"shared_update\" | \"shared_delete\"; name: string }\n | { action: \"shared_list\" }\n | { action: \"copy_shared\"; name: string };\n\nexport interface LinkToken extends TokenRecord {\n platform: PlatformName;\n platformUserId: string;\n vaultId: string;\n providerId: string;\n /** Conversation to notify when binding completes */\n conversationId: string;\n}\n\n/** Called after a binding is written, to notify the user in chat */\nexport type NotifyFn = (platform: string, conversationId: string, message: string) => Promise<void>;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/web/login/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { PlatformName } from \"../../adapter.js\";\nimport type { TokenRecord } from \"../types.js\";\n\nexport type LoginCredentialKind = \"api_key\" | \"oauth\";\n\ninterface OAuthAuthorizedUserFileOutput {\n type: \"authorized_user\";\n relativePath: string;\n targetPath?: string;\n envKey?: string;\n additionalEnvKeys?: string[];\n}\n\nexport interface OAuthService {\n id: string;\n label: string;\n aliases: string[];\n authorizationUrl: string;\n tokenUrl: string;\n scopes: string[];\n /** Env var names for reading the OAuth client credentials (input). */\n clientIdEnvKey: string;\n clientSecretEnvKey: string;\n /** Env var names to write the access token into after a successful exchange (output). */\n accessTokenEnvKeys?: string[];\n /** Env var name to write the refresh token into after a successful exchange (output). */\n refreshTokenEnvKey?: string;\n authorizationParams?: Record<string, string>;\n fileOutput?: OAuthAuthorizedUserFileOutput;\n}\n\nexport type ParsedLoginCommand =\n | { action: \"setup\" }\n | { action: \"shared_create\" | \"shared_update\" | \"shared_delete\"; name: string }\n | { action: \"shared_list\" }\n | { action: \"copy_shared\"; name: string };\n\nexport interface LinkToken extends TokenRecord {\n platform: PlatformName;\n platformUserId: string;\n vaultId: string;\n providerId: string;\n /** Conversation to notify when binding completes */\n conversationId: string;\n}\n\n/** Called after a binding is written, to notify the user in chat */\nexport type NotifyFn = (platform: string, conversationId: string, message: string) => Promise<void>;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/web/session-view/command.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAI3D,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAGrF","sourcesContent":["import { matchCommand } from \"../../commands/parse.js\";\nexport type { ParsedSessionViewCommand } from \"./types.js\";\nimport type { ParsedSessionViewCommand } from \"./types.js\";\n\nconst SESSION_VIEW_COMMANDS = [\"session\", \"/session\", \"/pi-session\"] as const;\n\nexport function parseSessionViewCommand(text: string): ParsedSessionViewCommand | null {\n const matched = matchCommand(text, SESSION_VIEW_COMMANDS);\n return matched ? { command: matched.command } : null;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { matchCommand } from "
|
|
1
|
+
import { matchCommand } from "../../commands/parse.js";
|
|
2
2
|
const SESSION_VIEW_COMMANDS = ["session", "/session", "/pi-session"];
|
|
3
3
|
export function parseSessionViewCommand(text) {
|
|
4
4
|
const matched = matchCommand(text, SESSION_VIEW_COMMANDS);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../../src/web/session-view/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAIvD,MAAM,qBAAqB,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,aAAa,CAAU,CAAC;AAE9E,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAC1D,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACvD,CAAC","sourcesContent":["import { matchCommand } from \"../../commands/parse.js\";\nexport type { ParsedSessionViewCommand } from \"./types.js\";\nimport type { ParsedSessionViewCommand } from \"./types.js\";\n\nconst SESSION_VIEW_COMMANDS = [\"session\", \"/session\", \"/pi-session\"] as const;\n\nexport function parseSessionViewCommand(text: string): ParsedSessionViewCommand | null {\n const matched = matchCommand(text, SESSION_VIEW_COMMANDS);\n return matched ? { command: matched.command } : null;\n}\n"]}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "http";
|
|
2
|
-
import type { Bot, BotHandler } from "../adapter.js";
|
|
3
2
|
import type { InMemorySessionViewTokenStore } from "./store.js";
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
botsByPlatform: Partial<Record<string, Bot>>;
|
|
7
|
-
}
|
|
3
|
+
export type { SessionViewInteractiveOptions } from "./types.js";
|
|
4
|
+
import type { SessionViewInteractiveOptions } from "./types.js";
|
|
8
5
|
export declare function handleSessionViewRequest(req: IncomingMessage, res: ServerResponse, url: URL, sessionViewTokenStore?: InMemorySessionViewTokenStore, interactive?: SessionViewInteractiveOptions): Promise<boolean>;
|
|
9
6
|
export declare function parseUserBody(raw: string): {
|
|
10
7
|
timestamp: string | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../src/web/session-view/portal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAe5D,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AA4DhE,YAAY,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAEhE,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,qBAAqB,CAAC,EAAE,6BAA6B,EACrD,WAAW,CAAC,EAAE,6BAA6B,GAC1C,OAAO,CAAC,OAAO,CAAC,CA2GlB;AAmID,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAC1C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB,CA8BA","sourcesContent":["import type { IncomingMessage, ServerResponse } from \"http\";\nimport { basename } from \"path\";\nimport MarkdownIt from \"markdown-it\";\nimport type { BotAdapters, BotEvent, ChatResponseContext } from \"../../adapter.js\";\nimport { escapeHtml } from \"../../utils/html.js\";\nimport * as log from \"../../log.js\";\nimport { renderPortalShell } from \"../../portal-shell.js\";\nimport { reportUserFacingError } from \"../../observability/sentry.js\";\nimport { inferConversationKind } from \"../../sessions/policy.js\";\nimport {\n loadSessionViewModel,\n resolveRequestedSessionFile,\n type SessionViewItem,\n type SessionViewRelation,\n} from \"./service.js\";\nimport type { InMemorySessionViewTokenStore } from \"./store.js\";\n\nconst markdown = new MarkdownIt({\n html: false,\n linkify: true,\n breaks: true,\n});\n\nconst defaultLinkOpen = markdown.renderer.rules.link_open;\ntype LinkOpenRule = NonNullable<typeof defaultLinkOpen>;\nmarkdown.renderer.rules.link_open = (...args: Parameters<LinkOpenRule>) => {\n const [tokens, idx, options, env, self] = args;\n const token = tokens[idx];\n token.attrSet(\"target\", \"_blank\");\n token.attrSet(\"rel\", \"noreferrer noopener\");\n return defaultLinkOpen\n ? defaultLinkOpen(tokens, idx, options, env, self)\n : self.renderToken(tokens, idx, options);\n};\n\ntype SessionStreamEvent =\n | { type: \"status\"; running: boolean }\n | { type: \"user\"; html: string }\n | { type: \"assistant\"; html: string }\n | { type: \"assistant_remove\" }\n | { type: \"tool\"; html: string }\n | { type: \"system\"; html: string }\n | {\n type: \"refresh\";\n timelineHtml: string;\n updatedAt: string;\n entryCount: number;\n running: boolean;\n }\n | { type: \"error\"; message: string };\n\nclass SessionViewStreamHub {\n private listeners = new Map<string, Set<(event: SessionStreamEvent) => void>>();\n\n subscribe(key: string, listener: (event: SessionStreamEvent) => void): () => void {\n const set = this.listeners.get(key) ?? new Set<(event: SessionStreamEvent) => void>();\n set.add(listener);\n this.listeners.set(key, set);\n return () => {\n const current = this.listeners.get(key);\n if (!current) return;\n current.delete(listener);\n if (current.size === 0) this.listeners.delete(key);\n };\n }\n\n publish(key: string, event: SessionStreamEvent): void {\n const set = this.listeners.get(key);\n if (!set) return;\n for (const listener of set) listener(event);\n }\n}\n\nconst sessionViewStreamHub = new SessionViewStreamHub();\n\nexport type { SessionViewInteractiveOptions } from \"./types.js\";\nimport type { SessionViewInteractiveOptions } from \"./types.js\";\n\nexport async function handleSessionViewRequest(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n sessionViewTokenStore?: InMemorySessionViewTokenStore,\n interactive?: SessionViewInteractiveOptions,\n): Promise<boolean> {\n if (req.method === \"POST\" && url.pathname === \"/session/message\") {\n await handleSessionMessageRequest(req, res, sessionViewTokenStore, interactive);\n return true;\n }\n\n if (req.method === \"GET\" && url.pathname === \"/session/stream\") {\n await handleSessionStreamRequest(req, res, url, sessionViewTokenStore, interactive);\n return true;\n }\n\n if (req.method !== \"GET\" || url.pathname !== \"/session\") {\n return false;\n }\n\n const token = url.searchParams.get(\"token\")?.trim();\n if (!token || !sessionViewTokenStore) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderStatusPage(\"Session unavailable\", \"This session link is invalid or has expired.\"),\n );\n return true;\n }\n\n const entry = sessionViewTokenStore.peek(token);\n if (!entry) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderStatusPage(\"Session unavailable\", \"This session link is invalid or has expired.\"),\n );\n return true;\n }\n\n const requestedSession = url.searchParams.get(\"session\");\n let targetSessionFile: string | null;\n try {\n targetSessionFile = resolveRequestedSessionFile(entry.sessionFile, requestedSession);\n } catch (error) {\n log.logWarning(\n `[${entry.conversationId}] Corrupted session file referenced for ${entry.sessionFile}`,\n error instanceof Error ? error.message : String(error),\n );\n reportUserFacingError(error, {\n domain: \"session_view\",\n surface: \"session_view\",\n operation: \"resolve_requested_session\",\n severity: \"error\",\n platform: entry.platform,\n context: {\n conversationId: entry.conversationId,\n sessionKey: entry.sessionKey,\n sessionFile: basename(entry.sessionFile),\n requestedSession,\n },\n });\n res.writeHead(500, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(\n renderStatusPage(\"Session unavailable\", \"The selected session file appears to be corrupted.\"),\n );\n return true;\n }\n if (!targetSessionFile) {\n res.writeHead(400, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderStatusPage(\"Session unavailable\", \"The selected session link is invalid.\"));\n return true;\n }\n\n try {\n const model = loadSessionViewModel(targetSessionFile);\n const displayedSessionKey = resolveDisplayedSessionKey(entry, targetSessionFile);\n const isRunning = interactive?.handler.isRunning(displayedSessionKey) ?? false;\n res.writeHead(200, {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n });\n res.end(\n renderSessionPage(\n model,\n entry.token,\n entry.expiresAt,\n isRunning,\n displayedSessionKey,\n entry.conversationId,\n ),\n );\n } catch (error) {\n log.logWarning(\n `[${entry.conversationId}] Failed to render session ${entry.sessionFile}`,\n error instanceof Error ? error.message : String(error),\n );\n reportUserFacingError(error, {\n domain: \"session_view\",\n surface: \"session_view\",\n operation: \"render_session\",\n severity: \"error\",\n platform: entry.platform,\n context: {\n conversationId: entry.conversationId,\n sessionKey: entry.sessionKey,\n sessionFile: basename(targetSessionFile),\n },\n });\n res.writeHead(500, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(renderStatusPage(\"Session unavailable\", \"The session could not be loaded right now.\"));\n }\n\n return true;\n}\n\nfunction resolveDisplayedSessionKey(\n entry: { platform: string; conversationId: string; sessionKey: string },\n sessionFile: string,\n): string {\n if (entry.platform === \"slack\") {\n const fileName = basename(sessionFile, \".jsonl\");\n if (/^\\d+\\.\\d+$/.test(fileName)) {\n return `${entry.conversationId}:${fileName}`;\n }\n return entry.conversationId;\n }\n return entry.sessionKey;\n}\n\nfunction sessionStreamKey(entry: {\n platform: string;\n conversationId: string;\n sessionKey: string;\n}): string {\n return `${entry.platform}:${entry.conversationId}:${entry.sessionKey}`;\n}\n\nfunction renderTimelineItems(items: SessionViewItem[], token: string): string {\n return items.length > 0\n ? items.map((item) => renderItem(item, token)).join(\"\\n\")\n : `<div class=\"system-event\"><span class=\"event-dot\"></span><span class=\"event-text\">No messages yet — send one to the bot, then refresh.</span></div>`;\n}\n\nfunction renderSessionPage(\n model: {\n title: string;\n sessionId: string;\n fileName: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n threads: SessionViewRelation[];\n },\n token: string,\n expiresAt: number,\n isRunning: boolean,\n displayedSessionKey: string,\n conversationId: string,\n): string {\n const items = renderTimelineItems(model.items, token);\n\n const relatedSections = model.parent\n ? `<section class=\"related-card stack\">\n <p class=\"eyebrow\">Parent session</p>\n ${renderRelationCard(model.parent, token)}\n </section>`\n : \"\";\n\n const body = `<header class=\"page-head\">\n <div>\n <p class=\"eyebrow\">Session</p>\n <h2 class=\"page-title\">${esc(model.title)}</h2>\n <p class=\"page-desc\">\n <span>Created ${esc(formatDate(model.createdAt))}</span> ·\n <span>Updated <strong data-session-updated>${esc(formatDate(model.updatedAt))}</strong></span> ·\n <span><strong data-session-entries>${esc(String(model.entryCount))}</strong> entries</span>\n </p>\n </div>\n <div class=\"session-side\">\n <span class=\"session-badge session-badge-status${isRunning ? \" is-running\" : \"\"}\"><span class=\"session-badge-dot\"></span><strong data-session-status>${esc(isRunning ? \"Running\" : \"Idle\")}</strong></span>\n <span class=\"session-badge\">${esc(displayedSessionKey === conversationId ? \"Channel\" : \"Thread\")}</span>\n </div>\n </header>\n\n <div class=\"session-detail-row\">\n <span class=\"session-detail\"><span class=\"session-detail-label\">Session</span><code>${esc(model.sessionId.slice(0, 8))}</code></span>\n <span class=\"session-detail\"><span class=\"session-detail-label\">File</span><code>${esc(model.fileName)}</code></span>\n <span class=\"session-detail\"><span class=\"session-detail-label\">Expires</span><span>${esc(formatDate(new Date(expiresAt).toISOString()))}</span></span>\n </div>\n\n ${relatedSections}\n\n <div class=\"timeline-shell\">\n <div class=\"timeline-list\" data-timeline-list>\n ${items}\n </div>\n </div>\n\n <button class=\"jump-latest-btn\" type=\"button\" hidden data-jump-latest aria-label=\"Jump to latest\" title=\"Jump to latest\">↓</button>\n\n <section class=\"composer-card\">\n <form class=\"composer-form\" data-session-composer>\n <input type=\"hidden\" name=\"token\" value=\"${esc(token)}\">\n <input type=\"hidden\" name=\"session\" value=\"${esc(model.fileName)}\">\n <input type=\"hidden\" name=\"sessionKey\" value=\"${esc(displayedSessionKey)}\">\n <textarea name=\"text\" rows=\"1\" placeholder=\"Ask mikan in this session… (replies stay in Session View)\" required></textarea>\n <div class=\"composer-actions\">\n <span class=\"composer-status\" data-composer-status></span>\n <button class=\"composer-send-btn\" type=\"submit\" aria-label=\"Send\" title=\"Send\">↑</button>\n </div>\n </form>\n </section>`;\n\n return renderHtmlDocument(`${model.title} · Session Viewer`, body, isRunning);\n}\n\nfunction renderRelationCard(relation: SessionViewRelation, token: string): string {\n const href = `/session?token=${encodeURIComponent(token)}&session=${encodeURIComponent(relation.fileName)}`;\n const summary = relation.summary ? `<p class=\"related-summary\">${esc(relation.summary)}</p>` : \"\";\n return `<a class=\"related-link\" href=\"${href}\">\n <span class=\"related-copy\">\n <strong class=\"related-title\">${esc(relation.title)}</strong>\n ${summary}\n <span class=\"related-meta\">${esc(formatDate(relation.updatedAt))} · ${esc(String(relation.entryCount))} entries · ${esc(relation.fileName)}</span>\n </span>\n <span class=\"related-arrow\" aria-hidden=\"true\">→</span>\n </a>`;\n}\n\nfunction renderThreadLinks(relations: SessionViewRelation[] | undefined, token: string): string {\n if (!relations || relations.length === 0) return \"\";\n return `<div class=\"thread-links\">${relations\n .map((relation) => {\n const href = `/session?token=${encodeURIComponent(token)}&session=${encodeURIComponent(relation.fileName)}`;\n return `<a class=\"thread-link\" href=\"${href}\" title=\"Open ${esc(relation.title)}\">\n <span class=\"thread-dot\" aria-hidden=\"true\"></span>\n <span class=\"thread-text\">Thread</span>\n </a>`;\n })\n .join(\"\")}</div>`;\n}\n\nexport function parseUserBody(raw: string): {\n timestamp: string | null;\n username: string | null;\n threadTs: string | null;\n header: string | null;\n content: string;\n} {\n // [timestamp] [username] [in-thread:ts]: content\n let m = raw.match(\n /^\\[([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2})\\]\\s*\\[([^\\]]+)\\](?:\\s*\\[in-thread:([^\\]]+)\\])?:\\s*([\\s\\S]*)$/,\n );\n if (m) {\n const header = [`[${m[1]}]`, `[${m[2]}]`, m[3] ? `[in-thread:${m[3]}]` : \"\"]\n .filter(Boolean)\n .join(\" \");\n return {\n timestamp: m[1],\n username: m[2],\n threadTs: m[3] ?? null,\n header,\n content: m[4],\n };\n }\n // [username] [in-thread:ts]: content\n m = raw.match(/^\\[([^\\]]+)\\](?:\\s*\\[in-thread:([^\\]]+)\\])?:\\s*([\\s\\S]*)$/);\n if (m) {\n const header = [`[${m[1]}]`, m[2] ? `[in-thread:${m[2]}]` : \"\"].filter(Boolean).join(\" \");\n return {\n timestamp: null,\n username: m[1],\n threadTs: m[2] ?? null,\n header,\n content: m[3],\n };\n }\n return { timestamp: null, username: null, threadTs: null, header: null, content: raw };\n}\n\ntype ParsedUserBody = ReturnType<typeof parseUserBody>;\n\nfunction renderCopyButton(label = \"Copy message\"): string {\n return `<div class=\"msg-actions\"><button class=\"copy-action-btn\" type=\"button\" data-copy-button data-copy-label=\"${esc(label)}\" aria-label=\"${esc(label)}\" title=\"${esc(label)}\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\" stroke=\"currentColor\" stroke-width=\"1.8\"></rect><path d=\"M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\"></path></svg></button></div>`;\n}\n\nfunction renderItem(item: SessionViewItem, token?: string): string {\n if (item.kind === \"system\") {\n const parts = [item.title, item.body].filter((x): x is string => Boolean(x)).map(esc);\n const time = item.meta\n ? ` · <time class=\"event-time\">${esc(formatDate(item.meta))}</time>`\n : \"\";\n return `<div class=\"system-event\"><span class=\"event-dot\"></span><span class=\"event-text\">${parts.join(\" — \")}</span>${time}</div>`;\n }\n\n if (item.kind === \"tool\") {\n const toneClass = item.tone === \"err\" ? \" tone-err\" : item.tone === \"ok\" ? \" tone-ok\" : \"\";\n const body = item.body ? `<pre class=\"tool-output${toneClass}\">${esc(item.body)}</pre>` : \"\";\n const time = item.meta ? `<time class=\"tool-time\">${esc(formatDate(item.meta))}</time>` : \"\";\n return `<div class=\"tool-block\">\n <div class=\"tool-header\">\n <span class=\"tool-icon\"><svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\"><path d=\"M1.5 2L5 5.5 1.5 9\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M6 9h2.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg></span>\n <span class=\"tool-name\">${esc(item.title)}</span>\n ${time}\n </div>\n ${body}\n</div>`;\n }\n\n const time = item.meta ? `<time class=\"msg-time\">${esc(formatDate(item.meta))}</time>` : \"\";\n\n if (item.kind === \"user\") {\n const parsed: ParsedUserBody = item.body\n ? parseUserBody(item.body)\n : { timestamp: null, username: null, threadTs: null, header: null, content: \"\" };\n const { username, threadTs, header, content } = parsed;\n const initial = username ? esc(username.slice(0, 2).toUpperCase()) : \"U\";\n const rawHeader = header ? `<div class=\"msg-raw-header\">${esc(header)}</div>` : \"\";\n const body = content ? renderMarkdownBlock(content, \"user\") : \"\";\n const threadBadge = threadTs\n ? `<div class=\"thread-badge\" title=\"Thread ${esc(threadTs)}\">Thread · <code>${esc(threadTs)}</code></div>`\n : \"\";\n const threads = renderThreadLinks(item.threads, token ?? \"\");\n return `<div class=\"msg-row msg-user copy-host\">\n <div class=\"msg-main user-main\">\n <div class=\"user-bubble\">\n ${rawHeader}\n ${threadBadge}\n ${body}\n ${threads}\n ${time}\n </div>\n ${renderCopyButton()}\n </div>\n <div class=\"msg-avatar user-avatar\" title=\"${username ? esc(username) : \"User\"}\">${initial}</div>\n</div>`;\n }\n\n // assistant\n const body = item.body ? renderMarkdownBlock(item.body, \"assistant\") : \"\";\n const threads = renderThreadLinks(item.threads, token ?? \"\");\n return `<div class=\"msg-row msg-assistant copy-host\">\n <div class=\"msg-avatar asst-avatar\" aria-hidden=\"true\">A</div>\n <div class=\"msg-main asst-main\">\n <div class=\"asst-card\">\n ${body}\n ${threads}\n ${time}\n </div>\n ${renderCopyButton()}\n </div>\n</div>`;\n}\n\nfunction renderMarkdownBlock(text: string, variant: \"user\" | \"assistant\"): string {\n return `<div class=\"msg-body markdown-body markdown-${variant}\">${markdown.render(text)}</div>`;\n}\n\nfunction renderLiveUserMessage(text: string, userName: string): string {\n const initial = esc(userName.slice(0, 2).toUpperCase());\n return `<div class=\"msg-row msg-user copy-host\" data-live-item>\n <div class=\"msg-main user-main\">\n <div class=\"user-bubble\">\n ${renderMarkdownBlock(text, \"user\")}\n </div>\n ${renderCopyButton()}\n </div>\n <div class=\"msg-avatar user-avatar\" title=\"${esc(userName)}\">${initial}</div>\n</div>`;\n}\n\nfunction renderLiveAssistantMessage(text: string): string {\n return `<div class=\"msg-row msg-assistant copy-host\" data-live-assistant>\n <div class=\"msg-avatar asst-avatar\" aria-hidden=\"true\">A</div>\n <div class=\"msg-main asst-main\">\n <div class=\"asst-card\">\n ${renderMarkdownBlock(text, \"assistant\")}\n </div>\n ${renderCopyButton()}\n </div>\n</div>`;\n}\n\nfunction renderLiveToolResult(result: {\n toolName: string;\n result: string;\n isError: boolean;\n}): string {\n const toneClass = result.isError ? \" tone-err\" : \" tone-ok\";\n return `<div class=\"tool-block\" data-live-item>\n <div class=\"tool-header\">\n <span class=\"tool-icon\"><svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"none\"><path d=\"M1.5 2L5 5.5 1.5 9\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><path d=\"M6 9h2.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/></svg></span>\n <span class=\"tool-name\">${esc(result.toolName)}</span>\n </div>\n <pre class=\"tool-output${toneClass}\">${esc(result.result)}</pre>\n</div>`;\n}\n\nfunction renderLiveSystemEvent(text: string, tone: \"default\" | \"err\" = \"default\"): string {\n const cls = tone === \"err\" ? \" system-event-err\" : \"\";\n return `<div class=\"system-event${cls}\" data-live-item><span class=\"event-dot\"></span><span class=\"event-text\">${esc(text)}</span></div>`;\n}\n\nasync function handleSessionStreamRequest(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n sessionViewTokenStore?: InMemorySessionViewTokenStore,\n interactive?: SessionViewInteractiveOptions,\n): Promise<void> {\n const token = url.searchParams.get(\"token\")?.trim() ?? \"\";\n if (!token || !sessionViewTokenStore || !interactive) {\n res.writeHead(400, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(\"Session stream unavailable\");\n return;\n }\n\n const entry = sessionViewTokenStore.peek(token);\n if (!entry) {\n res.writeHead(400, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(\"Invalid session token\");\n return;\n }\n\n const requestedSession = url.searchParams.get(\"session\");\n let targetSessionFile: string | null;\n try {\n targetSessionFile = resolveRequestedSessionFile(entry.sessionFile, requestedSession);\n } catch (error) {\n reportUserFacingError(error, {\n domain: \"session_view\",\n surface: \"session_view\",\n operation: \"session_stream\",\n severity: \"error\",\n platform: entry.platform,\n context: {\n conversationId: entry.conversationId,\n sessionKey: entry.sessionKey,\n sessionFile: basename(entry.sessionFile),\n requestedSession,\n },\n });\n res.writeHead(500, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(\"Session stream unavailable\");\n return;\n }\n if (!targetSessionFile) {\n res.writeHead(400, { \"Content-Type\": \"text/plain; charset=utf-8\" });\n res.end(\"Invalid session file\");\n return;\n }\n const activeSessionKey = resolveDisplayedSessionKey(entry, targetSessionFile);\n const streamKey = sessionStreamKey({ ...entry, sessionKey: activeSessionKey });\n res.writeHead(200, {\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n Connection: \"keep-alive\",\n });\n res.write(\n `data: ${JSON.stringify({ type: \"status\", running: interactive.handler.isRunning(activeSessionKey) })}\\n\\n`,\n );\n\n const unsubscribe = sessionViewStreamHub.subscribe(streamKey, (event) => {\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n });\n const heartbeat = setInterval(() => {\n res.write(\": keep-alive\\n\\n\");\n }, 15000);\n\n req.on(\"close\", () => {\n clearInterval(heartbeat);\n unsubscribe();\n });\n}\n\nasync function handleSessionMessageRequest(\n req: IncomingMessage,\n res: ServerResponse,\n sessionViewTokenStore?: InMemorySessionViewTokenStore,\n interactive?: SessionViewInteractiveOptions,\n): Promise<void> {\n if (!sessionViewTokenStore || !interactive) {\n json(res, 503, { ok: false, error: \"Session chat is not configured.\" });\n return;\n }\n\n let body: { token?: string; text?: string; session?: string; sessionKey?: string };\n try {\n body = JSON.parse(await readRequestBody(req)) as {\n token?: string;\n text?: string;\n session?: string;\n sessionKey?: string;\n };\n } catch {\n json(res, 400, { ok: false, error: \"Invalid request body.\" });\n return;\n }\n\n const token = body.token?.trim() ?? \"\";\n const text = body.text?.trim() ?? \"\";\n const requestedSession = body.session?.trim() || null;\n const requestedSessionKey = body.sessionKey?.trim() || \"\";\n if (!token || !text) {\n json(res, 400, { ok: false, error: \"Missing token or text.\" });\n return;\n }\n\n const entry = sessionViewTokenStore.peek(token);\n if (!entry) {\n json(res, 400, { ok: false, error: \"This session link is invalid or has expired.\" });\n return;\n }\n\n let targetSessionFile: string | null;\n try {\n targetSessionFile = resolveRequestedSessionFile(entry.sessionFile, requestedSession);\n } catch (error) {\n reportUserFacingError(error, {\n domain: \"session_view\",\n surface: \"session_view\",\n operation: \"session_message\",\n severity: \"error\",\n platform: entry.platform,\n context: {\n conversationId: entry.conversationId,\n sessionKey: entry.sessionKey,\n sessionFile: basename(entry.sessionFile),\n requestedSession,\n },\n });\n json(res, 500, { ok: false, error: \"Session file could not be loaded.\" });\n return;\n }\n if (!targetSessionFile) {\n json(res, 400, { ok: false, error: \"Invalid session file.\" });\n return;\n }\n const activeSessionKey = resolveDisplayedSessionKey(entry, targetSessionFile);\n if (requestedSessionKey && requestedSessionKey !== activeSessionKey) {\n json(res, 400, { ok: false, error: \"Session target mismatch.\" });\n return;\n }\n\n const bot = interactive.botsByPlatform[entry.platform];\n if (!bot) {\n json(res, 503, { ok: false, error: `No bot configured for ${entry.platform}.` });\n return;\n }\n\n const streamKey = sessionStreamKey({ ...entry, sessionKey: activeSessionKey });\n const conversationKind = inferConversationKind(entry.platform, entry.conversationId);\n const ts = (Date.now() / 1000).toFixed(6);\n const platformInfo = bot.getPlatformInfo();\n const platformUserName =\n entry.platformUserName ||\n platformInfo.users.find((user) => user.id === entry.platformUserId)?.userName ||\n platformInfo.users.find((user) => user.id === entry.platformUserId)?.displayName ||\n \"unknown\";\n const responseCtx = createSessionViewResponseContext((event) => {\n sessionViewStreamHub.publish(streamKey, event);\n });\n const event: BotEvent = {\n type: \"session_view\",\n conversationId: entry.conversationId,\n conversationKind,\n ts,\n user: entry.platformUserId,\n text,\n attachments: [],\n sessionKey: activeSessionKey,\n ...(activeSessionKey.includes(\":\")\n ? { thread_ts: activeSessionKey.split(\":\").slice(1).join(\":\") }\n : {}),\n };\n const adapters: BotAdapters = {\n message: {\n id: ts,\n sessionKey: activeSessionKey,\n conversationKind,\n userId: entry.platformUserId,\n userName: platformUserName,\n text,\n attachments: [],\n threadTs: event.thread_ts,\n },\n responseCtx,\n platform: { ...platformInfo, diagnostics: { showUsageSummary: false } },\n };\n\n sessionViewStreamHub.publish(streamKey, { type: \"status\", running: true });\n sessionViewStreamHub.publish(streamKey, {\n type: \"user\",\n html: renderLiveUserMessage(text, platformUserName),\n });\n\n void interactive.handler\n .handleEvent(event, bot, adapters)\n .then(() => {\n if (!targetSessionFile) {\n sessionViewStreamHub.publish(streamKey, { type: \"status\", running: false });\n return;\n }\n const model = loadSessionViewModel(targetSessionFile);\n sessionViewStreamHub.publish(streamKey, {\n type: \"refresh\",\n timelineHtml: renderTimelineItems(model.items, token),\n updatedAt: formatDate(model.updatedAt),\n entryCount: model.entryCount,\n running: false,\n });\n })\n .catch((error) => {\n log.logWarning(\n `[${entry.conversationId}] Session view message failed`,\n error instanceof Error ? error.message : String(error),\n );\n reportUserFacingError(error, {\n domain: \"session_view\",\n surface: \"session_view\",\n operation: \"interactive_message\",\n severity: \"error\",\n platform: entry.platform,\n context: {\n conversationId: entry.conversationId,\n sessionKey: activeSessionKey,\n messageId: ts,\n textLength: text.length,\n },\n });\n sessionViewStreamHub.publish(streamKey, {\n type: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n sessionViewStreamHub.publish(streamKey, { type: \"status\", running: false });\n });\n\n json(res, 202, { ok: true, accepted: true });\n}\n\nfunction createSessionViewResponseContext(\n publish: (event: SessionStreamEvent) => void,\n): ChatResponseContext {\n let accumulatedText = \"\";\n\n return {\n respond: async (text: string) => {\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${text}` : text;\n publish({ type: \"assistant\", html: renderLiveAssistantMessage(accumulatedText) });\n },\n replaceResponse: async (text: string) => {\n accumulatedText = text;\n publish({ type: \"assistant\", html: renderLiveAssistantMessage(accumulatedText) });\n },\n respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n if (options?.style === \"error\") {\n publish({ type: \"system\", html: renderLiveSystemEvent(text, \"err\") });\n }\n },\n respondToolResult: async (result) => {\n publish({ type: \"tool\", html: renderLiveToolResult(result) });\n },\n setTyping: async () => {\n publish({ type: \"status\", running: true });\n },\n setWorking: async (working: boolean) => {\n publish({ type: \"status\", running: working });\n },\n uploadFile: async () => {},\n deleteResponse: async () => {\n accumulatedText = \"\";\n publish({ type: \"assistant_remove\" });\n },\n };\n}\n\nfunction readRequestBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n req.setEncoding(\"utf8\");\n req.on(\"data\", (chunk) => {\n data += chunk;\n if (data.length > 1024 * 1024) {\n reject(new Error(\"Request body too large\"));\n req.destroy();\n }\n });\n req.on(\"end\", () => resolve(data));\n req.on(\"error\", reject);\n });\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, {\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n });\n res.end(JSON.stringify(body));\n}\n\nfunction renderStatusPage(title: string, message: string): string {\n return renderHtmlDocument(\n title,\n `<section class=\"card stack\">\n <p class=\"eyebrow\">mikan</p>\n <h1 class=\"page-title\">${esc(title)}</h1>\n <div class=\"status err\">${esc(message)}</div>\n </section>`,\n false,\n );\n}\n\nfunction renderHtmlDocument(title: string, shellContent: string, isRunning: boolean): string {\n return renderPortalShell({\n activeView: \"session\",\n pageTitle: \"Session\",\n body: shellContent,\n extraStyles: sessionViewStyles,\n bodyAttributes: { \"data-session-running\": isRunning ? \"true\" : \"false\" },\n inlineScript: sessionViewScript,\n });\n}\n\nconst sessionViewScript = `\n const form = document.querySelector('[data-session-composer]');\n const timelineList = document.querySelector('[data-timeline-list]');\n const jumpLatestBtn = document.querySelector('[data-jump-latest]');\n const statusEl = document.querySelector('[data-session-status]');\n const updatedEl = document.querySelector('[data-session-updated]');\n const entriesEl = document.querySelector('[data-session-entries]');\n const composerStatus = form?.querySelector('[data-composer-status]');\n const textarea = form?.querySelector('textarea[name=\"text\"]');\n const submitButton = form?.querySelector('button[type=\"submit\"]');\n let liveAssistant = null;\n let running = document.body.dataset.sessionRunning === 'true';\n\n const isNearBottom = () => window.innerHeight + window.scrollY >= document.body.offsetHeight - 120;\n const scrollToLatest = (behavior = 'smooth') => window.scrollTo({ top: document.body.scrollHeight, behavior });\n const toggleJumpButton = () => {\n if (!jumpLatestBtn) return;\n jumpLatestBtn.hidden = isNearBottom();\n };\n const updateFollowState = () => {\n if (isNearBottom()) scrollToLatest('smooth');\n else toggleJumpButton();\n };\n const canSubmit = () => Boolean(textarea && textarea.value.trim()) && !running;\n const updateSubmitButtonState = () => {\n if (submitButton) submitButton.disabled = !canSubmit();\n };\n const setRunning = (value) => {\n running = value;\n document.body.dataset.sessionRunning = value ? 'true' : 'false';\n if (statusEl) statusEl.textContent = value ? 'Running' : 'Idle';\n updateSubmitButtonState();\n if (composerStatus && !value && composerStatus.textContent === 'Thinking…') {\n composerStatus.textContent = '';\n }\n };\n\n jumpLatestBtn?.addEventListener('click', () => {\n scrollToLatest('smooth');\n toggleJumpButton();\n });\n document.addEventListener('click', async (event) => {\n const button = event.target instanceof Element ? event.target.closest('[data-copy-button]') : null;\n if (!(button instanceof HTMLButtonElement)) return;\n const label = button.dataset.copyLabel || 'Copy message';\n const source = button.closest('.msg-actions')?.previousElementSibling;\n const text = source instanceof HTMLElement ? (source.innerText || source.textContent || '').trim() : '';\n if (!text) return;\n const setState = (state, transient) => {\n button.dataset.copyState = state;\n button.title = transient;\n button.setAttribute('aria-label', transient);\n window.setTimeout(() => {\n if (!button.isConnected) return;\n delete button.dataset.copyState;\n button.title = label;\n button.setAttribute('aria-label', label);\n }, 1200);\n };\n try {\n await navigator.clipboard.writeText(text);\n setState('done', 'Copied');\n } catch {\n setState('error', 'Copy failed');\n }\n });\n window.addEventListener('scroll', toggleJumpButton, { passive: true });\n\n if (textarea) {\n const resize = () => {\n textarea.style.height = 'auto';\n textarea.style.height = Math.min(textarea.scrollHeight, 240) + 'px';\n };\n textarea.addEventListener('input', () => {\n resize();\n updateSubmitButtonState();\n });\n textarea.addEventListener('keydown', (event) => {\n if (event.key !== 'Enter' || event.shiftKey) return;\n if (event.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n if (!running) form?.requestSubmit();\n });\n resize();\n }\n\n setRunning(running);\n updateSubmitButtonState();\n\n const streamUrl = form\n ? '/session/stream?token=' + encodeURIComponent(form.token.value) + '&session=' + encodeURIComponent(form.session.value)\n : null;\n if (streamUrl) {\n const source = new EventSource(streamUrl);\n source.onmessage = (event) => {\n const payload = JSON.parse(event.data);\n switch (payload.type) {\n case 'status':\n setRunning(Boolean(payload.running));\n if (payload.running && composerStatus) composerStatus.textContent = 'Thinking…';\n break;\n case 'user':\n case 'tool':\n case 'system': {\n timelineList?.insertAdjacentHTML('beforeend', payload.html);\n updateFollowState();\n break;\n }\n case 'assistant': {\n if (!liveAssistant || !liveAssistant.isConnected) {\n timelineList?.insertAdjacentHTML('beforeend', payload.html);\n liveAssistant = timelineList?.querySelector('[data-live-assistant]:last-of-type') || null;\n } else {\n liveAssistant.outerHTML = payload.html;\n liveAssistant = timelineList?.querySelector('[data-live-assistant]:last-of-type') || null;\n }\n updateFollowState();\n break;\n }\n case 'assistant_remove':\n if (liveAssistant?.isConnected) liveAssistant.remove();\n liveAssistant = null;\n break;\n case 'refresh':\n if (timelineList) timelineList.innerHTML = payload.timelineHtml;\n liveAssistant = null;\n if (updatedEl) updatedEl.textContent = payload.updatedAt;\n if (entriesEl) entriesEl.textContent = String(payload.entryCount);\n setRunning(Boolean(payload.running));\n if (composerStatus) composerStatus.textContent = '';\n updateFollowState();\n break;\n case 'error':\n if (composerStatus) composerStatus.textContent = payload.message || 'Something went wrong';\n setRunning(false);\n break;\n }\n };\n }\n\n form?.addEventListener('submit', async (event) => {\n event.preventDefault();\n if (!textarea || !composerStatus) return;\n const text = textarea.value.trim();\n if (!text || running) return;\n composerStatus.textContent = 'Sending…';\n updateSubmitButtonState();\n try {\n const response = await fetch('/session/message', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token: form.token.value, session: form.session.value, sessionKey: form.sessionKey.value, text }),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || 'Request failed');\n textarea.value = '';\n textarea.style.height = 'auto';\n composerStatus.textContent = 'Thinking…';\n setRunning(true);\n updateSubmitButtonState();\n scrollToLatest('smooth');\n } catch (err) {\n composerStatus.textContent = err && err.message ? err.message : String(err);\n submitButton.disabled = false;\n }\n });\n\n toggleJumpButton();\n`;\n\nfunction formatDate(value: string): string {\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n return date.toLocaleString();\n}\n\nconst esc = escapeHtml;\n\nconst sessionViewStyles = `\n :root {\n --user-bg: #18181b;\n --user-text: #fafafa;\n --user-time: rgba(250, 250, 250, 0.5);\n\n --asst-border: #22c55e;\n --asst-avatar-bg: #f0fdf4;\n --asst-avatar-text: #16a34a;\n\n --tool-bg: #0d1117;\n --tool-header: #161b22;\n --tool-text: #c9d1d9;\n --tool-accent: #58a6ff;\n --tool-ok: #3fb950;\n --tool-err: #f85149;\n --tool-time: #484f58;\n }\n\n body {\n /* Extra bottom padding for the fixed composer */\n padding-bottom: calc(140px + env(safe-area-inset-bottom, 0px));\n overflow-x: hidden;\n }\n\n /* ── Session-specific page-head extras ─────────────────────────────── */\n\n .session-side {\n display: flex; flex-direction: column; align-items: flex-end; gap: 8px;\n flex-shrink: 0;\n }\n .session-badge {\n display: inline-flex; align-items: center; gap: 8px;\n padding: 6px 11px; border: 1px solid var(--border); border-radius: 999px;\n background: rgba(255,255,255,0.7); font-size: 0.78rem; color: var(--muted);\n line-height: 1;\n }\n .session-badge strong { color: var(--text); font-weight: 600; }\n .session-badge-status.is-running {\n background: #fff7ed; border-color: rgba(217, 119, 6, 0.18); color: #9a3412;\n }\n .session-badge-dot {\n width: 7px; height: 7px; border-radius: 50%;\n background: #a1a1aa; flex-shrink: 0;\n }\n .session-badge-status.is-running .session-badge-dot {\n background: #d97706; box-shadow: 0 0 0 4px rgba(217, 119, 6, 0.14);\n }\n\n .session-detail-row {\n display: flex; flex-wrap: wrap; gap: 8px;\n padding: 12px 14px; border: 1px solid var(--border); border-radius: 14px;\n background: rgba(255,255,255,0.6);\n }\n .session-detail {\n display: inline-flex; align-items: center; gap: 8px; min-width: 0;\n padding: 4px 10px; border-radius: 10px; background: rgba(0, 0, 0, 0.025);\n color: var(--muted); font-size: 0.78rem;\n }\n .session-detail-label {\n text-transform: uppercase; letter-spacing: 0.08em;\n font-size: 0.68rem; color: var(--subtle);\n }\n .session-detail code {\n min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.74rem;\n color: var(--text); padding: 0;\n }\n\n /* ── Timeline shell ───────────────────────────────────────────────────── */\n\n .thread-links {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 10px;\n }\n\n .thread-link {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 5px 10px;\n border-radius: 999px;\n border: 1px solid rgba(239, 68, 68, 0.18);\n background: rgba(254, 242, 242, 0.95);\n color: #b91c1c;\n text-decoration: none;\n font-size: 0.74rem;\n font-weight: 600;\n line-height: 1;\n transition: transform 120ms, background 120ms, border-color 120ms;\n }\n\n .thread-link:hover {\n transform: translateY(-1px);\n background: #fff1f2;\n border-color: rgba(239, 68, 68, 0.28);\n }\n\n .thread-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: #ef4444;\n box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.12);\n flex-shrink: 0;\n }\n\n .thread-text {\n white-space: nowrap;\n }\n\n .related-card {\n padding: 18px 20px;\n border: 1px solid var(--border);\n border-radius: 18px;\n background: rgba(255,255,255,0.78);\n box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 4px 16px rgba(0,0,0,0.04);\n backdrop-filter: blur(12px);\n }\n\n .related-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .related-link {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n border-radius: 14px;\n border: 1px solid var(--border);\n background: rgba(255,255,255,0.82);\n color: inherit;\n text-decoration: none;\n transition: transform 120ms, border-color 120ms, box-shadow 120ms, background 120ms;\n }\n\n .related-link:hover {\n transform: translateY(-1px);\n border-color: rgba(0,0,0,0.16);\n background: #fff;\n box-shadow: 0 8px 18px rgba(0,0,0,0.05);\n }\n\n .related-copy {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .related-title {\n color: var(--text);\n font-size: 0.94rem;\n line-height: 1.3;\n }\n\n .related-summary {\n color: var(--muted);\n font-size: 0.82rem;\n line-height: 1.45;\n }\n\n .related-meta {\n color: var(--subtle);\n font-size: 0.74rem;\n line-height: 1.4;\n }\n\n .related-arrow {\n flex-shrink: 0;\n color: var(--subtle);\n font-size: 1rem;\n }\n\n .timeline-shell {\n padding: 20px 0;\n }\n\n .timeline-list {\n display: flex;\n flex-direction: column;\n gap: 14px;\n min-width: 0;\n }\n\n .copy-host {\n position: relative;\n }\n\n .msg-actions {\n height: 32px;\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n opacity: 0;\n visibility: hidden;\n transition: opacity 140ms ease, visibility 140ms ease;\n }\n\n .copy-host:hover .msg-actions,\n .copy-host .msg-actions:hover,\n .copy-host:focus-within .msg-actions,\n .timeline-list > .copy-host:last-child .msg-actions,\n .copy-action-btn[data-copy-state] {\n opacity: 1;\n visibility: visible;\n }\n\n .copy-action-btn {\n width: 24px;\n height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: rgba(63,63,70,0.8);\n transition: color 140ms ease, opacity 140ms ease;\n cursor: pointer;\n padding: 0;\n appearance: none;\n }\n\n .copy-action-btn:hover {\n background: transparent;\n color: rgba(24,24,27,0.96);\n border-color: transparent;\n }\n\n .copy-action-btn[data-copy-state='done'] {\n background: transparent;\n border-color: transparent;\n color: rgba(24,24,27,0.96);\n }\n\n .copy-action-btn[data-copy-state='done'] svg {\n position: absolute;\n opacity: 0;\n transform: scale(0.6);\n pointer-events: none;\n }\n\n .copy-action-btn svg {\n transition: opacity 140ms ease, transform 140ms ease;\n }\n\n .copy-action-btn[data-copy-state='done']::before {\n content: '';\n width: 14px;\n height: 14px;\n background-color: currentColor;\n -webkit-mask: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='4 12 10 18 20 6'/></svg>\") center / contain no-repeat;\n mask: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='4 12 10 18 20 6'/></svg>\") center / contain no-repeat;\n animation: copy-check-in 200ms ease-out both;\n }\n\n @keyframes copy-check-in {\n from { opacity: 0; transform: scale(0.6); }\n to { opacity: 1; transform: scale(1); }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .copy-action-btn svg,\n .copy-action-btn[data-copy-state='done']::before {\n transition: none;\n animation: none;\n }\n }\n\n .copy-action-btn[data-copy-state='error'] {\n background: transparent;\n border-color: transparent;\n color: #b91c1c;\n }\n\n /* ── Message rows ─────────────────────────────────────────────────────── */\n\n .msg-row {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n padding: 4px 0;\n min-width: 0;\n }\n\n /* ── User messages ────────────────────────────────────────────────────── */\n\n .msg-user {\n justify-content: flex-end;\n }\n\n .msg-main {\n min-width: 0;\n }\n\n .user-main {\n max-width: 85%;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n }\n\n .user-bubble {\n max-width: 100%;\n min-width: 0;\n padding: 12px 16px;\n border-radius: 18px 18px 4px 18px;\n background: var(--user-bg);\n color: var(--user-text);\n box-shadow: 0 1px 2px rgba(0,0,0,0.12);\n }\n\n .msg-raw-header {\n margin-bottom: 8px;\n color: rgba(250, 250, 250, 0.72);\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.72rem;\n line-height: 1.5;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .thread-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 8px;\n padding: 4px 10px;\n border-radius: 999px;\n background: rgba(255,255,255,0.22);\n color: var(--user-text);\n font-size: 0.68rem;\n font-weight: 700;\n letter-spacing: 0.01em;\n }\n\n .thread-badge code {\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.66rem;\n background: rgba(255,255,255,0.16);\n padding: 1px 6px;\n border-radius: 999px;\n color: inherit;\n }\n\n .msg-user .msg-body {\n color: var(--user-text);\n }\n\n .msg-user .msg-time {\n display: block;\n margin-top: 6px;\n font-size: 0.72rem;\n color: var(--user-time);\n text-align: right;\n }\n\n /* ── Avatars ──────────────────────────────────────────────────────────── */\n\n .msg-avatar {\n flex: 0 0 28px;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n font-size: 0.68rem;\n font-weight: 700;\n display: flex;\n align-items: center;\n justify-content: center;\n letter-spacing: 0;\n flex-shrink: 0;\n }\n\n .user-avatar {\n background: #eff6ff;\n border: 1.5px solid #93c5fd;\n color: #1d4ed8;\n }\n\n .asst-avatar {\n background: var(--asst-avatar-bg);\n border: 1.5px solid var(--asst-border);\n color: var(--asst-avatar-text);\n margin-bottom: 2px;\n }\n\n /* ── Assistant messages ───────────────────────────────────────────────── */\n\n .msg-assistant {\n align-items: flex-end;\n gap: 8px;\n max-width: 85%;\n min-width: 0;\n }\n\n .asst-main {\n max-width: 100%;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n }\n\n .asst-card {\n min-width: 0;\n max-width: 100%;\n padding: 14px 18px;\n border: 1px solid var(--border);\n border-radius: 18px 18px 18px 4px;\n background: var(--surface);\n box-shadow: 0 1px 3px rgba(0,0,0,0.04);\n }\n\n .msg-assistant .msg-body {\n color: var(--text);\n }\n\n .msg-assistant .msg-time {\n display: block;\n margin-top: 8px;\n font-size: 0.72rem;\n color: var(--subtle);\n }\n\n /* ── Tool blocks ──────────────────────────────────────────────────────── */\n\n .tool-block {\n max-width: 92%;\n margin-left: 36px;\n border-radius: 10px;\n overflow: hidden;\n border: 1px solid rgba(255,255,255,0.06);\n box-shadow: 0 2px 8px rgba(0,0,0,0.16);\n margin: 2px 0;\n }\n\n .tool-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n background: var(--tool-header);\n border-bottom: 1px solid rgba(255,255,255,0.06);\n overflow: hidden;\n }\n\n .tool-icon {\n color: var(--tool-accent);\n flex-shrink: 0;\n display: flex;\n align-items: center;\n }\n\n .tool-name {\n flex: 1;\n font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;\n font-size: 0.75rem;\n font-weight: 500;\n color: var(--tool-accent);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .tool-time {\n flex-shrink: 0;\n font-family: 'JetBrains Mono', ui-monospace, monospace;\n font-size: 0.7rem;\n color: var(--tool-time);\n }\n\n .tool-output {\n display: block;\n padding: 12px 14px;\n background: var(--tool-bg);\n color: var(--tool-text);\n font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;\n font-size: 0.78rem;\n line-height: 1.6;\n white-space: pre-wrap;\n word-break: break-word;\n overflow-x: auto;\n max-height: 400px;\n overflow-y: auto;\n }\n\n .tool-output.tone-ok { color: var(--tool-ok); }\n .tool-output.tone-err { color: var(--tool-err); }\n\n /* ── Markdown blocks ──────────────────────────────────────────────────── */\n\n .markdown-body {\n font-family: 'DM Sans', system-ui, sans-serif;\n font-size: 0.9rem;\n line-height: 1.65;\n word-break: break-word;\n }\n\n .markdown-body > *:first-child { margin-top: 0; }\n .markdown-body > *:last-child { margin-bottom: 0; }\n .markdown-body p,\n .markdown-body ul,\n .markdown-body ol,\n .markdown-body blockquote,\n .markdown-body pre,\n .markdown-body table,\n .markdown-body hr {\n margin: 0 0 0.85em;\n }\n\n .markdown-body h1,\n .markdown-body h2,\n .markdown-body h3,\n .markdown-body h4,\n .markdown-body h5,\n .markdown-body h6 {\n margin: 0 0 0.55em;\n line-height: 1.25;\n font-weight: 700;\n letter-spacing: -0.01em;\n }\n\n .markdown-body h1 { font-size: 1.4rem; }\n .markdown-body h2 { font-size: 1.22rem; }\n .markdown-body h3 { font-size: 1.08rem; }\n .markdown-body h4,\n .markdown-body h5,\n .markdown-body h6 { font-size: 0.95rem; }\n\n .markdown-body ul,\n .markdown-body ol {\n padding-left: 1.3em;\n }\n\n .markdown-body li + li {\n margin-top: 0.22em;\n }\n\n .markdown-body blockquote {\n padding-left: 12px;\n border-left: 3px solid rgba(34, 197, 94, 0.35);\n opacity: 0.95;\n }\n\n .markdown-body a {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n }\n\n .markdown-body code {\n font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;\n font-size: 0.82em;\n padding: 0.16em 0.38em;\n border-radius: 6px;\n }\n\n .markdown-body pre {\n overflow-x: auto;\n border-radius: 12px;\n padding: 12px 14px;\n }\n\n .markdown-body pre code {\n display: block;\n padding: 0;\n border-radius: 0;\n background: transparent;\n font-size: 0.82rem;\n line-height: 1.6;\n }\n\n .markdown-body table {\n width: 100%;\n border-collapse: collapse;\n font-size: 0.85rem;\n }\n\n .markdown-body th,\n .markdown-body td {\n padding: 8px 10px;\n border: 1px solid rgba(0, 0, 0, 0.08);\n text-align: left;\n vertical-align: top;\n }\n\n .markdown-body img {\n max-width: 100%;\n border-radius: 12px;\n }\n\n .markdown-user code {\n background: rgba(255,255,255,0.14);\n color: var(--user-text);\n }\n\n .markdown-user pre {\n background: rgba(255,255,255,0.08);\n border: 1px solid rgba(255,255,255,0.08);\n }\n\n .markdown-user table th,\n .markdown-user table td {\n border-color: rgba(255,255,255,0.16);\n }\n\n .markdown-assistant code {\n background: #f4f4f5;\n color: #27272a;\n }\n\n .markdown-assistant pre {\n background: #0f172a;\n color: #e5e7eb;\n }\n\n .markdown-assistant pre code {\n background: transparent;\n color: inherit;\n }\n\n .markdown-assistant table th,\n .markdown-assistant table td {\n border-color: rgba(0, 0, 0, 0.08);\n }\n\n /* ── System events ────────────────────────────────────────────────────── */\n\n .system-event {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 10px 0;\n color: var(--subtle);\n font-size: 0.775rem;\n }\n\n .event-dot {\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: var(--subtle);\n flex-shrink: 0;\n opacity: 0.6;\n }\n\n .event-text {\n color: var(--muted);\n }\n\n .system-event-err .event-text {\n color: var(--err-text);\n }\n\n .event-time {\n color: var(--subtle);\n font-style: normal;\n }\n\n /* ── Status page ──────────────────────────────────────────────────────── */\n\n .stack > * + * { margin-top: 14px; }\n\n p { color: var(--muted); font-size: 0.9rem; line-height: 1.5; }\n\n .status {\n padding: 12px 16px;\n border-radius: 10px;\n font-size: 0.9rem;\n }\n\n .status.err {\n background: var(--err-bg);\n color: var(--err-text);\n border: 1px solid rgba(185, 28, 28, 0.12);\n }\n\n /* ── Composer ─────────────────────────────────────────────────────────── */\n\n .composer-card {\n position: fixed;\n left: calc(72px + (100vw - 72px) / 2);\n bottom: calc(16px + env(safe-area-inset-bottom, 0px));\n transform: translateX(-50%);\n width: min(960px, calc(100vw - 96px));\n padding: 10px 12px 10px 14px;\n border: 1px solid var(--border);\n border-radius: 22px;\n background: rgba(250, 248, 244, 0.92);\n box-shadow: 0 12px 36px rgba(0,0,0,0.10), 0 2px 6px rgba(0,0,0,0.04);\n backdrop-filter: blur(14px);\n -webkit-backdrop-filter: blur(14px);\n z-index: 20;\n }\n\n .composer-form {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .jump-latest-btn {\n position: fixed;\n left: calc(72px + (100vw - 72px) / 2);\n bottom: calc(env(safe-area-inset-bottom, 0px) + 120px);\n z-index: 25;\n width: 42px;\n height: 42px;\n border: 1px solid var(--border);\n border-radius: 999px;\n background: var(--bg);\n color: var(--text);\n font: 700 1rem/1 'DM Sans', sans-serif;\n box-shadow: 0 10px 30px rgba(0,0,0,0.12);\n cursor: pointer;\n backdrop-filter: blur(10px);\n transform: translateX(-50%);\n outline: none;\n appearance: none;\n -webkit-tap-highlight-color: transparent;\n }\n\n .jump-latest-btn:hover {\n transform: translateX(-50%) translateY(-1px);\n background: #e8e3d9;\n }\n\n .jump-latest-btn:focus,\n .jump-latest-btn:active {\n outline: none;\n }\n\n .jump-latest-btn:focus-visible {\n box-shadow: 0 10px 30px rgba(0,0,0,0.12), 0 0 0 3px rgba(0,0,0,0.08);\n }\n\n .composer-copy { margin-bottom: 12px; color: var(--muted); }\n\n .composer-form textarea {\n width: 100%;\n resize: none;\n overflow-y: auto;\n min-height: 28px;\n max-height: 200px;\n padding: 6px 6px 2px;\n border: 0;\n border-radius: 0;\n font: inherit;\n color: var(--text);\n background: transparent;\n }\n\n .composer-form textarea::placeholder {\n color: rgba(63,63,70,0.55);\n }\n\n .composer-form textarea:focus {\n outline: none;\n border: 0;\n }\n\n .composer-actions {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-top: 0;\n }\n\n .composer-status { color: var(--muted); font-size: 13px; }\n .composer-actions button:disabled { opacity: 0.55; cursor: wait; }\n\n .composer-send-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border: none;\n border-radius: 999px;\n background: #d97706;\n color: #ffffff;\n font: 700 1rem/1 'DM Sans', sans-serif;\n cursor: pointer;\n box-shadow: 0 10px 24px rgba(217, 119, 6, 0.26);\n transition: transform 120ms, filter 120ms, box-shadow 120ms, background 120ms;\n }\n\n .composer-send-btn:hover:not(:disabled) {\n transform: translateY(-1px);\n filter: saturate(1.06) brightness(0.98);\n box-shadow: 0 12px 28px rgba(217, 119, 6, 0.32);\n }\n\n .composer-send-btn:focus-visible {\n outline: 2px solid rgba(217, 119, 6, 0.28);\n outline-offset: 3px;\n }\n\n .composer-send-btn:disabled {\n background: #d4d4d8;\n color: rgba(24, 24, 27, 0.45);\n box-shadow: none;\n transform: none;\n filter: none;\n cursor: not-allowed;\n opacity: 1;\n }\n\n /* ── Responsive ───────────────────────────────────────────────────────── */\n\n @media (max-width: 900px) {\n .composer-card {\n left: 50%;\n width: min(960px, calc(100vw - 24px));\n }\n .jump-latest-btn { left: 50%; }\n }\n\n @media (max-width: 600px) {\n body { padding-bottom: calc(130px + env(safe-area-inset-bottom, 0px)); }\n\n .composer-card {\n width: calc(100vw - 16px);\n bottom: calc(8px + env(safe-area-inset-bottom, 0px));\n padding: 8px 10px;\n border-radius: 18px;\n }\n\n .session-side { align-items: flex-start; flex-direction: row; }\n\n .user-bubble,\n .msg-assistant,\n .tool-block { max-width: 100%; }\n\n .asst-avatar { display: none; }\n\n .asst-card { border-radius: 4px 14px 14px 14px; }\n }\n`;\n"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { basename } from "path";
|
|
2
2
|
import MarkdownIt from "markdown-it";
|
|
3
|
-
import { escapeHtml } from "
|
|
4
|
-
import * as log from "
|
|
5
|
-
import { renderPortalShell } from "
|
|
6
|
-
import { reportUserFacingError } from "
|
|
7
|
-
import { inferConversationKind } from "
|
|
3
|
+
import { escapeHtml } from "../../utils/html.js";
|
|
4
|
+
import * as log from "../../log.js";
|
|
5
|
+
import { renderPortalShell } from "../../portal-shell.js";
|
|
6
|
+
import { reportUserFacingError } from "../../observability/sentry.js";
|
|
7
|
+
import { inferConversationKind } from "../../sessions/policy.js";
|
|
8
8
|
import { loadSessionViewModel, resolveRequestedSessionFile, } from "./service.js";
|
|
9
9
|
const markdown = new MarkdownIt({
|
|
10
10
|
html: false,
|