@prismshadow/penguin-server 0.1.2 → 0.1.4
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/dist/api/types.d.ts +168 -2
- package/dist/index.js +864 -26
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/web-dist/assets/{index-zCEa-kcQ.js → index-7gbWDtby.js} +1 -1
- package/web-dist/assets/index-Bnva9EYm.js +102 -0
- package/web-dist/assets/index-DMcMNwnl.css +1 -0
- package/web-dist/index.html +2 -2
- package/web-dist/assets/index-BwOLjdOw.css +0 -1
- package/web-dist/assets/index-Ddrpqgqu.js +0 -104
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/app.ts","../src/db/database.ts","../src/db/schema.ts","../src/db/repos/agents.ts","../src/db/repos/auth-sessions.ts","../src/db/repos/errors.ts","../src/db/repos/members.ts","../src/db/repos/projects.ts","../src/db/repos/schedules.ts","../src/db/repos/sessions.ts","../src/db/repos/ui-prefs.ts","../src/db/repos/usage.ts","../src/db/repos/users.ts","../src/auth/middleware.ts","../src/http/errors.ts","../src/auth/service.ts","../src/auth/password.ts","../src/http/routes/admin.ts","../src/http/validate.ts","../src/http/routes/auth.ts","../src/http/routes/me.ts","../src/services/preview-token.ts","../src/http/routes/events.ts","../src/http/sse.ts","../src/http/routes/projects.ts","../src/http/routes/members.ts","../src/http/routes/models.ts","../src/http/routes/vault.ts","../src/http/routes/schedules.ts","../src/runtime/schedule-file.ts","../src/runtime/schedule-store.ts","../src/http/routes/benchmarks.ts","../src/http/routes/skills.ts","../src/http/routes/agent-transfer.ts","../src/http/routes/agents.ts","../src/http/settle.ts","../src/http/routes/dirs.ts","../src/http/routes/agent-config.ts","../src/http/routes/agent-traces.ts","../src/http/routes/usage.ts","../src/http/routes/sessions.ts","../src/services/workspace-guard.ts","../src/services/workspace-files-service.ts","../src/runtime/channel.ts","../src/internal/dates.ts","../src/runtime/error-recorder.ts","../src/runtime/session-manager.ts","../src/runtime/approvals.ts","../src/runtime/session-sources.ts","../src/runtime/stream-error-watcher.ts","../src/runtime/scheduler.ts","../src/runtime/title-generator.ts","../src/runtime/usage-recorder.ts","../src/services/ids.ts","../src/services/admin-service.ts","../src/services/agent-config-service.ts","../src/services/project-config-service.ts","../src/services/agent-service.ts","../src/services/benchmark-service.ts","../src/services/snapshot-service.ts","../src/services/project-service.ts","../src/services/session-service.ts","../src/services/trace-service.ts","../src/services/usage-service.ts","../src/http/routes/preview.ts","../src/config.ts"],"sourcesContent":["/**\n * Server startup entry point: dotenv → config → assembly → listen\n * → graceful shutdown.\n *\n * SIGINT / SIGTERM: interrupt all active runs (pending approvals converge to deny), wait\n * ≤5s for wrap-up, then close HTTP and SQLite. Tests never go through this file\n * (injected via app.request() instead).\n * There's also a process-level error fallback (uncaughtException / unhandledRejection):\n * persist + log, with the fatal one still shutting down per existing semantics (see the\n * comment below).\n */\nimport { config as loadDotenv } from \"dotenv\";\nimport { serve } from \"@hono/node-server\";\nimport { buildAppDeps, createApp } from \"./app.js\";\nimport { resolveServerConfig } from \"./config.js\";\nimport { loopbackHostRoles } from \"./services/preview-token.js\";\n\nloadDotenv({ quiet: true });\n\nconst config = resolveServerConfig();\nconst deps = buildAppDeps(config);\nconst app = createApp(deps);\n\n// Built-in admin seed (idempotent): creates admin (initial password penguin-2026) and adopts default_project when the users table is empty.\nawait deps.authService.seedAdmin();\n\n// Schedule scheduler: startup reconciliation (missed, don't backfill) + periodic scan; only active while the server is running.\nawait deps.scheduler.start();\n\n// On a loopback bind the App is canonicalized onto one name (`localhost`) and its\n// counterpart is reserved for previews, so advertise the canonical name — the other one\n// only 302s back here for App routes (see the canonical-host guard in app.ts).\nconst appHost = loopbackHostRoles(config.host)?.app ?? config.host;\nconst server = serve({ fetch: app.fetch, hostname: config.host, port: config.port }, (info) => {\n console.log(`penguin-server started: http://${appHost}:${info.port}`);\n console.log(`Data root: ${config.root}`);\n console.log(`SQLite: ${config.dbPath}`);\n});\n\n/**\n * Second loopback listener so the preview origin is actually reachable.\n *\n * Workspace HTML previews are served from the loopback counterpart of the host the App\n * is used on (`127.0.0.1` <-> `localhost`, see design § \"Workspace 文件预览\"). On most\n * systems `localhost` resolves to `::1` first, so a server bound only to `127.0.0.1`\n * would leave every preview URL refusing connections. Binding `::1` as well closes that\n * gap. Failure is non-fatal — the App keeps working, previews just fall back.\n */\nconst ipv6Loopback =\n config.host === \"127.0.0.1\" || config.host === \"localhost\"\n ? serve({ fetch: app.fetch, hostname: \"::1\", port: config.port })\n : null;\nipv6Loopback?.on(\"error\", (err: NodeJS.ErrnoException) => {\n console.warn(\n `[server] IPv6 loopback listener unavailable (${err.code ?? err.message}); previews via localhost may not resolve.`,\n );\n});\n\nlet shuttingDown = false;\nasync function shutdown(signal: string, exitCode = 0): Promise<void> {\n if (shuttingDown) return;\n shuttingDown = true;\n console.log(`Received ${signal}, shutting down…`);\n deps.scheduler.stop();\n await deps.manager.shutdown(5000);\n deps.channels.dispose();\n ipv6Loopback?.close();\n server.close(() => {\n deps.db.close();\n process.exit(exitCode);\n });\n // Fallback: a long-lived SSE connection may block the close callback, so force exit after 1s.\n setTimeout(() => process.exit(exitCode), 1000).unref();\n}\n\nprocess.on(\"SIGINT\", () => void shutdown(\"SIGINT\"));\nprocess.on(\"SIGTERM\", () => void shutdown(\"SIGTERM\"));\n\n// Process-level error fallback: once a background\n// fire-and-forget promise (title generation, Session drive, etc.) throws, the error\n// reaches the process without passing through any catch — persist it first for a\n// record, then handle each case according to its nature.\nprocess.on(\"uncaughtException\", (err) => {\n console.error(`[server] Uncaught exception: ${err.stack ?? err.message}`);\n deps.errors.record({ source: \"process\", err, code: \"uncaught_exception\" });\n // From this point the process state can't be trusted (the error was never converged\n // by any catch): don't swallow it — wrap up per existing shutdown semantics and exit\n // with a nonzero code (equivalent to Node's default crash exit, just with an extra\n // persist and graceful wrap-up).\n // Must exit even if shutdown itself errors — never let \"caught a fatal error\" turn\n // into \"the process limps along in a broken state\".\n void shutdown(\"uncaughtException\", 1).catch(() => process.exit(1));\n});\nprocess.on(\"unhandledRejection\", (reason) => {\n const err = reason instanceof Error ? reason : new Error(String(reason));\n console.error(`[server] Unhandled promise rejection: ${err.stack ?? err.message}`);\n deps.errors.record({ source: \"process\", err, code: \"unhandled_rejection\" });\n // Unlike uncaughtException, this **doesn't** exit: a rejected promise is a localized\n // failure of some background task, and the process state isn't compromised; dragging\n // down the entire service for it (Node's default behavior) isn't worth it — persist +\n // log, then keep serving.\n});\n","/**\n * Hono app assembly: middleware + route mounting + static hosting +\n * error handling.\n *\n * `createApp(deps)` is pure assembly (does not listen on a port): tests inject requests via\n * `app.request()`; `buildAppDeps(config)` assembles all services from config (test doubles\n * like SessionLoader can be injected). The startup entry point is in index.ts.\n */\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ServerConfig } from \"./config.js\";\nimport { openDatabase } from \"./db/database.js\";\nimport { AgentsRepo } from \"./db/repos/agents.js\";\nimport { AuthSessionsRepo } from \"./db/repos/auth-sessions.js\";\nimport { ErrorsRepo } from \"./db/repos/errors.js\";\nimport { MembersRepo } from \"./db/repos/members.js\";\nimport { ProjectsRepo } from \"./db/repos/projects.js\";\nimport { SchedulesRepo } from \"./db/repos/schedules.js\";\nimport { SessionsRepo } from \"./db/repos/sessions.js\";\nimport { UiPrefsRepo } from \"./db/repos/ui-prefs.js\";\nimport { UsageRepo } from \"./db/repos/usage.js\";\nimport { UsersRepo } from \"./db/repos/users.js\";\nimport type { UserRow } from \"./db/repos/users.js\";\nimport { authMiddleware, jsonOnlyWrites } from \"./auth/middleware.js\";\nimport type { AppEnv } from \"./auth/middleware.js\";\nimport { AuthService } from \"./auth/service.js\";\nimport { handleError, HttpError, errorBody } from \"./http/errors.js\";\nimport { adminUsersRoutes } from \"./http/routes/admin.js\";\nimport { authRoutes } from \"./http/routes/auth.js\";\nimport { meRoutes } from \"./http/routes/me.js\";\nimport { eventsRoutes, userChannelKey } from \"./http/routes/events.js\";\nimport { projectsRoutes } from \"./http/routes/projects.js\";\nimport { membersRoutes } from \"./http/routes/members.js\";\nimport { modelsRoutes } from \"./http/routes/models.js\";\nimport { vaultRoutes } from \"./http/routes/vault.js\";\nimport { scheduleRoutes } from \"./http/routes/schedules.js\";\nimport { benchmarksRoutes } from \"./http/routes/benchmarks.js\";\nimport { agentSkillsRoutes, skillLibraryRoutes } from \"./http/routes/skills.js\";\nimport { agentTransferRoutes } from \"./http/routes/agent-transfer.js\";\nimport { agentsRoutes } from \"./http/routes/agents.js\";\nimport { dirsRoutes } from \"./http/routes/dirs.js\";\nimport { agentConfigRoutes } from \"./http/routes/agent-config.js\";\nimport { agentTracesRoutes } from \"./http/routes/agent-traces.js\";\nimport { usageRoutes } from \"./http/routes/usage.js\";\nimport { agentSessionsRoutes, sessionsRoutes } from \"./http/routes/sessions.js\";\nimport { ChannelHub } from \"./runtime/channel.js\";\nimport { ErrorRecorder } from \"./runtime/error-recorder.js\";\nimport { createCoreSessionLoader, SessionManager } from \"./runtime/session-manager.js\";\nimport { SessionSources } from \"./runtime/session-sources.js\";\nimport type { SessionLoader } from \"./runtime/session-manager.js\";\nimport { Scheduler } from \"./runtime/scheduler.js\";\nimport { TitleGenerator } from \"./runtime/title-generator.js\";\nimport type { TitleNotifier } from \"./runtime/title-generator.js\";\nimport { UsageRecorder } from \"./runtime/usage-recorder.js\";\nimport { AdminService } from \"./services/admin-service.js\";\nimport { AgentConfigService } from \"./services/agent-config-service.js\";\nimport { AgentService } from \"./services/agent-service.js\";\nimport { BenchmarkService } from \"./services/benchmark-service.js\";\nimport { SnapshotService } from \"./services/snapshot-service.js\";\nimport { ProjectConfigService } from \"./services/project-config-service.js\";\nimport { ProjectService } from \"./services/project-service.js\";\nimport { SessionService } from \"./services/session-service.js\";\nimport { TraceService } from \"./services/trace-service.js\";\nimport { UsageService } from \"./services/usage-service.js\";\nimport { WorkspaceFilesService } from \"./services/workspace-files-service.js\";\nimport {\n createPreviewTokenSigner,\n hostOnly,\n loopbackHostRoles,\n requestAuthority,\n} from \"./services/preview-token.js\";\nimport type { PreviewTokenSigner } from \"./services/preview-token.js\";\nimport { previewRoutes } from \"./http/routes/preview.js\";\n\n/** Request body size limit (tasks may carry data: images): 20MB. */\nconst MAX_BODY_BYTES = 20 * 1024 * 1024;\n\nexport interface AppDeps {\n config: ServerConfig;\n db: DatabaseSync;\n sessionsRepo: SessionsRepo;\n prefsRepo: UiPrefsRepo;\n authService: AuthService;\n adminService: AdminService;\n projectService: ProjectService;\n projectConfigService: ProjectConfigService;\n agentService: AgentService;\n agentConfigService: AgentConfigService;\n sessionService: SessionService;\n traceService: TraceService;\n usageService: UsageService;\n workspaceFiles: WorkspaceFilesService;\n /** Signs/verifies short-lived Workspace preview tokens (separate preview origin). */\n previewTokens: PreviewTokenSigner;\n benchmarks: BenchmarkService;\n snapshots: SnapshotService;\n schedulesRepo: SchedulesRepo;\n scheduler: Scheduler;\n channels: ChannelHub;\n manager: SessionManager;\n /** Session-origin registry derived from session_meta (single source of truth; no DB column). */\n sessionSources: SessionSources;\n /** Error persistence (shared by app.onError and various background capture points; the process-level fallback is in index.ts). */\n errors: ErrorRecorder;\n /** Request log output (minimal one-liner); tests inject a noop. */\n log: (line: string) => void;\n}\n\nexport interface BuildDepsOverrides {\n /** Test double: session-manager's underlying loader (avoids the real LLM/SDK path). */\n loader?: SessionLoader;\n /** Test double: Session title generator (avoids real LLM requests). */\n titles?: TitleNotifier;\n log?: (line: string) => void;\n now?: () => Date;\n}\n\n/** Assemble all services from config (shared by production and tests; tests pass dbPath=\":memory:\" and a temp root). */\nexport function buildAppDeps(config: ServerConfig, overrides: BuildDepsOverrides = {}): AppDeps {\n const db = openDatabase(config.dbPath);\n const log = overrides.log ?? ((line: string) => console.log(line));\n\n const usersRepo = new UsersRepo(db);\n const authSessionsRepo = new AuthSessionsRepo(db);\n const projectsRepo = new ProjectsRepo(db);\n const membersRepo = new MembersRepo(db);\n const agentsRepo = new AgentsRepo(db);\n const sessionsRepo = new SessionsRepo(db);\n const usageRepo = new UsageRepo(db);\n const errorsRepo = new ErrorsRepo(db);\n const prefsRepo = new UiPrefsRepo(db);\n const schedulesRepo = new SchedulesRepo(db);\n\n const projectConfigService = new ProjectConfigService(config.root);\n const agentConfigService = new AgentConfigService(config.root);\n const agentService = new AgentService(config.root, agentsRepo, agentConfigService);\n const traceService = new TraceService(config.root);\n const workspaceFiles = new WorkspaceFilesService();\n // Per-process secret: preview tokens are short-lived, so losing them on restart is\n // harmless and there is nothing to persist or rotate.\n const previewTokens = createPreviewTokenSigner();\n const benchmarks = new BenchmarkService(config.root);\n const snapshots = new SnapshotService(config.root);\n const usageService = new UsageService(\n usageRepo,\n errorsRepo,\n (projectId, provider, modelId) => projectConfigService.getPricing(projectId, provider, modelId),\n overrides.now ?? (() => new Date()),\n );\n\n // Channel idle reclamation skips active Sessions (running/compacting can go a long time\n // without a publish, e.g. while waiting for approval).\n // manager is created after channels: use a lazy predicate (managerRef is assigned by the\n // time the sweep timer fires).\n let managerRef: SessionManager | undefined;\n const channels = new ChannelHub({\n isActive: (key) => managerRef !== undefined && managerRef.statusOf(key) !== \"idle\",\n });\n const recorder = new UsageRecorder(usageRepo, overrides.now ?? (() => new Date()));\n const errors = new ErrorRecorder(errorsRepo, overrides.now ?? (() => new Date()));\n const titles =\n overrides.titles ??\n new TitleGenerator({ sessions: sessionsRepo, channels, recorder, errors, log });\n // Session-origin registry: session_meta is the single source of truth (no DB column);\n // shared by the manager (subagent registration), the loader (self-heal rebuild) and\n // SessionService (creation / adoption / lazy list resolution).\n const sessionSources = new SessionSources();\n const manager = new SessionManager({\n sessions: sessionsRepo,\n channels,\n loader: overrides.loader ?? createCoreSessionLoader(config.root, sessionSources),\n sources: sessionSources,\n recorder,\n errors,\n titles,\n log,\n });\n managerRef = manager;\n\n const projectService = new ProjectService({\n root: config.root,\n users: usersRepo,\n projects: projectsRepo,\n members: membersRepo,\n agents: agentsRepo,\n sessions: sessionsRepo,\n usage: usageRepo,\n errors: errorsRepo,\n schedules: schedulesRepo,\n projectConfig: projectConfigService,\n manager,\n });\n const authService = new AuthService({\n users: usersRepo,\n authSessions: authSessionsRepo,\n provisionInitialProject: (user, isAdmin) =>\n projectService.provisionInitialProject(user, isAdmin),\n sessionTtlMs: config.authSessionTtlMs,\n sessionRenewMs: config.authSessionRenewMs,\n ...(overrides.now ? { now: overrides.now } : {}),\n });\n const adminService = new AdminService({\n users: usersRepo,\n authSessions: authSessionsRepo,\n projects: projectsRepo,\n projectService,\n ...(overrides.now ? { now: overrides.now } : {}),\n });\n const sessionService = new SessionService({\n root: config.root,\n sessions: sessionsRepo,\n manager,\n projectConfig: projectConfigService,\n sources: sessionSources,\n });\n // Schedule scheduler: active only while the server is running. Only\n // assembled here; start() is called in index.ts (tests drive it via tickOnce, no real timer).\n const scheduler = new Scheduler({\n root: config.root,\n repo: schedulesRepo,\n projects: projectsRepo,\n sessions: sessionsRepo,\n runner: manager,\n sessionCreator: sessionService,\n errors,\n notify: (userId, event) => {\n channels.get(userChannelKey(userId)).publish(event, \"server_event\");\n },\n ...(overrides.now ? { now: () => overrides.now!().getTime() } : {}),\n });\n\n return {\n config,\n db,\n sessionsRepo,\n prefsRepo,\n authService,\n adminService,\n projectService,\n projectConfigService,\n agentService,\n agentConfigService,\n sessionService,\n traceService,\n usageService,\n workspaceFiles,\n previewTokens,\n benchmarks,\n snapshots,\n schedulesRepo,\n scheduler,\n channels,\n manager,\n sessionSources,\n errors,\n log,\n };\n}\n\n/** Assembles the Hono app (does not listen on a port). */\nexport function createApp(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n // Error recording is layered in a lambda wrapping onError: handleError stays a\n // pure function with unchanged behavior (HttpError is mapped as-is, unknown\n // exceptions are logged with a stack trace and collapsed to 500), and recording\n // to the DB is just a side-effect layered on top.\n app.onError((err, c) => {\n const projectId = attributedProjectId(c, deps);\n deps.errors.record({\n source: \"http\",\n err,\n ...(projectId !== undefined ? { ctx: { projectId } } : {}),\n });\n return handleError(err, c);\n });\n app.notFound((c) => c.json(errorBody(\"not_found\", \"Endpoint does not exist.\"), 404));\n\n // Request logging: a minimal one-liner (method path status ms).\n app.use(\"*\", async (c, next) => {\n const start = performance.now();\n await next();\n const ms = Math.round(performance.now() - start);\n deps.log(`${c.req.method} ${c.req.path} ${c.res.status} ${ms}ms`);\n });\n\n // Canonical-host guard (loopback binds only): the App is served on one loopback name and\n // previews on its counterpart, but the SAME process answers on both. Without this, Agent-\n // written preview HTML on the preview host could call /api same-origin and — if a session\n // cookie had ever been set on that host — act as the user. So the preview host serves ONLY\n // /preview/*: /api answers 401 (it never sets or honors a cookie there, closing both the\n // login and the stale-cookie paths), and everything else 302s to the canonical App host.\n // See design § \"Workspace 文件预览\". Off when PENGUIN_PREVIEW_ORIGIN is set: previews then\n // use that origin rather than the loopback counterpart, so 127.0.0.1 is an ordinary App\n // access point and must not be locked down — deployments enforce the equivalent at the\n // reverse proxy (route only /preview/* to the App on the preview origin).\n const previewRoles = deps.config.previewOrigin ? null : loopbackHostRoles(deps.config.host);\n if (previewRoles) {\n app.use(\"*\", async (c, next) => {\n const host = hostOnly(requestAuthority(c.req.url, c.req.header(\"host\"))).toLowerCase();\n if (host === previewRoles.preview && !c.req.path.startsWith(\"/preview/\")) {\n if (c.req.path.startsWith(\"/api/\")) {\n throw new HttpError(401, \"unauthorized\", \"The API is not served on the preview host.\");\n }\n const url = new URL(c.req.url);\n url.hostname = previewRoles.app;\n return c.redirect(url.toString(), 302);\n }\n await next();\n });\n }\n\n // API common defenses: request body size cap (20MB) and write-request Content-Type (one of the CSRF MVP defenses).\n app.use(\"/api/*\", async (c, next) => {\n const contentLength = Number(c.req.header(\"content-length\") ?? 0);\n if (contentLength > MAX_BODY_BYTES) {\n throw new HttpError(413, \"payload_too_large\", \"Request body exceeds the 20MB limit.\");\n }\n await next();\n });\n app.use(\"/api/*\", jsonOnlyWrites);\n\n // Public routes (no login required).\n app.route(\"/api/auth\", authRoutes(deps));\n\n // Protected routes: cookie -> auth_session -> user.\n const auth = authMiddleware(deps.authService);\n app.use(\"/api/*\", auth);\n app.route(\"/api/me\", meRoutes(deps));\n app.route(\"/api/admin/users\", adminUsersRoutes(deps));\n app.route(\"/api/events\", eventsRoutes(deps));\n // Skill library listing: readable once logged in, not nested under a Project prefix.\n app.route(\"/api/skills\", skillLibraryRoutes());\n app.route(\"/api/projects\", projectsRoutes(deps));\n app.route(\"/api/projects/:projectId/members\", membersRoutes(deps));\n app.route(\"/api/projects/:projectId/models\", modelsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents\", agentsRoutes(deps));\n app.route(\"/api/projects/:projectId/dirs\", dirsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/config\", agentConfigRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/vault\", vaultRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/schedules\", scheduleRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/benchmarks\", benchmarksRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/skills\", agentSkillsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId\", agentTransferRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/traces\", agentTracesRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/sessions\", agentSessionsRoutes(deps));\n app.route(\"/api/projects/:projectId/usage\", usageRoutes(deps));\n app.route(\"/api/sessions\", sessionsRoutes(deps));\n\n // Workspace HTML preview on the separate preview origin: deliberately outside /api and\n // outside the auth middleware — that origin never receives the session cookie, so the\n // signed token in the path is the only credential. Mounted before static hosting so the\n // SPA fallback cannot swallow it. See design § \"Workspace 文件预览\".\n app.route(\"/preview\", previewRoutes(deps));\n\n // Static hosting (production): serves the frontend build output when webDist exists, with SPA fallback to index.html.\n if (fs.existsSync(deps.config.webDist)) {\n registerStaticRoutes(app, deps.config.webDist);\n }\n\n return app;\n}\n\n/**\n * The Project an error is attributed to: only\n * attributed when the URL has a `:projectId` **and** the requester genuinely has\n * access to that Project; otherwise recorded as unattributed (`project_id IS\n * NULL`, visible only to admins).\n *\n * onError also has to handle requests that **haven't passed permission checks\n * yet** — a 401 from being logged out, a 404 from not being a member, both get\n * recorded here. Attributing directly from the URL parameter would let anyone\n * (not necessarily a member of that Project, or even logged in) pick a projectId\n * and hammer it repeatedly to pollute another user's Project with error stats.\n * Traces that can't be attributed simply fall into the admin view (unattributed\n * errors are only visible to admins by design anyway), which is exactly where\n * unauthorized probing belongs.\n *\n * Two defenses here, because this code runs on the error-handling path:\n * - `c.var.user`'s static type is non-null, but authMiddleware never sets it\n * **before** throwing the 401 when logged out, so at runtime it may actually be\n * undefined — it can only be read safely, never destructured directly.\n * - Exceptions are swallowed entirely: throwing here would break onError itself\n * (possibly recursively); any judgment failure falls back to unattributed.\n */\nfunction attributedProjectId(c: Context<AppEnv>, deps: AppDeps): string | undefined {\n try {\n const projectId = c.req.param(\"projectId\");\n if (projectId === undefined) return undefined;\n const user = c.get(\"user\") as UserRow | undefined;\n if (user === undefined) return undefined;\n return deps.projectService.canAccess(user.userId, projectId) ? projectId : undefined;\n } catch {\n return undefined;\n }\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".json\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".ico\": \"image/x-icon\",\n \".map\": \"application/json\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".woff2\": \"font/woff2\",\n};\n\n/** Minimal static file server (avoiding an extra dependency): path traversal protection + SPA fallback. */\nfunction registerStaticRoutes(app: Hono<AppEnv>, webDist: string): void {\n app.get(\"*\", async (c) => {\n const reqPath = decodeURIComponent(c.req.path);\n if (reqPath.startsWith(\"/api/\")) {\n return c.json(errorBody(\"not_found\", \"Endpoint does not exist.\"), 404);\n }\n const rel = reqPath.replace(/^\\/+/, \"\");\n const resolved = path.resolve(webDist, rel === \"\" ? \"index.html\" : rel);\n // Guard against path traversal: once resolved, it must still be inside webDist.\n const base = path.resolve(webDist);\n const target =\n resolved === base || resolved.startsWith(base + path.sep)\n ? resolved\n : path.join(base, \"index.html\");\n let file = target;\n try {\n const stat = await fsp.stat(file);\n if (stat.isDirectory()) file = path.join(file, \"index.html\");\n await fsp.access(file);\n } catch {\n file = path.join(base, \"index.html\"); // SPA fallback\n }\n let content: Buffer;\n try {\n content = await fsp.readFile(file);\n } catch {\n return c.json(errorBody(\"not_found\", \"Resource does not exist.\"), 404);\n }\n const type = CONTENT_TYPES[path.extname(file).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(new Uint8Array(content), {\n status: 200,\n headers: { \"Content-Type\": type },\n });\n });\n}\n","/**\n * SQLite connection & initialization (node:sqlite DatabaseSync).\n *\n * Single process, single writer: a synchronous API is sufficient and avoids a connection\n * pool; WAL mode and foreign key constraints are enabled. Table-creation SQL runs on open\n * (idempotent), with no migration branches (product not yet released).\n */\nimport { mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { SCHEMA_SQL } from \"./schema.js\";\n\n// Fetch the runtime module via process.getBuiltinModule (node >=22.3): avoids static\n// resolution of `node:sqlite` by bundlers/vite (some tools' builtin lists don't yet\n// recognize this experimental module).\nconst sqlite = process.getBuiltinModule(\"node:sqlite\");\n\n/** Open (creating if necessary) the database: ensure the parent directory exists, set PRAGMAs, run table creation. */\nexport function openDatabase(dbPath: string): DatabaseSync {\n if (dbPath !== \":memory:\") {\n mkdirSync(path.dirname(dbPath), { recursive: true });\n }\n const db = new sqlite.DatabaseSync(dbPath);\n db.exec(\"PRAGMA journal_mode = WAL;\");\n db.exec(\"PRAGMA foreign_keys = ON;\");\n db.exec(SCHEMA_SQL);\n return db;\n}\n","/**\n * SQLite table-creation SQL.\n *\n * SQLite stores only indexes and aggregates: users / login sessions / Project authorization /\n * Agent & Session indexes / usage summaries / error records / UI preferences. Agent State,\n * Trace, and Workspace still follow the local directory-based storage rules.\n * Product not yet released: no migration branches — everything is CREATE IF NOT EXISTS, formed once.\n */\n\nexport const SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS users (\n user_id TEXT PRIMARY KEY, -- semantic id doubles as login name: ^[a-z][a-z0-9_-]{1,31}$\n password_hash TEXT NOT NULL, -- scrypt$N$r$p$salt$hash(base64)\n is_admin INTEGER NOT NULL DEFAULT 0, -- 1 for the built-in admin (seeded at startup)\n password_is_initial INTEGER NOT NULL DEFAULT 0, -- 1=initial password (seeded/admin-set); cleared once the user changes it\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS auth_sessions (\n token_hash TEXT PRIMARY KEY, -- sha256(token) hex; the cookie stores only the raw token\n user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL -- 7-day sliding renewal (topped up when <6 days remain)\n);\nCREATE TABLE IF NOT EXISTS projects (\n project_id TEXT PRIMARY KEY, -- directory name doubles as id; display name lives in project_config.toml\n owner_user_id TEXT NOT NULL REFERENCES users(user_id),\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_members ( -- member grants only; owners are not stored here\n project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n PRIMARY KEY (project_id, user_id)\n);\nCREATE TABLE IF NOT EXISTS agents ( -- index only; name/description live in system_config.yaml\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (project_id, agent_id)\n);\nCREATE TABLE IF NOT EXISTS sessions (\n session_id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n provider TEXT NOT NULL, -- provider group of the session model (paired with model_id as a model reference)\n model_id TEXT NOT NULL, -- upstream model id (sent to AgentHub as-is; never concatenate <provider>/<id>)\n workspace TEXT NOT NULL,\n approval_mode TEXT NOT NULL DEFAULT 'allow-all', -- allow-all|deny-all|read-only|always-ask\n title TEXT, -- auto-generated by the model after the first exchange; NULL=not yet (frontend shows \"New chat\")\n archived_at TEXT, -- archive time; NULL=not archived (shown by default; archived ones move under \"Archived\")\n created_at TEXT NOT NULL\n); -- session origin is NOT stored: session_meta in the Trace is the single source of truth (see runtime/session-sources.ts)\nCREATE TABLE IF NOT EXISTS usage_records (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts TEXT NOT NULL,\n date TEXT NOT NULL, -- local date yyyy-mm-dd (aggregation key; same convention as Trace date directories)\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n session_id TEXT NOT NULL, -- top-level Session (child-session usage counts toward its main Session)\n origin_session_id TEXT, -- immediate source child Session (last of the origin chain); NULL=main session\n provider TEXT NOT NULL, -- provider group: paired with model_id as the attribution key; always GROUP BY provider, model_id\n model_id TEXT NOT NULL, -- upstream model id (paired with provider; same model_id across providers aggregates separately)\n cache_read INTEGER NOT NULL,\n cache_write INTEGER NOT NULL,\n output INTEGER NOT NULL,\n total INTEGER NOT NULL, -- from token_usage.request (one row per Request)\n status TEXT NOT NULL DEFAULT 'completed' -- request outcome: completed=success (with tokens); others=failure (0 tokens, for success rate)\n); -- cost is not stored: computed at query time from current pricing\nCREATE INDEX IF NOT EXISTS idx_usage_project_date ON usage_records(project_id, date);\nCREATE INDEX IF NOT EXISTS idx_usage_session ON usage_records(session_id);\nCREATE TABLE IF NOT EXISTS error_records ( -- server-side error capture (the Costs page's \"Errors\" tab)\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts TEXT NOT NULL,\n date TEXT NOT NULL, -- local date yyyy-mm-dd (same convention as usage_records)\n project_id TEXT, -- nullable: sign-in/registration and process-level errors have no Project context\n agent_id TEXT,\n session_id TEXT,\n source TEXT NOT NULL, -- http | session | usage | title | subagent | process | llm | environment | schedule\n kind TEXT NOT NULL, -- expected (HttpError, business 4xx) | unexpected (500/runtime)\n code TEXT NOT NULL, -- HttpError.code / internal / session_run_failed / ...\n status INTEGER, -- HTTP status code; NULL for non-HTTP sources\n message TEXT NOT NULL -- truncated to 500 chars (no stack stored: stacks go to logs only)\n);\nCREATE INDEX IF NOT EXISTS idx_error_project_date ON error_records(project_id, date);\nCREATE TABLE IF NOT EXISTS schedule_state ( -- schedule runtime state (files are declarative intent; the system never writes back)\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n name TEXT NOT NULL, -- file name (without .toml) doubles as the identifier\n creator_user_id TEXT, -- creator (recorded on API create; hand-edited file reconciliation falls back to the Project owner)\n start_at_ms INTEGER NOT NULL, -- defines identity: a start_at change counts as a new task instance, resetting fire state\n def_hash TEXT NOT NULL, -- file content fingerprint: any change clears the invalid flag (the file takes effect again after edits)\n last_slot_ms INTEGER, -- latest due slot already consumed (advances on fire or skip; no re-fire across restarts)\n last_fired_at TEXT, -- last actual fire time (for display)\n fired_once INTEGER NOT NULL DEFAULT 0, -- one-shot task has fired\n missed INTEGER NOT NULL DEFAULT 0, -- one-shot task was missed (flagged by startup/registration reconciliation; not backfilled)\n invalid_reason TEXT, -- invalidation reason (e.g. the bound Session was deleted); NULL=healthy\n PRIMARY KEY (project_id, agent_id, name)\n);\nCREATE TABLE IF NOT EXISTS ui_prefs (\n user_id TEXT PRIMARY KEY REFERENCES users(user_id) ON DELETE CASCADE,\n prefs_json TEXT NOT NULL -- {theme?, lastProjectId?, ...} free-form JSON\n);\n`;\n","/**\n * agents table repo: Agent index; name/description live in system_config.yaml.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface AgentRow {\n projectId: string;\n agentId: string;\n createdAt: string;\n}\n\nexport class AgentsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n /** Idempotent insert: backfills an untracked Agent discovered via directory scan; shared with explicit creation. */\n insertOrIgnore(row: AgentRow): void {\n this.db\n .prepare(\"INSERT OR IGNORE INTO agents (project_id, agent_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.agentId, row.createdAt);\n }\n\n exists(projectId: string, agentId: string): boolean {\n const r = this.db\n .prepare(\"SELECT 1 AS x FROM agents WHERE project_id = ? AND agent_id = ?\")\n .get(projectId, agentId);\n return r !== undefined;\n }\n\n list(projectId: string): AgentRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM agents WHERE project_id = ? ORDER BY created_at ASC, agent_id ASC\")\n .all(projectId);\n return rows.map((r) => ({\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n createdAt: r.created_at as string,\n }));\n }\n\n delete(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM agents WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM agents WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * auth_sessions table repo (server-side sessions backing the HttpOnly cookie).\n *\n * Stores only the sha256(token) hex hash; the raw token appears only in the cookie.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface AuthSessionRow {\n tokenHash: string;\n userId: string;\n createdAt: string;\n expiresAt: string;\n}\n\nexport class AuthSessionsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: AuthSessionRow): void {\n this.db\n .prepare(\n \"INSERT INTO auth_sessions (token_hash, user_id, created_at, expires_at) VALUES (?, ?, ?, ?)\",\n )\n .run(row.tokenHash, row.userId, row.createdAt, row.expiresAt);\n }\n\n findByTokenHash(tokenHash: string): AuthSessionRow | null {\n const r = this.db.prepare(\"SELECT * FROM auth_sessions WHERE token_hash = ?\").get(tokenHash);\n if (!r) return null;\n return {\n tokenHash: r.token_hash as string,\n userId: r.user_id as string,\n createdAt: r.created_at as string,\n expiresAt: r.expires_at as string,\n };\n }\n\n /** Sliding renewal: update the expiration time. */\n touch(tokenHash: string, expiresAt: string): void {\n this.db\n .prepare(\"UPDATE auth_sessions SET expires_at = ? WHERE token_hash = ?\")\n .run(expiresAt, tokenHash);\n }\n\n delete(tokenHash: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE token_hash = ?\").run(tokenHash);\n }\n\n /** Opportunistically clean up expired sessions (called during login/validation). */\n deleteExpired(nowIso: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE expires_at < ?\").run(nowIso);\n }\n\n /** Clear all sessions for a user (forces re-login after an admin resets the password). */\n deleteByUser(userId: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE user_id = ?\").run(userId);\n }\n}\n","/**\n * error_records table repo: one row per error the server catches.\n *\n * Key difference from usage_records: **all attribution columns are nullable** — errors\n * from the login/register endpoints have no Project, and process-level catch-alls\n * (uncaughtException) don't even have a request. These **unattributed errors are visible\n * only to admins** (`ErrorFilter.includeGlobal`, defaults to false): they represent other\n * users' login failures, misdirected Session access, and process crashes (the message may\n * contain internal paths and variable values), so surfacing them in any regular member's\n * statistics center would be a cross-tenant information leak — regular members see only\n * their own Project's errors. Admins still see the category that most needs visibility.\n *\n * **Row cap** (MAX_ROWS): errors often come in storms (an API scan producing a wall of\n * 404s, a tool failing repeatedly in a loop), and an uncapped table would blow up disk\n * usage. So the insert path enforces the cap: check capacity every PRUNE_EVERY inserts\n * (insertion sits on the error-handling path, so we avoid COUNT on every call), and when\n * over the limit, evict the oldest rows in ascending id order. The excess is computed via\n * an **exact COUNT**, not an approximation like `id <= MAX(id) - :max` — after\n * deleteByProject removes rows, id and row count diverge, and the approximation would\n * wrongly delete still-valid data within the cap. The first line of defense is\n * ErrorRecorder's short-window deduplication.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/** Row cap: once exceeded, oldest rows are evicted in ascending id order (see file header). */\nexport const MAX_ROWS = 20000;\n\n/** Check capacity every N inserts (see file header: insertion sits on the error-handling path, so we avoid COUNT on every call). */\nexport const PRUNE_EVERY = 200;\n\n/** Capacity parameters (default to the two constants above; tests inject small values to exercise the eviction path). */\nexport interface ErrorsRepoLimits {\n maxRows?: number;\n pruneEvery?: number;\n}\n\nexport interface ErrorRecordInsert {\n ts: string;\n date: string;\n projectId: string | null;\n agentId: string | null;\n sessionId: string | null;\n source: string;\n /** expected (HttpError, business 4xx) | unexpected (500 / unforeseen runtime error). */\n kind: string;\n code: string;\n status: number | null;\n message: string;\n}\n\n/** Generic filter: date range + agent (errors have no Model dimension, so no model filter). */\nexport interface ErrorFilter {\n from?: string;\n to?: string;\n agentId?: string;\n /** Whether to include unattributed errors (`project_id IS NULL`): admins only, defaults to false (see file header). */\n includeGlobal?: boolean;\n}\n\n/** Total error count and how many are unexpected (stats for the statistics center's error panel). */\nexport interface ErrorSummary {\n total: number;\n unexpected: number;\n}\n\n/** Occurrence count for one source · code pair (the error panel's \"most common\" metric). */\nexport interface ErrorCodeCount {\n source: string;\n code: string;\n kind: string;\n count: number;\n}\n\n/** One error summary row (a row in the error panel's table). */\nexport interface ErrorItem {\n ts: string;\n source: string;\n code: string;\n kind: string;\n message: string;\n}\n\nexport class ErrorsRepo {\n private readonly maxRows: number;\n private readonly pruneEvery: number;\n /** Insert count since the last capacity check (see file header: avoids COUNT on every call). */\n private sinceCheck = 0;\n\n constructor(\n private readonly db: DatabaseSync,\n limits: ErrorsRepoLimits = {},\n ) {\n this.maxRows = limits.maxRows ?? MAX_ROWS;\n this.pruneEvery = limits.pruneEvery ?? PRUNE_EVERY;\n }\n\n insert(r: ErrorRecordInsert): void {\n this.db\n .prepare(\n `INSERT INTO error_records\n (ts, date, project_id, agent_id, session_id, source, kind, code, status, message)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n r.ts,\n r.date,\n r.projectId,\n r.agentId,\n r.sessionId,\n r.source,\n r.kind,\n r.code,\n r.status,\n r.message,\n );\n if (++this.sinceCheck >= this.pruneEvery) {\n this.sinceCheck = 0;\n this.pruneOverflow();\n }\n }\n\n /** Capacity enforcement (see file header): compute the exact excess via COUNT, then delete the oldest rows in ascending id order. */\n private pruneOverflow(): void {\n const row = this.db.prepare(\"SELECT COUNT(*) AS n FROM error_records\").get()!;\n const excess = (row.n as number) - this.maxRows;\n if (excess <= 0) return;\n this.db\n .prepare(\n `DELETE FROM error_records WHERE id IN (\n SELECT id FROM error_records ORDER BY id ASC LIMIT :excess\n )`,\n )\n .run({ excess });\n }\n\n /** WHERE fragment and named params: this Project (admins additionally get unattributed errors), plus optional date/agent filter. */\n private conds(\n projectId: string,\n f: ErrorFilter,\n ): { where: string; params: Record<string, string> } {\n // Unattributed errors (login failures, process crashes, ...) are visible only to admins; otherwise it's a cross-tenant leak — see file header.\n const conds = [\n f.includeGlobal === true ? \"(project_id = :pid OR project_id IS NULL)\" : \"project_id = :pid\",\n ];\n const params: Record<string, string> = { pid: projectId };\n if (f.from !== undefined) {\n conds.push(\"date >= :from\");\n params.from = f.from;\n }\n if (f.to !== undefined) {\n conds.push(\"date <= :to\");\n params.to = f.to;\n }\n if (f.agentId !== undefined) {\n // Filtering by Agent naturally leaves only that Agent's errors (HTTP / process-level errors have no agent_id).\n conds.push(\"agent_id = :agentId\");\n params.agentId = f.agentId;\n }\n return { where: conds.join(\" AND \"), params };\n }\n\n /** Total count + how many are unexpected. */\n summary(projectId: string, f: ErrorFilter = {}): ErrorSummary {\n const { where, params } = this.conds(projectId, f);\n const row = this.db\n .prepare(\n `SELECT COUNT(*) AS total,\n COALESCE(SUM(CASE WHEN kind = 'unexpected' THEN 1 ELSE 0 END), 0) AS unexpected\n FROM error_records WHERE ${where}`,\n )\n .get(params)!;\n return { total: row.total as number, unexpected: row.unexpected as number };\n }\n\n /** The most frequent source · code (ties broken by code's lexicographic order); null if there are no errors. */\n topCode(projectId: string, f: ErrorFilter = {}): ErrorCodeCount | null {\n const { where, params } = this.conds(projectId, f);\n const row = this.db\n .prepare(\n `SELECT source, code, kind, COUNT(*) AS count\n FROM error_records WHERE ${where}\n GROUP BY source, code, kind\n ORDER BY count DESC, code ASC\n LIMIT 1`,\n )\n .get(params);\n if (!row) return null;\n return {\n source: row.source as string,\n code: row.code as string,\n kind: row.kind as string,\n count: row.count as number,\n };\n }\n\n /** The most recent `limit` entries (reverse chronological order). */\n recent(projectId: string, f: ErrorFilter = {}, limit = 20): ErrorItem[] {\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT ts, source, code, kind, message\n FROM error_records WHERE ${where}\n ORDER BY id DESC LIMIT :limit`,\n )\n .all({ ...params, limit });\n return rows.map((r) => ({\n ts: r.ts as string,\n source: r.source as string,\n code: r.code as string,\n kind: r.kind as string,\n message: r.message as string,\n }));\n }\n\n /** Cascading cleanup on Project deletion (unattributed errors belong to no Project and are unaffected). */\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM error_records WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * Repo for the project_members table: only member\n * authorization relationships — the owner is never in this table.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface MemberRow {\n projectId: string;\n userId: string;\n createdAt: string;\n}\n\nexport class MembersRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: MemberRow): void {\n this.db\n .prepare(\"INSERT INTO project_members (project_id, user_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.userId, row.createdAt);\n }\n\n isMember(projectId: string, userId: string): boolean {\n const r = this.db\n .prepare(\"SELECT 1 AS x FROM project_members WHERE project_id = ? AND user_id = ?\")\n .get(projectId, userId);\n return r !== undefined;\n }\n\n list(projectId: string): MemberRow[] {\n const rows = this.db\n .prepare(\n \"SELECT project_id, user_id, created_at FROM project_members WHERE project_id = ? ORDER BY created_at ASC\",\n )\n .all(projectId);\n return rows.map((r) => ({\n projectId: r.project_id as string,\n userId: r.user_id as string,\n createdAt: r.created_at as string,\n }));\n }\n\n delete(projectId: string, userId: string): void {\n this.db\n .prepare(\"DELETE FROM project_members WHERE project_id = ? AND user_id = ?\")\n .run(projectId, userId);\n }\n}\n","/**\n * Repo for the projects table: an index of ownership\n * relationships; the display name lives in project_config.toml.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ProjectRole } from \"../../api/types.js\";\n\nexport interface ProjectRow {\n projectId: string;\n ownerUserId: string;\n createdAt: string;\n}\n\nexport interface AccessibleProjectRow extends ProjectRow {\n role: ProjectRole;\n}\n\nfunction mapRow(r: Record<string, unknown>): ProjectRow {\n return {\n projectId: r.project_id as string,\n ownerUserId: r.owner_user_id as string,\n createdAt: r.created_at as string,\n };\n}\n\nexport class ProjectsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: ProjectRow): void {\n this.db\n .prepare(\"INSERT INTO projects (project_id, owner_user_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.ownerUserId, row.createdAt);\n }\n\n findById(projectId: string): ProjectRow | null {\n const r = this.db.prepare(\"SELECT * FROM projects WHERE project_id = ?\").get(projectId);\n return r ? mapRow(r) : null;\n }\n\n /** All Projects (used by the scheduler's reconciliation scan), ascending by creation time. */\n listAll(): ProjectRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM projects ORDER BY created_at ASC, project_id ASC\")\n .all();\n return rows.map((r) => mapRow(r as Record<string, unknown>));\n }\n\n /** Projects owned by or shared with the current user (including their role), ascending by creation time. */\n listAccessible(userId: string): AccessibleProjectRow[] {\n const rows = this.db\n .prepare(\n `SELECT p.project_id, p.owner_user_id, p.created_at,\n CASE WHEN p.owner_user_id = :uid THEN 'owner' ELSE 'member' END AS role\n FROM projects p\n WHERE p.owner_user_id = :uid\n OR EXISTS (SELECT 1 FROM project_members m\n WHERE m.project_id = p.project_id AND m.user_id = :uid)\n ORDER BY p.created_at ASC, p.project_id ASC`,\n )\n .all({ uid: userId });\n return rows.map((r) => ({\n ...mapRow(r),\n role: r.role as ProjectRole,\n }));\n }\n\n /** All Projects owned by a given user (used for cascading cleanup when an admin deletes a user). */\n listByOwner(userId: string): ProjectRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM projects WHERE owner_user_id = ? ORDER BY created_at ASC\")\n .all(userId);\n return rows.map(mapRow);\n }\n\n delete(projectId: string): void {\n this.db.prepare(\"DELETE FROM projects WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * Repo for schedule runtime state: intent and state are separate — the file is\n * declarative intent, this table only records runtime state such as\n * \"fired before / last fired / missed / disabled\" plus the creator.\n *\n * Identity rule: a change to `start_at` is treated as a new task instance\n * (registerOrSync resets the trigger state); a change to the file content fingerprint\n * only clears the disabled flag (the file becomes effective again after reconciliation).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface ScheduleStateRow {\n projectId: string;\n agentId: string;\n name: string;\n creatorUserId: string | null;\n startAtMs: number;\n defHash: string;\n lastSlotMs: number | null;\n lastFiredAt: string | null;\n firedOnce: boolean;\n missed: boolean;\n invalidReason: string | null;\n}\n\nfunction mapRow(r: Record<string, unknown>): ScheduleStateRow {\n return {\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n name: r.name as string,\n creatorUserId: (r.creator_user_id as string | null) ?? null,\n startAtMs: Number(r.start_at_ms),\n defHash: r.def_hash as string,\n lastSlotMs: r.last_slot_ms === null ? null : Number(r.last_slot_ms),\n lastFiredAt: (r.last_fired_at as string | null) ?? null,\n firedOnce: Number(r.fired_once) === 1,\n missed: Number(r.missed) === 1,\n invalidReason: (r.invalid_reason as string | null) ?? null,\n };\n}\n\nexport class SchedulesRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n find(projectId: string, agentId: string, name: string): ScheduleStateRow | null {\n const r = this.db\n .prepare(\"SELECT * FROM schedule_state WHERE project_id = ? AND agent_id = ? AND name = ?\")\n .get(projectId, agentId, name);\n return r ? mapRow(r as Record<string, unknown>) : null;\n }\n\n listByAgent(projectId: string, agentId: string): ScheduleStateRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM schedule_state WHERE project_id = ? AND agent_id = ? ORDER BY name\")\n .all(projectId, agentId);\n return rows.map((r) => mapRow(r as Record<string, unknown>));\n }\n\n /**\n * Register or sync a task's runtime state, returning the synced row plus a `fresh`\n * flag:\n * - Insert if it doesn't exist (creator is only persisted at this point; a hand-edited\n * file gets registered via reconciliation, with creator falling back to the Project\n * owner);\n * - A change to `start_at` resets the trigger state (a new task instance);\n * - Otherwise, a change to the file fingerprint only clears the disabled flag.\n * `fresh` = this call was an insert or reset — the scheduler only establishes its\n * \"missed, don't backfill\" baseline at this moment; afterward, last_slot being NULL\n * only means \"no scheduled time has been consumed yet\", and it must fire normally once\n * reached.\n */\n registerOrSync(args: {\n projectId: string;\n agentId: string;\n name: string;\n startAtMs: number;\n defHash: string;\n creatorUserId: string | null;\n }): { row: ScheduleStateRow; fresh: boolean } {\n const existing = this.find(args.projectId, args.agentId, args.name);\n let fresh = false;\n if (!existing) {\n this.db\n .prepare(\n `INSERT INTO schedule_state\n (project_id, agent_id, name, creator_user_id, start_at_ms, def_hash)\n VALUES (?, ?, ?, ?, ?, ?)`,\n )\n .run(\n args.projectId,\n args.agentId,\n args.name,\n args.creatorUserId,\n args.startAtMs,\n args.defHash,\n );\n fresh = true;\n } else if (existing.startAtMs !== args.startAtMs) {\n this.db\n .prepare(\n `UPDATE schedule_state\n SET start_at_ms = ?, def_hash = ?, last_slot_ms = NULL, last_fired_at = NULL,\n fired_once = 0, missed = 0, invalid_reason = NULL\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(args.startAtMs, args.defHash, args.projectId, args.agentId, args.name);\n fresh = true;\n } else if (existing.defHash !== args.defHash) {\n this.db\n .prepare(\n `UPDATE schedule_state SET def_hash = ?, invalid_reason = NULL\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(args.defHash, args.projectId, args.agentId, args.name);\n }\n const row = this.find(args.projectId, args.agentId, args.name);\n if (!row) throw new Error(\"Failed to read back schedule_state after registration\");\n return { row, fresh };\n }\n\n /** Advance the consumed scheduled time (advances whether triggered or skipped; restarts don't re-trigger). */\n markSlot(projectId: string, agentId: string, name: string, slotMs: number): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET last_slot_ms = ?\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(slotMs, projectId, agentId, name);\n }\n\n /** Record an actual send (also sets fired_once for a one-shot task). */\n markFired(\n projectId: string,\n agentId: string,\n name: string,\n firedAt: string,\n oneShot: boolean,\n ): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET last_fired_at = ?, fired_once = CASE WHEN ? THEN 1 ELSE fired_once END\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(firedAt, oneShot ? 1 : 0, projectId, agentId, name);\n }\n\n /** Missed marker for a one-shot task (the scheduled time had already passed at startup/registration reconciliation; missed means not backfilled). */\n markMissed(projectId: string, agentId: string, name: string): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET missed = 1\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(projectId, agentId, name);\n }\n\n /** Mark as disabled (e.g. the bound Session was deleted); cleared via registerOrSync after the file is modified. */\n markInvalid(projectId: string, agentId: string, name: string, reason: string): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET invalid_reason = ?\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(reason, projectId, agentId, name);\n }\n\n /** Deleting the file removes the task: clears its runtime state. */\n delete(projectId: string, agentId: string, name: string): void {\n this.db\n .prepare(\"DELETE FROM schedule_state WHERE project_id = ? AND agent_id = ? AND name = ?\")\n .run(projectId, agentId, name);\n }\n\n /** Reconciliation cleanup: deletes state rows under this Agent that aren't in the current file list, returning the removed names. */\n deleteMissing(projectId: string, agentId: string, presentNames: string[]): string[] {\n const rows = this.listByAgent(projectId, agentId);\n const present = new Set(presentNames);\n const removed: string[] = [];\n for (const row of rows) {\n if (!present.has(row.name)) {\n this.delete(projectId, agentId, row.name);\n removed.push(row.name);\n }\n }\n return removed;\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM schedule_state WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * sessions table repo:\n * Session index, approval mode, and auto-generated title; Session-level routes use this to look up project ownership.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ApprovalMode } from \"../../api/types.js\";\n\nexport interface SessionRow {\n sessionId: string;\n projectId: string;\n agentId: string;\n /** Provider group of the session's model (pairs with `modelId` to form the model reference). */\n provider: string;\n /** Upstream model_id of the session's model (sent as-is to AgentHub; never concatenated). */\n modelId: string;\n workspace: string;\n approvalMode: ApprovalMode;\n /** Auto-generated session title; NULL = not yet generated (frontend shows \"New Conversation\"). */\n title: string | null;\n /** Archive timestamp, ISO; NULL = not archived (omitting on insert defaults to NULL). */\n archivedAt?: string | null;\n // The Session origin (schedule / subagent) is deliberately NOT a row field: core\n // session_meta in the Trace is the single source of truth (runtime/session-sources.ts).\n createdAt: string;\n}\n\nfunction mapRow(r: Record<string, unknown>): SessionRow {\n return {\n sessionId: r.session_id as string,\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n provider: r.provider as string,\n modelId: r.model_id as string,\n workspace: r.workspace as string,\n approvalMode: r.approval_mode as ApprovalMode,\n title: (r.title as string | null) ?? null,\n archivedAt: (r.archived_at as string | null) ?? null,\n createdAt: r.created_at as string,\n };\n}\n\nexport class SessionsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: SessionRow): void {\n this.db\n .prepare(\n `INSERT INTO sessions (session_id, project_id, agent_id, provider, model_id, workspace, approval_mode, title, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n row.sessionId,\n row.projectId,\n row.agentId,\n row.provider,\n row.modelId,\n row.workspace,\n row.approvalMode,\n row.title,\n row.createdAt,\n );\n }\n\n /** Idempotent insert: used when Trace directory discovery backfills a row (concurrent listing discovering the same Session no longer triggers a UNIQUE violation). */\n insertOrIgnore(row: SessionRow): void {\n this.db\n .prepare(\n `INSERT OR IGNORE INTO sessions (session_id, project_id, agent_id, provider, model_id, workspace, approval_mode, title, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n row.sessionId,\n row.projectId,\n row.agentId,\n row.provider,\n row.modelId,\n row.workspace,\n row.approvalMode,\n row.title,\n row.createdAt,\n );\n }\n\n findById(sessionId: string): SessionRow | null {\n const r = this.db.prepare(\"SELECT * FROM sessions WHERE session_id = ?\").get(sessionId);\n return r ? mapRow(r) : null;\n }\n\n listByAgent(projectId: string, agentId: string): SessionRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM sessions WHERE project_id = ? AND agent_id = ?\")\n .all(projectId, agentId);\n return rows.map(mapRow);\n }\n\n listByProject(projectId: string): SessionRow[] {\n const rows = this.db.prepare(\"SELECT * FROM sessions WHERE project_id = ?\").all(projectId);\n return rows.map(mapRow);\n }\n\n updateApprovalMode(sessionId: string, mode: ApprovalMode): void {\n this.db\n .prepare(\"UPDATE sessions SET approval_mode = ? WHERE session_id = ?\")\n .run(mode, sessionId);\n }\n\n updateTitle(sessionId: string, title: string): void {\n this.db.prepare(\"UPDATE sessions SET title = ? WHERE session_id = ?\").run(title, sessionId);\n }\n\n /**\n * Writes only if the title is still NULL. Subagent-session registration and Trace\n * directory discovery backfill can race on the same row, and both are insert-only:\n * whichever inserts first determines the title. Discovery backfill can only supply NULL,\n * so this method fills the title back in without overwriting an existing one (including\n * a user rename or an already-generated title).\n */\n updateTitleIfNull(sessionId: string, title: string): void {\n this.db\n .prepare(\"UPDATE sessions SET title = ? WHERE session_id = ? AND title IS NULL\")\n .run(title, sessionId);\n }\n\n /** Archive / unarchive (archivedAt = ISO or NULL). */\n setArchived(sessionId: string, archivedAt: string | null): void {\n this.db\n .prepare(\"UPDATE sessions SET archived_at = ? WHERE session_id = ?\")\n .run(archivedAt, sessionId);\n }\n\n /** Self-healing: after rebuilding a broken Session with no Trace, update the primary key to the new id. */\n replaceId(oldSessionId: string, newSessionId: string): void {\n this.db\n .prepare(\"UPDATE sessions SET session_id = ? WHERE session_id = ?\")\n .run(newSessionId, oldSessionId);\n }\n\n deleteByAgent(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM sessions WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM sessions WHERE project_id = ?\").run(projectId);\n }\n\n deleteById(sessionId: string): void {\n this.db.prepare(\"DELETE FROM sessions WHERE session_id = ?\").run(sessionId);\n }\n}\n","/**\n * ui_prefs table repo (UI preferences): free-form JSON storage.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport class UiPrefsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n /** Returns the raw JSON string; null if never set. */\n get(userId: string): string | null {\n const r = this.db.prepare(\"SELECT prefs_json FROM ui_prefs WHERE user_id = ?\").get(userId);\n return r ? (r.prefs_json as string) : null;\n }\n\n set(userId: string, prefsJson: string): void {\n this.db\n .prepare(\n `INSERT INTO ui_prefs (user_id, prefs_json) VALUES (?, ?)\n ON CONFLICT(user_id) DO UPDATE SET prefs_json = excluded.prefs_json`,\n )\n .run(userId, prefsJson);\n }\n}\n","/**\n * usage_records table repo:\n * one row per token_usage (per-request bucket). Stores Token counts only, not cost —\n * cost is computed on the fly by usage-service against current pricing at query time,\n * so every aggregation is broken down by the `(provider, model_id)` pair and returns\n * raw Token sums (a model_id shared across providers is aggregated separately; never concatenated).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { UsageGroupBy } from \"../../api/types.js\";\n\nexport interface UsageRecordInsert {\n ts: string;\n date: string;\n projectId: string;\n agentId: string;\n sessionId: string;\n originSessionId: string | null;\n /** Provider group (pairs with modelId to form the attribution key). */\n provider: string;\n /** Upstream model id (pairs with provider). */\n modelId: string;\n cacheRead: number;\n cacheWrite: number;\n output: number;\n total: number;\n /** Request outcome; defaults to completed (success, carries tokens). Failed requests are stored with 0 tokens + status, for success-rate calculations. */\n status?: string;\n}\n\n/** Generic filter: date range + agent / model dimensions (cost center top bar switches by agent/model). */\nexport interface UsageFilter {\n from?: string;\n to?: string;\n agentId?: string;\n /** Provider filter paired with modelId (the frontend dropdown always sends them together). */\n provider?: string;\n modelId?: string;\n}\n\n/**\n * Raw request success-rate counts for a single Model (paired reference).\n * `total` is **the success-rate denominator**: all requests minus aborted — the user\n * clicking \"stop\" is not a model failure, and counting it would make the success rate\n * drop every time stop is pressed. `aborted` is counted separately for display.\n */\nexport interface UsageStatusCount {\n provider: string;\n modelId: string;\n completed: number;\n total: number;\n aborted: number;\n failed: number;\n timeout: number;\n malformed: number;\n}\n\n/** Raw Token sums for a single Model (paired reference) — the smallest unit for cost conversion. */\nexport interface UsageModelSums {\n provider: string;\n modelId: string;\n cacheRead: number;\n cacheWrite: number;\n output: number;\n total: number;\n requests: number;\n}\n\n/** Raw Token sums by group key x Model. */\nexport interface UsageGroupModelSums extends UsageModelSums {\n key: string;\n}\n\n/** groupBy dimension -> column name allowlist (prevents injection; only these four columns can be group keys). */\nconst GROUP_COLUMNS: Record<UsageGroupBy, string> = {\n date: \"date\",\n agent: \"agent_id\",\n model: \"model_id\",\n session: \"session_id\",\n};\n\nconst SUM_COLUMNS = `COALESCE(SUM(cache_read), 0) AS cache_read,\n COALESCE(SUM(cache_write), 0) AS cache_write,\n COALESCE(SUM(output), 0) AS output,\n COALESCE(SUM(total), 0) AS total,\n COUNT(*) AS requests`;\n\nfunction toSums(r: Record<string, unknown>): UsageModelSums {\n return {\n provider: r.provider as string,\n modelId: r.model_id as string,\n cacheRead: r.cache_read as number,\n cacheWrite: r.cache_write as number,\n output: r.output as number,\n total: r.total as number,\n requests: r.requests as number,\n };\n}\n\nexport class UsageRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(r: UsageRecordInsert): void {\n this.db\n .prepare(\n `INSERT INTO usage_records\n (ts, date, project_id, agent_id, session_id, origin_session_id, provider, model_id,\n cache_read, cache_write, output, total, status)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n r.ts,\n r.date,\n r.projectId,\n r.agentId,\n r.sessionId,\n r.originSessionId,\n r.provider,\n r.modelId,\n r.cacheRead,\n r.cacheWrite,\n r.output,\n r.total,\n r.status ?? \"completed\",\n );\n }\n\n /** WHERE fragment (project + optional date/agent/model) plus named params. */\n private conds(\n projectId: string,\n f: UsageFilter,\n ): { where: string; params: Record<string, string> } {\n const conds = [\"project_id = :pid\"];\n const params: Record<string, string> = { pid: projectId };\n if (f.from !== undefined) {\n conds.push(\"date >= :from\");\n params.from = f.from;\n }\n if (f.to !== undefined) {\n conds.push(\"date <= :to\");\n params.to = f.to;\n }\n if (f.agentId !== undefined) {\n conds.push(\"agent_id = :agentId\");\n params.agentId = f.agentId;\n }\n if (f.provider !== undefined) {\n conds.push(\"provider = :provider\");\n params.provider = f.provider;\n }\n if (f.modelId !== undefined) {\n conds.push(\"model_id = :modelId\");\n params.modelId = f.modelId;\n }\n return { where: conds.join(\" AND \"), params };\n }\n\n /** Sums (broken down by paired reference): date range + optional agent/model filter. */\n bucketByModel(projectId: string, f: UsageFilter = {}): UsageModelSums[] {\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT provider, model_id, ${SUM_COLUMNS}\n FROM usage_records WHERE ${where}\n GROUP BY provider, model_id`,\n )\n .all(params);\n return rows.map(toSums);\n }\n\n /** Grouped aggregation (group key x paired reference breakdown): date range + optional agent/model filter. */\n groupsByModel(\n projectId: string,\n groupBy: UsageGroupBy,\n f: UsageFilter = {},\n ): UsageGroupModelSums[] {\n const col = GROUP_COLUMNS[groupBy];\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT ${col} AS key, provider, model_id, ${SUM_COLUMNS}\n FROM usage_records WHERE ${where}\n GROUP BY ${col}, provider, model_id`,\n )\n .all(params);\n return rows.map((r) => ({ key: r.key as string, ...toSums(r) }));\n }\n\n /**\n * Raw success-rate counts per Model (paired reference) (completed / non-aborted requests):\n * powers the cost center's \"Model Success Rate\" chart. The denominator excludes aborted\n * (user-initiated interruption); failure breakdowns (failed / timeout / malformed) are\n * also returned for hover display. Unknown statuses aren't broken out but still count\n * toward the denominator (conservative: anything non-completed counts as a failure).\n */\n statusByModel(projectId: string, f: UsageFilter = {}): UsageStatusCount[] {\n const { where, params } = this.conds(projectId, f);\n const count = (status: string) =>\n `COALESCE(SUM(CASE WHEN status = '${status}' THEN 1 ELSE 0 END), 0) AS ${status}`;\n const rows = this.db\n .prepare(\n `SELECT provider, model_id,\n ${count(\"completed\")},\n ${count(\"aborted\")},\n ${count(\"failed\")},\n ${count(\"timeout\")},\n ${count(\"malformed\")},\n COALESCE(SUM(CASE WHEN status <> 'aborted' THEN 1 ELSE 0 END), 0) AS total\n FROM usage_records WHERE ${where} GROUP BY provider, model_id`,\n )\n .all(params);\n return rows.map((r) => ({\n provider: r.provider as string,\n modelId: r.model_id as string,\n completed: r.completed as number,\n total: r.total as number,\n aborted: r.aborted as number,\n failed: r.failed as number,\n timeout: r.timeout as number,\n malformed: r.malformed as number,\n }));\n }\n\n /** Distinct agent_id values seen for this Project (for filter dropdowns). */\n distinctAgentIds(projectId: string): string[] {\n const rows = this.db\n .prepare(\n \"SELECT DISTINCT agent_id AS v FROM usage_records WHERE project_id = ? ORDER BY agent_id\",\n )\n .all(projectId);\n return rows.map((r) => r.v as string);\n }\n\n /** Distinct Model paired references seen for this Project (for filter dropdowns). */\n distinctModels(projectId: string): Array<{ provider: string; modelId: string }> {\n const rows = this.db\n .prepare(\n `SELECT DISTINCT provider, model_id FROM usage_records\n WHERE project_id = ? ORDER BY provider, model_id`,\n )\n .all(projectId);\n return rows.map((r) => ({ provider: r.provider as string, modelId: r.model_id as string }));\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM usage_records WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * users table repo: pure SQL wrapper, no business rules.\n * user_id is the login name (a semantic id, specified at creation, immutable).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface UserRow {\n userId: string;\n passwordHash: string;\n isAdmin: boolean;\n /** Still using the initial password (seeded / set by an admin); cleared to 0 once the user changes it. */\n passwordIsInitial: boolean;\n createdAt: string;\n}\n\nfunction mapRow(r: Record<string, unknown>): UserRow {\n return {\n userId: r.user_id as string,\n passwordHash: r.password_hash as string,\n isAdmin: (r.is_admin as number) === 1,\n passwordIsInitial: (r.password_is_initial as number) === 1,\n createdAt: r.created_at as string,\n };\n}\n\nexport class UsersRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: UserRow): void {\n this.db\n .prepare(\n \"INSERT INTO users (user_id, password_hash, is_admin, password_is_initial, created_at) VALUES (?, ?, ?, ?, ?)\",\n )\n .run(\n row.userId,\n row.passwordHash,\n row.isAdmin ? 1 : 0,\n row.passwordIsInitial ? 1 : 0,\n row.createdAt,\n );\n }\n\n findById(userId: string): UserRow | null {\n const r = this.db.prepare(\"SELECT * FROM users WHERE user_id = ?\").get(userId);\n return r ? mapRow(r) : null;\n }\n\n /** All users (for the admin user backend), ordered by creation time ascending. */\n list(): UserRow[] {\n const rows = this.db.prepare(\"SELECT * FROM users ORDER BY created_at ASC, user_id ASC\").all();\n return rows.map(mapRow);\n }\n\n count(): number {\n const r = this.db.prepare(\"SELECT COUNT(*) AS n FROM users\").get();\n return (r?.n as number) ?? 0;\n }\n\n /** Update the password hash; isInitial marks whether the password was set by someone else (seed / admin). */\n updatePassword(userId: string, passwordHash: string, isInitial: boolean): void {\n this.db\n .prepare(\"UPDATE users SET password_hash = ?, password_is_initial = ? WHERE user_id = ?\")\n .run(passwordHash, isInitial ? 1 : 0, userId);\n }\n\n /** Used by admin user deletion and account-creation compensation paths (owned Projects must be cleaned up first). */\n delete(userId: string): void {\n this.db.prepare(\"DELETE FROM users WHERE user_id = ?\").run(userId);\n }\n}\n","/**\n * Auth middleware: cookie -> auth_session ->\n * user injected into c.var.\n *\n * Accessing a protected API while logged out -> 401 `{error:{code:\"unauthorized\"}}`.\n * CSRF (MVP): SameSite=Lax cookie + write requests only accept\n * `Content-Type: application/json` (an HTML form can't forge that Content-Type),\n * see the README security notes.\n */\nimport type { MiddlewareHandler } from \"hono\";\nimport { getCookie } from \"hono/cookie\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { UserRow } from \"../db/repos/users.js\";\nimport type { AuthService } from \"./service.js\";\n\n/** Session cookie name. */\nexport const SESSION_COOKIE = \"penguin_session\";\n\n/** Hono env: variables injected by the auth middleware. */\nexport type AppEnv = {\n Variables: {\n user: UserRow;\n };\n};\n\n/** Gets the current user (available after authMiddleware). */\nexport function currentUser(c: { var: { user: UserRow } }): UserRow {\n return c.var.user;\n}\n\nexport function authMiddleware(auth: AuthService): MiddlewareHandler<AppEnv> {\n return async (c, next) => {\n const token = getCookie(c, SESSION_COOKIE);\n const user = token ? auth.authenticate(token) : null;\n if (!user) {\n throw new HttpError(401, \"unauthorized\", \"Not signed in or the sign-in has expired.\");\n }\n c.set(\"user\", user);\n await next();\n };\n}\n\nconst WRITE_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * Content-Type defense for write requests: a write request with a Content-Type\n * other than application/json is rejected (a request with no Content-Type and an\n * empty body is let through — an HTML form always carries a form-type Content-Type).\n */\nexport const jsonOnlyWrites: MiddlewareHandler = async (c, next) => {\n if (WRITE_METHODS.has(c.req.method)) {\n const contentType = c.req.header(\"content-type\");\n if (contentType && !contentType.toLowerCase().startsWith(\"application/json\")) {\n throw new HttpError(\n 415,\n \"unsupported_media_type\",\n \"Write requests only accept application/json.\",\n );\n }\n }\n await next();\n};\n","/**\n * Unified HTTP error: `{error: {code, message}}` response body,\n * with message in English.\n *\n * Routes and services express business errors via throw HttpError; app-level onError\n * uniformly converges these into a JSON response, with unknown errors converged to 500\n * (never leaking internal details to the client).\n */\nimport type { Context } from \"hono\";\nimport type { ErrorBody } from \"../api/types.js\";\n\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport function errorBody(code: string, message: string): ErrorBody {\n return { error: { code, message } };\n}\n\n/**\n * Model missing a credential: the provider SDK throws this at **client-construction\n * time** (hit by both creating a Session and resuming a Session); the original message\n * is full of environment variable names, meaningless to a user — uniformly replaced\n * with a single actionable sentence. The frontend produces localized text by code\n * (message is only a fallback); see web's lib/api-error.ts.\n */\nexport function isMissingCredential(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /missing credentials|api[_ ]?key/i.test(message);\n}\n\nexport function modelCredentialMissing(modelId: string): HttpError {\n return new HttpError(\n 400,\n \"model_credential_missing\",\n `Model ${modelId} has no API key yet. Configure it on the Models page first.`,\n );\n}\n\n/** app.onError handler: maps HttpError through as-is; everything else is logged and converged to 500. */\nexport function handleError(err: Error, c: Context): Response {\n if (err instanceof HttpError) {\n return c.json(errorBody(err.code, err.message), err.status as 400);\n }\n // Unknown error: print the stack for diagnosis, but never expose details externally.\n console.error(`[server] Unhandled exception: ${err.stack ?? err.message}`);\n return c.json(errorBody(\"internal\", \"Internal server error.\"), 500);\n}\n","/**\n * Auth service: built-in admin seeding /\n * login / logout / password change / session validation.\n *\n * - No open registration: on startup, if there are no users at all, the built-in\n * admin `admin` is seeded (initial password penguin-2026), and it adopts\n * `default_project`; all other users are created by an admin via the user\n * backend (admin-service).\n * - An initial password (whether seeded or set by an admin) is flagged with\n * password_is_initial, which the frontend uses to prompt for a password change soon.\n * - Sessions: a 32-byte random token, with only its sha256 hash stored in the DB;\n * valid for 7 days, with sliding renewal once less than 6 days remain.\n */\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type { UserInfo } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { AuthSessionsRepo } from \"../db/repos/auth-sessions.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport { hashPassword, verifyPassword } from \"./password.js\";\n\nexport const MIN_PASSWORD_LENGTH = 8;\n\n/** Built-in admin: user_id and initial password (matches the README and login-page hint). */\nexport const ADMIN_USER_ID = \"admin\";\nexport const ADMIN_INITIAL_PASSWORD = \"penguin-2026\";\n\nfunction sha256Hex(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function toUserInfo(row: UserRow): UserInfo {\n return {\n userId: row.userId,\n isAdmin: row.isAdmin,\n passwordIsInitial: row.passwordIsInitial,\n createdAt: row.createdAt,\n };\n}\n\nexport interface AuthServiceDeps {\n users: UsersRepo;\n authSessions: AuthSessionsRepo;\n /** Provisions the initial Project at signup (injected by project-service, to avoid a circular dependency). */\n provisionInitialProject: (user: UserRow, isAdmin: boolean) => Promise<void>;\n sessionTtlMs: number;\n sessionRenewMs: number;\n now?: () => Date;\n}\n\nexport class AuthService {\n private readonly now: () => Date;\n\n constructor(private readonly deps: AuthServiceDeps) {\n this.now = deps.now ?? (() => new Date());\n }\n\n /**\n * Startup seeding (idempotent): creates the built-in admin and adopts\n * default_project when the users table is empty; if the initial Project fails,\n * the user row is rolled back and the server retries on next startup.\n */\n async seedAdmin(): Promise<void> {\n if (this.deps.users.count() > 0) return;\n const user: UserRow = {\n userId: ADMIN_USER_ID,\n passwordHash: await hashPassword(ADMIN_INITIAL_PASSWORD),\n isAdmin: true,\n passwordIsInitial: true,\n createdAt: this.now().toISOString(),\n };\n this.deps.users.insert(user);\n try {\n await this.deps.provisionInitialProject(user, true);\n } catch (err) {\n this.deps.users.delete(user.userId);\n throw err;\n }\n }\n\n async login(userId: string, password: string): Promise<{ user: UserInfo; token: string }> {\n const row = this.deps.users.findById(userId);\n const ok = row !== null && (await verifyPassword(password, row.passwordHash));\n if (!row || !ok) {\n throw new HttpError(401, \"invalid_credentials\", \"Incorrect username or password.\");\n }\n this.deps.authSessions.deleteExpired(this.now().toISOString());\n return { user: toUserInfo(row), token: this.issueSession(row.userId) };\n }\n\n /** Self password change (user settings): validates the old password, and on success clears the initial-password flag; the current session remains valid. */\n async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void> {\n const row = this.deps.users.findById(userId);\n if (!row || !(await verifyPassword(oldPassword, row.passwordHash))) {\n throw new HttpError(400, \"password_mismatch\", \"Current password is incorrect.\");\n }\n if (newPassword.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"Password must be at least 8 characters.\");\n }\n this.deps.users.updatePassword(userId, await hashPassword(newPassword), false);\n }\n\n logout(token: string): void {\n this.deps.authSessions.delete(sha256Hex(token));\n }\n\n /** Validates the cookie token: returns null if expired/unknown; sliding renewal once less than 6 days remain. */\n authenticate(token: string): UserRow | null {\n const tokenHash = sha256Hex(token);\n const session = this.deps.authSessions.findByTokenHash(tokenHash);\n if (!session) return null;\n const now = this.now();\n const expiresAt = Date.parse(session.expiresAt);\n if (!(expiresAt > now.getTime())) {\n this.deps.authSessions.delete(tokenHash);\n return null;\n }\n if (expiresAt - now.getTime() < this.deps.sessionRenewMs) {\n this.deps.authSessions.touch(\n tokenHash,\n new Date(now.getTime() + this.deps.sessionTtlMs).toISOString(),\n );\n }\n return this.deps.users.findById(session.userId);\n }\n\n private issueSession(userId: string): string {\n const token = randomBytes(32).toString(\"base64url\");\n const now = this.now();\n this.deps.authSessions.insert({\n tokenHash: sha256Hex(token),\n userId,\n createdAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + this.deps.sessionTtlMs).toISOString(),\n });\n return token;\n }\n}\n","/**\n * Password hashing (slow-hash storage).\n *\n * Uses node:crypto's scrypt (built-in, no extra dependency, meets the same\n * slow-hash requirement as bcrypt/argon2). Storage format:\n * `scrypt$N$r$p$<salt b64>$<hash b64>` — parameters are stored alongside the hash,\n * so old hashes remain verifiable after future parameter tuning; comparison uses\n * timingSafeEqual to guard against timing side-channels.\n */\nimport { randomBytes, scrypt, timingSafeEqual } from \"node:crypto\";\n\nconst SCRYPT_N = 16384;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\nconst SALT_BYTES = 16;\nconst KEY_BYTES = 64;\n\nfunction scryptAsync(\n password: string,\n salt: Buffer,\n keyLen: number,\n n: number,\n r: number,\n p: number,\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keyLen, { N: n, r, p, maxmem: 128 * 1024 * 1024 }, (err, key) => {\n if (err) reject(err);\n else resolve(key);\n });\n });\n}\n\n/** Generates a password hash in `scrypt$N$r$p$salt$hash` format. */\nexport async function hashPassword(password: string): Promise<string> {\n const salt = randomBytes(SALT_BYTES);\n const key = await scryptAsync(password, salt, KEY_BYTES, SCRYPT_N, SCRYPT_R, SCRYPT_P);\n return [\n \"scrypt\",\n String(SCRYPT_N),\n String(SCRYPT_R),\n String(SCRYPT_P),\n salt.toString(\"base64\"),\n key.toString(\"base64\"),\n ].join(\"$\");\n}\n\n/** Verifies a password; returns false if the stored string has an invalid format (never throws, so the login path can uniformly treat it as a credential error). */\nexport async function verifyPassword(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(\"$\");\n if (parts.length !== 6 || parts[0] !== \"scrypt\") return false;\n const n = Number(parts[1]);\n const r = Number(parts[2]);\n const p = Number(parts[3]);\n if (!Number.isInteger(n) || !Number.isInteger(r) || !Number.isInteger(p)) return false;\n let salt: Buffer;\n let expected: Buffer;\n try {\n salt = Buffer.from(parts[4]!, \"base64\");\n expected = Buffer.from(parts[5]!, \"base64\");\n } catch {\n return false;\n }\n if (salt.length === 0 || expected.length === 0) return false;\n let actual: Buffer;\n try {\n actual = await scryptAsync(password, salt, expected.length, n, r, p);\n } catch {\n return false;\n }\n return actual.length === expected.length && timingSafeEqual(actual, expected);\n}\n","/**\n * Admin user-backend routes: only the built-in admin can use these (403 for non-admins).\n * GET|POST /api/admin/users, POST /api/admin/users/:userId/password, DELETE /api/admin/users/:userId.\n */\nimport { Hono } from \"hono\";\nimport type { AdminUserCreateResponse, AdminUsersResponse } from \"../../api/types.js\";\nimport { HttpError } from \"../errors.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { pathParam, readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function adminUsersRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.use(\"*\", async (c, next) => {\n if (!c.var.user.isAdmin) {\n throw new HttpError(403, \"admin_required\", \"Only an admin can perform this operation.\");\n }\n await next();\n });\n\n app.get(\"/\", (c) => {\n return c.json({ users: deps.adminService.listUsers() } satisfies AdminUsersResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const password = requireString(body, \"password\", { label: \"password\" });\n const user = await deps.adminService.createUser(userId, password);\n return c.json({ user } satisfies AdminUserCreateResponse, 201);\n });\n\n app.post(\"/:userId/password\", async (c) => {\n const body = await readJson(c);\n const password = requireString(body, \"password\", { label: \"password\" });\n await deps.adminService.resetPassword(pathParam(c, \"userId\"), password);\n return c.body(null, 204);\n });\n\n app.delete(\"/:userId\", async (c) => {\n await deps.adminService.deleteUser(pathParam(c, \"userId\"));\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Hand-rolled request body validation helpers (fields follow TypeScript types;\n * this adds a runtime safety net).\n *\n * No validation library: each helper checks one basic shape, throwing a 400 HttpError on failure.\n */\nimport type { Context } from \"hono\";\nimport { isValidId } from \"@prismshadow/penguin-core\";\nimport { HttpError } from \"./errors.js\";\n\nexport function badRequest(message: string): HttpError {\n return new HttpError(400, \"bad_request\", message);\n}\n\n/**\n * Get a path parameter (under sub-route mounting, hono infers string | undefined; the\n * route guarantees presence at runtime — treat a defensive missing value as 404).\n */\nexport function pathParam(c: Context, name: string): string {\n const v = c.req.param(name);\n if (v === undefined || v === \"\") {\n throw new HttpError(404, \"not_found\", \"Missing path parameter.\");\n }\n return v;\n}\n\n/**\n * Get a path parameter and validate the id (alphanumeric, underscore, and hyphen\n * only, to prevent path traversal). Hono decodes URL-encoded `%2F` into a single path\n * parameter; an id containing `/` or `..` passed straight into path construction could\n * escape the resource directory (cross-Project privilege escalation). So validate right\n * after reading the value — any invalid id is rejected with 404 (not leaking resource\n * existence), before any service-layer or path-construction code runs.\n */\nexport function requireValidId(c: Context, name: string): string {\n const v = pathParam(c, name);\n if (!isValidId(v)) {\n throw new HttpError(404, \"not_found\", \"Resource does not exist or you do not have access.\");\n }\n return v;\n}\n\n/** Parse a positive-integer path parameter (e.g. Trace file index). */\nexport function positiveIntParam(c: Context, name: string): number {\n // Match digits only: Number.parseInt would accept trailing garbage (\"12abc\" -> 12).\n const raw = pathParam(c, name);\n if (!/^\\d+$/.test(raw)) throw badRequest(`${name} must be a positive integer.`);\n const v = Number.parseInt(raw, 10);\n // isSafeInteger (not isInteger) also rejects overlong indices like \"99999999999999999999\"\n // that would otherwise parse to an imprecise float (1e20).\n if (!Number.isSafeInteger(v) || v < 1) throw badRequest(`${name} must be a positive integer.`);\n return v;\n}\n\n/** Parse Trace pagination query params: offset >= 0 (default 0), limit 1-1000 (default 200). */\nexport function paginationQuery(c: Context): { offset: number; limit: number } {\n const offset = Number.parseInt(c.req.query(\"offset\") ?? \"0\", 10);\n const limit = Number.parseInt(c.req.query(\"limit\") ?? \"200\", 10);\n if (!Number.isInteger(offset) || offset < 0)\n throw badRequest(\"offset must be a non-negative integer.\");\n if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {\n throw badRequest(\"limit must be an integer between 1 and 1000.\");\n }\n return { offset, limit };\n}\n\n/**\n * Parse OPTIONAL list paging query params: both absent = null (caller returns the full\n * list — the pre-paging behavior stays intact for existing callers). When paging, `limit`\n * is required (1-1000) and `offset` optional (>= 0, default 0) — an offset alone would\n * silently return the full list shifted, which no caller ever means.\n */\nexport function optionalPagingQuery(c: Context): { offset: number; limit: number } | null {\n const rawLimit = c.req.query(\"limit\");\n const rawOffset = c.req.query(\"offset\");\n if (rawLimit === undefined && rawOffset === undefined) return null;\n if (rawLimit === undefined) throw badRequest(\"offset requires limit.\");\n const limit = Number.parseInt(rawLimit, 10);\n if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {\n throw badRequest(\"limit must be an integer between 1 and 1000.\");\n }\n const offset = Number.parseInt(rawOffset ?? \"0\", 10);\n if (!Number.isInteger(offset) || offset < 0) {\n throw badRequest(\"offset must be a non-negative integer.\");\n }\n return { offset, limit };\n}\n\n/** Read the JSON request body (parse failure / non-object -> 400). */\nexport async function readJson(c: Context): Promise<Record<string, unknown>> {\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n throw badRequest(\"Request body must be valid JSON.\");\n }\n if (body === null || typeof body !== \"object\" || Array.isArray(body)) {\n throw badRequest(\"Request body must be a JSON object.\");\n }\n return body as Record<string, unknown>;\n}\n\nexport interface StringRule {\n minLen?: number;\n maxLen?: number;\n pattern?: RegExp;\n /** Display name for the field in error messages (defaults to key). */\n label?: string;\n}\n\nexport function requireString(\n obj: Record<string, unknown>,\n key: string,\n rule: StringRule = {},\n): string {\n const v = obj[key];\n const label = rule.label ?? key;\n if (typeof v !== \"string\") throw badRequest(`${label} must be a string.`);\n if (rule.minLen !== undefined && v.length < rule.minLen) {\n throw badRequest(`${label} must be at least ${rule.minLen} characters.`);\n }\n if (rule.maxLen !== undefined && v.length > rule.maxLen) {\n throw badRequest(`${label} must be at most ${rule.maxLen} characters.`);\n }\n if (rule.pattern !== undefined && !rule.pattern.test(v)) {\n throw badRequest(`${label} has an invalid format.`);\n }\n return v;\n}\n\nexport function optionalString(\n obj: Record<string, unknown>,\n key: string,\n rule: StringRule = {},\n): string | undefined {\n if (obj[key] === undefined) return undefined;\n return requireString(obj, key, rule);\n}\n\nexport function requireEnum<T extends string>(\n obj: Record<string, unknown>,\n key: string,\n values: readonly T[],\n label = key,\n): T {\n const v = obj[key];\n if (typeof v !== \"string\" || !(values as readonly string[]).includes(v)) {\n throw badRequest(`${label} must be one of ${values.join(\" / \")}.`);\n }\n return v as T;\n}\n\nexport function optionalEnum<T extends string>(\n obj: Record<string, unknown>,\n key: string,\n values: readonly T[],\n label = key,\n): T | undefined {\n if (obj[key] === undefined) return undefined;\n return requireEnum(obj, key, values, label);\n}\n\nexport interface NumberRule {\n /** Require positive or -1 (Agent runtime parameter convention: >0 active, -1 disabled). */\n positiveOrMinusOne?: boolean;\n /** Require non-negative. */\n nonNegative?: boolean;\n /** Require integer. */\n integer?: boolean;\n label?: string;\n}\n\nexport function optionalNumber(\n obj: Record<string, unknown>,\n key: string,\n rule: NumberRule = {},\n): number | undefined {\n const v = obj[key];\n if (v === undefined) return undefined;\n const label = rule.label ?? key;\n if (typeof v !== \"number\" || !Number.isFinite(v)) throw badRequest(`${label} must be a number.`);\n if (rule.integer && !Number.isInteger(v)) throw badRequest(`${label} must be an integer.`);\n if (rule.positiveOrMinusOne && !(v > 0 || v === -1)) {\n throw badRequest(`${label} must be greater than 0 or equal to -1.`);\n }\n if (rule.nonNegative && v < 0) throw badRequest(`${label} must not be negative.`);\n return v;\n}\n\nexport function optionalBoolean(\n obj: Record<string, unknown>,\n key: string,\n label = key,\n): boolean | undefined {\n const v = obj[key];\n if (v === undefined) return undefined;\n if (typeof v !== \"boolean\") throw badRequest(`${label} must be a boolean.`);\n return v;\n}\n\n/** Validate a yyyy-mm-dd query parameter (defaults to undefined). */\nexport function optionalDateParam(value: string | undefined, label: string): string | undefined {\n if (value === undefined || value === \"\") return undefined;\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value);\n // The shape check alone accepts impossible dates (2026-13-40, 2026-02-30); verify it's a real\n // calendar day by round-tripping through UTC (which never rolls over into an adjacent month).\n if (m) {\n const [, y, mo, d] = m;\n const dt = new Date(`${value}T00:00:00Z`);\n if (\n !Number.isNaN(dt.getTime()) &&\n dt.getUTCFullYear() === Number(y) &&\n dt.getUTCMonth() + 1 === Number(mo) &&\n dt.getUTCDate() === Number(d)\n ) {\n return value;\n }\n }\n throw badRequest(`${label} must be a valid date in YYYY-MM-DD format.`);\n}\n","/**\n * Auth routes: POST /api/auth/login | logout.\n * No self-registration: users are created by an admin in the user backend (/api/admin/users).\n * Login issues a cookie session; logout deletes the server-side session and clears the cookie.\n */\nimport { Hono } from \"hono\";\nimport { deleteCookie, getCookie, setCookie } from \"hono/cookie\";\nimport type { AuthResponse } from \"../../api/types.js\";\nimport { SESSION_COOKIE } from \"../../auth/middleware.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Session cookie attributes: HttpOnly, SameSite=Lax, 7 days. */\nfunction cookieOptions(c: { req: { header(name: string): string | undefined } }) {\n return {\n httpOnly: true,\n sameSite: \"Lax\" as const,\n path: \"/\",\n maxAge: 7 * 24 * 60 * 60,\n // Add Secure when the reverse proxy declares https.\n ...(c.req.header(\"x-forwarded-proto\") === \"https\" ? { secure: true } : {}),\n };\n}\n\nexport function authRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.post(\"/login\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const password = requireString(body, \"password\", { label: \"password\" });\n const { user, token } = await deps.authService.login(userId, password);\n setCookie(c, SESSION_COOKIE, token, cookieOptions(c));\n return c.json({ user } satisfies AuthResponse);\n });\n\n app.post(\"/logout\", (c) => {\n const token = getCookie(c, SESSION_COOKIE);\n if (token) deps.authService.logout(token);\n deleteCookie(c, SESSION_COOKIE, { path: \"/\" });\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Current-user routes: GET /api/me, PUT /api/me/password, GET|PUT /api/me/prefs.\n * ui_prefs is free-form JSON (theme / lastProjectId / credentialGuideSeen, etc.): GET reads\n * it whole, PUT shallow-merges (PATCH semantics) — several independent writers each write\n * their own fields without clobbering each other.\n */\nimport { Hono } from \"hono\";\nimport type { MeResponse, PrefsResponse, UiPrefs } from \"../../api/types.js\";\nimport { toUserInfo } from \"../../auth/service.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { resolvePreviewTarget } from \"../../services/preview-token.js\";\n\nexport function meRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n // previewIsolated depends on the host this request came in on, so it is computed\n // here rather than stored: the same server answers on 127.0.0.1, localhost and\n // possibly a LAN address, and only the first two have a loopback counterpart.\n const target = resolvePreviewTarget(\n c.req.url,\n c.req.header(\"host\"),\n deps.config.previewOrigin,\n deps.config,\n );\n return c.json({\n user: toUserInfo(c.var.user),\n previewIsolated: target !== null,\n } satisfies MeResponse);\n });\n\n // Self-service password change (user settings): validates the old password; on success, the initial-password prompt disappears from GET /api/me.\n app.put(\"/password\", async (c) => {\n const body = await readJson(c);\n const oldPassword = requireString(body, \"oldPassword\", { label: \"oldPassword\" });\n const newPassword = requireString(body, \"newPassword\", { label: \"newPassword\" });\n await deps.authService.changePassword(c.var.user.userId, oldPassword, newPassword);\n return c.body(null, 204);\n });\n\n app.get(\"/prefs\", (c) => {\n const raw = deps.prefsRepo.get(c.var.user.userId);\n let prefs: UiPrefs = {};\n if (raw !== null) {\n try {\n prefs = JSON.parse(raw) as UiPrefs;\n } catch {\n prefs = {}; // Corrupted prefs fall back to an empty object\n }\n }\n return c.json({ prefs } satisfies PrefsResponse);\n });\n\n // PATCH semantics: the request body is **shallow-merged** into existing prefs, not a\n // full replace. prefs has several independent writers (lastProjectId /\n // credentialGuideSeen, etc., each writing their own field); a full replace would wipe\n // out each other's fields — e.g. writing lastProjectId when switching Projects would\n // clear credentialGuideSeen, breaking the \"show onboarding once ever\" guarantee.\n app.put(\"/prefs\", async (c) => {\n const body = await readJson(c);\n const raw = deps.prefsRepo.get(c.var.user.userId);\n let current: UiPrefs = {};\n if (raw !== null) {\n try {\n current = JSON.parse(raw) as UiPrefs;\n } catch {\n current = {}; // Corrupted prefs fall back to an empty object (consistent with GET).\n }\n }\n const merged = { ...current, ...(body as UiPrefs) };\n deps.prefsRepo.set(c.var.user.userId, JSON.stringify(merged));\n return c.json({ prefs: merged } satisfies PrefsResponse);\n });\n\n return app;\n}\n","/**\n * Signed tokens for Workspace HTML preview on a separate origin.\n *\n * The preview origin deliberately differs from the App origin (see\n * design/specs/05-ARCHITECTURE.md § \"Workspace 文件预览\"), so it never receives the\n * session cookie — cookies are keyed by host and ignore port, which is why the two\n * must differ by hostname and not merely by port. Authorization therefore travels in\n * the URL as a short-lived HMAC token instead.\n *\n * The token binds three things: the Session whose Workspace may be read, the host the\n * preview must be served from, and an expiry. Binding the host is what keeps this from\n * becoming a same-origin XSS hole — the same process also answers on the App origin, so\n * without that check `/preview/<token>/index.html` would happily execute Agent-written\n * HTML with the App's cookies. See previewRoutes for the enforcement.\n *\n * The signing secret is generated per process: tokens are short-lived anyway, so losing\n * them across a restart costs nothing and there is no secret to persist or rotate.\n */\nimport { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\n\n/** Preview tokens live just long enough to open a page and let it pull its subresources. */\nexport const PREVIEW_TOKEN_TTL_MS = 10 * 60 * 1000;\n\nexport interface PreviewTokenPayload {\n /** Session whose Workspace subtree the token grants read access to. */\n sessionId: string;\n /** Host (no port) the preview must be served from; anything else is refused. */\n host: string;\n /** Expiry, epoch milliseconds. */\n expiresAt: number;\n}\n\n/** Wire form: `<base64url(json)>.<base64url(hmac)>`. */\ntype Signer = {\n sign(payload: PreviewTokenPayload): string;\n verify(token: string): PreviewTokenPayload | null;\n};\n\nfunction b64url(buf: Buffer): string {\n return buf.toString(\"base64url\");\n}\n\nexport function createPreviewTokenSigner(secret: Buffer = randomBytes(32)): Signer {\n const mac = (body: string): Buffer => createHmac(\"sha256\", secret).update(body).digest();\n\n return {\n sign(payload) {\n const body = b64url(Buffer.from(JSON.stringify(payload), \"utf8\"));\n return `${body}.${b64url(mac(body))}`;\n },\n\n verify(token) {\n const dot = token.indexOf(\".\");\n if (dot <= 0 || dot === token.length - 1) return null;\n const body = token.slice(0, dot);\n const provided = Buffer.from(token.slice(dot + 1), \"base64url\");\n const expected = mac(body);\n // Length check first: timingSafeEqual throws on a length mismatch.\n if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) return null;\n\n let payload: PreviewTokenPayload;\n try {\n payload = JSON.parse(\n Buffer.from(body, \"base64url\").toString(\"utf8\"),\n ) as PreviewTokenPayload;\n } catch {\n return null;\n }\n if (\n typeof payload?.sessionId !== \"string\" ||\n typeof payload?.host !== \"string\" ||\n typeof payload?.expiresAt !== \"number\"\n ) {\n return null;\n }\n if (Date.now() >= payload.expiresAt) return null;\n return payload;\n },\n };\n}\n\nexport type PreviewTokenSigner = ReturnType<typeof createPreviewTokenSigner>;\n\n/** Host header without its port (IPv6 literals keep their brackets). */\nexport function hostOnly(hostHeader: string): string {\n const trimmed = hostHeader.trim();\n if (trimmed.startsWith(\"[\")) {\n const end = trimmed.indexOf(\"]\");\n return end === -1 ? trimmed : trimmed.slice(0, end + 1);\n }\n const colon = trimmed.lastIndexOf(\":\");\n return colon === -1 ? trimmed : trimmed.slice(0, colon);\n}\n\n/**\n * The loopback counterpart of the host the App is being used on: `127.0.0.1` and\n * `localhost` are distinct hosts for cookie purposes, so serving previews from the\n * other one isolates them with no extra port, no DNS, and nothing to configure.\n * Returns null for any other host (LAN IP, real domain) — those need an explicit\n * PENGUIN_PREVIEW_ORIGIN, and the caller degrades to the same-origin sandbox.\n */\nexport function loopbackCounterpart(host: string): string | null {\n const h = host.toLowerCase();\n if (h === \"127.0.0.1\") return \"localhost\";\n if (h === \"localhost\") return \"127.0.0.1\";\n // ::1 shares localhost's cookie jar semantics poorly across browsers; send it to the\n // IPv4 literal, which is unambiguous.\n if (h === \"[::1]\") return \"127.0.0.1\";\n return null;\n}\n\n/**\n * When the server is bound to a loopback host, the App is canonicalized onto one loopback\n * name and previews are served from the other. Fixing the assignment — rather than serving\n * previews from the counterpart of whichever name the caller happens to type — is what\n * keeps the preview host free of a session cookie. The same process answers on both names,\n * so if the App could be reached (and logged into) on the preview name too, Agent-written\n * preview HTML would call `/api` same-origin and regain a full App session. `app` is the\n * only name that serves the App and sets/accepts the session cookie; `preview` serves\n * `/preview/*` and nothing else (enforced by the canonical-host guard in app.ts).\n *\n * Null for non-loopback binds (wildcard, LAN IP, a bare `::1`): they can't offer a loopback\n * preview the IPv6 companion listener actually serves, so they must set\n * PENGUIN_PREVIEW_ORIGIN and otherwise fall back to the same-origin sandbox.\n */\nexport function loopbackHostRoles(bindHost: string): { app: string; preview: string } | null {\n const h = bindHost.trim().toLowerCase();\n if (h !== \"127.0.0.1\" && h !== \"localhost\") return null;\n const app = \"localhost\";\n const preview = loopbackCounterpart(app);\n if (!preview) return null;\n return { app, preview };\n}\n\n/**\n * The host:port this request was addressed to. Prefers the Host header (what the browser\n * actually sent, and what decides the origin), falling back to the request URL's\n * authority — some server runtimes and test harnesses build a Request from a full URL\n * without materializing a Host header.\n */\nexport function requestAuthority(requestUrl: string, hostHeader: string | undefined): string {\n const header = (hostHeader ?? \"\").trim();\n if (header !== \"\") return header;\n try {\n return new URL(requestUrl).host;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Where previews for this request should be served from: the configured origin when\n * PENGUIN_PREVIEW_ORIGIN is set, otherwise the loopback counterpart of the host the\n * caller is using. Null when neither applies — or when the configured origin's host is\n * the request's own host (no boundary) — and the caller degrades to the same-origin\n * sandbox rather than silently serving unisolated content.\n *\n * The port comes from **this server's own binding**, never from the request. Those differ\n * in development: the SPA is served by Vite on its own port and only proxies `/api`, so a\n * preview URL built from the browser's current port would point at a port where nothing\n * serves `/preview` — connection refused, or a Vite 404. In production the two are the\n * same and this is a no-op. Only a loopback-name bind (`127.0.0.1` / `localhost`) offers a\n * loopback preview — it is the bind where the IPv6 companion listener runs and where the\n * App is canonicalized (see `loopbackHostRoles`); any other bind (wildcard, LAN IP) has no\n * reachable counterpart and falls back.\n */\nexport function resolvePreviewTarget(\n requestUrl: string,\n hostHeader: string | undefined,\n configuredOrigin: string | null,\n serverBind: { host: string; port: number },\n): { origin: string; host: string } | null {\n const requestHost = hostOnly(requestAuthority(requestUrl, hostHeader)).toLowerCase();\n if (configuredOrigin) {\n const url = new URL(configuredOrigin);\n // A configured origin that lands on the very host this request is using is no\n // boundary at all: \"isolated\" previews would be same-origin with the App, and the\n // allow-same-origin iframe the UI mounts for them would hand Agent HTML the App's\n // origin. Such a misconfiguration degrades to null — previewIsolated reports false\n // and the redirect falls back to the same-origin sandbox — instead of claiming an\n // isolation that does not exist. Host-only comparison on purpose: cookies ignore\n // ports, so a port difference would not separate anything either.\n if (url.hostname.toLowerCase() === requestHost) return null;\n return { origin: url.origin, host: url.hostname };\n }\n if (requestHost === \"\") return null;\n // The loopback counterpart differs from the request host by construction, so the\n // same-host guard above is only needed for the configured branch.\n const counterpart = loopbackCounterpart(requestHost);\n if (!counterpart) return null;\n if (!loopbackHostRoles(serverBind.host)) return null;\n\n let protocol: string;\n try {\n protocol = new URL(requestUrl).protocol;\n } catch {\n protocol = \"http:\";\n }\n const suffix =\n (protocol === \"http:\" && serverBind.port === 80) ||\n (protocol === \"https:\" && serverBind.port === 443)\n ? \"\"\n : `:${serverBind.port}`;\n return { origin: `${protocol}//${counterpart}${suffix}`, host: counterpart };\n}\n","/**\n * User-level server event stream: GET /api/events (SSE user channel).\n * Carries cross-Session notifications (reserved for automated tasks); sends a `hello` handshake event on connect.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { sseEndpoint } from \"../sse.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** The user channel's key in ChannelHub. */\nexport function userChannelKey(userId: string): string {\n return `user:${userId}`;\n}\n\nexport function eventsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n const channel = deps.channels.get(userChannelKey(c.var.user.userId));\n return sseEndpoint(c, channel, { initialEvents: [{ type: \"hello\" }] });\n });\n\n return app;\n}\n","/**\n * SSE endpoint adapter:\n * writes the runtime/channel event stream as an SSE response, shared by both the Session\n * channel and the user channel.\n *\n * - Response headers: text/event-stream, no-cache, `X-Accel-Buffering: no` (disables\n * buffering on reverse proxies);\n * - Heartbeat: writes a `: ping` comment line every 20s; the connection is torn down on write failure;\n * - Replay protocol: a fresh subscription without Last-Event-ID does not replay the buffer\n * (history is served by the messages endpoint) — it only sends the initial events the\n * caller supplied (pending approvals / hello). With a Last-Event-ID that hits the buffer,\n * replay resumes from there; on a miss, `resync_required` is sent first, then the\n * connection continues.\n * Docs: /docs/server-api § \"Delivery Guarantees\".\n */\nimport type { Context } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { ServerEvent } from \"../api/types.js\";\nimport type { Channel, ChannelEvent, ChannelListener } from \"../runtime/channel.js\";\n\nconst HEARTBEAT_MS = 20_000;\n\nexport interface SseEndpointOptions {\n /** Initial server events to send privately after the subscription is established (in order): pending-approval replay / user channel hello. */\n initialEvents?: ServerEvent[];\n}\n\n/** Stream out a Channel as an SSE response. */\nexport function sseEndpoint(c: Context, channel: Channel, opts: SseEndpointOptions = {}): Response {\n const lastEventIdHeader = c.req.header(\"Last-Event-ID\");\n c.header(\"X-Accel-Buffering\", \"no\");\n c.header(\"Cache-Control\", \"no-cache\");\n\n return streamSSE(c, async (stream) => {\n let closed = false;\n let finish: () => void = () => {};\n const done = new Promise<void>((resolve) => {\n finish = () => {\n if (closed) return;\n closed = true;\n resolve();\n };\n });\n\n // Write serialization: SSE events must be written fully and in order; any write failure tears down the connection.\n let chain: Promise<void> = Promise.resolve();\n const enqueue = (write: () => Promise<unknown>): void => {\n chain = chain\n .then(async () => {\n if (!closed) await write();\n })\n .catch(() => finish());\n };\n const listener: ChannelListener = (evt: ChannelEvent) => {\n enqueue(() =>\n stream.writeSSE({\n data: evt.data,\n // Event id is an opaque string generated by the channel (`<epoch>-<seq>`), passed through as-is.\n id: evt.id,\n ...(evt.event !== undefined ? { event: evt.event } : {}),\n }),\n );\n };\n\n const unsubscribe = channel.subscribe(listener);\n // Subscribe first (to avoid dropping events in a race with broadcasts), then replay\n // synchronously — event order: buffered replay (or resync_required) -> initial events\n // (task_state snapshot / pending approvals / hello) -> live stream.\n if (lastEventIdHeader !== undefined) {\n const replay = channel.replayAfter(lastEventIdHeader);\n if (!replay.hit) {\n const resync: ServerEvent = { type: \"resync_required\" };\n channel.sendTo(listener, resync, \"server_event\");\n } else {\n for (const evt of replay.events) listener(evt);\n }\n }\n for (const event of opts.initialEvents ?? []) {\n channel.sendTo(listener, event, \"server_event\");\n }\n\n const heartbeat = setInterval(() => {\n enqueue(() => stream.write(\": ping\\n\\n\"));\n }, HEARTBEAT_MS);\n\n stream.onAbort(() => finish());\n try {\n await done;\n } finally {\n clearInterval(heartbeat);\n unsubscribe();\n }\n });\n}\n","/**\n * Project routes: GET|POST /api/projects, DELETE /api/projects/:p.\n */\nimport { Hono } from \"hono\";\nimport type { ProjectCreateResponse, ProjectsResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { optionalString, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function projectsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projects = await deps.projectService.listProjects(c.var.user.userId);\n return c.json({ projects } satisfies ProjectsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const projectId = requireString(body, \"projectId\", { label: \"projectId\" });\n const name = optionalString(body, \"name\", { minLen: 1, maxLen: 100, label: \"name\" });\n const project = await deps.projectService.createProject(c.var.user, projectId, name);\n return c.json({ project } satisfies ProjectCreateResponse, 201);\n });\n\n app.delete(\"/:projectId\", async (c) => {\n // Defensive id validation (FD-4): deleteProject constructs the project directory path and recursively deletes it.\n await deps.projectService.deleteProject(c.var.user.userId, requireValidId(c, \"projectId\"));\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Member authorization routes:\n * GET|POST /api/projects/:p/members, DELETE /api/projects/:p/members/:userId.\n * Reading requires access; adding/removing is owner-only (validated inside the service).\n */\nimport { Hono } from \"hono\";\nimport type { MemberAddResponse, MembersResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { pathParam, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function membersRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n // Defensive id validation (FD-4).\n const members = deps.projectService.listMembers(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n );\n return c.json({ members } satisfies MembersResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const member = deps.projectService.addMember(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n userId,\n );\n return c.json({ member } satisfies MemberAddResponse, 201);\n });\n\n app.delete(\"/:userId\", (c) => {\n deps.projectService.removeMember(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n pathParam(c, \"userId\"),\n );\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Model & credential config routes:\n * GET|PUT /api/projects/:p/models, POST /api/projects/:p/models/test (the model reference\n * `(provider, modelId)` is sent as a pair in the request body, avoiding URL-encoding\n * issues). Any member can read (api_key is masked); only the owner can modify or test.\n */\nimport { Hono } from \"hono\";\nimport type {\n ModelRefDto,\n ModelsUpdateRequest,\n ModelTestRequest,\n ModelUpdateEntry,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Validate a paired reference object ({ provider, modelId }); shape mismatch throws 400. */\nfunction parseRef(value: unknown, label: string): ModelRefDto {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw badRequest(`${label} must be a { provider, modelId } object.`);\n }\n const r = value as Record<string, unknown>;\n return {\n provider: requireString(r, \"provider\", { minLen: 1, maxLen: 64, label: `${label}.provider` }),\n modelId: requireString(r, \"modelId\", { minLen: 1, maxLen: 200, label: `${label}.modelId` }),\n };\n}\n\n/** Validate the PUT request body and shape it into a ModelsUpdateRequest (rejects any shape errors). */\nfunction parseModelsUpdate(body: Record<string, unknown>): ModelsUpdateRequest {\n if (!Array.isArray(body.models)) throw badRequest(\"models must be an array.\");\n const models: ModelUpdateEntry[] = body.models.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`models[${i}] must be an object.`);\n }\n const m = item as Record<string, unknown>;\n const entry: ModelUpdateEntry = {\n provider: requireString(m, \"provider\", {\n minLen: 1,\n maxLen: 64,\n label: `models[${i}].provider`,\n }),\n modelId: requireString(m, \"modelId\", {\n minLen: 1,\n maxLen: 200,\n label: `models[${i}].modelId`,\n }),\n };\n if (m.displayName !== undefined) {\n if (typeof m.displayName !== \"string\" || m.displayName.length > 100) {\n throw badRequest(`models[${i}].displayName must be a string of at most 100 characters.`);\n }\n if (m.displayName) entry.displayName = m.displayName;\n }\n // A key change (either the provider group or the upstream id) goes through renamedFrom's paired old reference; unknown fields are ignored.\n if (m.renamedFrom !== undefined) {\n entry.renamedFrom = parseRef(m.renamedFrom, `models[${i}].renamedFrom`);\n }\n if (m.contextWindow !== undefined) {\n if (typeof m.contextWindow !== \"number\" || !(m.contextWindow > 0)) {\n throw badRequest(`models[${i}].contextWindow must be a positive number.`);\n }\n entry.contextWindow = m.contextWindow;\n }\n if (m.clientType !== undefined) {\n if (typeof m.clientType !== \"string\" || m.clientType.length > 64) {\n throw badRequest(`models[${i}].clientType must be a string of at most 64 characters.`);\n }\n // An empty string is treated as \"unspecified\", leaving AgentHub to infer it from modelId.\n if (m.clientType) entry.clientType = m.clientType;\n }\n if (m.vision !== undefined) {\n if (typeof m.vision !== \"boolean\") {\n throw badRequest(`models[${i}].vision must be a boolean.`);\n }\n entry.vision = m.vision;\n }\n if (m.maxTokens !== undefined) {\n if (typeof m.maxTokens !== \"number\" || !Number.isInteger(m.maxTokens) || m.maxTokens <= 0) {\n throw badRequest(`models[${i}].maxTokens must be a positive integer.`);\n }\n entry.maxTokens = m.maxTokens;\n }\n if (m.pricing !== undefined) {\n const p = m.pricing as Record<string, unknown>;\n if (p === null || typeof p !== \"object\" || Array.isArray(p)) {\n throw badRequest(`models[${i}].pricing must be an object.`);\n }\n for (const key of [\"cacheRead\", \"cacheWrite\", \"output\"] as const) {\n const v = p[key];\n if (typeof v !== \"number\" || !Number.isFinite(v) || v < 0) {\n throw badRequest(`models[${i}].pricing.${key} must be a non-negative number.`);\n }\n }\n entry.pricing = {\n cacheRead: p.cacheRead as number,\n cacheWrite: p.cacheWrite as number,\n output: p.output as number,\n };\n }\n if (m.apiKey !== undefined) {\n if (typeof m.apiKey !== \"string\" || m.apiKey.length === 0) {\n throw badRequest(`models[${i}].apiKey must be a non-empty string.`);\n }\n entry.apiKey = m.apiKey;\n }\n if (m.clearApiKey !== undefined) {\n if (typeof m.clearApiKey !== \"boolean\") {\n throw badRequest(`models[${i}].clearApiKey must be a boolean.`);\n }\n entry.clearApiKey = m.clearApiKey;\n }\n if (m.baseUrl !== undefined) {\n if (m.baseUrl !== null && typeof m.baseUrl !== \"string\") {\n throw badRequest(`models[${i}].baseUrl must be a string or null.`);\n }\n entry.baseUrl = m.baseUrl as string | null;\n }\n return entry;\n });\n const req: ModelsUpdateRequest = { models };\n if (body.defaultModel !== undefined) {\n req.defaultModel = parseRef(body.defaultModel, \"defaultModel\");\n }\n if (body.visionModel !== undefined) {\n req.visionModel = parseRef(body.visionModel, \"visionModel\");\n }\n return req;\n}\n\nexport function modelsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4).\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.projectConfigService.getModels(projectId));\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const req = parseModelsUpdate(await readJson(c));\n return c.json(await deps.projectConfigService.updateModels(projectId, req));\n });\n\n // Connectivity test (owner): the model reference `(provider, modelId)` is sent as a pair\n // in the request body; sends one minimal request using that model's config. May include\n // not-yet-saved apiKey / baseUrl / clientType — when the model isn't in the config yet\n // (adding a custom model), everything is taken from the request body.\n app.post(\"/test\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const body = await readJson(c);\n const req: ModelTestRequest = {\n provider: requireString(body, \"provider\", { minLen: 1, maxLen: 64 }),\n modelId: requireString(body, \"modelId\", { minLen: 1, maxLen: 200 }),\n };\n if (body.apiKey !== undefined) {\n if (typeof body.apiKey !== \"string\") throw badRequest(\"apiKey must be a string.\");\n if (body.apiKey) req.apiKey = body.apiKey;\n }\n if (body.clearApiKey !== undefined) {\n if (typeof body.clearApiKey !== \"boolean\") throw badRequest(\"clearApiKey must be a boolean.\");\n req.clearApiKey = body.clearApiKey;\n }\n if (body.speed !== undefined) {\n if (typeof body.speed !== \"boolean\") throw badRequest(\"speed must be a boolean.\");\n req.speed = body.speed;\n }\n // null = explicit clear (test against the draft, don't fall back to the stored value); empty string is treated as null.\n if (body.baseUrl !== undefined) {\n if (body.baseUrl !== null && typeof body.baseUrl !== \"string\") {\n throw badRequest(\"baseUrl must be a string or null.\");\n }\n req.baseUrl = body.baseUrl ? body.baseUrl : null;\n }\n if (body.clientType !== undefined) {\n if (typeof body.clientType !== \"string\") throw badRequest(\"clientType must be a string.\");\n if (body.clientType) req.clientType = body.clientType;\n }\n return c.json(await deps.projectConfigService.testModel(projectId, req));\n });\n\n return app;\n}\n","/**\n * Vault environment variable routes:\n * GET|PUT /api/projects/:p/agents/:a/vault (Agent-level, agent_state/.vault.toml).\n * Any member can read (values masked); only the owner can modify; 404 if the Agent doesn't exist.\n */\nimport { Hono } from \"hono\";\nimport type { VaultEntryUpdate, VaultUpdateRequest } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Validate the PUT request body and shape it into a VaultUpdateRequest (semantic checks like key-name rules live in the service layer). */\nfunction parseVaultUpdate(body: Record<string, unknown>): VaultUpdateRequest {\n if (!Array.isArray(body.entries)) throw badRequest(\"entries must be an array.\");\n const entries: VaultEntryUpdate[] = body.entries.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`entries[${i}] must be an object.`);\n }\n const e = item as Record<string, unknown>;\n const entry: VaultEntryUpdate = {\n key: requireString(e, \"key\", { minLen: 1, maxLen: 200, label: `entries[${i}].key` }),\n };\n if (e.value !== undefined) {\n if (typeof e.value !== \"string\" || e.value.length === 0) {\n throw badRequest(`entries[${i}].value must be a non-empty string.`);\n }\n entry.value = e.value;\n }\n return entry;\n });\n return { entries };\n}\n\nexport function vaultRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.agentConfigService.getVault(projectId, agentId));\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const req = parseVaultUpdate(await readJson(c));\n const res = await deps.agentConfigService.updateVault(projectId, agentId, req);\n // Effective-value semantics: no hot swap into a Task already in flight, but every\n // runtime built before this update is invalidated — the next Task on any Session\n // of this Agent re-resumes and picks up the new vault values.\n deps.manager.invalidateAgentRuntimes(projectId, agentId);\n return c.json(res);\n });\n\n return app;\n}\n","/**\n * Schedule routes:\n * GET|POST /api/projects/:p/agents/:a/schedules\n * GET|PUT|DELETE /api/projects/:p/agents/:a/schedules/:name (name is the file name)\n * Any member can read; only the owner can modify. The file is declarative intent:\n * POST/PUT fully replace the file, validation always goes through parseScheduleFile\n * (same rules as hand-edited files), and writes take effect immediately via reconciliation.\n */\nimport { createHash } from \"node:crypto\";\nimport { Hono } from \"hono\";\nimport { isValidId } from \"@prismshadow/penguin-core\";\nimport type { ScheduleItem, ScheduleStatus, SchedulesResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { HttpError } from \"../errors.js\";\nimport {\n badRequest,\n optionalString,\n readJson,\n requireString,\n requireValidId,\n} from \"../validate.js\";\nimport type { ScheduleDefinition } from \"../../runtime/schedule-file.js\";\nimport {\n latestSlotAt,\n nextSlotAfter,\n parseScheduleFile,\n slotInWindow,\n} from \"../../runtime/schedule-file.js\";\nimport type { ScheduleStateRow } from \"../../db/repos/schedules.js\";\nimport {\n deleteScheduleFile,\n readScheduleFile,\n serializeSchedule,\n validateScheduleModelRef,\n writeScheduleFile,\n} from \"../../runtime/schedule-store.js\";\n\n/** Validate and shape the POST/PUT request body into file fields (semantic validation is left to parseScheduleFile). */\nfunction parseUpsertBody(body: Record<string, unknown>): {\n prompt: string;\n enabled: boolean;\n startAt: string;\n period?: string;\n endAt?: string;\n sessionId?: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n} {\n if (typeof body.enabled !== \"boolean\") throw badRequest(\"enabled must be a boolean.\");\n const prompt = requireString(body, \"prompt\", { minLen: 1, maxLen: 100_000 });\n const startAt = requireString(body, \"startAt\", { minLen: 1, maxLen: 100 });\n const period = optionalString(body, \"period\", { minLen: 1, maxLen: 20 });\n const endAt = optionalString(body, \"endAt\", { minLen: 1, maxLen: 100 });\n const sessionId = optionalString(body, \"sessionId\", { minLen: 1, maxLen: 200 });\n const workspace = optionalString(body, \"workspace\", { minLen: 1, maxLen: 4096 });\n const modelId = optionalString(body, \"modelId\", { minLen: 1, maxLen: 200 });\n const provider = optionalString(body, \"provider\", { minLen: 1, maxLen: 64 });\n return {\n prompt,\n enabled: body.enabled,\n startAt,\n ...(period !== undefined ? { period } : {}),\n ...(endAt !== undefined ? { endAt } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n };\n}\n\n/** Next scheduled fire time: none when disabled/invalid/done/missed; an undigested due slot counts as-is. */\nfunction nextFireAt(\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n nowMs: number,\n): string | undefined {\n if (!def.enabled || state.invalidReason !== null) return undefined;\n if (def.periodMs === undefined && (state.firedOnce || state.missed)) return undefined;\n const due = latestSlotAt(def, nowMs);\n if (\n due !== null &&\n slotInWindow(def, due) &&\n (state.lastSlotMs === null || due > state.lastSlotMs)\n ) {\n return new Date(due).toISOString();\n }\n const next = nextSlotAfter(def, nowMs);\n return next !== null ? new Date(next).toISOString() : undefined;\n}\n\n/** Displayed status precedence: invalid > done/missed (one-shot) > expired > enabled flag. */\nfunction statusOf(def: ScheduleDefinition, state: ScheduleStateRow, nowMs: number): ScheduleStatus {\n if (state.invalidReason !== null) return \"invalid\";\n if (def.periodMs === undefined && state.firedOnce) return \"done\";\n if (def.periodMs === undefined && state.missed) return \"missed\";\n if (def.endAtMs !== undefined && nowMs > def.endAtMs) return \"expired\";\n return def.enabled ? \"active\" : \"disabled\";\n}\n\nfunction toItem(\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n queued: boolean,\n nowMs: number,\n): ScheduleItem {\n const next = nextFireAt(def, state, nowMs);\n return {\n name: def.name,\n prompt: def.prompt,\n enabled: def.enabled,\n startAt: def.startAt,\n ...(def.period !== undefined ? { period: def.period } : {}),\n ...(def.endAt !== undefined ? { endAt: def.endAt } : {}),\n ...(def.sessionId !== undefined ? { sessionId: def.sessionId } : {}),\n ...(def.workspace !== undefined ? { workspace: def.workspace } : {}),\n ...(def.modelId !== undefined ? { modelId: def.modelId } : {}),\n ...(def.provider !== undefined ? { provider: def.provider } : {}),\n status: statusOf(def, state, nowMs),\n ...(state.invalidReason !== null ? { invalidReason: state.invalidReason } : {}),\n ...(next !== undefined ? { nextFireAt: next } : {}),\n ...(state.lastFiredAt !== null ? { lastFiredAt: state.lastFiredAt } : {}),\n queued,\n ...(state.creatorUserId !== null ? { creatorUserId: state.creatorUserId } : {}),\n };\n}\n\n/** Schedule name in the path: same character rules as directories/files, validated before any path construction. */\nfunction requireScheduleName(raw: string | undefined): string {\n if (!raw || !isValidId(raw)) throw badRequest(\"Invalid schedule name.\");\n return raw;\n}\n\nexport function scheduleRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const { entries, invalid } = await deps.scheduler.listAgent(projectId, agentId);\n const nowMs = Date.now();\n const res: SchedulesResponse = {\n schedules: entries.map((e) => toItem(e.def, e.state, e.queued, nowMs)),\n invalidFiles: invalid,\n };\n return c.json(res);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const name = requireScheduleName(requireString(body, \"name\", { minLen: 1, maxLen: 100 }));\n if (await readScheduleFile(deps.config.root, projectId, agentId, name)) {\n throw new HttpError(409, \"schedule_exists\", `Schedule already exists: ${name}`);\n }\n await upsert(deps, c.var.user.userId, projectId, agentId, name, body);\n return c.json(await readItem(deps, projectId, agentId, name), 201);\n });\n\n app.get(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n const item = await readItem(deps, projectId, agentId, name);\n return c.json(item);\n });\n\n app.put(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n if (!(await readScheduleFile(deps.config.root, projectId, agentId, name))) {\n throw new HttpError(404, \"schedule_not_found\", `Schedule does not exist: ${name}`);\n }\n const body = await readJson(c);\n await upsert(deps, c.var.user.userId, projectId, agentId, name, body);\n return c.json(await readItem(deps, projectId, agentId, name));\n });\n\n app.delete(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n const removed = await deleteScheduleFile(deps.config.root, projectId, agentId, name);\n if (!removed)\n throw new HttpError(404, \"schedule_not_found\", `Schedule does not exist: ${name}`);\n deps.scheduler.dropEntry(projectId, agentId, name);\n return c.body(null, 204);\n });\n\n return app;\n}\n\n/** Write + register creator + reconcile immediately (API changes take effect right away). */\nasync function upsert(\n deps: AppDeps,\n userId: string,\n projectId: string,\n agentId: string,\n name: string,\n body: Record<string, unknown>,\n): Promise<void> {\n const fields = parseUpsertBody(body);\n const raw = serializeSchedule(fields);\n const parsed = parseScheduleFile(name, raw);\n if (!parsed.ok) throw badRequest(`Invalid schedule configuration: ${parsed.error}`);\n // At save time, verify the (provider, modelId) pair names a configured model (same rules as reconciliation) so we never persist a broken file.\n // The pairing rule itself is enforced by parseScheduleFile above, which rejects half a reference.\n const refError = await validateScheduleModelRef(deps.config.root, projectId, parsed.def);\n if (refError !== null) throw badRequest(`Invalid schedule configuration: ${refError}`);\n await writeScheduleFile(deps.config.root, projectId, agentId, name, raw);\n // Creator attribution: the API writer is the creator (falls back to the Project owner only for hand-edited files).\n deps.schedulesRepo.registerOrSync({\n projectId,\n agentId,\n name,\n startAtMs: parsed.def.startAtMs,\n defHash: createHash(\"sha1\").update(raw).digest(\"hex\"),\n creatorUserId: userId,\n });\n await deps.scheduler.reconcileAgent(projectId, agentId);\n}\n\nasync function readItem(\n deps: AppDeps,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<ScheduleItem> {\n const { entries, invalid } = await deps.scheduler.listAgent(projectId, agentId);\n const entry = entries.find((e) => e.def.name === name);\n if (entry) return toItem(entry.def, entry.state, entry.queued, Date.now());\n const bad = invalid.find((i) => i.name === name);\n if (bad) throw badRequest(`Invalid schedule file: ${bad.error}`);\n throw new HttpError(404, \"schedule_not_found\", `Schedule does not exist: ${name}`);\n}\n","/**\n * Schedule file parsing, validation, and trigger-time computation.\n *\n * `agent_state/schedule/<name>.toml` is declarative intent; the system never writes it\n * back. This module does pure parsing and pure time math only: an invalid file returns\n * an error (the scheduler skips it and records the error); runtime state (fired /\n * missed / disabled) doesn't live here — it belongs to SQLite (db/repos/schedules.ts).\n * Docs: /docs/configuration § \"Schedules\".\n */\nimport { parse as parseToml } from \"smol-toml\";\n\n/** `period` lower bound: below 5 minutes is treated as an invalid file (guards against runaway high-frequency tasks). */\nexport const MIN_PERIOD_MS = 5 * 60_000;\n\n/** A parsed schedule definition (the filename minus `.toml` is its identity). */\nexport interface ScheduleDefinition {\n name: string;\n /** The Prompt to send (required). */\n prompt: string;\n /** Enabled switch; disabled by default. */\n enabled: boolean;\n /** Original text of the first trigger time (for API echo, preserving the written form). */\n startAt: string;\n /** First trigger time (epoch ms). */\n startAtMs: number;\n /** Original text of the end time. */\n endAt?: string;\n /** Original text of the trigger period (e.g. `30m`, for API echo); undefined means a one-shot task. */\n period?: string;\n /** Trigger period (ms); undefined means a one-shot task. */\n periodMs?: number;\n /** End time (epoch ms); no more triggers once past it. */\n endAtMs?: number;\n /** The target Session to bind to; defaults to creating a new Session each time. */\n sessionId?: string;\n /** Workspace for new-Session mode (same semantics as manually starting a session; auto-creates a temp directory if unspecified). */\n workspace?: string;\n /** Model for new-Session mode (upstream id, always paired with provider; omit both for the Project's default reference). */\n modelId?: string;\n /**\n * Vendor grouping for `model_id`; always present when `model_id` is (the pairing rule\n * is enforced here). Whether that pair names a configured model is validated by the\n * caller against config at reconciliation/save time — this module does pure parsing\n * and never touches config.\n */\n provider?: string;\n}\n\nexport type ScheduleParseResult =\n { ok: true; def: ScheduleDefinition } | { ok: false; error: string };\n\n/** Parse a fixed interval in `30m` / `12h` / `7d` form; returns null if invalid. */\nexport function parsePeriod(raw: string): number | null {\n const m = /^(\\d+)([mhd])$/.exec(raw.trim());\n if (!m) return null;\n const n = Number(m[1]);\n if (!Number.isInteger(n) || n <= 0) return null;\n const unit = m[2] === \"m\" ? 60_000 : m[2] === \"h\" ? 3_600_000 : 86_400_000;\n return n * unit;\n}\n\n/** Parse an ISO 8601 instant into epoch ms plus the original text for echo; returns null if invalid (smol-toml's date values are also accepted). */\nfunction parseInstant(value: unknown): { ms: number; raw: string } | null {\n if (value instanceof Date) {\n const ms = value.getTime();\n return Number.isNaN(ms) ? null : { ms, raw: value.toISOString() };\n }\n if (typeof value !== \"string\") return null;\n const ms = Date.parse(value);\n return Number.isNaN(ms) ? null : { ms, raw: value };\n}\n\n/**\n * Parse and validate a schedule file. A field with the wrong type invalidates the whole\n * file (the baseline for hand-edit tolerance is to never let bad config reach the\n * scheduler); unknown keys are ignored (forward compatibility).\n */\nexport function parseScheduleFile(name: string, raw: string): ScheduleParseResult {\n let parsed: unknown;\n try {\n parsed = parseToml(raw);\n } catch (err) {\n return {\n ok: false,\n error: `Failed to parse TOML: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n if (parsed === null || typeof parsed !== \"object\")\n return { ok: false, error: \"Content is not a TOML table\" };\n const t = parsed as Record<string, unknown>;\n\n const prompt = t[\"prompt\"];\n if (typeof prompt !== \"string\" || prompt.trim() === \"\") {\n return { ok: false, error: \"Missing required field prompt\" };\n }\n const enabled = t[\"enabled\"] === undefined ? false : t[\"enabled\"];\n if (typeof enabled !== \"boolean\") return { ok: false, error: \"enabled must be a boolean\" };\n\n const startAt = parseInstant(t[\"start_at\"]);\n if (startAt === null)\n return { ok: false, error: \"start_at is missing or not a valid ISO 8601 instant\" };\n\n let period: string | undefined;\n let periodMs: number | undefined;\n if (t[\"period\"] !== undefined) {\n if (typeof t[\"period\"] !== \"string\") return { ok: false, error: \"period must be a string\" };\n const ms = parsePeriod(t[\"period\"]);\n if (ms === null) return { ok: false, error: \"period must look like 30m / 12h / 7d\" };\n if (ms < MIN_PERIOD_MS) return { ok: false, error: \"period is below the 5m minimum\" };\n period = t[\"period\"].trim();\n periodMs = ms;\n }\n\n let endAt: { ms: number; raw: string } | undefined;\n if (t[\"end_at\"] !== undefined) {\n const parsedEnd = parseInstant(t[\"end_at\"]);\n if (parsedEnd === null) return { ok: false, error: \"end_at is not a valid ISO 8601 instant\" };\n if (parsedEnd.ms <= startAt.ms)\n return { ok: false, error: \"end_at must be later than start_at\" };\n endAt = parsedEnd;\n }\n\n let sessionId: string | undefined;\n if (t[\"session_id\"] !== undefined) {\n if (typeof t[\"session_id\"] !== \"string\" || t[\"session_id\"] === \"\") {\n return { ok: false, error: \"session_id must be a non-empty string\" };\n }\n sessionId = t[\"session_id\"];\n }\n let workspace: string | undefined;\n if (t[\"workspace\"] !== undefined) {\n if (typeof t[\"workspace\"] !== \"string\" || t[\"workspace\"] === \"\") {\n return { ok: false, error: \"workspace must be a non-empty string\" };\n }\n workspace = t[\"workspace\"];\n }\n let modelId: string | undefined;\n if (t[\"model_id\"] !== undefined) {\n if (typeof t[\"model_id\"] !== \"string\" || t[\"model_id\"] === \"\") {\n return { ok: false, error: \"model_id must be a non-empty string\" };\n }\n modelId = t[\"model_id\"];\n }\n let provider: string | undefined;\n if (t[\"provider\"] !== undefined) {\n if (typeof t[\"provider\"] !== \"string\" || t[\"provider\"] === \"\") {\n return { ok: false, error: \"provider must be a non-empty string\" };\n }\n provider = t[\"provider\"];\n }\n // Target conflicts are reported first: when session_id is set the whole model reference\n // is out of place, so complaining about its shape would point at the wrong fix.\n if (\n sessionId !== undefined &&\n (workspace !== undefined || modelId !== undefined || provider !== undefined)\n ) {\n return {\n ok: false,\n error: \"Pick one target: workspace and provider / model_id are only for new-Session mode\",\n };\n }\n // A model reference is always a complete (provider, model_id) pair: neither half is\n // ever inferred from the other, so a file carrying only one of them is invalid rather\n // than something to resolve later. Omit both to run on the Project's default model.\n // A file written before this rule existed (model_id alone) therefore parses as\n // invalid: it is listed in invalidFiles and skipped, never scheduled.\n if ((modelId === undefined) !== (provider === undefined)) {\n return {\n ok: false,\n error:\n \"provider and model_id must be given together (a model reference is always a pair); omit both to use the Project's default model\",\n };\n }\n\n return {\n ok: true,\n def: {\n name,\n prompt,\n enabled,\n startAt: startAt.raw,\n startAtMs: startAt.ms,\n ...(period !== undefined ? { period } : {}),\n ...(periodMs !== undefined ? { periodMs } : {}),\n ...(endAt !== undefined ? { endAt: endAt.raw, endAtMs: endAt.ms } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n },\n };\n}\n\n/**\n * Step from `start_at` by `period` and return the most recent scheduled time not later\n * than `nowMs`; null if `start_at` hasn't been reached yet. A one-shot task's only slot\n * is `start_at` itself.\n */\nexport function latestSlotAt(def: ScheduleDefinition, nowMs: number): number | null {\n if (nowMs < def.startAtMs) return null;\n if (def.periodMs === undefined) return def.startAtMs;\n const k = Math.floor((nowMs - def.startAtMs) / def.periodMs);\n return def.startAtMs + k * def.periodMs;\n}\n\n/** Whether a scheduled slot still falls within the `[start_at, end_at]` window (always true if there's no end_at). */\nexport function slotInWindow(def: ScheduleDefinition, slotMs: number): boolean {\n return def.endAtMs === undefined || slotMs <= def.endAtMs;\n}\n\n/**\n * The next scheduled time strictly after `nowMs` (used to display \"next trigger\");\n * for a one-shot task this only has a value while start_at hasn't been reached, and\n * returns null once past end_at.\n */\nexport function nextSlotAfter(def: ScheduleDefinition, nowMs: number): number | null {\n let next: number;\n if (nowMs < def.startAtMs) {\n next = def.startAtMs;\n } else if (def.periodMs === undefined) {\n return null;\n } else {\n const k = Math.floor((nowMs - def.startAtMs) / def.periodMs) + 1;\n next = def.startAtMs + k * def.periodMs;\n }\n return slotInWindow(def, next) ? next : null;\n}\n","/**\n * Schedule file access: `agent_state/schedule/<name>.toml`, where\n * the filename (a semantic name) is the identity. Reads are fault-tolerant (an invalid\n * file is recorded as an error and skipped by the caller); writes only go through the\n * API routes (the system never rewrites existing file content — PUT is a full-file\n * replacement expressing user intent).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { stringify as stringifyToml } from \"smol-toml\";\nimport { loadProjectConfig, resolveModelRef, scheduleDir } from \"@prismshadow/penguin-core\";\nimport type { ScheduleDefinition } from \"./schedule-file.js\";\nimport { parseScheduleFile, type ScheduleParseResult } from \"./schedule-file.js\";\n\nexport interface ScheduleFileEntry {\n name: string;\n raw: string;\n parsed: ScheduleParseResult;\n}\n\n/** List all schedule files for this Agent (a missing directory is treated as empty). */\nexport async function listScheduleFiles(\n root: string,\n projectId: string,\n agentId: string,\n): Promise<ScheduleFileEntry[]> {\n const dir = scheduleDir(root, projectId, agentId);\n let names: string[];\n try {\n names = await fs.readdir(dir);\n } catch {\n return [];\n }\n const entries: ScheduleFileEntry[] = [];\n for (const file of names.sort()) {\n if (!file.endsWith(\".toml\")) continue;\n const name = file.slice(0, -\".toml\".length);\n let raw: string;\n try {\n raw = await fs.readFile(path.join(dir, file), \"utf8\");\n } catch {\n continue; // Deleted during reconciliation: revisit next round.\n }\n entries.push({ name, raw, parsed: parseScheduleFile(name, raw) });\n }\n return entries;\n}\n\nexport async function readScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<ScheduleFileEntry | null> {\n const file = path.join(scheduleDir(root, projectId, agentId), `${name}.toml`);\n try {\n const raw = await fs.readFile(file, \"utf8\");\n return { name, raw, parsed: parseScheduleFile(name, raw) };\n } catch {\n return null;\n }\n}\n\n/** Serialize API fields into file content (validation uniformly goes through parseScheduleFile, avoiding two sets of rules). */\nexport function serializeSchedule(fields: {\n prompt: string;\n enabled: boolean;\n startAt: string;\n period?: string;\n endAt?: string;\n sessionId?: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n}): string {\n const table: Record<string, unknown> = {\n prompt: fields.prompt,\n enabled: fields.enabled,\n start_at: fields.startAt,\n ...(fields.period !== undefined ? { period: fields.period } : {}),\n ...(fields.endAt !== undefined ? { end_at: fields.endAt } : {}),\n ...(fields.sessionId !== undefined ? { session_id: fields.sessionId } : {}),\n ...(fields.workspace !== undefined ? { workspace: fields.workspace } : {}),\n ...(fields.provider !== undefined ? { provider: fields.provider } : {}),\n ...(fields.modelId !== undefined ? { model_id: fields.modelId } : {}),\n };\n return `${stringifyToml(table)}\\n`;\n}\n\n/**\n * Config check for a schedule's model reference (shared by save and reconciliation):\n * the reference is a complete `(provider, model_id)` pair and must name an entry in the\n * Project config — nothing is inferred, so a pair that matches no entry is simply an\n * error. Returns an error message (no such model / half a reference / config read\n * failure), or null when the pair is configured (or there is no model reference at all).\n */\nexport async function validateScheduleModelRef(\n root: string,\n projectId: string,\n def: Pick<ScheduleDefinition, \"modelId\" | \"provider\">,\n): Promise<string | null> {\n if (def.modelId === undefined && def.provider === undefined) return null;\n // parseScheduleFile already rejects half a reference, so this only guards callers that\n // build a definition by hand — the missing half is reported, never guessed.\n if (def.modelId === undefined || def.provider === undefined) {\n return \"provider and model_id must be given together (a model reference is always a pair); omit both to use the Project's default model\";\n }\n try {\n const cfg = await loadProjectConfig(root, projectId);\n resolveModelRef(cfg, def.modelId, def.provider);\n return null;\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n}\n\n/** Write a schedule file to disk (full-file replacement for POST/PUT). */\nexport async function writeScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n raw: string,\n): Promise<void> {\n const dir = scheduleDir(root, projectId, agentId);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(path.join(dir, `${name}.toml`), raw, \"utf8\");\n}\n\n/** Delete a schedule file; returns false if it doesn't exist. */\nexport async function deleteScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<boolean> {\n const file = path.join(scheduleDir(root, projectId, agentId), `${name}.toml`);\n try {\n await fs.unlink(file);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Benchmark scoring routes:\n * GET /api/projects/:p/agents/:a/benchmarks (any member, read-only)\n * Returns the Agent's Benchmark list (title/description from benchmark_config.toml)\n * along with the evaluations[] from scoreboard.yaml.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { requireValidId } from \"../validate.js\";\n\nexport function benchmarksRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(await deps.benchmarks.list(projectId, agentId));\n });\n\n return app;\n}\n","/**\n * Skill library & Agent-installed-Skills routes:\n * GET /api/skills # library groups & metadata (any logged-in user)\n * GET|POST /api/projects/:p/agents/:a/skills # installed list / install from library (any member)\n * DELETE /api/projects/:p/agents/:a/skills/:name # uninstall (any member)\n * Installing writes the library's SKILL.md verbatim to agent_state/skills/<name>/;\n * reinstalling overwrites with the library content (i.e. an update). The scope is small\n * enough to skip a service layer — routes call core's disk-writing functions directly.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport {\n installSkill,\n listInstalledSkills,\n removeSkill,\n skillsDir,\n} from \"@prismshadow/penguin-core\";\nimport { librarySkill, loadSkillGroups } from \"@prismshadow/penguin-skills\";\nimport type { LibrarySkill, SkillMetadata } from \"@prismshadow/penguin-skills\";\nimport type {\n AgentSkillsResponse,\n SkillLibraryResponse,\n SkillMetadataItem,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { HttpError } from \"../errors.js\";\nimport { badRequest, readJson, requireValidId } from \"../validate.js\";\n\n/**\n * Strips the content off a LibrarySkill: the API only sends metadata; the full body is\n * written to disk on install and read by the model on demand. The optional short\n * description (shortDescription(Zh)) and custom icon (icon.svg source) are conditionally\n * passed through — both the library side (LibrarySkill) and the installed side (core\n * InstalledSkill) carry these fields.\n */\nfunction toMetadataItem(skill: SkillMetadata & { icon?: string }): SkillMetadataItem {\n return {\n name: skill.name,\n description: skill.description,\n ...(skill.shortDescription !== undefined ? { shortDescription: skill.shortDescription } : {}),\n ...(skill.shortDescriptionZh !== undefined\n ? { shortDescriptionZh: skill.shortDescriptionZh }\n : {}),\n ...(skill.icon !== undefined ? { icon: skill.icon } : {}),\n version: skill.version,\n updated: skill.updated,\n };\n}\n\n/** Library listing response: the files are the source of truth — read and parse the library directory fresh on every request (files are small, requests infrequent, no caching needed). */\nfunction libraryResponse(): SkillLibraryResponse {\n return {\n groups: loadSkillGroups().map((group) => ({\n id: group.id,\n title: group.title,\n ...(group.titleZh !== undefined ? { titleZh: group.titleZh } : {}),\n skills: group.skills.map(toMetadataItem),\n })),\n };\n}\n\n/** Validate the POST request body: names must be a non-empty array of strings. */\nfunction parseInstallNames(body: Record<string, unknown>): string[] {\n if (!Array.isArray(body.names) || body.names.length === 0) {\n throw badRequest(\"names must be a non-empty array.\");\n }\n return body.names.map((v, i) => {\n if (typeof v !== \"string\" || v.length === 0) {\n throw badRequest(`names[${i}] must be a non-empty string.`);\n }\n return v;\n });\n}\n\n/** GET /api/skills: Skill library groups & metadata (any logged-in user; no Project check). */\nexport function skillLibraryRoutes(): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n app.get(\"/\", (c) => c.json(libraryResponse()));\n return app;\n}\n\n/** /api/projects/:p/agents/:a/skills: read, install, and uninstall are all Project-member operations. */\nexport function agentSkillsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n const listResponse = async (\n projectId: string,\n agentId: string,\n ): Promise<AgentSkillsResponse> => ({\n skills: (await listInstalledSkills(deps.config.root, projectId, agentId)).map(toMetadataItem),\n });\n\n app.get(\"/\", async (c) => {\n // Defensive id validation happens before any path construction (FD-4: prevents path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(await listResponse(projectId, agentId));\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const names = parseInstallNames(await readJson(c));\n // Verify all names up front before writing anything: if any name isn't in the library, reject the whole request rather than leaving a half-installed state.\n const skills: LibrarySkill[] = names.map((name) => {\n const skill = librarySkill(name);\n if (!skill) throw new HttpError(404, \"unknown_skill\", `Skill is not in the library: ${name}`);\n return skill;\n });\n for (const skill of skills) {\n await installSkill(deps.config.root, projectId, agentId, skill);\n }\n return c.json(await listResponse(projectId, agentId), 201);\n });\n\n app.delete(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const name = requireValidId(c, \"name\");\n // Installed-check uses the same criterion as listInstalledSkills: skills/<name>/SKILL.md exists.\n const file = path.join(skillsDir(deps.config.root, projectId, agentId), name, \"SKILL.md\");\n try {\n await fs.access(file);\n } catch {\n throw new HttpError(404, \"not_found\", `Skill is not installed: ${name}`);\n }\n await removeSkill(deps.config.root, projectId, agentId, name);\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Agent State export/import routes:\n * GET /api/projects/:p/agents/:a/export (any member; auto-packages if no snapshot exists, downloads tar.gz)\n * POST /api/projects/:p/agents/:a/import (owner only; version conflicts require a confirm flag)\n */\nimport fs from \"node:fs/promises\";\nimport { Hono } from \"hono\";\nimport type { AgentImportResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\n\n/** Import archive size cap: aligned with the global request body limit (stays within 20MB after base64). */\nconst MAX_ARCHIVE_BYTES = 14 * 1024 * 1024;\n\nexport function agentTransferRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/export\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const { file, fileName } = await deps.snapshots.exportArchive(projectId, agentId);\n const bytes = await fs.readFile(file);\n return new Response(new Uint8Array(bytes), {\n headers: {\n \"Content-Type\": \"application/gzip\",\n \"Content-Disposition\": `attachment; filename=\"${fileName}\"`,\n },\n });\n });\n\n app.post(\"/import\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const dataBase64 = requireString(body, \"dataBase64\", { minLen: 1, maxLen: 20 * 1024 * 1024 });\n const confirm = body.confirm === true;\n let archive: Buffer;\n try {\n archive = Buffer.from(dataBase64, \"base64\");\n } catch {\n throw badRequest(\"dataBase64 is not valid base64.\");\n }\n if (archive.byteLength === 0) throw badRequest(\"Import package is empty.\");\n if (archive.byteLength > MAX_ARCHIVE_BYTES)\n throw badRequest(\"Import package exceeds the 14MB limit.\");\n const { version } = await deps.snapshots.importArchive(projectId, agentId, archive, confirm);\n const res: AgentImportResponse = { version };\n return c.json(res);\n });\n\n return app;\n}\n","/**\n * Agent routes:\n * GET|POST /api/projects/:p/agents, DELETE /:agentId (owner only).\n * The list is the union of DB entries and directory scan results, including active\n * Session count, total Session count, and config last-modified time.\n */\nimport { Hono } from \"hono\";\nimport type { AgentCreateResponse, AgentsResponse, AgentSummary } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { settleWithin } from \"../settle.js\";\nimport { optionalString, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Window size in days for the card's activity sparkline (last 30 days, including today). */\nconst ACTIVITY_DAYS = 30;\n\nexport function agentsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4): don't rely on the implicit invariant that requireProjectAccess always runs before path construction.\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const items = await deps.agentService.listAgents(projectId);\n const agents: AgentSummary[] = await Promise.all(\n items.map(async (item) => {\n const stats = await deps.sessionService.sessionStats(\n projectId,\n item.agentId,\n ACTIVITY_DAYS,\n );\n return {\n ...item,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, item.agentId),\n sessionCount: stats.sessionCount,\n sessionActivity: stats.activity,\n };\n }),\n );\n return c.json({ agents } satisfies AgentsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const body = await readJson(c);\n const agentId = requireString(body, \"agentId\", { label: \"agentId\" });\n const name = optionalString(body, \"name\", { minLen: 1, maxLen: 100, label: \"name\" });\n const description = optionalString(body, \"description\", {\n maxLen: 2000,\n label: \"description\",\n });\n const item = await deps.agentService.createAgent(projectId, agentId, name, description);\n const agent: AgentSummary = {\n ...item,\n activeSessionCount: 0,\n sessionCount: 0,\n sessionActivity: Array.from({ length: ACTIVITY_DAYS }, () => 0),\n };\n return c.json({ agent } satisfies AgentCreateResponse, 201);\n });\n\n app.delete(\"/:agentId\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n // Deletion is a Project-level management operation: owner only.\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n // Mark as deleting and converge active runs (beginAgentDeletion): any new Task during\n // this window gets 409, preventing the race where a new task recreates the directory\n // and revives the Agent between abort and rm. Abort cleanup writes the Trace\n // asynchronously; wait for it to finish before removing the directory, and clear the\n // deleting flag once deletion completes (success or failure).\n const runnings = deps.manager.beginAgentDeletion(projectId, agentId);\n try {\n await settleWithin(runnings, 5000);\n await deps.agentService.deleteAgent(projectId, agentId);\n deps.sessionsRepo.deleteByAgent(projectId, agentId);\n } finally {\n deps.manager.endAgentDeletion(projectId, agentId);\n }\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Convergent wait for delete routes (currently used by agents DELETE; sessions DELETE\n * uses the same inline pattern and could later be unified onto this): waits for aborted\n * runs to wind down, up to ms milliseconds; returns whether all of them settled within\n * the window. The timer is unref'd so it never blocks process exit.\n */\nexport async function settleWithin(promises: Promise<unknown>[], ms: number): Promise<boolean> {\n if (promises.length === 0) return true;\n return Promise.race([\n Promise.allSettled(promises).then(() => true),\n new Promise<boolean>((resolve) => setTimeout(() => resolve(false), ms).unref?.()),\n ]);\n}\n","/**\n * Server directory browsing:\n * GET /api/projects/:p/dirs?path=<absolute>.\n *\n * Lets the user interactively pick a Workspace directory when creating a Session via\n * advanced mode. Defaults to the home directory of the account running the service, and\n * can be browsed all the way up to the root `/` — reachability is governed by OS file\n * permissions; the server no longer restricts browsing to within the Project directory\n * tree (same convention as workspace-guard). Lists subdirectories only, not files.\n *\n * `projectId` remains the authorization anchor: the caller must have access to that Project.\n */\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { DirListResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { HttpError } from \"../errors.js\";\nimport { requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function dirsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n\n // Default starting point: home directory; an explicit path must be absolute (the frontend always sends back the realpath result).\n const raw = c.req.query(\"path\");\n const target = raw && raw.trim() ? raw.trim() : os.homedir();\n if (!path.isAbsolute(target)) {\n throw new HttpError(400, \"dir_not_absolute\", \"Directory must be an absolute path.\");\n }\n\n let real: string;\n try {\n real = await fs.realpath(target);\n } catch {\n throw new HttpError(\n 404,\n \"dir_not_found\",\n `Directory does not exist or is inaccessible: ${target}.`,\n );\n }\n const stat = await fs.stat(real);\n if (!stat.isDirectory()) {\n throw new HttpError(400, \"not_a_dir\", \"Not a directory.\");\n }\n\n let dirents: import(\"node:fs\").Dirent[] = [];\n try {\n dirents = await fs.readdir(real, { withFileTypes: true });\n } catch {\n // No read permission: return an empty list instead of an error, so the user can still navigate back up.\n dirents = [];\n }\n const entries = dirents\n .filter((d) => d.isDirectory())\n .map((d) => ({ name: d.name, path: path.join(real, d.name) }))\n .sort((a, b) => a.name.localeCompare(b.name));\n\n const parent = path.dirname(real);\n return c.json({\n path: real,\n parent: parent === real ? null : parent,\n entries,\n } satisfies DirListResponse);\n });\n\n return app;\n}\n","/**\n * Agent config routes (reads/writes system_config.yaml and AGENTS.md):\n * GET|PUT /api/projects/:p/agents/:a/config, plus POST …/config/reset to adopt the\n * current default config (keeps only name/description/version — the config-side\n * analogue of a skill update). Members can read and write (unrestricted).\n */\nimport { Hono } from \"hono\";\nimport type { AgentConfigResponse, AgentConfigUpdateRequest } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, optionalString, readJson, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function agentConfigRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const view = await deps.agentConfigService.getConfig(projectId, agentId);\n return c.json({\n ...view,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId),\n } satisfies AgentConfigResponse);\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const body = await readJson(c);\n const req: AgentConfigUpdateRequest = {};\n const agentsMd = optionalString(body, \"agentsMd\", { label: \"agentsMd\" });\n if (agentsMd !== undefined) req.agentsMd = agentsMd;\n if (body.config !== undefined) {\n if (body.config === null || typeof body.config !== \"object\" || Array.isArray(body.config)) {\n throw badRequest(\"config must be an object.\");\n }\n req.config = body.config as AgentConfigUpdateRequest[\"config\"];\n }\n // Fine-grained validation (numeric ranges / enums) is done inside agent-config-service.\n await deps.agentConfigService.updateConfig(projectId, agentId, req);\n const view = await deps.agentConfigService.getConfig(projectId, agentId);\n return c.json({\n ...view,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId),\n } satisfies AgentConfigResponse);\n });\n\n // Overwrite system_config.yaml with the current defaults (see AgentConfigService.resetConfig\n // for the exact semantics); same authorization as PUT, and responds like GET/PUT with the\n // fresh config so the client can refresh in place.\n app.post(\"/reset\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.resetConfig(projectId, agentId);\n const view = await deps.agentConfigService.getConfig(projectId, agentId);\n return c.json({\n ...view,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId),\n } satisfies AgentConfigResponse);\n });\n\n return app;\n}\n","/**\n * Agent-level Trace browsing routes:\n * - GET /api/projects/:p/agents/:a/traces — drills down Agent -> date -> Session -> index (reverse order);\n * - GET /api/projects/:p/agents/:a/traces/:sessionId/:index (including /analysis) —\n * read-only Trace detail endpoints (FD-3): locate the Trace file directly by\n * (projectId, agentId, sessionId), without depending on the sessions table for\n * tracking — any entry visible in the directory tree (subagent child Sessions,\n * CLI-created Sessions) can be opened and read; access is enforced by requireProjectAccess.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { paginationQuery, positiveIntParam, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function agentTracesRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.traceService.agentTraces(projectId, agentId));\n });\n\n app.get(\"/:sessionId/:index\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const sessionId = requireValidId(c, \"sessionId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const index = positiveIntParam(c, \"index\");\n const { offset, limit } = paginationQuery(c);\n return c.json(\n await deps.traceService.readEvents(projectId, agentId, sessionId, index, offset, limit),\n );\n });\n\n app.get(\"/:sessionId/:index/analysis\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const sessionId = requireValidId(c, \"sessionId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const index = positiveIntParam(c, \"index\");\n return c.json(await deps.traceService.analyze(projectId, agentId, sessionId, index));\n });\n\n return app;\n}\n","/**\n * Usage statistics routes:\n * GET /api/projects/:p/usage?from&to&groupBy&agentId&provider&modelId\n * (model filter is paired: provider and modelId are given together).\n */\nimport { Hono } from \"hono\";\nimport type { UsageGroupBy } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, optionalDateParam, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nconst GROUP_BYS: readonly UsageGroupBy[] = [\"date\", \"agent\", \"model\", \"session\"];\n\nexport function usageRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4).\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const groupByRaw = c.req.query(\"groupBy\") ?? \"date\";\n if (!(GROUP_BYS as readonly string[]).includes(groupByRaw)) {\n throw badRequest(`groupBy must be one of ${GROUP_BYS.join(\" / \")}.`);\n }\n const from = optionalDateParam(c.req.query(\"from\"), \"from\");\n const to = optionalDateParam(c.req.query(\"to\"), \"to\");\n const agentId = c.req.query(\"agentId\");\n const provider = c.req.query(\"provider\");\n const modelId = c.req.query(\"modelId\");\n return c.json(\n await deps.usageService.query(projectId, {\n groupBy: groupByRaw as UsageGroupBy,\n // Unattributed errors (login failures, process crashes, etc. with no Project\n // context) are visible only to admins: requireProjectAccess only guarantees\n // \"is a member of this Project\" — a regular member seeing another tenant's errors\n // would be a cross-tenant information leak.\n includeGlobalErrors: c.var.user.isAdmin,\n ...(from !== undefined ? { from } : {}),\n ...(to !== undefined ? { to } : {}),\n ...(agentId !== undefined && agentId !== \"\" ? { agentId } : {}),\n ...(provider !== undefined && provider !== \"\" ? { provider } : {}),\n ...(modelId !== undefined && modelId !== \"\" ? { modelId } : {}),\n }),\n );\n });\n\n return app;\n}\n","/**\n * Session routes.\n *\n * Two entry groups:\n * - Agent-level: GET|POST /api/projects/:p/agents/:a/sessions (list including run state / create);\n * - Session-level: /api/sessions/:sessionId/* (no projectId; looks up project_id via the\n * sessions index, then goes through requireProjectAccess; 404 if the index has no such Session).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport { imageUrlMessage, scratchpadDir, userText } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage, ThinkingLevelName } from \"@prismshadow/penguin-core\";\nimport type {\n ApprovalMode,\n FilesStatResponse,\n MessagesResponse,\n ServerEvent,\n SessionCategory,\n SessionCreateResponse,\n SessionResponse,\n SessionsResponse,\n TaskCreateResponse,\n} from \"../../api/types.js\";\nimport { PREVIEW_TOKEN_TTL_MS, resolvePreviewTarget } from \"../../services/preview-token.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { SessionRow } from \"../../db/repos/sessions.js\";\nimport { assertWorkspaceAllowed } from \"../../services/workspace-guard.js\";\nimport { HttpError } from \"../errors.js\";\nimport { sseEndpoint } from \"../sse.js\";\nimport {\n badRequest,\n optionalEnum,\n optionalPagingQuery,\n optionalString,\n paginationQuery,\n pathParam,\n positiveIntParam,\n readJson,\n requireEnum,\n requireValidId,\n} from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { MAX_UPLOAD_BYTES } from \"../../services/workspace-files-service.js\";\n\n/** Max title length for manual renames: looser than the auto-generated 30-char limit, to accommodate users' own organizing conventions. */\nconst SESSION_TITLE_MAX = 120;\n\n/** Max path count and per-path length for a single files/stat check (message file-card candidates never exceed this scale). */\nconst STAT_MAX_PATHS = 100;\nconst STAT_MAX_PATH_LEN = 512;\n\nconst APPROVAL_MODES: readonly ApprovalMode[] = [\n \"allow-all\",\n \"deny-all\",\n \"read-only\",\n \"always-ask\",\n];\n\n/** The five valid per-turn thinking level names (TaskCreateRequest.thinkingLevel). */\nconst THINKING_LEVELS: readonly ThinkingLevelName[] = [\"none\", \"low\", \"medium\", \"high\", \"xhigh\"];\n\n/** Accepted `category` query values of the list endpoint (SessionCategory, spelled out for validation). */\nconst SESSION_CATEGORIES: readonly SessionCategory[] = [\n \"active\",\n \"subagent\",\n \"schedule\",\n \"archived\",\n];\n\n/** Validate Prompt input parts: text or image (data: / http(s) URL). */\nfunction parseTaskInput(body: Record<string, unknown>): OmniMessage[] {\n const input = body.input;\n if (!Array.isArray(input) || input.length === 0) {\n throw badRequest(\"input must be an array with at least one item.\");\n }\n return input.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`input[${i}] must be an object.`);\n }\n const part = item as Record<string, unknown>;\n if (part.type === \"text\") {\n if (typeof part.text !== \"string\" || part.text.length === 0) {\n throw badRequest(`input[${i}].text must be a non-empty string.`);\n }\n return userText(part.text);\n }\n if (part.type === \"image_url\") {\n const url = part.imageUrl;\n if (\n typeof url !== \"string\" ||\n !(url.startsWith(\"data:\") || url.startsWith(\"http://\") || url.startsWith(\"https://\"))\n ) {\n throw badRequest(`input[${i}].imageUrl only supports data: or http(s) URLs.`);\n }\n return imageUrlMessage(url);\n }\n throw badRequest(`input[${i}].type must be one of text / image_url.`);\n });\n}\n\n/** Agent-level entry: /api/projects/:p/agents/:a/sessions. */\nexport function agentSessionsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validity is checked before any path is constructed (FD-4: guards against agentId path traversal across Projects).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n // Optional paging (absent = full list, the pre-paging contract): the sidebar requests\n // limit+1 and shows limit, detecting \"has more\" without a response-envelope change.\n const paging = optionalPagingQuery(c);\n // Optional category filter (paging then applies within the category) and per-category\n // totals — the sidebar loads active rows only and labels the collapsed folders from counts.\n const rawCategory = c.req.query(\"category\");\n if (rawCategory !== undefined && !SESSION_CATEGORIES.includes(rawCategory as SessionCategory)) {\n throw badRequest(`category must be one of ${SESSION_CATEGORIES.join(\" / \")}.`);\n }\n const rawCounts = c.req.query(\"counts\");\n if (rawCounts !== undefined && rawCounts !== \"1\") throw badRequest(\"counts only accepts 1.\");\n const { sessions, counts, workspaceCounts } = await deps.sessionService.listSessions(\n projectId,\n agentId,\n {\n ...(paging ? { paging } : {}),\n ...(rawCategory !== undefined ? { category: rawCategory as SessionCategory } : {}),\n ...(rawCounts !== undefined ? { withCounts: true } : {}),\n },\n );\n return c.json({\n sessions,\n ...(counts ? { counts } : {}),\n ...(workspaceCounts ? { workspaceCounts } : {}),\n } satisfies SessionsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const modelId = optionalString(body, \"modelId\", { minLen: 1, label: \"modelId\" });\n const provider = optionalString(body, \"provider\", { minLen: 1, label: \"provider\" });\n // Model reference is submitted as a pair — both or neither. Neither half is ever\n // inferred from the other, so half a reference is rejected here instead of being\n // resolved (core does the same validation; this catches it early). Omitting both\n // falls back to the Project's default model.\n if ((modelId === undefined) !== (provider === undefined)) {\n throw badRequest(\n \"modelId and provider must be given together as a model reference pair: specify both, or neither to use the Project's default model.\",\n );\n }\n const approvalMode = optionalEnum(body, \"approvalMode\", APPROVAL_MODES);\n let workspace = optionalString(body, \"workspace\", { minLen: 1, label: \"workspace\" });\n if (workspace !== undefined) {\n // An explicitly specified Workspace must be an existing directory (never auto-created); reachability is determined by file permissions.\n workspace = await assertWorkspaceAllowed({ workspace });\n }\n const session = await deps.sessionService.createSession({\n projectId,\n agentId,\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(approvalMode !== undefined ? { approvalMode } : {}),\n });\n return c.json({ session } satisfies SessionCreateResponse, 201);\n });\n\n return app;\n}\n\n/** Session-level entry point: /api/sessions/:sessionId/*. */\nexport function sessionsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n /** Look up ownership and check access (404 if the index has no such Session, or access is denied — never leaking existence). */\n const resolveSession = (c: Context<AppEnv>): SessionRow => {\n const sessionId = c.req.param(\"sessionId\");\n const row = sessionId ? deps.sessionsRepo.findById(sessionId) : null;\n if (!row) {\n throw new HttpError(\n 404,\n \"session_not_found\",\n \"Session does not exist or you do not have access.\",\n );\n }\n try {\n deps.projectService.requireProjectAccess(c.var.user.userId, row.projectId);\n } catch {\n throw new HttpError(\n 404,\n \"session_not_found\",\n \"Session does not exist or you do not have access.\",\n );\n }\n return row;\n };\n\n app.get(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n const hasTrace = await deps.sessionService.hasTrace(row);\n const info = await deps.sessionService.toInfo(row, hasTrace);\n // Single-session GET only: the latest Trace file's absolute path (a directory walk per\n // call — too costly for list rows). The web's /model switch hands it to the new session's\n // [model_switch_from] block so the model can read the source history itself.\n const tracePath = hasTrace ? await deps.sessionService.latestTracePath(row) : undefined;\n return c.json({\n session: { ...info, ...(tracePath !== undefined ? { tracePath } : {}) },\n } satisfies SessionResponse);\n });\n\n app.patch(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const approvalMode = optionalEnum(body, \"approvalMode\", APPROVAL_MODES);\n const archivedRaw = (body as Record<string, unknown>).archived;\n const archived = typeof archivedRaw === \"boolean\" ? archivedRaw : undefined;\n const titleRaw = (body as Record<string, unknown>).title;\n let title: string | undefined;\n if (titleRaw !== undefined) {\n if (typeof titleRaw !== \"string\") {\n throw new HttpError(400, \"invalid_title\", \"title must be a string.\");\n }\n title = titleRaw.trim();\n if (!title || title.length > SESSION_TITLE_MAX) {\n throw new HttpError(\n 400,\n \"invalid_title\",\n `title must be 1–${SESSION_TITLE_MAX} characters.`,\n );\n }\n }\n if (approvalMode === undefined && archived === undefined && title === undefined) {\n throw new HttpError(\n 400,\n \"no_update\",\n \"No updatable field provided (approvalMode / archived / title).\",\n );\n }\n let updated: SessionRow = { ...row };\n if (title !== undefined) {\n // Manual renaming takes priority over auto-generation: TitleGenerator only persists a title while it's still NULL.\n deps.sessionsRepo.updateTitle(row.sessionId, title);\n updated = { ...updated, title };\n }\n if (approvalMode !== undefined) {\n // Takes effect immediately: a running approve callback re-reads the DB on every decision.\n deps.sessionsRepo.updateApprovalMode(row.sessionId, approvalMode);\n updated = { ...updated, approvalMode };\n }\n if (archived !== undefined) {\n const at = archived ? new Date().toISOString() : null;\n deps.sessionsRepo.setArchived(row.sessionId, at);\n updated = { ...updated, archivedAt: at };\n }\n const hasTrace = await deps.sessionService.hasTrace(updated);\n return c.json({\n session: await deps.sessionService.toInfo(updated, hasTrace),\n } satisfies SessionResponse);\n });\n\n app.delete(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n // Mark as being deleted and converge active runs (beginSessionDeletion): new\n // Tasks/compactions are always rejected with 409 during this window\n // (assertSessionNotDeleting), preventing the race where a new task recreates the\n // entry and Trace after abort but before the files are deleted, reviving an\n // already-deleted Session. Interrupt cleanup writes the Trace asynchronously, so we\n // wait for it to finish (≤5s cap) before deleting the files and index row; the\n // being-deleted marker is cleared once deletion finishes (success or failure).\n const runnings = deps.manager.beginSessionDeletion(row.sessionId);\n try {\n if (runnings.length > 0) {\n await Promise.race([\n Promise.allSettled(runnings).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, 5000).unref?.()),\n ]);\n }\n await deps.traceService.deleteSessionTraces(row.projectId, row.agentId, row.sessionId);\n // The session-level scratchpad (model temp files + input images saved to disk for image-unsupported models) is deleted along with the session.\n await fs.rm(\n path.join(scratchpadDir(deps.config.root, row.projectId, row.agentId), row.sessionId),\n { recursive: true, force: true },\n );\n deps.sessionsRepo.deleteById(row.sessionId);\n // Drop the derived-origin entry along with the Session (bulk Agent/Project deletion\n // may leave stale entries; session ids are never reused, so they are never matched).\n deps.sessionSources.delete(row.sessionId);\n } finally {\n deps.manager.endSessionDeletion(row.sessionId);\n }\n return c.body(null, 204);\n });\n\n // Session scratchpad files (e.g. input images saved to disk for image-unsupported\n // models): read by filename, so the conversation UI can render a message's\n // \"[attached image: <path>]\" attachment line back into an image. Restricted to this\n // session's own scratchpad directory (the filename must not contain a path\n // separator, blocking traversal); filenames include a timestamp and are globally\n // unique, so the response is marked immutable and long-cacheable.\n app.get(\"/:sessionId/scratchpad/:fileName\", async (c) => {\n const row = resolveSession(c);\n const fileName = c.req.param(\"fileName\") ?? \"\";\n if (!/^[A-Za-z0-9._-]+$/.test(fileName) || fileName.includes(\"..\")) {\n throw new HttpError(404, \"file_not_found\", \"File does not exist.\");\n }\n const filePath = path.join(\n scratchpadDir(deps.config.root, row.projectId, row.agentId),\n row.sessionId,\n fileName,\n );\n let bytes: Buffer;\n try {\n bytes = await fs.readFile(filePath);\n } catch {\n throw new HttpError(404, \"file_not_found\", \"File does not exist.\");\n }\n const MIME_BY_EXT: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n };\n const mime = MIME_BY_EXT[path.extname(fileName).toLowerCase()] ?? \"application/octet-stream\";\n return c.body(new Uint8Array(bytes), 200, {\n \"content-type\": mime,\n \"cache-control\": \"private, max-age=31536000, immutable\",\n });\n });\n\n app.get(\"/:sessionId/messages\", async (c) => {\n const row = resolveSession(c);\n const messages = await deps.traceService.readMessages(\n row.projectId,\n row.agentId,\n row.sessionId,\n );\n return c.json({ messages } satisfies MessagesResponse);\n });\n\n app.get(\"/:sessionId/stream\", (c) => {\n const row = resolveSession(c);\n const channel = deps.channels.get(row.sessionId);\n // FD-1: the first event of every new subscription (including reconnects and resync\n // rebuilds) is always a snapshot of the current running state — the frontend treats\n // this as authoritative, eliminating input-area lockup or premature Task closure\n // caused by a stale running/idle in the list; followed by replaying all still-pending\n // approval requests.\n const initialEvents: ServerEvent[] = [\n {\n type: \"task_state\",\n state: deps.manager.statusOf(row.sessionId),\n queued: deps.manager.pendingFollowUpCount(row.sessionId),\n },\n ...deps.manager.pendingApprovals(row.sessionId).map((p) => ({\n type: \"approval_request\" as const,\n toolCall: p.toolCall,\n ...(p.origin !== undefined ? { origin: p.origin } : {}),\n })),\n ];\n return sseEndpoint(c, channel, { initialEvents });\n });\n\n app.post(\"/:sessionId/tasks\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const input = parseTaskInput(body);\n // Per-turn thinking level (optional): validated against the five names; omitted follows\n // the session's default. A queued follow-up keeps its level for its auto-start.\n const thinkingLevel = optionalEnum(body, \"thinkingLevel\", THINKING_LEVELS);\n // Follow-up queue: with queueIfBusy, a busy session enqueues the input instead of 409\n // (auto-starts as an ordinary next task once idle; the response says which happened).\n const queueIfBusy = body.queueIfBusy === true;\n // 202: the Task executes on the server, decoupled from the SSE connection; sessionId is the current actual id (the new id after self-heal).\n const { sessionId, queued } = await deps.manager.startTask(row.sessionId, input, {\n ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),\n queueIfBusy,\n });\n return c.json({ sessionId, queued } satisfies TaskCreateResponse, 202);\n });\n\n // Mid-run steering: queue a user message for the running Task; core delivers it between\n // turns as a standalone `[user_steering]` user message (the model sees it without the loop\n // being interrupted). 409 not_running when no Task is in progress — the frontend then falls\n // back to a normal task POST.\n app.post(\"/:sessionId/steer\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const text = typeof body.text === \"string\" ? body.text.trim() : \"\";\n if (!text) throw badRequest(\"text must be a non-empty string.\");\n deps.manager.steer(row.sessionId, text);\n return c.body(null, 202);\n });\n\n app.post(\"/:sessionId/approvals/:toolCallId\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const decision = requireEnum(body, \"decision\", [\"allow\", \"deny\"] as const);\n const ok = deps.manager.decideApproval(row.sessionId, pathParam(c, \"toolCallId\"), decision);\n if (!ok) {\n throw new HttpError(\n 404,\n \"approval_not_found\",\n \"Approval does not exist or has already been decided.\",\n );\n }\n return c.body(null, 204);\n });\n\n app.post(\"/:sessionId/abort\", (c) => {\n const row = resolveSession(c);\n const aborted = deps.manager.abortTask(row.sessionId);\n // No Task in progress → 204 no-op; interrupt was triggered → 202 (wrap-up is completed by the SDK's \"interrupt cleanup\").\n return c.body(null, aborted ? 202 : 204);\n });\n\n app.post(\"/:sessionId/compact\", async (c) => {\n const row = resolveSession(c);\n const { sessionId } = await deps.manager.startCompact(row.sessionId);\n return c.json({ sessionId } satisfies TaskCreateResponse, 202);\n });\n\n // —— Workspace file browsing (Files tab) ——\n\n app.get(\"/:sessionId/files\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n return c.json(await deps.workspaceFiles.list(row.workspace, rel));\n });\n\n app.get(\"/:sessionId/files/content\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n const download = c.req.query(\"download\") === \"1\";\n // Sandboxed top-level preview (\"open in a new tab\" for html): the document keeps its REAL\n // content type but carries a CSP sandbox WITHOUT allow-same-origin — it renders and runs\n // fully in an opaque origin, so agent-generated markup cannot reach this origin's cookies\n // or API. The request itself still authenticates (top-level GET sends the Lax cookie).\n const preview = !download && c.req.query(\"preview\") === \"1\";\n const { data, fileName, contentType, scriptable } = await deps.workspaceFiles.read(\n row.workspace,\n rel,\n );\n const disposition = download ? \"attachment\" : \"inline\";\n // Same-origin XSS defense: html/svg inline previews are always returned as plain\n // text (Workspace files may be Agent-generated and untrusted); downloads\n // (attachment) keep the real content type, and sandboxed previews keep it under the\n // CSP above. Paired with nosniff to prevent MIME sniffing from undoing this.\n const effectiveType =\n !download && scriptable && !preview ? \"text/plain; charset=utf-8\" : contentType;\n return new Response(new Uint8Array(data), {\n status: 200,\n headers: {\n \"Content-Type\": effectiveType,\n \"Content-Disposition\": `${disposition}; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n \"X-Content-Type-Options\": \"nosniff\",\n ...(preview && scriptable\n ? {\n \"Content-Security-Policy\":\n \"sandbox allow-scripts allow-popups allow-modals allow-forms\",\n }\n : {}),\n },\n });\n });\n\n // \"Open in a new tab\" for Workspace HTML: mints a token and redirects to the separate\n // preview origin (see design § \"Workspace 文件预览\").\n //\n // A redirect rather than a JSON endpoint the UI fetches, because the alternative is\n // worse on two counts: opening the tab after an await trips popup blockers, and a\n // window opened by script keeps an `opener` handle back to the App — exactly the\n // reference this design exists to deny. A plain link with rel=\"noopener noreferrer\"\n // has neither problem.\n //\n // Minting on GET is safe: a cross-site request can make the browser follow the\n // redirect, but the response is opaque to the initiating page, so no token leaks — and\n // what it would grant is a preview of the victim's own file.\n //\n // With no usable preview origin (the App is reached on something other than a loopback\n // name and PENGUIN_PREVIEW_ORIGIN is unset), this falls back to the sandboxed\n // same-origin preview: the page still renders, but storage and third-party embeds do\n // not. The UI flags that ahead of time via `previewIsolated` on /api/me.\n app.get(\"/:sessionId/files/preview-redirect\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n // Validate existence + containment while the caller is still authenticated, so a bad\n // path fails here rather than as an opaque 404 from the unauthenticated preview origin.\n // A stat, not a read: the file itself is fetched later, on the preview origin — reading\n // it here (up to 50MB) only to discard the bytes would be wasted work on every click.\n const [exists] = await deps.workspaceFiles.statExisting(row.workspace, [rel]);\n if (!exists) throw new HttpError(404, \"file_not_found\", \"File does not exist.\");\n\n const target = resolvePreviewTarget(\n c.req.url,\n c.req.header(\"host\"),\n deps.config.previewOrigin,\n deps.config,\n );\n if (!target) {\n return c.redirect(\n `/api/sessions/${row.sessionId}/files/content?path=${encodeURIComponent(rel)}&preview=1`,\n 302,\n );\n }\n\n const token = deps.previewTokens.sign({\n sessionId: row.sessionId,\n host: target.host,\n expiresAt: Date.now() + PREVIEW_TOKEN_TTL_MS,\n });\n const encoded = rel.split(\"/\").map(encodeURIComponent).join(\"/\");\n return c.redirect(`${target.origin}/preview/${token}/${encoded}`, 302);\n });\n\n // Bulk existence check (message file cards list only files that actually exist):\n // path-confinement resolution shares the same logic as files/content\n // (WorkspaceFilesService.statExisting reuses resolveRead); out-of-bounds or\n // resolution failures count as not-existing, always 200 — existence itself is the\n // question being answered, and a 4xx would only leak confinement details.\n app.post(\"/:sessionId/files/stat\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const paths = body.paths;\n if (\n !Array.isArray(paths) ||\n paths.length > STAT_MAX_PATHS ||\n !paths.every((p) => typeof p === \"string\" && p.length <= STAT_MAX_PATH_LEN)\n ) {\n throw badRequest(\n `paths must be an array of strings (≤${STAT_MAX_PATHS} items, each ≤${STAT_MAX_PATH_LEN} characters).`,\n );\n }\n const existing = await deps.workspaceFiles.statExisting(row.workspace, paths as string[]);\n return c.json({ existing } satisfies FilesStatResponse);\n });\n\n app.put(\"/:sessionId/files/content\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n const body = await readJson(c);\n if (typeof body.dataBase64 !== \"string\") {\n throw badRequest(\"dataBase64 must be a base64 string.\");\n }\n const data = Buffer.from(body.dataBase64, \"base64\");\n if (data.length > MAX_UPLOAD_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"Uploaded file exceeds the 14MB limit.\");\n }\n await deps.workspaceFiles.write(row.workspace, rel, data);\n return c.body(null, 204);\n });\n\n app.get(\"/:sessionId/traces\", async (c) => {\n const row = resolveSession(c);\n const files = await deps.traceService.listTraceFiles(row.projectId, row.agentId, row.sessionId);\n return c.json({ files });\n });\n\n app.get(\"/:sessionId/traces/:index\", async (c) => {\n const row = resolveSession(c);\n const index = positiveIntParam(c, \"index\");\n const { offset, limit } = paginationQuery(c);\n return c.json(\n await deps.traceService.readEvents(\n row.projectId,\n row.agentId,\n row.sessionId,\n index,\n offset,\n limit,\n ),\n );\n });\n\n app.get(\"/:sessionId/traces/:index/analysis\", async (c) => {\n const row = resolveSession(c);\n const index = positiveIntParam(c, \"index\");\n return c.json(\n await deps.traceService.analyze(row.projectId, row.agentId, row.sessionId, index),\n );\n });\n\n return app;\n}\n","/**\n * Workspace validation.\n *\n * When a user explicitly specifies a Workspace: after realpath normalization\n * (resolving `..` and symlinks), it's required to be an **existing directory**\n * (never auto-created). The directory location is not constrained to the\n * Project directory — a Workspace can be any path on the server, with actual\n * reachability governed by the file permissions of the OS account running the\n * service.\n * When no Workspace is specified, this module isn't involved (the SDK creates its\n * own temporary directory).\n */\nimport fs from \"node:fs/promises\";\nimport { HttpError } from \"../http/errors.js\";\n\n/**\n * Validates and returns the normalized (realpath) Workspace path.\n *\n * @throws 400 workspace_not_found: the path doesn't exist, isn't readable, or isn't a directory.\n */\nexport async function assertWorkspaceAllowed(args: { workspace: string }): Promise<string> {\n let ws: string;\n try {\n ws = await fs.realpath(args.workspace);\n } catch {\n throw new HttpError(\n 400,\n \"workspace_not_found\",\n `Workspace does not exist or is inaccessible: ${args.workspace}. Specify an existing directory, or leave it empty to use a temporary directory.`,\n );\n }\n const stat = await fs.stat(ws);\n if (!stat.isDirectory()) {\n throw new HttpError(\n 400,\n \"workspace_not_found\",\n `Workspace is not a directory: ${args.workspace}.`,\n );\n }\n return ws;\n}\n","/**\n * Workspace file browsing: list directory / read\n * file (preview & download) / write file (upload). Security: a relative path, once\n * resolved, must stay inside the Workspace — a logical prefix check plus a realpath\n * check against the nearest existing ancestor (guards against `..` and symlink escapes).\n */\nimport fs from \"node:fs/promises\";\nimport { constants as fsc } from \"node:fs\";\nimport path from \"node:path\";\nimport type { WorkspaceFilesResponse } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\n\n/** Per-file read cap (a safety limit since preview/download reads the whole file into memory). */\nconst MAX_READ_BYTES = 50 * 1024 * 1024;\n/** Upload cap (stays within the 20MB request body limit even after base64 encoding). */\nexport const MAX_UPLOAD_BYTES = 14 * 1024 * 1024;\n\nconst CONTENT_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".htm\": \"text/html; charset=utf-8\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".md\": \"text/markdown; charset=utf-8\",\n \".json\": \"application/json\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".ts\": \"text/plain; charset=utf-8\",\n \".tsx\": \"text/plain; charset=utf-8\",\n \".py\": \"text/plain; charset=utf-8\",\n \".sh\": \"text/plain; charset=utf-8\",\n \".yaml\": \"text/plain; charset=utf-8\",\n \".yml\": \"text/plain; charset=utf-8\",\n \".toml\": \"text/plain; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".csv\": \"text/plain; charset=utf-8\",\n \".log\": \"text/plain; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".pdf\": \"application/pdf\",\n};\n\nexport interface WorkspaceFileContent {\n data: Buffer;\n fileName: string;\n contentType: string;\n /** Types whose same-origin inline rendering would execute scripts (html/svg): inline preview must fall back to plain text. */\n scriptable: boolean;\n}\n\nexport class WorkspaceFilesService {\n /** Canonical path (realpath) of the Workspace root; 404 if it doesn't exist. */\n private async realBase(workspace: string): Promise<string> {\n try {\n return await fs.realpath(path.resolve(workspace));\n } catch {\n throw new HttpError(404, \"workspace_missing\", \"This Session's Workspace no longer exists.\");\n }\n }\n\n /**\n * Lexical containment check: whether target is inside base (including equal to\n * base). Uses path.relative rather than prefix concatenation, so it works when\n * base is the filesystem root (\"/\" concatenated with sep would produce a \"//\"\n * prefix that no subpath could ever match); only a full \"..\" segment is\n * compared, so a legitimate name like \"..foo\" isn't mistakenly rejected.\n */\n private isInside(target: string, base: string): boolean {\n const rel = path.relative(base, target);\n return rel !== \"..\" && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);\n }\n\n /** Lexical prefix check (a relative path, once resolved, must still be inside the Workspace); returns the absolute target path. */\n private lexicalTarget(base: string, rel: string): string {\n if (rel.includes(\"\\0\")) throw badRequest(\"path is invalid.\");\n const target = path.resolve(base, rel === \"\" ? \".\" : rel);\n if (!this.isInside(target, base)) {\n throw badRequest(\"path must be inside the Workspace.\");\n }\n return target;\n }\n\n private assertInside(real: string, realBase: string): void {\n if (!this.isInside(real, realBase)) {\n throw badRequest(\"path must be inside the Workspace.\");\n }\n }\n\n /**\n * Read-path resolution: realpath the entire path (following all symlinks to get\n * a link-free canonical path), then check containment and **perform IO on the\n * canonical path** — since the canonical path contains no symlink segments at\n * all, this eliminates check-then-use TOCTOU escapes (an out-of-bounds symlink\n * is already resolved and rejected at the realpath step).\n */\n private async resolveRead(workspace: string, rel: string): Promise<string> {\n const realBase = await this.realBase(workspace);\n const target = this.lexicalTarget(path.resolve(workspace), rel);\n let canonical: string;\n try {\n canonical = await fs.realpath(target);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new HttpError(404, \"path_not_found\", \"File does not exist.\");\n }\n throw err;\n }\n this.assertInside(canonical, realBase);\n return canonical;\n }\n\n /**\n * Write-path resolution: realpaths the parent directory (whose canonical path\n * has no symlink segments) and checks containment, then appends the final\n * segment as the file name. When the parent directory is missing, it is safely\n * created (uploading a folder needs to preserve directory structure): first the\n * nearest **existing** ancestor is found and its canonical path checked against\n * the Workspace — this exposes it if a middle segment was preset as a symlink\n * pointing outside; the missing segments are then created recursively beneath it\n * (a brand-new directory can never be a symlink), followed by a second realpath\n * check after creation. The actual write opens with O_NOFOLLOW (refusing to\n * follow a symlink at the final segment), blocking the sandbox-escape pattern of\n * \"Agent presets a symlink -> an upload is used as leverage to overwrite a file\n * outside the sandbox\". Returns the canonical parent directory + file name.\n */\n private async resolveWriteParent(\n workspace: string,\n rel: string,\n ): Promise<{ dir: string; name: string }> {\n const realBase = await this.realBase(workspace);\n const target = this.lexicalTarget(path.resolve(workspace), rel);\n const name = path.basename(target);\n if (name === \"\" || name === \".\" || name === \"..\") throw badRequest(\"path must be a file path.\");\n const parent = path.dirname(target);\n let canonicalParent: string;\n try {\n canonicalParent = await fs.realpath(parent);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n let probe = parent;\n while (true) {\n try {\n this.assertInside(await fs.realpath(probe), realBase);\n break;\n } catch (probeErr) {\n if ((probeErr as NodeJS.ErrnoException).code !== \"ENOENT\") throw probeErr;\n const up = path.dirname(probe);\n if (up === probe) throw badRequest(\"path is invalid.\");\n probe = up;\n }\n }\n await fs.mkdir(parent, { recursive: true });\n canonicalParent = await fs.realpath(parent);\n }\n this.assertInside(canonicalParent, realBase);\n return { dir: canonicalParent, name };\n }\n\n /**\n * Batch existence check (a message's file card lists only files that actually\n * exist): each item goes through the same containment resolution as reading\n * (resolveRead); out-of-bounds, resolution failure, missing Workspace, or an\n * irregular file are all treated as non-existent — the card scenario only asks\n * \"can this be opened\", and throwing a 4xx would only add frontend branches while\n * leaking containment details. Returns the deduplicated existing items in input order.\n */\n async statExisting(workspace: string, rels: string[]): Promise<string[]> {\n const unique = [...new Set(rels)];\n const exists = await Promise.all(\n unique.map(async (rel) => {\n try {\n const stat = await fs.stat(await this.resolveRead(workspace, rel));\n return stat.isFile();\n } catch {\n return false;\n }\n }),\n );\n return unique.filter((_, i) => exists[i]);\n }\n\n /** List a directory: dirs come first, each group sorted by name; kind follows the symlink target (consistent with read behavior). */\n async list(workspace: string, rel: string): Promise<WorkspaceFilesResponse> {\n const dir = await this.resolveRead(workspace, rel);\n let dirents;\n try {\n dirents = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new HttpError(404, \"path_not_found\", \"Directory does not exist.\");\n }\n if ((err as NodeJS.ErrnoException).code === \"ENOTDIR\") {\n throw badRequest(\"path is not a directory.\");\n }\n throw err;\n }\n const entries = await Promise.all(\n dirents.map(async (d) => {\n let sizeBytes = 0;\n let mtime = \"\";\n // Dirent doesn't report the target type for a symlink, so stat (following the link) is used to determine dir/file.\n let isDir = d.isDirectory();\n try {\n const stat = await fs.stat(path.join(dir, d.name));\n sizeBytes = stat.size;\n mtime = stat.mtime.toISOString();\n isDir = stat.isDirectory();\n } catch {\n // A dangling symlink or similar: keep the entry, with size/time left at defaults.\n }\n return {\n name: d.name,\n kind: isDir ? (\"dir\" as const) : (\"file\" as const),\n sizeBytes,\n mtime,\n };\n }),\n );\n entries.sort((a, b) =>\n a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === \"dir\" ? -1 : 1,\n );\n return { path: rel, entries };\n }\n\n /** Read a file (preview/download): IO on the canonical path (resolveRead has already eliminated symlink escapes). */\n async read(workspace: string, rel: string): Promise<WorkspaceFileContent> {\n const file = await this.resolveRead(workspace, rel);\n let stat;\n try {\n stat = await fs.stat(file);\n } catch {\n throw new HttpError(404, \"path_not_found\", \"File does not exist.\");\n }\n if (stat.isDirectory()) throw badRequest(\"path is a directory.\");\n if (stat.size > MAX_READ_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"File exceeds the 50MB read limit.\");\n }\n const data = await fs.readFile(file);\n const ext = path.extname(file).toLowerCase();\n return {\n data,\n fileName: path.basename(file),\n contentType: CONTENT_TYPES[ext] ?? \"application/octet-stream\",\n scriptable: ext === \".html\" || ext === \".htm\" || ext === \".svg\",\n };\n }\n\n /**\n * Write a file (upload, overwriting a same-named one). If the parent directory\n * is missing, it's automatically created under sandbox checks (preserving\n * directory structure for folder uploads); the final segment is opened with\n * O_NOFOLLOW, refusing to follow a symlink to write outside the Workspace\n * (together with resolveWriteParent's canonical-parent check, this blocks\n * sandbox escapes).\n */\n async write(workspace: string, rel: string, data: Buffer): Promise<void> {\n if (rel === \"\" || rel.endsWith(\"/\")) throw badRequest(\"path must be a file path.\");\n if (data.length > MAX_UPLOAD_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"Uploaded file exceeds the 14MB limit.\");\n }\n const { dir, name } = await this.resolveWriteParent(workspace, rel);\n const file = path.join(dir, name);\n // O_NOFOLLOW: open reports ELOOP if the final segment is a symlink, refusing to use it as leverage to overwrite a file outside the sandbox.\n const flags = fsc.O_WRONLY | fsc.O_CREAT | fsc.O_TRUNC | (fsc.O_NOFOLLOW ?? 0);\n let handle;\n try {\n handle = await fs.open(file, flags, 0o644);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ELOOP\") throw badRequest(\"path must not be a symlink.\");\n if (code === \"ENOENT\")\n throw new HttpError(404, \"path_not_found\", \"Parent directory does not exist.\");\n if (code === \"EISDIR\") throw badRequest(\"path is a directory.\");\n throw err;\n }\n try {\n await handle.writeFile(data);\n } finally {\n await handle.close();\n }\n }\n}\n","/**\n * SSE event channel.\n *\n * The Session channel and the user channel share this implementation:\n * - Event id is an opaque string `<epoch>-<seq>`: epoch is a random short string\n * generated when each Channel instance is created, seq is a monotonically increasing\n * integer within the channel. epoch necessarily changes when the channel is\n * recycled/recreated or the process restarts, so a stale Last-Event-ID always misses\n * and falls through to resync — this prevents a silent false-hit event loss when the\n * new epoch's event count happens to exceed the old id;\n * - A bounded ring buffer (most recent 10,000 entries or 8MB, whichever comes first,\n * evicting the oldest on overflow) serves replay-on-reconnect via `Last-Event-ID`;\n * an evicted/unknown id is handled by the caller sending `resync_required`;\n * - Unicast (sendTo) is used for one-off replay at subscribe time (pending approvals /\n * resync / hello): it consumes a seq number but doesn't enter the buffer or get\n * broadcast — if that subscriber later reconnects with this id, the hit check is\n * still safe (seq is monotonic).\n *\n * This module only handles event numbering / buffering / dispatch, not HTTP — SSE\n * output is adapted at the routing layer.\n * Docs: /docs/server-api § \"Delivery Guarantees\".\n */\nimport { randomUUID } from \"node:crypto\";\n\n/** A numbered channel event; `id` is `<epoch>-<seq>`, `data` is serialized single-line JSON. */\nexport interface ChannelEvent {\n id: string;\n /** SSE event name; omitted (OmniMessage) means no `event:` line. */\n event?: string;\n data: string;\n}\n\nexport type ChannelListener = (evt: ChannelEvent) => void;\n\nexport interface ChannelOptions {\n maxBufferCount?: number;\n maxBufferBytes?: number;\n}\n\n// Sized so a mid-stream reconnect during a fast large-code reply still hits replay instead of\n// resync_required: the server publishes one event per provider delta (a 240KB reply ≈ 5k events\n// / 1.4MB), and 1000 events covered only ~6s of such a stream — every longer blip forced a full\n// client-side history rebuild.\nconst DEFAULT_MAX_COUNT = 10_000;\nconst DEFAULT_MAX_BYTES = 8 * 1024 * 1024;\n\n/** Buffered entry: seq is stored separately so hit checks never need to parse the string id. */\ninterface BufferedEvent {\n seq: number;\n evt: ChannelEvent;\n}\n\nexport class Channel {\n /** Channel epoch: generated at instance creation, prefixed onto event ids (necessarily changes after recycle/recreate or restart). */\n readonly epoch: string = randomUUID().slice(0, 8);\n private nextSeq = 1;\n private buffer: BufferedEvent[] = [];\n private bufferBytes = 0;\n /** Max seq among evicted events (0 means never evicted): lower bound for hit checks. */\n private lastEvictedSeq = 0;\n private readonly listeners = new Set<ChannelListener>();\n private readonly maxCount: number;\n private readonly maxBytes: number;\n /** Timestamp of last activity (publish/subscription change), used for idle-reclaim checks. */\n lastActivityMs = Date.now();\n\n constructor(opts: ChannelOptions = {}) {\n this.maxCount = opts.maxBufferCount ?? DEFAULT_MAX_COUNT;\n this.maxBytes = opts.maxBufferBytes ?? DEFAULT_MAX_BYTES;\n }\n\n get subscriberCount(): number {\n return this.listeners.size;\n }\n\n /** Broadcast an event: number it, buffer it (evicting the oldest), notify all subscribers. */\n publish(data: unknown, event?: string): ChannelEvent {\n const entry = this.makeEvent(data, event);\n this.buffer.push(entry);\n this.bufferBytes += entry.evt.data.length;\n while (\n this.buffer.length > 0 &&\n (this.buffer.length > this.maxCount || this.bufferBytes > this.maxBytes)\n ) {\n const evicted = this.buffer.shift()!;\n this.bufferBytes -= evicted.evt.data.length;\n this.lastEvictedSeq = Math.max(this.lastEvictedSeq, evicted.seq);\n }\n for (const listener of this.listeners) listener(entry.evt);\n return entry.evt;\n }\n\n /** Unicast an event to a single subscriber: consumes a seq but doesn't buffer or broadcast (used for replay at subscribe time). */\n sendTo(listener: ChannelListener, data: unknown, event?: string): ChannelEvent {\n const entry = this.makeEvent(data, event);\n listener(entry.evt);\n return entry.evt;\n }\n\n subscribe(listener: ChannelListener): () => void {\n this.listeners.add(listener);\n this.lastActivityMs = Date.now();\n return () => {\n this.listeners.delete(listener);\n this.lastActivityMs = Date.now();\n };\n }\n\n /**\n * Compute replay from a Last-Event-ID (`<epoch>-<seq>`): a mismatched epoch (channel\n * recycled/recreated, process restarted, or malformed id) always misses; a matching\n * epoch hits the buffer (if no events after that seq have been evicted and the seq was\n * indeed assigned by this channel) and returns the buffered events after it; otherwise\n * miss (the caller should send `resync_required` first).\n */\n replayAfter(lastEventId: string): { hit: boolean; events: ChannelEvent[] } {\n const sep = lastEventId.lastIndexOf(\"-\");\n if (sep <= 0) return { hit: false, events: [] };\n const epoch = lastEventId.slice(0, sep);\n const seq = Number.parseInt(lastEventId.slice(sep + 1), 10);\n if (epoch !== this.epoch || !Number.isInteger(seq) || seq < 0) {\n return { hit: false, events: [] };\n }\n const hit = seq >= this.lastEvictedSeq && seq < this.nextSeq;\n if (!hit) return { hit: false, events: [] };\n return { hit: true, events: this.buffer.filter((e) => e.seq > seq).map((e) => e.evt) };\n }\n\n private makeEvent(data: unknown, event?: string): BufferedEvent {\n this.lastActivityMs = Date.now();\n const serialized = typeof data === \"string\" ? data : JSON.stringify(data);\n const seq = this.nextSeq++;\n const evt: ChannelEvent = { id: `${this.epoch}-${seq}`, data: serialized };\n if (event !== undefined) evt.event = event;\n return { seq, evt };\n }\n}\n\nconst DEFAULT_IDLE_MS = 30 * 60 * 1000;\nconst SWEEP_INTERVAL_MS = 60 * 1000;\n\nexport interface ChannelHubOptions {\n idleMs?: number;\n /**\n * Active check: keys for which this returns true are excluded from idle reclaim (app\n * assembly injects `manager.statusOf(key) !== \"idle\"`, so a running/compacting\n * Session channel is never reclaimed no matter how long since its last publish; a\n * user channel key looks like `user:<id>` and is always considered active).\n */\n isActive?: (key: string) => boolean;\n}\n\n/**\n * Channel collection: lazily created by key (Session id or `user:<user_id>`);\n * a channel whose Session is idle and has had no subscribers for over 30 minutes is\n * reclaimed, releasing its buffer as well.\n */\nexport class ChannelHub {\n private readonly channels = new Map<string, Channel>();\n private readonly timer: NodeJS.Timeout;\n private readonly idleMs: number;\n private readonly isActive: (key: string) => boolean;\n\n constructor(opts: ChannelHubOptions = {}) {\n this.idleMs = opts.idleMs ?? DEFAULT_IDLE_MS;\n this.isActive = opts.isActive ?? (() => false);\n this.timer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);\n this.timer.unref?.();\n }\n\n get(key: string): Channel {\n let ch = this.channels.get(key);\n if (!ch) {\n ch = new Channel();\n this.channels.set(key, ch);\n }\n return ch;\n }\n\n peek(key: string): Channel | undefined {\n return this.channels.get(key);\n }\n\n /** Reclaim idle channels (skips active Sessions: no reclaim even without a publish while awaiting approval); `now` is injectable for tests. */\n sweep(now: number = Date.now()): void {\n for (const [key, ch] of this.channels) {\n if (this.isActive(key)) continue;\n if (ch.subscriberCount === 0 && now - ch.lastActivityMs > this.idleMs) {\n this.channels.delete(key);\n }\n }\n }\n\n dispose(): void {\n clearInterval(this.timer);\n this.channels.clear();\n }\n}\n","/**\n * Local date helpers (same convention as core's Trace date directories: local timezone\n * yyyy-mm-dd). Shared by the usage_records.date aggregation key and stats windows\n * (today / last 7 days / last 30 days).\n */\n\n/** Format a time as a local `yyyy-mm-dd` (4-digit year, zero-padded 2-digit month/day). */\nexport function formatLocalDate(date: Date): string {\n const year = date.getFullYear().toString().padStart(4, \"0\");\n const month = (date.getMonth() + 1).toString().padStart(2, \"0\");\n const day = date.getDate().toString().padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/** Subtract N days from a local date (used for the start of the last-7-days / last-30-days windows). */\nexport function localDateMinusDays(date: Date, days: number): string {\n const d = new Date(date);\n d.setDate(d.getDate() - days);\n return formatLocalDate(d);\n}\n","/**\n * Error persistence: errors caught on the server are all\n * written to error_records through here, for display on the stats dashboard. Shape\n * mirrors usage-recorder — persist only raw facts, leave aggregation to query time.\n *\n * **The classification (kind) criterion is \"does a human need to step in\"**, not where\n * the error originated:\n *\n * - `expected`: anticipated by the system, has a defined handling path, part of normal\n * operation, no human needed — HTTP business errors (`HttpError`, mostly 4xx); LLM\n * `timeout` / `malformed` (the engine already reconnects and retries); tool execution\n * `failed` / `timeout` (the error is fed back to the model, and the Agent adjusts on\n * its own).\n * - `unexpected`: shouldn't happen, usually a bug or a config/environment fault,\n * **needs a human** — internal errors converged to 500; process crashes; runtime\n * errors escaping from background tasks (Session drive / usage persistence / title\n * generation / subagent registration); LLM `failed` (not retryable: auth failure,\n * invalid params, etc.).\n * - User-initiated actions **are not errors** and are never recorded: request/tool\n * `aborted` (user clicked \"stop\", or denied a tool).\n *\n * Determination: HTTP sources are inferred automatically from `HttpError` (preserving\n * existing behavior); other sources must pass `kind` explicitly at the capture site.\n * The frontend highlights unexpected by default; expected is still recorded without\n * losing information.\n *\n * Sources cover HTTP, Session drive, LLM requests, Environment (tool execution), usage\n * persistence, title generation, subagent registration, and process-level fallback;\n * among these, `llm` / `environment` errors are not expressed via throw (core converges\n * them into the message stream instead), and are fished out by stream-error-watcher from\n * the Session output stream.\n *\n * **This recorder never throws**: it's hooked onto app.onError, and throwing from\n * within it would turn error handling into infinite recursion; if persistence itself\n * fails (disk full / DB already closed, etc.), it's fine to drop that one record.\n *\n * **Short-window dedup (DEDUP_WINDOW_MS)**: error storms are the norm — someone scanning\n * the API produces a wall of 404s, or a tool fails repeatedly in a loop. Persisting each\n * one both write-amplifies and floods the table, and makes the dashboard's \"most recent\n * 20\" all the same error. So the same `(source, code, Project)` is persisted at most\n * once per window; repeats within the window are **dropped outright** (not persisted);\n * only an actual persist refreshes the timestamp, so a sustained storm leaves a steady\n * one record per window instead of being suppressed indefinitely.\n * **Tradeoff**: aggregate counts therefore **underestimate** — a storm of the same error\n * only counts once, check the logs for true frequency; in exchange, a single error storm\n * doesn't drown out error_records or the stats dashboard. The second line of defense is\n * ErrorsRepo's capacity cap. The dedup table (lastSeen) must stay bounded: past\n * DEDUP_KEYS_MAX, expired entries are cleared first, and if still over the limit the\n * whole table is cleared — better to miss some dedup than let it grow unbounded across\n * different error codes.\n */\nimport { formatLocalDate } from \"../internal/dates.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { ErrorsRepo } from \"../db/repos/errors.js\";\n\n/** Capture-site source (maps one-to-one to error_records.source). */\nexport type ErrorSource =\n | \"http\"\n | \"session\"\n | \"llm\"\n | \"environment\"\n | \"usage\"\n | \"title\"\n | \"subagent\"\n | \"process\"\n | \"schedule\";\n\n/** Error classification: see file header — the criterion is \"does a human need to step in\". */\nexport type ErrorKind = \"expected\" | \"unexpected\";\n\n/** Attribution context (all optional: the login endpoint has no Project, and process-level fallback has no request at all). */\nexport interface ErrorContext {\n projectId?: string;\n agentId?: string;\n sessionId?: string;\n}\n\nexport interface ErrorRecordArgs {\n source: ErrorSource;\n /** The caught error (unknown: the value caught may not be an Error; failures from the message stream pass the reason text directly). */\n err: unknown;\n ctx?: ErrorContext;\n /** Semantic code (required for non-HTTP sources, e.g. session_run_failed); defaults to HttpError.code. */\n code?: string;\n /** HTTP status code; leave empty for non-HTTP sources. */\n status?: number;\n /** Explicit classification (see file header); defaults to inferring from `HttpError` — HTTP sources rely on this, other sources should pass it explicitly. */\n kind?: ErrorKind;\n}\n\n/** Message truncation length (keep only a readable summary; the full stack is still logged). */\nexport const MESSAGE_MAX = 500;\n\n/** Short-window dedup window: the same (source, code, Project) is persisted at most once per window (see the file header's tradeoff). */\nexport const DEDUP_WINDOW_MS = 2000;\n\n/** Cap on dedup table keys (bounded; over the limit, expired entries are cleared first, and if still over, the whole table is cleared). */\nexport const DEDUP_KEYS_MAX = 1000;\n\nfunction messageOf(err: unknown): string {\n const raw = err instanceof Error ? err.message : String(err);\n return raw.length > MESSAGE_MAX ? raw.slice(0, MESSAGE_MAX) : raw;\n}\n\nexport class ErrorRecorder {\n /** Dedup table: `source \\0 code \\0 projectId` → timestamp of the last **persist** (see file header). */\n private readonly lastSeen = new Map<string, number>();\n\n constructor(\n private readonly errors: ErrorsRepo,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n /** Record an error (synchronous, fails silently; same-window duplicates are dropped outright, see file header). */\n record(args: ErrorRecordArgs): void {\n try {\n const http = args.err instanceof HttpError ? args.err : null;\n const now = this.now();\n const projectId = args.ctx?.projectId ?? null;\n const code = args.code ?? http?.code ?? \"internal\";\n // Short-window dedup: coarse-grained to \"same kind of error for the same Project\"; repeats within the window aren't persisted.\n if (this.deduped(`${args.source}\\0${code}\\0${projectId ?? \"\"}`, now.getTime())) return;\n this.errors.insert({\n ts: now.toISOString(),\n date: formatLocalDate(now),\n projectId,\n agentId: args.ctx?.agentId ?? null,\n sessionId: args.ctx?.sessionId ?? null,\n source: args.source,\n // Explicit classification takes priority; otherwise infer from HttpError (business error = expected, else unexpected).\n kind: args.kind ?? (http ? \"expected\" : \"unexpected\"),\n code,\n // Unexpected errors from HTTP sources are converged to 500 externally (matches handleError's response).\n status: args.status ?? http?.status ?? (args.source === \"http\" ? 500 : null),\n message: messageOf(args.err),\n });\n } catch {\n // See file header: if the recorder itself errors, dropping this one record is the only option — never rethrow.\n }\n }\n\n /** true if a same-kind error was already recorded within the window (drop it); otherwise register this persist timestamp and keep the dedup table bounded. */\n private deduped(key: string, nowMs: number): boolean {\n const last = this.lastSeen.get(key);\n if (last !== undefined && nowMs - last < DEDUP_WINDOW_MS) return true;\n this.lastSeen.set(key, nowMs);\n if (this.lastSeen.size > DEDUP_KEYS_MAX) this.evict(nowMs);\n return false;\n }\n\n /** Keep the dedup table bounded (see file header): clear expired entries first; if still over the limit (hundreds/thousands of distinct error codes erupting at once), clear it entirely. */\n private evict(nowMs: number): void {\n for (const [key, at] of this.lastSeen) {\n if (nowMs - at >= DEDUP_WINDOW_MS) this.lastSeen.delete(key);\n }\n if (this.lastSeen.size > DEDUP_KEYS_MAX) this.lastSeen.clear();\n }\n}\n\n/** Minimal dependency a capture site needs on the recorder (tests inject a fake; structurally matches SessionManager's UsageRecorderLike). */\nexport type ErrorSink = Pick<ErrorRecorder, \"record\">;\n","/**\n * Active Session runtime.\n *\n * Responsibilities:\n * - get-or-resume-or-heal: use it directly on an active-table hit; with a Trace,\n * recover via `agent.resumeSession`; a stale Session that was created but never run\n * and survived a process restart (no Trace) **self-heals** — recreated via\n * createSession using the index row's workspace/modelId, yielding a new session_id\n * and updating the index's primary key; the Task response body always returns the\n * current actual id;\n * - Vault effectiveness: a vault update bumps the Agent's config generation\n * (invalidateAgentRuntimes); runtimes built earlier are discarded on their next\n * idle access and re-resumed, so the next Task always runs with current values;\n * - Per-Session mutual exclusion: only one Task/compaction may be in progress at a\n * time;\n * - run/compact drive: consumes the output stream in the background, publishing each\n * message to the SSE channel and handing it to usage-recorder for persistence;\n * on completion (including errors) resets to idle and pushes a `task_state` server\n * event;\n * - Approval registration and interrupt convergence: each approval decision re-reads\n * approval_mode from the DB (takes effect immediately); an interrupt first\n * converges pending approvals to deny, then aborts.\n *\n * The underlying implementation of get-or-resume-or-heal is injected via\n * `SessionLoader`: production uses the core SDK (createCoreSessionLoader), tests inject\n * a fake Session (issuing no real LLM requests).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n createAgent,\n findLatestTraceFile,\n isSessionMeta,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type {\n ApproveFn,\n CompactAvailability,\n OmniMessage,\n SessionMetaPayload,\n SessionTitleResult,\n TextPayload,\n ThinkingLevelName,\n} from \"@prismshadow/penguin-core\";\nimport type { ServerEvent, SessionStatus } from \"../api/types.js\";\nimport { HttpError, isMissingCredential, modelCredentialMissing } from \"../http/errors.js\";\nimport type { SessionRow, SessionsRepo } from \"../db/repos/sessions.js\";\nimport { ApprovalRegistry, makeApprove } from \"./approvals.js\";\nimport type { PendingApproval } from \"./approvals.js\";\nimport type { ChannelHub } from \"./channel.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport { asSessionSource } from \"./session-sources.js\";\nimport type { SessionSources } from \"./session-sources.js\";\nimport { StreamErrorWatcher } from \"./stream-error-watcher.js\";\nimport type { TitleNotifier } from \"./title-generator.js\";\nimport type { UsageContext } from \"./usage-recorder.js\";\n\n/** 409 for when there's nothing to compact: give the specific reason rather than a one-size-fits-none message. */\nfunction compactUnavailable(why: Exclude<CompactAvailability, \"ok\">): HttpError {\n const messages: Record<typeof why, string> = {\n unsupported: \"This Agent does not have context compaction configured.\",\n empty: \"The current context has nothing to compact (no completed conversation turns yet).\",\n just_compacted:\n \"The context was just compacted and there is no new conversation since; no need to compact again.\",\n };\n return new HttpError(409, \"nothing_to_compact\", messages[why]);\n}\n\n/** Minimal interface for a runtime Session (satisfied by core Session; tests may inject a fake implementation). */\nexport interface RuntimeSession {\n readonly sessionId: string;\n run(\n newMessages: OmniMessage[],\n opts: { approve: ApproveFn; signal: AbortSignal; thinkingLevel?: ThinkingLevelName },\n ): AsyncGenerator<OmniMessage>;\n compact(opts: { signal: AbortSignal }): AsyncGenerator<OmniMessage>;\n /** Whether compaction is possible and why; when not ok, compact() yields no messages (see core ContextEngine.compactability). */\n compactability(): CompactAvailability;\n /** Queues a mid-run steering message (core `Session.steer`); false when no Task is running. */\n steer(text: string): boolean;\n toolPermission(name: string): \"r\" | \"rw\" | undefined;\n /**\n * Out-of-band one-shot request for title generation (core `Session.generateTitle`,\n * writes no history/Trace). Material defaults to what the Session collects itself\n * (the first Task's text gathered during run); `material` overrides this for\n * subagents.\n */\n generateTitle(args?: {\n material?: { userText: string; assistantText: string };\n signal?: AbortSignal;\n }): Promise<SessionTitleResult>;\n}\n\n/** The underlying loader behind get-or-resume-or-heal. */\nexport interface SessionLoader {\n /**\n * Load a runtime Session from an index row: recover (with a Trace) or self-heal\n * rebuild (no Trace, session_id will change). Throws HttpError(409) for unrecoverable\n * cases such as a missing Workspace.\n */\n load(row: SessionRow): Promise<RuntimeSession>;\n}\n\n/**\n * Production loader: the core SDK's resumeSession / createSession. `sources` (when given)\n * lets the no-Trace self-heal rebuild re-record a known origin into the fresh session_meta;\n * with no registry entry (e.g. the process restarted and no Trace was ever written) the\n * rebuilt Session is unsourced — session_meta is the single source of truth, and none survived.\n */\nexport function createCoreSessionLoader(root: string, sources?: SessionSources): SessionLoader {\n return {\n async load(row: SessionRow): Promise<RuntimeSession> {\n const agent = await createAgent({\n root,\n projectId: row.projectId,\n agentId: row.agentId,\n });\n const located = await findLatestTraceFile(\n tracesDir(root, row.projectId, row.agentId),\n row.sessionId,\n );\n if (located) {\n // With a Trace: rebuild via \"Session Recovery\" (history injected via setHistory,\n // carrying over any residual state).\n // core's recognizable recovery failures (Workspace deleted / Model removed from\n // config / Trace missing session_meta, etc.) are converged to 409, preserving\n // the original message rather than bubbling up as 500.\n try {\n return await agent.resumeSession({ sessionId: row.sessionId });\n } catch (err) {\n // The credential key was deleted after the Session was created: only caught\n // here at recovery time; give the same actionable message.\n if (isMissingCredential(err)) throw modelCredentialMissing(row.modelId);\n throw toUnrecoverableError(err);\n }\n }\n // No Trace (created but never run, and the process has restarted since): self-heal\n // rebuild. A missing Workspace → 409.\n try {\n const stat = await fs.stat(row.workspace);\n if (!stat.isDirectory()) throw new Error(\"not a directory\");\n } catch {\n throw new HttpError(\n 409,\n \"workspace_missing\",\n `This Session's Workspace no longer exists: ${row.workspace}, so it cannot continue. Create a new Session.`,\n );\n }\n const knownSource = sources?.get(row.sessionId);\n try {\n return await agent.createSession({\n workspaceDir: row.workspace,\n modelId: row.modelId,\n provider: row.provider,\n // The rebuilt Session re-records a known origin in its fresh session_meta.\n ...(knownSource != null ? { source: knownSource } : {}),\n });\n } catch (err) {\n if (isMissingCredential(err)) throw modelCredentialMissing(row.modelId);\n throw toUnrecoverableError(err);\n }\n },\n };\n}\n\n/** A plain Error thrown by core recovery/self-heal rebuild → 409 (preserving the original, actionable message). */\nfunction toUnrecoverableError(err: unknown): HttpError {\n if (err instanceof HttpError) return err;\n return new HttpError(\n 409,\n \"session_unrecoverable\",\n err instanceof Error ? err.message : String(err),\n );\n}\n\nexport interface UsageRecorderLike {\n record(ctx: UsageContext, msg: OmniMessage): Promise<void>;\n}\n\nexport interface SessionManagerDeps {\n sessions: SessionsRepo;\n channels: ChannelHub;\n loader: SessionLoader;\n /** Session-origin registry (session_meta is the single source of truth; subagent registration records the forwarded meta's source here). */\n sources: SessionSources;\n recorder: UsageRecorderLike;\n /** Automatic Session title generation (optional: not injected in tests or when disabled). */\n titles?: TitleNotifier;\n /** Error persistence (optional: without it, only logs — same as before this was wired up). */\n errors?: ErrorSink;\n log?: (line: string) => void;\n}\n\n/** One queued follow-up task (`queueIfBusy`): the task input plus the per-turn thinking level it was posted with. */\ninterface QueuedFollowUp {\n input: OmniMessage[];\n thinkingLevel?: ThinkingLevelName;\n}\n\n/** Active-table entry: a loaded runtime Session plus its running state. */\ninterface RuntimeEntry {\n sessionId: string;\n projectId: string;\n agentId: string;\n /** Vendor grouping for the Session's model (paired with modelId to form a model reference). */\n provider: string;\n modelId: string;\n session: RuntimeSession;\n status: SessionStatus;\n approvals: ApprovalRegistry;\n abort: AbortController | null;\n /** The in-flight drive Promise (awaited during graceful shutdown). */\n running: Promise<void> | null;\n /**\n * Agent config generation this runtime was built under (see\n * invalidateAgentRuntimes): once it falls behind the Agent's current generation,\n * the entry is discarded on its next idle access and re-resumed via the loader.\n */\n generation: number;\n /**\n * Queued follow-up tasks (`queueIfBusy`): task inputs accepted while a Task/compaction was\n * in progress, auto-started one at a time (in order) once the session returns to idle —\n * ordinary tasks with normal semantics, unlike the mid-run steering queue. Each entry\n * keeps the per-turn thinkingLevel it was posted with (applied at auto-start).\n * Deliberately NOT discarded on abort: they are future tasks the user explicitly queued.\n */\n followUps: QueuedFollowUp[];\n /** Timestamp of last activity (refreshed on load / status flip / drive completion), used for idle-eviction checks. */\n lastActivityMs: number;\n}\n\n/** Active-table idle eviction: same convention as the SSE channel (an idle entry with no activity for 30 minutes releases its memory). */\nconst ENTRY_IDLE_MS = 30 * 60 * 1000;\nconst ENTRY_SWEEP_INTERVAL_MS = 60 * 1000;\n\n/** Cap on collected model text for title material (accumulation stops beyond this; the generator side also truncates further). */\nconst TITLE_EXCERPT_LIMIT = 4000;\n/**\n * Early title trigger: once this many characters of main-session body text have streamed,\n * title generation starts right away instead of waiting for the Task to finish — the core\n * Session has captured its (capped) material by then, and a long answer would only overrun\n * it. Short conversations are still covered by the completion trigger in drive's finally.\n */\nconst EARLY_TITLE_BODY_CHARS = 1000;\n\n/** Composite Agent key (used as a Set key, avoiding projectId/agentId concatenation ambiguity). */\nfunction agentKey(projectId: string, agentId: string): string {\n return `${projectId}\\0${agentId}`;\n}\n\n/** If msg is a run_subagent tool call carrying a `prompt`, return its id and prompt (for use as the subagent's title); otherwise null. */\nfunction runSubagentCall(msg: OmniMessage): { toolCallId: string; prompt: string } | null {\n const p = msg.payload as {\n type?: string;\n name?: string;\n arguments?: string;\n tool_call_id?: string;\n };\n if (msg.type !== \"model_msg\" || p.type !== \"tool_call\" || p.name !== \"run_subagent\") return null;\n if (typeof p.arguments !== \"string\" || typeof p.tool_call_id !== \"string\") return null;\n try {\n const args = JSON.parse(p.arguments) as { prompt?: unknown };\n if (typeof args.prompt !== \"string\" || !args.prompt.trim()) return null;\n return { toolCallId: p.tool_call_id, prompt: args.prompt };\n } catch {\n return null; // Arguments were truncated/malformed: this call is doomed, no subagent will result\n }\n}\n\n/** The denied tool_call_id (approval_decision with decision ≠ allow); otherwise null. */\nfunction deniedToolCallId(msg: OmniMessage): string | null {\n const p = msg.payload as { type?: string; decision?: string; tool_call_id?: string };\n if (msg.type !== \"event_msg\" || p.type !== \"approval_decision\") return null;\n if (p.decision === \"allow\" || typeof p.tool_call_id !== \"string\") return null;\n return p.tool_call_id;\n}\n\n/** The tool_call_id of a parent-level tool call that has settled (a complete tool_call_output); otherwise null. */\nfunction settledToolCallId(msg: OmniMessage): string | null {\n const p = msg.payload as { type?: string; tool_call_id?: string };\n if (msg.type !== \"model_msg\" || p.type !== \"tool_call_output\") return null;\n return typeof p.tool_call_id === \"string\" ? p.tool_call_id : null;\n}\n\n/** A subagent registered during this run, plus its title material. */\ninterface ChildSession {\n sessionId: string;\n agentId: string;\n modelId: string;\n /** The prompt of the run_subagent call that spawned it (user material for title generation, and the fallback title). */\n prompt: string;\n /** The model text the subagent itself produced (assistant material for title generation). */\n assistantExcerpt: string;\n}\n\n/** Predicate for a plain-text message on the main session (no origin): title material is drawn only from user/model text. */\nfunction isPlainText(role: \"user\" | \"assistant\") {\n return (msg: OmniMessage): msg is OmniMessage<TextPayload> => {\n const payload = msg.payload as { type?: string; role?: string };\n return (\n msg.type === \"model_msg\" &&\n payload.type === \"text\" &&\n payload.role === role &&\n (!msg.origin || msg.origin.length === 0)\n );\n };\n}\n\n/** For a nested message, the owning Session (end of the origin chain) and text of the model reply; null if it isn't model text. */\nfunction nestedAssistantText(msg: OmniMessage): { sessionId: string; text: string } | null {\n const p = msg.payload as { type?: string; role?: string; text?: string };\n if (msg.type !== \"model_msg\" || p.type !== \"text\" || p.role !== \"assistant\") return null;\n if (!msg.origin || msg.origin.length === 0 || typeof p.text !== \"string\") return null;\n return { sessionId: msg.origin[msg.origin.length - 1]!, text: p.text };\n}\n\nexport class SessionManager {\n private readonly entries = new Map<string, RuntimeEntry>();\n /** Per-Session mutex (serializes get-or-load and status flips); auto-cleaned once the chain drains. */\n private readonly locks = new Map<string, Promise<unknown>>();\n private readonly log: (line: string) => void;\n /** Graceful-shutdown flag: once set, new Tasks/compactions are rejected (503). */\n private closed = false;\n /** Agents currently being deleted (key = agentKey): new Tasks/compactions are always rejected with 409 during this window. */\n private readonly deletingAgents = new Set<string>();\n /** Sessions currently being deleted (guards against the entry/Trace file being rebuilt and reviving it inside the deletion race window). */\n private readonly deletingSessions = new Set<string>();\n /** Per-Agent config generation (key = agentKey), bumped by invalidateAgentRuntimes on vault updates. */\n private readonly agentGenerations = new Map<string, number>();\n private readonly sweepTimer: NodeJS.Timeout;\n\n constructor(private readonly deps: SessionManagerDeps) {\n this.log = deps.log ?? ((line) => console.error(line));\n this.sweepTimer = setInterval(() => this.sweepIdle(), ENTRY_SWEEP_INTERVAL_MS);\n this.sweepTimer.unref?.();\n }\n\n // —— Query surface (used by Session listing / Agent active-count / SSE subscription replay) ——\n\n statusOf(sessionId: string): SessionStatus {\n return this.entries.get(sessionId)?.status ?? \"idle\";\n }\n\n pendingApprovalCount(sessionId: string): number {\n return this.entries.get(sessionId)?.approvals.size ?? 0;\n }\n\n pendingApprovals(sessionId: string): PendingApproval[] {\n return this.entries.get(sessionId)?.approvals.list() ?? [];\n }\n\n /** Number of queued follow-up tasks (`queueIfBusy`) awaiting auto-start. */\n pendingFollowUpCount(sessionId: string): number {\n return this.entries.get(sessionId)?.followUps.length ?? 0;\n }\n\n /** Number of Sessions for this Agent that are currently running / compacting. */\n activeCountForAgent(projectId: string, agentId: string): number {\n let n = 0;\n for (const e of this.entries.values()) {\n if (e.projectId === projectId && e.agentId === agentId && e.status !== \"idle\") n++;\n }\n return n;\n }\n\n /** Add a newly created Session to the active table (status idle), avoiding a redundant load on the next Task. */\n adopt(row: SessionRow, session: RuntimeSession): void {\n this.entries.set(row.sessionId, {\n sessionId: row.sessionId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n session,\n status: \"idle\",\n approvals: new ApprovalRegistry(),\n abort: null,\n running: null,\n generation: this.generationOf(row.projectId, row.agentId),\n followUps: [],\n lastActivityMs: Date.now(),\n });\n }\n\n /**\n * After an Agent's vault is updated: bump the Agent's config generation so every\n * runtime built before the update is discarded on its next idle access and\n * re-resumed via the loader — resume re-reads agent_state/.vault.toml, so the next\n * Task on any of this Agent's Sessions runs with the new values (history is\n * preserved through the Trace). A Task already in flight is neither aborted nor\n * hot-swapped: it keeps the values it started with, and its entry is rebuilt on\n * the first access after it returns to idle (see ensureEntry).\n */\n invalidateAgentRuntimes(projectId: string, agentId: string): void {\n const key = agentKey(projectId, agentId);\n this.agentGenerations.set(key, this.generationOf(projectId, agentId) + 1);\n }\n\n // —— Task / compaction drive ——\n\n /**\n * Start a Task: get-or-load → 409\n * mutual-exclusion check → publish the input messages first → drive run in the\n * background. Returns the current actual session_id (the new id after self-heal).\n * `opts.thinkingLevel` (optional, validated by the route) rides into this run's\n * `session.run` options — a per-turn parameter, applied to this Task only.\n * With `queueIfBusy`, a busy session (running/compacting) enqueues the input as a\n * follow-up instead of 409: it auto-starts as an ordinary next task once the session\n * returns to idle (`queued: true` in the result; see startQueuedFollowUp), keeping its\n * thinkingLevel for that auto-start.\n */\n async startTask(\n sessionId: string,\n input: OmniMessage[],\n opts?: { thinkingLevel?: ThinkingLevelName; queueIfBusy?: boolean },\n ): Promise<{ sessionId: string; queued: boolean }> {\n return this.withLock(sessionId, async () => {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = await this.ensureEntry(sessionId);\n if (entry.status !== \"idle\" && opts?.queueIfBusy) {\n entry.followUps.push({\n input,\n ...(opts.thinkingLevel !== undefined ? { thinkingLevel: opts.thinkingLevel } : {}),\n });\n entry.lastActivityMs = Date.now();\n // Re-publish the current state so subscribers pick up the new queued count (the\n // input itself is published when the follow-up actually starts).\n this.publishState(entry, entry.status);\n return { sessionId: entry.sessionId, queued: true };\n }\n this.assertIdle(entry);\n this.launchTask(entry, input, opts?.thinkingLevel);\n return { sessionId: entry.sessionId, queued: false };\n });\n }\n\n /** Shared task launch (fresh tasks and auto-started follow-ups): flips to running, publishes the input, and drives the run with the per-turn thinking level (if any). Caller holds the session lock and has verified idle. */\n private launchTask(\n entry: RuntimeEntry,\n input: OmniMessage[],\n thinkingLevel?: ThinkingLevelName,\n ): void {\n const channel = this.deps.channels.get(entry.sessionId);\n const ac = new AbortController();\n entry.status = \"running\";\n entry.abort = ac;\n entry.lastActivityMs = Date.now();\n // Publish the input messages first (visible to other subscribers; the Trace is\n // persisted by the SDK), then flip the running status.\n for (const msg of input) channel.publish(msg);\n this.publishState(entry, \"running\");\n\n const approve = makeApprove({\n // Re-reads approval_mode from the DB on every decision (a PATCH takes effect immediately).\n getMode: () => this.deps.sessions.findById(entry.sessionId)?.approvalMode ?? \"always-ask\",\n toolPermission: (name) => entry.session.toolPermission(name),\n registry: entry.approvals,\n publishRequest: (pending) =>\n this.publishEvent(entry, {\n type: \"approval_request\",\n toolCall: pending.toolCall,\n ...(pending.origin !== undefined ? { origin: pending.origin } : {}),\n }),\n });\n const gen = entry.session.run(input, {\n approve,\n signal: ac.signal,\n ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),\n });\n // Title material is collected by the core Session itself during run; here we only\n // keep this call's input user text, used both as the \"material present → attempt\n // generation\" criterion and as the fallback title source if the LLM call fails.\n const userExcerpt = input\n .filter(isPlainText(\"user\"))\n .map((m) => m.payload.text)\n .join(\"\\n\");\n entry.running = this.drive(entry, gen, { userExcerpt });\n }\n\n /**\n * Auto-start of queued follow-ups: called from drive's finally whenever a Task or\n * compaction finishes (abort included — follow-ups are future tasks the user queued, so\n * an abort of the current run does not discard them). Re-validates everything under the\n * session lock (another task may have snuck in, the server may be shutting down, the\n * session may be getting deleted) and launches exactly one queued input — the next\n * finish picks up the one after, keeping order and one-at-a-time semantics.\n */\n private async startQueuedFollowUp(sessionId: string): Promise<void> {\n try {\n await this.withLock(sessionId, async () => {\n if (this.closed || this.deletingSessions.has(sessionId)) return;\n const row = this.deps.sessions.findById(sessionId);\n if (row && this.deletingAgents.has(agentKey(row.projectId, row.agentId))) return;\n const entry = this.entries.get(sessionId);\n if (!entry || entry.status !== \"idle\" || entry.followUps.length === 0) return;\n const next = entry.followUps.shift()!;\n this.launchTask(entry, next.input, next.thinkingLevel);\n });\n } catch (err) {\n this.log(`[followup] auto-start failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n /** Manually compact the context: 409 if already running; compaction output also flows into the SSE channel. */\n async startCompact(sessionId: string): Promise<{ sessionId: string }> {\n return this.withLock(sessionId, async () => {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = await this.ensureEntry(sessionId);\n this.assertIdle(entry);\n // When there's nothing to compact, core's compact() yields no messages at all: we\n // can't just return 202 and walk away, or the frontend would wait forever for a\n // compaction banner that never comes (this is exactly the \"/compact does nothing\n // after an interrupt\" complaint). Reject explicitly, and **say why** clearly —\n // \"just compacted\" and \"haven't talked yet\" share the same internal state\n // (sessionTurns === 0), but are two completely different messages to the user:\n // telling someone who just compacted that there's \"no completed conversation turn\n // yet\" tells them nothing.\n const why = entry.session.compactability();\n if (why !== \"ok\") throw compactUnavailable(why);\n const ac = new AbortController();\n entry.status = \"compacting\";\n entry.abort = ac;\n entry.lastActivityMs = Date.now();\n this.publishState(entry, \"compacting\");\n const gen = entry.session.compact({ signal: ac.signal });\n entry.running = this.drive(entry, gen);\n return { sessionId: entry.sessionId };\n });\n }\n\n /** Submit an approval decision; returns false if the pending approval doesn't exist (already decided/unknown). */\n decideApproval(sessionId: string, toolCallId: string, decision: \"allow\" | \"deny\"): boolean {\n const entry = this.entries.get(sessionId);\n if (!entry) return false;\n return entry.approvals.decide(toolCallId, decision);\n }\n\n /**\n * Mid-run steering: forward the text to the running Session (core delivers it between\n * turns as a standalone `[user_steering]` user message — no SSE event of its own; the\n * message arrives through the stream the drive loop already publishes). 409 when the\n * Session isn't running a Task (idle / compacting / not loaded) or the run finished in the\n * race window — the caller falls back to submitting a normal task.\n */\n steer(sessionId: string, text: string): void {\n const entry = this.entries.get(sessionId);\n if (!entry || entry.status !== \"running\" || !entry.session.steer(text)) {\n throw new HttpError(\n 409,\n \"not_running\",\n \"This Session has no Task in progress; send the message as a new task instead.\",\n );\n }\n entry.lastActivityMs = Date.now();\n }\n\n /**\n * Interrupt the current Task/compaction: pending approvals converge to deny first,\n * then the AbortSignal fires. Returns false if nothing is in progress (the route\n * treats this as a 204 no-op).\n */\n abortTask(sessionId: string): boolean {\n const entry = this.entries.get(sessionId);\n if (!entry || !entry.abort) return false;\n entry.approvals.denyAll();\n entry.abort.abort();\n return true;\n }\n\n /**\n * Before deleting a Project, converge all its active runs and clear them out of the\n * active table. Returns the in-flight drive Promises of the affected entries: the\n * caller (deleteProject) should await them before removing the directory, so that\n * interrupt-cleanup Trace writes don't recreate the directory after deletion.\n */\n abortProject(projectId: string): Promise<void>[] {\n const runnings: Promise<void>[] = [];\n for (const [key, entry] of [...this.entries]) {\n if (entry.projectId !== projectId) continue;\n entry.approvals.denyAll();\n entry.abort?.abort();\n if (entry.running) runnings.push(entry.running);\n this.entries.delete(key);\n }\n return runnings;\n }\n\n /**\n * Before deleting an Agent, converge all its active runs and clear them out of the\n * active table (same semantics as abortProject). Also marks this Agent as \"being\n * deleted\": new Tasks/compactions entering during the deletion process are always\n * rejected with 409 (assertAgentNotDeleting), closing the race window where a new\n * task recreates the directory and revives an already-deleted Agent between the\n * abortAgent snapshot and the directory removal. The caller must call\n * endAgentDeletion once deletion finishes (success or failure).\n */\n beginAgentDeletion(projectId: string, agentId: string): Promise<void>[] {\n this.deletingAgents.add(agentKey(projectId, agentId));\n const runnings: Promise<void>[] = [];\n for (const [key, entry] of [...this.entries]) {\n if (entry.projectId !== projectId || entry.agentId !== agentId) continue;\n entry.approvals.denyAll();\n entry.abort?.abort();\n if (entry.running) runnings.push(entry.running);\n this.entries.delete(key);\n }\n return runnings;\n }\n\n endAgentDeletion(projectId: string, agentId: string): void {\n this.deletingAgents.delete(agentKey(projectId, agentId));\n }\n\n /**\n * Before deleting a single Session, converge its active run and clear it out of the\n * active table (same semantics as beginAgentDeletion). Also marks this Session as\n * \"being deleted\": new Tasks/compactions entering during the deletion process are\n * always rejected with 409 (assertSessionNotDeleting), closing the race window where\n * a new task recreates the entry and Trace file, reviving an already-deleted Session\n * between the abort snapshot and the file removal. The caller must call\n * endSessionDeletion once deletion finishes (success or failure). Returns the\n * in-flight drive Promise: the caller should await it before deleting the Trace file,\n * so cleanup writes don't recreate the file.\n */\n beginSessionDeletion(sessionId: string): Promise<void>[] {\n this.deletingSessions.add(sessionId);\n const entry = this.entries.get(sessionId);\n if (!entry) return [];\n entry.approvals.denyAll();\n entry.abort?.abort();\n this.entries.delete(sessionId);\n return entry.running ? [entry.running] : [];\n }\n\n endSessionDeletion(sessionId: string): void {\n this.deletingSessions.delete(sessionId);\n }\n\n /** Graceful shutdown: reject new tasks (503), interrupt all active runs, and wait for them to finish (default ≤5s). */\n async shutdown(timeoutMs = 5000): Promise<void> {\n this.closed = true;\n clearInterval(this.sweepTimer);\n const pending: Promise<void>[] = [];\n for (const entry of this.entries.values()) {\n if (!entry.abort) continue;\n entry.approvals.denyAll();\n entry.abort.abort();\n if (entry.running) pending.push(entry.running);\n }\n if (pending.length === 0) return;\n await Promise.race([\n Promise.allSettled(pending).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs).unref?.()),\n ]);\n }\n\n /**\n * Active-table idle eviction: removes entries that are idle (idle status, no pending\n * approvals, no in-flight drive) and have been inactive past the timeout, releasing\n * the core Session's full in-memory history. This is purely memory reclamation: the\n * next access re-resumes via the loader, so correctness is unaffected. Lock-table\n * entries are auto-cleaned by withLock once their chain drains (including leftover\n * entries under the old id after self-heal). `now` / `idleMs` are injectable for\n * tests and timers.\n */\n sweepIdle(now: number = Date.now(), idleMs: number = ENTRY_IDLE_MS): void {\n for (const [key, entry] of this.entries) {\n if (entry.status !== \"idle\" || entry.approvals.size !== 0 || entry.running !== null) continue;\n if (entry.followUps.length > 0) continue; // queued follow-ups must not be evicted with the entry\n if (now - entry.lastActivityMs <= idleMs) continue;\n this.entries.delete(key);\n }\n }\n\n // —— Internal ——\n\n private assertOpen(): void {\n if (this.closed) {\n throw new HttpError(\n 503,\n \"shutting_down\",\n \"Server is shutting down; not accepting new Tasks.\",\n );\n }\n }\n\n /** The Agent owning this Session is being deleted → 409 (guards against directory recreation inside the deletion race window). */\n private assertAgentNotDeleting(sessionId: string): void {\n const row = this.deps.sessions.findById(sessionId);\n if (row && this.deletingAgents.has(agentKey(row.projectId, row.agentId))) {\n throw new HttpError(\n 409,\n \"agent_deleting\",\n \"This Agent is being deleted; not accepting new Tasks.\",\n );\n }\n }\n\n /** This Session is being deleted → 409 (guards against the entry/Trace being rebuilt and reviving it inside the deletion race window). */\n private assertSessionNotDeleting(sessionId: string): void {\n if (this.deletingSessions.has(sessionId)) {\n throw new HttpError(\n 409,\n \"session_deleting\",\n \"This Session is being deleted; not accepting new Tasks.\",\n );\n }\n }\n\n private assertIdle(entry: RuntimeEntry): void {\n if (entry.status === \"running\") {\n throw new HttpError(409, \"task_in_progress\", \"This Session already has a Task in progress.\");\n }\n if (entry.status === \"compacting\") {\n throw new HttpError(\n 409,\n \"compacting\",\n \"This Session is compacting its context; not accepting new input.\",\n );\n }\n }\n\n private generationOf(projectId: string, agentId: string): number {\n return this.agentGenerations.get(agentKey(projectId, agentId)) ?? 0;\n }\n\n /** get-or-resume-or-heal: use directly on an active-table hit; otherwise load via the loader, updating the index's primary key on self-heal. */\n private async ensureEntry(sessionId: string): Promise<RuntimeEntry> {\n const existing = this.entries.get(sessionId);\n if (existing) {\n if (existing.generation === this.generationOf(existing.projectId, existing.agentId)) {\n return existing;\n }\n // Built before the last vault update: discard once idle and fall through to a\n // fresh load (resume re-reads the vault). A busy entry is returned as-is — the\n // in-flight run keeps its values and assertIdle rejects the new Task anyway;\n // it is rebuilt on the first access after it finishes.\n if (\n existing.status !== \"idle\" ||\n existing.running !== null ||\n existing.approvals.size !== 0\n ) {\n return existing;\n }\n this.entries.delete(sessionId);\n }\n const row = this.deps.sessions.findById(sessionId);\n if (!row) {\n throw new HttpError(\n 404,\n \"session_not_found\",\n \"Session does not exist or you do not have access.\",\n );\n }\n // Captured before the (awaited) load: a vault update racing with the load leaves\n // this entry stale, so the access after next rebuilds it with the new values.\n const generation = this.generationOf(row.projectId, row.agentId);\n const session = await this.deps.loader.load(row);\n // The Session/Agent was marked for deletion while loading: discard the load result,\n // don't rebuild the entry (avoids reviving an orphaned Trace).\n this.assertSessionNotDeleting(row.sessionId);\n this.assertAgentNotDeleting(row.sessionId);\n let currentId = row.sessionId;\n if (session.sessionId !== row.sessionId) {\n // Self-heal produced a new session_id: update the index's primary key; the SSE\n // channel and pending state are naturally empty for it.\n this.deps.sessions.replaceId(row.sessionId, session.sessionId);\n currentId = session.sessionId;\n }\n const entry: RuntimeEntry = {\n sessionId: currentId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n session,\n status: \"idle\",\n approvals: new ApprovalRegistry(),\n abort: null,\n running: null,\n generation,\n followUps: [],\n lastActivityMs: Date.now(),\n };\n this.entries.set(currentId, entry);\n return entry;\n }\n\n /**\n * Drive the output stream in the background: publish each message + persist usage +\n * persist LLM/tool errors; on completion (including errors) resets to idle and pushes\n * the status. `titleSource` is passed only for Task runs (compaction doesn't generate\n * a title): it collects model text for automatic title generation.\n */\n private async drive(\n entry: RuntimeEntry,\n gen: AsyncGenerator<OmniMessage>,\n titleSource?: { userExcerpt: string },\n ): Promise<void> {\n let earlyTitleFired = false;\n let mainBodyChars = 0;\n const ctx: UsageContext = {\n projectId: entry.projectId,\n agentId: entry.agentId,\n sessionId: entry.sessionId,\n provider: entry.provider,\n modelId: entry.modelId,\n };\n // LLM request failures and tool execution failures aren't expressed via throw (core\n // converges them into the message stream), so the try/catch below can't catch them:\n // the watcher inspects messages one by one and fishes them out for persistence\n // (subagent failures flow through this same stream too; see stream-error-watcher).\n const watcher = this.deps.errors\n ? new StreamErrorWatcher(this.deps.errors, {\n projectId: entry.projectId,\n agentId: entry.agentId,\n sessionId: entry.sessionId,\n })\n : null;\n // Subagent (origin) registration: as soon as session_meta arrives, the child Session\n // is persisted so it appears immediately in the sidebar (the frontend picks it up\n // when it refreshes the list at task completion). The title material is \"the prompt\n // of the run_subagent call that spawned this subagent\" — the subagent's user input\n // is never replayed onto the parent stream (ContextEngine writes the Trace but never\n // yields it), so we can't rely on the subagent's first user message; instead we use\n // the run_subagent tool_call arguments immediately preceding it on the parent stream\n // (depth limited to 1, spawned in order, so taking the most recent one suffices).\n /** Subagents registered during this run (keyed by session id); titles are generated for each on completion. */\n const children = new Map<string, ChildSession>();\n // Unclaimed run_subagent prompts, queued in call order: a single round may spawn\n // multiple subagents in parallel, and a subagent's session_meta only carries the\n // session id (no tool_call_id), so pairing can only be approximated via FIFO (when\n // spawned in parallel and session_meta arrives out of order, two subagents' titles\n // may end up swapped — this only affects the displayed title). A call that will\n // never produce a subagent must be dequeued, or its prompt would be mismatched onto\n // the next subagent: this covers denied calls (approval_decision ≠ allow), and calls\n // that were approved but failed before spawning the subagent (e.g. agent_id doesn't\n // exist) — the latter is cleaned up when the parent-level tool_call_output settles;\n // if the call is still in the queue at that point, it never produced a session_meta.\n const subagentPrompts = new Map<string, string>();\n try {\n for await (const msg of gen) {\n // A parent-level (no origin) run_subagent call: record its prompt for the child\n // session_meta that arrives later to use as its title.\n if (!msg.origin || msg.origin.length === 0) {\n const call = runSubagentCall(msg);\n if (call) subagentPrompts.set(call.toolCallId, call.prompt);\n const denied = deniedToolCallId(msg);\n if (denied) subagentPrompts.delete(denied);\n const settled = settledToolCallId(msg);\n if (settled) subagentPrompts.delete(settled);\n } else if (isSessionMeta(msg)) {\n // Subagent registration is only a \"side effect\" — it must never interrupt the\n // main run flow on error: wrap the whole thing in a defensive try/catch.\n try {\n const child = this.registerChildSession(entry, msg, children);\n // Only a **direct** subagent (origin length 1) claims a queued parent-level\n // run_subagent prompt; deeper sessions are spawned by their own parent and\n // shouldn't consume from this queue.\n if (child && msg.origin!.length === 1) {\n const [pendingId] = subagentPrompts.keys();\n if (pendingId !== undefined) {\n child.prompt = subagentPrompts.get(pendingId) ?? \"\";\n subagentPrompts.delete(pendingId); // Consumed by this session_meta\n }\n }\n } catch (err) {\n this.log(\n `[subagent] Failed to register child session: ${err instanceof Error ? err.message : String(err)}`,\n );\n this.deps.errors?.record({\n source: \"subagent\",\n err,\n ctx,\n code: \"subagent_register_failed\",\n });\n }\n } else {\n // A subagent's model text: its title is generated from the subagent's **own\n // conversation**, so the material is accumulated here.\n const nested = nestedAssistantText(msg);\n const child = nested ? children.get(nested.sessionId) : undefined;\n if (nested && child && child.assistantExcerpt.length < TITLE_EXCERPT_LIMIT) {\n child.assistantExcerpt += (child.assistantExcerpt ? \"\\n\" : \"\") + nested.text;\n }\n }\n // Early title trigger (see EARLY_TITLE_BODY_CHARS): fire as soon as enough main-\n // session body text has streamed; maybeGenerate self-guards (NULL title, single\n // flight), so the completion trigger in finally stays as the short-answer fallback.\n if (!earlyTitleFired && titleSource?.userExcerpt.trim()) {\n const p = msg.payload as { type?: string; role?: string; text?: string };\n if (\n (!msg.origin || msg.origin.length === 0) &&\n msg.type === \"model_msg\" &&\n p.type === \"text\" &&\n p.role === \"assistant\" &&\n p.text\n ) {\n mainBodyChars += p.text.length;\n if (mainBodyChars >= EARLY_TITLE_BODY_CHARS) {\n earlyTitleFired = true;\n this.deps.titles?.maybeGenerate(ctx, entry.session, {\n fallbackText: titleSource.userExcerpt,\n });\n }\n }\n }\n // Re-fetch the channel before every publish (matches publishEvent): the channel\n // may have been recycled and recreated during a long wait on approval, and\n // holding a stale reference would send output to an orphaned, detached channel.\n this.deps.channels.get(entry.sessionId).publish(msg);\n watcher?.observe(msg);\n try {\n await this.deps.recorder.record(ctx, msg);\n } catch (err) {\n this.log(`[usage] Insert failed: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"usage\", err, ctx, code: \"usage_insert_failed\" });\n }\n }\n } catch (err) {\n // The SDK doesn't normally throw (errors are converged into the message stream);\n // this is a defensive record here to avoid crashing the runtime.\n this.log(\n `[session] Run failed: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`,\n );\n this.deps.errors?.record({ source: \"session\", err, ctx, code: \"session_run_failed\" });\n } finally {\n // Wrap-up: persist any still-pending LLM failure and clear the tool-name cache (the watcher's state doesn't carry across runs).\n watcher?.close();\n entry.approvals.denyAll();\n entry.status = \"idle\";\n entry.abort = null;\n entry.running = null;\n entry.lastActivityMs = Date.now();\n this.publishState(entry, \"idle\");\n if (titleSource && titleSource.userExcerpt.trim()) {\n // Attempt generation whenever there's user material; whether generation is\n // actually needed (title still NULL, etc.) is decided by the generator itself.\n // Material is collected by the core Session during run; here we only pass the\n // fallback text.\n this.deps.titles?.maybeGenerate(ctx, entry.session, {\n fallbackText: titleSource.userExcerpt,\n });\n }\n // Queued follow-ups: whenever a run finishes (Task or compaction, abort included),\n // the next queued input auto-starts as an ordinary task. Fire-and-forget — it\n // revalidates under the session lock.\n if (entry.followUps.length > 0) void this.startQueuedFollowUp(entry.sessionId);\n // A subagent's title is likewise generated by the model, with material being the\n // subagent's **own conversation**: the prompt that spawned it plus its own reply\n // (material the parent Session collects belongs to the parent, hence the explicit\n // override here). It piggybacks a one-shot request on the parent Session's bare\n // LLM (the child Session object never leaves the SDK); on failure/empty result the\n // generator falls back to the prompt's first line.\n for (const child of children.values()) {\n if (!child.prompt.trim()) continue;\n this.deps.titles?.maybeGenerate(\n // Bookkeeping: Session/Agent record the subagent (the title belongs to it),\n // but the model reference still uses ctx's **parent-Session** pair\n // (provider, modelId) — this one-shot request really does run on the parent\n // Session's bare LLM (a subagent may switch models via run_subagent's\n // model_id).\n { ...ctx, agentId: child.agentId, sessionId: child.sessionId },\n entry.session,\n {\n fallbackText: child.prompt,\n material: { userText: child.prompt, assistantText: child.assistantExcerpt },\n notifyOn: entry.sessionId, // Notify the frontend via the parent Session's SSE channel\n },\n );\n }\n }\n }\n\n /**\n * Register a subagent: persisted only when the origin message is session_meta\n * (agentId is derived from the agent_state path: `<…>/<agentId>/agent_state`).\n * **The title is left blank** — it's generated at the end of this run by the model\n * from the subagent's own conversation (see drive's finally), falling back to the\n * first line of the run_subagent prompt if generation fails. Idempotent (children\n * dedup + insertOrIgnore); a subagent has its own Trace, so it's visible in both the\n * list and the trace view. On successful registration, the entry is put into\n * `children` and returned; a duplicate session_meta returns null.\n */\n private registerChildSession(\n entry: RuntimeEntry,\n msg: OmniMessage,\n children: Map<string, ChildSession>,\n ): ChildSession | null {\n if (!isSessionMeta(msg)) return null;\n const childSid = msg.origin![msg.origin!.length - 1]!;\n if (children.has(childSid)) return null;\n const p = msg.payload as SessionMetaPayload;\n const agentId = path.basename(path.dirname(p.agent_state));\n if (!agentId || agentId === \".\" || agentId === \"..\") return null;\n // The forwarded session_meta records the origin at the source (core's spawn site); fall\n // back to inferring \"subagent\" from the registration path for older metas (narrowed —\n // a junk value also falls back). It goes into the in-process registry only — the index\n // row deliberately stores no source column.\n const source = asSessionSource(p.source) ?? \"subagent\";\n this.deps.sources.set(childSid, source);\n this.deps.sessions.insertOrIgnore({\n sessionId: childSid,\n projectId: entry.projectId,\n agentId,\n provider: p.provider,\n modelId: p.model_id,\n workspace: p.workspace,\n // A subagent's approvals are inherited from the parent Session; the index row is\n // inserted with defaults (matches the convention for Sessions discovered by the CLI).\n approvalMode: \"allow-all\",\n title: null,\n createdAt: new Date().toISOString(),\n });\n // Make the subagent appear immediately in the sidebar: notify via the parent\n // Session's channel (a frontend currently watching the parent run refreshes its list in place).\n this.publishEvent(entry, {\n type: \"session_created\",\n projectId: entry.projectId,\n agentId,\n sessionId: childSid,\n source,\n });\n const child: ChildSession = {\n sessionId: childSid,\n agentId,\n modelId: p.model_id,\n prompt: \"\",\n assistantExcerpt: \"\",\n };\n children.set(childSid, child);\n return child;\n }\n\n private publishState(entry: RuntimeEntry, state: SessionStatus): void {\n // Every state flip also reports the queued follow-up count, so subscribers can render\n // the \"N queued\" hint without a dedicated event type.\n this.publishEvent(entry, { type: \"task_state\", state, queued: entry.followUps.length });\n }\n\n private publishEvent(entry: RuntimeEntry, event: ServerEvent): void {\n this.deps.channels.get(entry.sessionId).publish(event, \"server_event\");\n }\n\n /** Serialize (mutually exclude) execution by sessionId; cleans up the lock-table entry once its chain drains (avoids unbounded growth). */\n private async withLock<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {\n // What's stored in the chain is the already-caught version (used only for\n // sequencing, never propagates errors); the caller gets the original result from `next`.\n const prev = this.locks.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn);\n const settled: Promise<void> = next\n .then(\n () => undefined,\n () => undefined,\n )\n .then(() => {\n // Only delete if still the tail of the chain (no later waiter): preserves mutual-exclusion semantics.\n if (this.locks.get(sessionId) === settled) this.locks.delete(sessionId);\n });\n this.locks.set(sessionId, settled);\n return next;\n }\n}\n","/**\n * Tool call approval: ApproveFn factory + pending\n * approval registry.\n *\n * Approval mode semantics match the CLI (packages/cli/src/approval.ts):\n * allow-all auto-approves; deny-all auto-denies; read-only allows read-only tools\n * (permission===\"r\") and routes the rest to manual approval; always-ask routes\n * everything to manual approval. Routing to manual approval registers a pending entry\n * and pushes an `approval_request` via SSE, suspending until the frontend decides via\n * `POST /approvals/:toolCallId`; no timeout — pending approvals are resolved to deny\n * when the Task is interrupted (then proceeds through the abort flow).\n *\n * Every approval decision re-reads the current approval_mode (`getMode` reads the DB),\n * so mode changes take effect immediately.\n * Docs: /docs/tools § \"Approval\".\n */\nimport type { ApprovalMode } from \"../api/types.js\";\nimport type {\n ApprovalDecision,\n ApproveFn,\n OmniMessage,\n ToolCallPayload,\n} from \"@prismshadow/penguin-core\";\n\nexport interface PendingApproval {\n toolCall: OmniMessage<ToolCallPayload>;\n origin?: string[];\n}\n\ninterface PendingEntry extends PendingApproval {\n resolve: (decision: ApprovalDecision) => void;\n}\n\n/** Pending approval registry (key = tool_call_id), one per Session runtime. */\nexport class ApprovalRegistry {\n private readonly pending = new Map<string, PendingEntry>();\n\n get size(): number {\n return this.pending.size;\n }\n\n /** All currently pending approvals (for subscription replay). */\n list(): PendingApproval[] {\n return [...this.pending.values()].map(({ toolCall, origin }) => ({\n toolCall,\n ...(origin !== undefined ? { origin } : {}),\n }));\n }\n\n /** Register and wait for a decision. Re-registering the same id (defensive) resolves the old entry as deny. */\n wait(toolCall: OmniMessage<ToolCallPayload>): Promise<ApprovalDecision> {\n const id = toolCall.payload.tool_call_id;\n this.pending.get(id)?.resolve(\"deny\");\n return new Promise<ApprovalDecision>((resolve) => {\n const entry: PendingEntry = {\n toolCall,\n ...(toolCall.origin !== undefined ? { origin: toolCall.origin } : {}),\n resolve: (decision) => {\n this.pending.delete(id);\n resolve(decision);\n },\n };\n this.pending.set(id, entry);\n });\n }\n\n /** Submit a decision; returns false if not found (already decided/unknown). */\n decide(toolCallId: string, decision: ApprovalDecision): boolean {\n const entry = this.pending.get(toolCallId);\n if (!entry) return false;\n entry.resolve(decision);\n return true;\n }\n\n /** Interruption convergence: resolve all pending approvals as deny. */\n denyAll(): void {\n for (const entry of [...this.pending.values()]) entry.resolve(\"deny\");\n }\n}\n\n/**\n * Build the approve callback: re-reads the approval mode on every call; when routed to\n * manual approval, registers a pending entry and suspends after pushing an\n * `approval_request` server event via `publishRequest`.\n */\nexport function makeApprove(args: {\n getMode: () => ApprovalMode;\n toolPermission: (name: string) => \"r\" | \"rw\" | undefined;\n registry: ApprovalRegistry;\n publishRequest: (pending: PendingApproval) => void;\n}): ApproveFn {\n const { getMode, toolPermission, registry, publishRequest } = args;\n const manual = (toolCall: OmniMessage<ToolCallPayload>): Promise<ApprovalDecision> => {\n const promise = registry.wait(toolCall);\n publishRequest({\n toolCall,\n ...(toolCall.origin !== undefined ? { origin: toolCall.origin } : {}),\n });\n return promise;\n };\n return async (toolCall) => {\n switch (getMode()) {\n case \"allow-all\":\n return \"allow\";\n case \"deny-all\":\n return \"deny\";\n case \"read-only\":\n // Auto-approve read-only tools; route read-write/unknown tools to manual approval (matches CLI semantics).\n if (toolPermission(toolCall.payload.name) === \"r\") return \"allow\";\n return manual(toolCall);\n case \"always-ask\":\n default:\n return manual(toolCall);\n }\n };\n}\n","/**\n * In-process registry of Session origins, derived from core `session_meta` — the single\n * source of truth for a Session's origin (the DB stores no `source` column).\n *\n * Populated wherever the server actually has the meta in hand: Session creation\n * (SessionService reads the just-created core Session's meta), subagent registration\n * (SessionManager reads the forwarded child meta), and Trace adoption / lazy list\n * resolution (SessionService reads the Trace head's session_meta). `null` records a\n * **known** user-created Session (meta seen, no source) so the Trace is not re-read on\n * every list; an absent entry means \"unknown\" and the list path resolves it from the\n * Trace once per process lifetime.\n */\nimport type { SessionSource } from \"../api/types.js\";\n\n/**\n * Narrows an untrusted value (on-disk Trace JSON / forwarded meta) to a SessionSource:\n * only the exact known origins pass; anything else — including junk written by third\n * parties — is treated as absent rather than cast through.\n */\nexport function asSessionSource(v: unknown): SessionSource | undefined {\n return v === \"schedule\" || v === \"subagent\" ? v : undefined;\n}\n\nexport class SessionSources {\n private readonly map = new Map<string, SessionSource | null>();\n\n /** Records a Session's origin as read from session_meta (`null` = meta seen, user-created). */\n set(sessionId: string, source: SessionSource | null): void {\n this.map.set(sessionId, source);\n }\n\n /** Known origin, `null` for a known user-created Session, `undefined` when this process has not seen the meta. */\n get(sessionId: string): SessionSource | null | undefined {\n return this.map.get(sessionId);\n }\n\n /** Drops a deleted Session's entry (bulk Agent/Project deletion may leave stale entries; they are never matched again). */\n delete(sessionId: string): void {\n this.map.delete(sessionId);\n }\n}\n","/**\n * Error capture within the message stream: LLM request\n * failures and tool execution failures are **both never expressed via throw** — core\n * converges them into the message stream (LLM and Environment handle errors\n * internally and never throw), so a try/catch can't catch a single one. This watcher\n * hooks onto SessionManager's drive, inspects messages one by one, and fishes them out\n * into error_records (source = `llm` / `environment`), matching usage-recorder's shape:\n * recognizes only a few payload types, no-op on the rest. **One instance per run/compact**\n * (its state wraps up accordingly, see close).\n *\n * LLM (source = `llm`): reads the status of `request_end` —\n * - `failed` → unexpected (not retryable: auth failure, invalid params, etc., needs a human);\n * - `timeout` / `malformed` → expected (the engine already reconnects and retries, part\n * of normal operation);\n * - `aborted` / `completed` are not recorded (the former is a user-initiated interrupt,\n * not an error).\n *\n * The message uses the real reason: `request_end` only carries status, and **the only\n * place core carries the actual failure-reason text is the `abort` event's reason**\n * (e.g. `llm request error: 401 …` / `malformed response failed after N retries`). So a\n * `request_end` failure is first held pending, not persisted immediately, and is\n * resolved at the next request boundary:\n * - Immediately followed by `abort` → use its reason as the message (the real reason);\n * - Immediately followed by `request_begin` (the engine is retrying) → no reason text\n * left to wait for, use the status text;\n * - Still unresolved when the run ends → close persists it as a fallback.\n * Exception: when reason is a user-interrupt message (`aborted …`), it's not trusted —\n * \"the user clicked stop during backoff\" isn't the reason for this timeout, so the\n * status text is used instead (that timeout is a genuine failure and is still recorded).\n * Pending state is bucketed by origin: subagent messages interleave with the parent\n * session's (even more so with parallel subagents), and mixing them up would misattribute.\n *\n * Environment (source = `environment`): reads `tool_call_output`'s stop_reason ∈\n * {failed, timeout} → expected (the error is fed back to the model, and the Agent\n * adjusts on its own; `aborted` is denial/interruption, not recorded).\n * `tool_call_output` only has tool_call_id, no tool name, so `tool_call_id → tool name`\n * is cached (tool_call always arrives before its output), and the tool name is written\n * into code (`tool_failed:exec_command`) — so the stats dashboard's \"most common error\n * code\" and the error table can show at a glance which tool failed.\n *\n * **Attribution (ctx) is recorded against the session that actually produced the error,\n * not always the parent Session**: a subagent's LLM failures and tool failures also flow\n * through this same stream (carrying origin); if we simply reused the parent ctx passed\n * in at construction, filtering errors by Agent would always show 0 for the child Agent\n * and an inflated count for the parent — both attribution stats and the troubleshooting\n * target would be wrong. So we recognize `session_meta` carrying origin (a subagent's\n * first message, always arriving before any of its failures), registering\n * `origin → {agentId, sessionId}` (agentId derived from the agent_state path, matching\n * SessionManager.registerChildSession's convention); at persist time we look up the\n * message's origin: a hit records the subagent, a miss (a main-session message, or\n * session_meta hasn't arrived yet) falls back to the parent ctx. projectId is always\n * taken from the parent — a subagent is necessarily in the same Project.\n */\nimport { isEventMessage, isModelMessage, isSessionMeta } from \"@prismshadow/penguin-core\";\nimport path from \"node:path\";\nimport type { OmniMessage, SessionMetaMessage, StopReason } from \"@prismshadow/penguin-core\";\nimport { MESSAGE_MAX } from \"./error-recorder.js\";\nimport type { ErrorContext, ErrorKind, ErrorSink } from \"./error-recorder.js\";\n\n/** Cap on the tool-name cache (bounded, to prevent unbounded growth over a long run; over the limit, evicts the oldest by registration order). */\nexport const TOOL_NAMES_MAX = 1000;\n\n/**\n * Cap on the subagent-identity cache (bounded, same reasoning as TOOL_NAMES_MAX: prevent\n * unbounded growth over a long run). The number of in-flight subagents is naturally\n * bounded by the subagent concurrency limit and falls far short of this value; over the\n * limit, evicts the oldest by registration order (those subagents have long since\n * settled, so even if a failure still arrives, it just falls back to the parent ctx —\n * i.e., the pre-fix behavior).\n */\nexport const ORIGIN_CTX_MAX = 200;\n\n/** Recorded LLM failure states (`aborted` / `completed` are not errors and aren't included here). */\ntype LlmFailure = \"failed\" | \"timeout\" | \"malformed\";\n\n/** LLM failure state → error code, classification, and fallback message (used when the abort reason isn't available). */\nconst LLM_FAILURES: Record<LlmFailure, { code: string; kind: ErrorKind; text: string }> = {\n failed: { code: \"llm_failed\", kind: \"unexpected\", text: \"LLM request failed (not retryable).\" },\n timeout: {\n code: \"llm_timeout\",\n kind: \"expected\",\n text: \"LLM request timed out (the engine reconnects and retries).\",\n },\n malformed: {\n code: \"llm_malformed\",\n kind: \"expected\",\n text: \"LLM response could not be parsed (the engine reconnects and retries).\",\n },\n};\n\n/** Recorded tool failure states (`aborted` = denial/interruption, not an error). */\ntype ToolFailure = \"failed\" | \"timeout\";\n\nfunction isLlmFailure(s: unknown): s is LlmFailure {\n return s === \"failed\" || s === \"timeout\" || s === \"malformed\";\n}\n\nfunction isToolFailure(s: unknown): s is ToolFailure {\n return s === \"failed\" || s === \"timeout\";\n}\n\n/** A user-interrupt abort message (core's `aborted by user` / `aborted during …`): not a failure reason. */\nfunction isUserAbortReason(reason: string): boolean {\n return /^aborted\\b/i.test(reason);\n}\n\n/** The session a message belongs to (last origin element; empty string for the main session) — both pending state and the tool-name cache are bucketed by it. */\nfunction originKey(msg: OmniMessage): string {\n const origin = msg.origin;\n return origin && origin.length > 0 ? origin[origin.length - 1]! : \"\";\n}\n\n/**\n * Take the **tail** of the tool output (not the head) as the message: core appends the\n * failure reason (`[tool error] …` / `[tool timeout: …]` / exit code) at the end of the\n * output, so truncating from the head would leave only a chunk of normal stdout and\n * drop the reason.\n */\nfunction toolFailureText(output: string): string {\n if (!output) return \"Tool execution failed (no output).\";\n if (output.length <= MESSAGE_MAX) return output;\n return `…${output.slice(output.length - (MESSAGE_MAX - 1))}`;\n}\n\nexport class StreamErrorWatcher {\n /** LLM failures awaiting a real reason: origin → failure state (see file header; each session has at most one in-flight Request). */\n private readonly pending = new Map<string, LlmFailure>();\n /** Names of in-flight tool calls: `origin \\0 tool_call_id` → tool name (dequeued once output arrives). */\n private readonly toolNames = new Map<string, string>();\n /** Subagent identity: origin → that subagent's `{agentId, sessionId}` (see the file header's attribution section). */\n private readonly originCtx = new Map<string, { agentId: string; sessionId: string }>();\n\n constructor(\n private readonly errors: ErrorSink,\n private readonly ctx: ErrorContext,\n ) {}\n\n /** Consume one outgoing message; messages irrelevant to this watcher are a no-op. */\n observe(msg: OmniMessage): void {\n if (isSessionMeta(msg)) {\n this.registerOrigin(msg);\n return;\n }\n if (isModelMessage(msg)) {\n this.observeTool(msg);\n return;\n }\n if (isEventMessage(msg)) this.observeLlm(msg);\n }\n\n /** run/compact wrap-up: persist any still-pending failure (that never got its abort), and clear caches (prevents leaks). */\n close(): void {\n for (const key of [...this.pending.keys()]) this.flush(key);\n this.pending.clear();\n this.toolNames.clear();\n this.originCtx.clear();\n }\n\n // —— Attribution (see file header) ——\n\n /**\n * Register a subagent's identity: `session_meta` carrying origin is the subagent's\n * first message, always arriving before any of its failures. agentId is derived from\n * the absolute agent_state path (`<…>/<agentId>/agent_state`) — matching\n * SessionManager.registerChildSession's convention; not registered if the path is\n * malformed (that subagent's failures fall back to the parent ctx — better to\n * misattribute than write into a nonexistent agentId). The main session's session_meta\n * (no origin) is already the parent ctx and isn't registered.\n */\n private registerOrigin(msg: SessionMetaMessage): void {\n const key = originKey(msg);\n if (!key) return; // Main session\n const agentId = path.basename(path.dirname(msg.payload.agent_state));\n if (!agentId || agentId === \".\" || agentId === \"..\") return;\n // Bounded (re-registering refreshes registration order; over the limit, evicts the oldest).\n this.originCtx.delete(key);\n this.originCtx.set(key, { agentId, sessionId: key });\n if (this.originCtx.size > ORIGIN_CTX_MAX) {\n const oldest = this.originCtx.keys().next().value;\n if (oldest !== undefined) this.originCtx.delete(oldest);\n }\n }\n\n /**\n * The attribution to persist for this origin: a registered subagent hit → record its\n * own Agent/Session; a miss (a main-session message, or session_meta hasn't arrived\n * yet) → fall back to the parent ctx passed at construction. projectId is always taken\n * from the parent (a subagent is necessarily in the same Project).\n */\n private ctxFor(key: string): ErrorContext {\n const child = this.originCtx.get(key);\n if (!child) return this.ctx;\n return { projectId: this.ctx.projectId, agentId: child.agentId, sessionId: child.sessionId };\n }\n\n // —— LLM ——\n\n private observeLlm(msg: OmniMessage): void {\n const p = msg.payload as { type?: string; status?: StopReason; reason?: string | null };\n const key = originKey(msg);\n if (p.type === \"request_end\") {\n this.flush(key); // Defensive: if a previous failure is still pending (normally resolved by request_begin), persist it first\n if (isLlmFailure(p.status)) this.pending.set(key, p.status);\n return;\n }\n // A new attempt begins (the engine is retrying): no reason text left to wait for the previous failure, persist using the status text.\n if (p.type === \"request_begin\") {\n this.flush(key);\n return;\n }\n // Interrupted/failed exit: reason is core's only failure-reason text.\n if (p.type === \"abort\") {\n this.flush(key, typeof p.reason === \"string\" ? p.reason : null);\n }\n }\n\n /**\n * Persist a pending LLM failure (no-op if none is pending); `reason` is the abort\n * message that arrived afterward. Pending state is already bucketed by origin, so\n * `key` is exactly \"the session that produced this failure\" — attribution is looked\n * up from it (see file header).\n */\n private flush(key: string, reason?: string | null): void {\n const status = this.pending.get(key);\n if (status === undefined) return;\n this.pending.delete(key);\n const spec = LLM_FAILURES[status];\n const trimmed = reason?.trim();\n // A user-interrupt message isn't a failure reason (see file header); fall back to the status text.\n const message = trimmed && !isUserAbortReason(trimmed) ? trimmed : spec.text;\n this.errors.record({\n source: \"llm\",\n err: message,\n ctx: this.ctxFor(key),\n code: spec.code,\n kind: spec.kind,\n });\n }\n\n // —— Environment (tool execution) ——\n\n private observeTool(msg: OmniMessage): void {\n const p = msg.payload as {\n type?: string;\n name?: string;\n output?: string;\n tool_call_id?: string;\n stop_reason?: StopReason;\n };\n if (typeof p.tool_call_id !== \"string\") return;\n const origin = originKey(msg); // The session that made this call (both attribution and the tool-name cache are bucketed by it)\n const key = `${origin}\\0${p.tool_call_id}`;\n\n if (p.type === \"tool_call\" && typeof p.name === \"string\") {\n // tool_call arrives before its output: record the tool name (bounded, re-registering refreshes registration order).\n this.toolNames.delete(key);\n this.toolNames.set(key, p.name);\n if (this.toolNames.size > TOOL_NAMES_MAX) {\n const oldest = this.toolNames.keys().next().value;\n if (oldest !== undefined) this.toolNames.delete(oldest);\n }\n return;\n }\n if (p.type !== \"tool_call_output\") return;\n\n const name = this.toolNames.get(key);\n this.toolNames.delete(key); // This call has settled: dequeue it, the cache only keeps in-flight calls\n if (!isToolFailure(p.stop_reason)) return; // completed / aborted (denial, user interrupt) are not errors\n this.errors.record({\n source: \"environment\",\n err: toolFailureText(p.output ?? \"\"),\n ctx: this.ctxFor(origin),\n // The tool name goes into code: so the stats dashboard's \"most common error code\" and table can show which tool failed.\n code: `tool_${p.stop_reason}:${name ?? \"unknown\"}`,\n kind: \"expected\",\n });\n }\n}\n","/**\n * Schedule runner: a Web server runtime component, active only\n * while the server runs. Loaded at server startup, then periodically reconciles by scanning the\n * `schedule/` directory.\n *\n * Key semantics:\n * - Intent vs state are separate: the file is declarative intent (never written back by the system);\n * run state lives in SQLite.\n * - No backfill for missed fires: any due time earlier than when this scheduler first learned of the\n * task (startup reconcile / first registration / start_at reset) is skipped — periodic tasks advance\n * last_slot straight to now, one-shot tasks are marked missed.\n * - Queue when busy: if the bound Session is running, queue (at most one per task; new due times during\n * the wait only advance, they don't stack), and send once it becomes idle.\n * - Bound Session deleted: record an error and mark invalid; editing the file re-activates it via reconcile.\n * - Deleting the file removes the task; reconcile also cleans up its SQLite run state and queue entry.\n */\nimport { createHash } from \"node:crypto\";\nimport { readdir } from \"node:fs/promises\";\nimport { agentsDir, buildScheduledMessage, userText } from \"@prismshadow/penguin-core\";\nimport type { ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { SchedulesRepo, ScheduleStateRow } from \"../db/repos/schedules.js\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport type { ScheduleDefinition } from \"./schedule-file.js\";\nimport { latestSlotAt, slotInWindow } from \"./schedule-file.js\";\nimport { listScheduleFiles, readScheduleFile, validateScheduleModelRef } from \"./schedule-store.js\";\nimport type { ScheduleServerEvent } from \"../api/types.js\";\n\n/** Reconcile and fire-check interval (min period is 5m, so 30s granularity is plenty). */\nconst TICK_INTERVAL_MS = 30_000;\n\n/** Minimal dependency the scheduler needs from SessionManager (eases test doubles). */\nexport interface ScheduleTaskRunner {\n statusOf(sessionId: string): string;\n startTask(\n sessionId: string,\n input: ReturnType<typeof userText>[],\n ): Promise<{ sessionId: string }>;\n}\n\n/** Minimal dependency the scheduler needs from SessionService: new-Session mode (model ref passed through as a pair). */\nexport interface ScheduleSessionCreator {\n createSession(args: {\n projectId: string;\n agentId: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n source?: \"schedule\";\n }): Promise<{ sessionId: string }>;\n}\n\n/**\n * Trigger input = a `[scheduled_task]` origin block (task name and fire time) + the prompt\n * body: tells the model this was fired by a schedule; the frontend collapses the origin block\n * into a one-line schedule hint (Trace shows it verbatim). The block itself is built by core's\n * marker module, which also owns the parser the frontend uses.\n */\nexport const scheduledMessage = buildScheduledMessage;\n\nexport interface SchedulerDeps {\n root: string;\n repo: SchedulesRepo;\n projects: ProjectsRepo;\n sessions: SessionsRepo;\n runner: ScheduleTaskRunner;\n sessionCreator: ScheduleSessionCreator;\n errors: ErrorSink;\n /** Fire and send are notified over the user-level event stream (app layer binds userChannelKey). */\n notify: (userId: string, event: ScheduleServerEvent) => void;\n now?: () => number;\n intervalMs?: number;\n}\n\n/** A queued fire (at most one per task). */\ninterface PendingFire {\n projectId: string;\n agentId: string;\n name: string;\n sessionId: string;\n}\n\n/** In-memory view of a registered task (definition + run state + queued flag). */\nexport interface ScheduleEntryView {\n def: ScheduleDefinition;\n state: ScheduleStateRow;\n queued: boolean;\n}\n\nexport class Scheduler {\n private readonly now: () => number;\n private readonly intervalMs: number;\n private timer: ReturnType<typeof setInterval> | null = null;\n /** key = `${projectId}\\0${agentId}\\0${name}` */\n private readonly pending = new Map<string, PendingFire>();\n private ticking = false;\n\n constructor(private readonly deps: SchedulerDeps) {\n this.now = deps.now ?? (() => Date.now());\n this.intervalMs = deps.intervalMs ?? TICK_INTERVAL_MS;\n }\n\n /** Start: run one reconcile immediately (startup semantics: no backfill), then enter the periodic tick. */\n async start(): Promise<void> {\n await this.tickOnce();\n this.timer = setInterval(() => {\n void this.tickOnce();\n }, this.intervalMs);\n this.timer.unref?.();\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n /** One reconcile + fire pass (deterministic entry for tests and routes; concurrent calls run only one). */\n async tickOnce(): Promise<void> {\n if (this.ticking) return;\n this.ticking = true;\n try {\n for (const project of this.deps.projects.listAll()) {\n await this.reconcileProject(project.projectId, project.ownerUserId);\n }\n await this.drainQueue();\n } catch (err) {\n this.deps.errors.record({ source: \"schedule\", err, code: \"schedule_tick_failed\" });\n } finally {\n this.ticking = false;\n }\n }\n\n /** Immediate-effect entry after a route write: reconcile just one Agent and drain its queue. */\n async reconcileAgent(projectId: string, agentId: string): Promise<void> {\n const project = this.deps.projects.findById(projectId);\n if (!project) return;\n await this.reconcileOneAgent(projectId, agentId, project.ownerUserId);\n await this.drainQueue();\n }\n\n /** For route display: the task list after reconcile (including files that failed to parse). */\n async listAgent(\n projectId: string,\n agentId: string,\n ): Promise<{ entries: ScheduleEntryView[]; invalid: Array<{ name: string; error: string }> }> {\n const project = this.deps.projects.findById(projectId);\n const owner = project?.ownerUserId ?? null;\n const files = await listScheduleFiles(this.deps.root, projectId, agentId);\n const entries: ScheduleEntryView[] = [];\n const invalid: Array<{ name: string; error: string }> = [];\n for (const file of files) {\n if (!file.parsed.ok) {\n invalid.push({ name: file.name, error: file.parsed.error });\n continue;\n }\n // A model ref whose (provider, model_id) pair isn't in the config is treated like a parse failure: goes to invalidFiles, not scheduled.\n const refError = await validateScheduleModelRef(this.deps.root, projectId, file.parsed.def);\n if (refError !== null) {\n invalid.push({ name: file.name, error: refError });\n continue;\n }\n const state = this.registerEntry(projectId, agentId, owner, file.parsed.def, file.raw);\n entries.push({\n def: file.parsed.def,\n state,\n queued: this.pending.has(this.keyOf(projectId, agentId, file.name)),\n });\n }\n this.cleanupMissing(\n projectId,\n agentId,\n files.map((f) => f.name),\n );\n return { entries, invalid };\n }\n\n /** For routes: state cleanup after a task is deleted. */\n dropEntry(projectId: string, agentId: string, name: string): void {\n this.pending.delete(this.keyOf(projectId, agentId, name));\n this.deps.repo.delete(projectId, agentId, name);\n }\n\n // -------------------------------------------------------------------------\n\n private keyOf(projectId: string, agentId: string, name: string): string {\n return `${projectId}\\0${agentId}\\0${name}`;\n }\n\n private async reconcileProject(projectId: string, ownerUserId: string): Promise<void> {\n for (const agentId of await this.listAgentIds(projectId)) {\n await this.reconcileOneAgent(projectId, agentId, ownerUserId);\n }\n }\n\n /** Enumerate Agents under a Project: scheduling only cares about Agent dirs that exist on disk (no dir → no tasks). */\n private async listAgentIds(projectId: string): Promise<string[]> {\n try {\n const items = await readdir(agentsDir(this.deps.root, projectId), { withFileTypes: true });\n return items.filter((d) => d.isDirectory()).map((d) => d.name);\n } catch {\n return [];\n }\n }\n\n private async reconcileOneAgent(\n projectId: string,\n agentId: string,\n ownerUserId: string,\n ): Promise<void> {\n const files = await listScheduleFiles(this.deps.root, projectId, agentId);\n for (const file of files) {\n if (!file.parsed.ok) {\n // Skip invalid files and record an error (the recorder dedups within a short window, so storms don't spam).\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(`Invalid schedule file: ${file.name}.toml — ${file.parsed.error}`),\n code: \"schedule_invalid_file\",\n ctx: { projectId, agentId },\n });\n continue;\n }\n const def = file.parsed.def;\n // At reconcile time, check the (provider, model_id) pair names a configured model: a\n // reference that doesn't is treated like an invalid file — skip scheduling and record an\n // error (recorder dedups in a short window); it recovers once the file/config is fixed.\n const refError = await validateScheduleModelRef(this.deps.root, projectId, def);\n if (refError !== null) {\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(`Invalid schedule file: ${file.name}.toml — ${refError}`),\n code: \"schedule_invalid_file\",\n ctx: { projectId, agentId },\n });\n continue;\n }\n const state = this.registerEntry(projectId, agentId, ownerUserId, def, file.raw);\n await this.evaluateEntry(projectId, agentId, def, state);\n }\n this.cleanupMissing(\n projectId,\n agentId,\n files.map((f) => f.name),\n );\n }\n\n /** Register (or sync) run state; set the no-backfill baseline only at first registration / start_at reset. */\n private registerEntry(\n projectId: string,\n agentId: string,\n ownerUserId: string | null,\n def: ScheduleDefinition,\n raw: string,\n ): ScheduleStateRow {\n const defHash = createHash(\"sha1\").update(raw).digest(\"hex\");\n const { row, fresh } = this.deps.repo.registerOrSync({\n projectId,\n agentId,\n name: def.name,\n startAtMs: def.startAtMs,\n defHash,\n creatorUserId: ownerUserId,\n });\n if (!fresh) return row;\n // Baseline: if the due time is already in the past at registration → no backfill (mark one-shot tasks\n // missed, let periodic tasks consume all past slots); if start_at is still in the future, do nothing and fire normally when due.\n const slot = latestSlotAt(def, this.now());\n if (slot === null) return row;\n if (def.periodMs === undefined) {\n this.deps.repo.markMissed(projectId, agentId, def.name);\n } else {\n this.deps.repo.markSlot(projectId, agentId, def.name, slot);\n }\n return this.deps.repo.find(projectId, agentId, def.name) ?? row;\n }\n\n /** Clean up run state and queue entries for deleted files (deleting a file removes the task). */\n private cleanupMissing(projectId: string, agentId: string, presentNames: string[]): void {\n const removed = this.deps.repo.deleteMissing(projectId, agentId, presentNames);\n for (const name of removed) this.pending.delete(this.keyOf(projectId, agentId, name));\n }\n\n /** Fire decision: if enabled and within the window, consume new due times step by step. */\n private async evaluateEntry(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n ): Promise<void> {\n if (!def.enabled || state.invalidReason !== null) return;\n if (def.periodMs === undefined && (state.firedOnce || state.missed)) return;\n const nowMs = this.now();\n const slot = latestSlotAt(def, nowMs);\n if (slot === null || !slotInWindow(def, slot)) return;\n if (state.lastSlotMs !== null && slot <= state.lastSlotMs) return;\n // Consume this slot: never retry the same slot whether the send then succeeds, queues, or fails (the twin rule of no-backfill).\n this.deps.repo.markSlot(projectId, agentId, def.name, slot);\n await this.dispatch(projectId, agentId, def, state);\n }\n\n /** Send one fire: queue if the bound Session is busy; in new-Session mode, create and send immediately. */\n private async dispatch(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n ): Promise<void> {\n const key = this.keyOf(projectId, agentId, def.name);\n if (def.sessionId !== undefined) {\n const row = this.deps.sessions.findById(def.sessionId);\n if (!row || row.projectId !== projectId || row.agentId !== agentId) {\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(\n `Schedule ${def.name} is bound to a Session that does not exist: ${def.sessionId}`,\n ),\n code: \"schedule_session_missing\",\n ctx: { projectId, agentId, sessionId: def.sessionId },\n });\n this.deps.repo.markInvalid(projectId, agentId, def.name, \"session_missing\");\n return;\n }\n if (this.deps.runner.statusOf(def.sessionId) !== \"idle\") {\n // Queue when busy: at most one per task; new slots during the wait are consumed but don't stack.\n if (!this.pending.has(key)) {\n this.pending.set(key, { projectId, agentId, name: def.name, sessionId: def.sessionId });\n this.notifyFor(state, {\n type: \"schedule_queued\",\n projectId,\n agentId,\n name: def.name,\n sessionId: def.sessionId,\n });\n }\n return;\n }\n await this.send(projectId, agentId, def, state, def.sessionId);\n return;\n }\n // New-Session mode: each fire opens a new session (optional workspace and paired model ref; same semantics as opening a session manually).\n try {\n const info = await this.deps.sessionCreator.createSession({\n projectId,\n agentId,\n ...(def.workspace !== undefined ? { workspace: def.workspace } : {}),\n ...(def.modelId !== undefined ? { modelId: def.modelId } : {}),\n ...(def.provider !== undefined ? { provider: def.provider } : {}),\n source: \"schedule\",\n });\n await this.send(projectId, agentId, def, state, info.sessionId);\n } catch (err) {\n this.deps.errors.record({\n source: \"schedule\",\n err,\n code: \"schedule_create_session_failed\",\n ctx: { projectId, agentId },\n });\n }\n }\n\n private async send(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n sessionId: string,\n ): Promise<void> {\n const firedAt = new Date(this.now()).toISOString();\n try {\n await this.deps.runner.startTask(sessionId, [\n userText(scheduledMessage(def.name, firedAt, def.prompt)),\n ]);\n } catch (err) {\n this.deps.errors.record({\n source: \"schedule\",\n err,\n code: \"schedule_send_failed\",\n ctx: { projectId, agentId, sessionId },\n });\n return;\n }\n this.deps.repo.markFired(projectId, agentId, def.name, firedAt, def.periodMs === undefined);\n this.notifyFor(state, {\n type: \"schedule_fired\",\n projectId,\n agentId,\n name: def.name,\n sessionId,\n });\n }\n\n /** Drain the queue: send once the target Session is idle; drop if the task is disabled/deleted/invalid. */\n private async drainQueue(): Promise<void> {\n for (const [key, fire] of [...this.pending]) {\n const state = this.deps.repo.find(fire.projectId, fire.agentId, fire.name);\n if (!state || state.invalidReason !== null) {\n this.pending.delete(key);\n continue;\n }\n const file = await readScheduleFile(this.deps.root, fire.projectId, fire.agentId, fire.name);\n if (!file || !file.parsed.ok || !file.parsed.def.enabled) {\n this.pending.delete(key);\n continue;\n }\n const row = this.deps.sessions.findById(fire.sessionId);\n if (!row) {\n this.pending.delete(key);\n this.deps.repo.markInvalid(fire.projectId, fire.agentId, fire.name, \"session_missing\");\n continue;\n }\n if (this.deps.runner.statusOf(fire.sessionId) !== \"idle\") continue;\n this.pending.delete(key);\n await this.send(fire.projectId, fire.agentId, file.parsed.def, state, fire.sessionId);\n }\n }\n\n /** Notify the creator (falls back to the Project owner at registration; silent if still absent). */\n private notifyFor(state: ScheduleStateRow, event: ScheduleServerEvent): void {\n const userId = state.creatorUserId;\n if (userId) this.deps.notify(userId, event);\n }\n}\n","/**\n * The **policy layer** for automatic Session title generation (conversation-page\n * extension).\n *\n * \"How to generate\" lives in the core SDK (`session.generateTitle`: an out-of-band\n * one-shot request on the session's own Model, no tools, thinking disabled, writes no\n * history/Trace); this module is only responsible for host-side policy:\n * - When to generate: after a Task completes and the DB row's title is still NULL\n * (i.e. after the first successful conversation);\n * - Persistence and notification: writes sessions.title and pushes a `session_title`\n * server event to the Session channel;\n * - Bookkeeping: the one-shot request's token consumption is converted to token_usage\n * and handed to usage-recorder for persistence;\n * - Silent failure (logged): the title stays NULL and naturally retries after the next\n * Task completes.\n */\nimport {\n emptyTokenCounts,\n sanitizeTitle,\n stripConversationMarkers,\n tokenUsage,\n} from \"@prismshadow/penguin-core\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { ChannelHub } from \"./channel.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport type { RuntimeSession } from \"./session-manager.js\";\nimport type { UsageContext, UsageRecorder } from \"./usage-recorder.js\";\n\nexport interface TitleGeneratorDeps {\n sessions: SessionsRepo;\n channels: ChannelHub;\n recorder: Pick<UsageRecorder, \"record\">;\n /** Error persistence (optional: without it, only logs — same as before this was wired up). */\n errors?: ErrorSink;\n log?: (line: string) => void;\n}\n\n/** Host-side parameters for one title-generation request. */\nexport interface TitleRequest {\n /** Fallback material for when the LLM fails or returns an empty result (cleaned and truncated from the first non-empty line). */\n fallbackText: string;\n /** Material override (for subagents — the material is the subagent's own conversation); defaults to the first Task's material self-collected by the core Session. */\n material?: { userText: string; assistantText: string };\n /** The channel to push the `session_title` event to; defaults to `ctx.sessionId`. A\n * subagent has no SSE channel of its own, so its title must reach the frontend via\n * the **parent Session's** channel (the list updates in place by sessionId). */\n notifyOn?: string;\n}\n\n/** session-manager's minimal dependency on the title generator (tests inject a fake implementation). */\nexport interface TitleNotifier {\n maybeGenerate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): void;\n}\n\nexport class TitleGenerator implements TitleNotifier {\n private readonly inflight = new Set<string>();\n private readonly log: (line: string) => void;\n\n constructor(private readonly deps: TitleGeneratorDeps) {\n this.log = deps.log ?? ((line) => console.error(line));\n }\n\n /** Generate a title in the background (fire-and-forget) when conditions are met: the row exists, title is still NULL, and no generation is already in flight. */\n maybeGenerate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): void {\n const row = this.deps.sessions.findById(ctx.sessionId);\n if (!row || row.title !== null) return;\n if (this.inflight.has(ctx.sessionId)) return;\n this.inflight.add(ctx.sessionId);\n void this.generate(ctx, session, req)\n .catch((err: unknown) => {\n this.log(`[title] Generation failed: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"title\", err, ctx, code: \"title_failed\" });\n })\n .finally(() => {\n this.inflight.delete(ctx.sessionId);\n });\n }\n\n private async generate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): Promise<void> {\n let title: string | null = null;\n try {\n // Material defaults to what the core Session self-collects during run; it's only\n // overridden in scenarios like subagents where the material isn't on that Session.\n const res = await session.generateTitle(\n req.material ? { material: req.material } : undefined,\n );\n title = res.title;\n // The one-shot request's real consumption is metered as usual (converted to token_usage and handed to recorder, attributed to this Session).\n if (res.usage) {\n try {\n await this.deps.recorder.record(ctx, tokenUsage(emptyTokenCounts(), res.usage));\n } catch (err) {\n this.log(\n `[title] Usage insert failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n this.deps.errors?.record({\n source: \"title\",\n err,\n ctx,\n code: \"title_usage_insert_failed\",\n });\n }\n }\n } catch (err) {\n // A model request error (rate limit / timeout / network, etc.) shouldn't leave the\n // title permanently missing: log it and fall through to the fallback.\n this.log(`[title] Model request failed: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"title\", err, ctx, code: \"title_llm_failed\" });\n }\n // When the LLM produces no usable title (failure / empty result), truncate the fallback material's first line — this guarantees a title is always generated.\n const finalTitle = title ?? fallbackTitle(req.fallbackText);\n if (finalTitle === null) return;\n // There may already be a concurrent write during generation (e.g. a future manual rename): only persist if still NULL.\n const latest = this.deps.sessions.findById(ctx.sessionId);\n if (!latest || latest.title !== null) return;\n this.deps.sessions.updateTitle(ctx.sessionId, finalTitle);\n this.deps.channels\n .get(req.notifyOn ?? ctx.sessionId)\n .publish(\n { type: \"session_title\", sessionId: ctx.sessionId, title: finalTitle },\n \"server_event\",\n );\n }\n}\n\n/** Fallback title: take the material's first non-empty line, sanitize and truncate; if sanitizing empties it out (pure punctuation, etc.) fall back to the truncated original text; returns null if all-whitespace. */\nfunction fallbackTitle(text: string): string | null {\n // Strip machine markers first: a skill invocation prepends a `[use_skills]` block, so the\n // raw first non-empty line would otherwise be that marker rather than the user's request.\n const firstLine = stripConversationMarkers(text)\n .split(\"\\n\")\n .find((l) => l.trim().length > 0);\n if (!firstLine) return null;\n // sanitizeTitle strips a pure-punctuation line down to empty — in that case keep the truncated original text, guaranteeing \"a title is always obtained\".\n return sanitizeTitle(firstLine) ?? firstLine.trim().slice(0, 30);\n}\n","/**\n * Usage persistence.\n *\n * Consumes the Session output stream:\n * - A subagent's `session_meta` (carrying origin) → registers the mapping \"origin's\n * last session_id → (provider, model_id)\" (a subagent may use a different Model, so\n * cost is priced against the actual Model used);\n * - `token_usage` → inserts one usage_records row: the four token fields come from\n * `payload.request` (per-Request increment; subagents are recorded one row at a\n * time, with `session_id` attributed to their owning main Session). Only tokens are\n * persisted, not cost — pricing may be added later, and cost is computed on the fly\n * against current pricing when usage-service queries.\n * The attribution key is always the paired reference `(provider, model_id)` (the same\n * model_id name across different vendors is attributed separately).\n */\nimport { isEventMessage, isSessionMeta } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport { formatLocalDate } from \"../internal/dates.js\";\nimport type { UsageRepo } from \"../db/repos/usage.js\";\n\n/** Attribution context for one record (top-level Session scope). */\nexport interface UsageContext {\n projectId: string;\n agentId: string;\n /** Top-level Session id (the current actual id after self-heal). */\n sessionId: string;\n /** Vendor grouping for the top-level Session's model (paired with modelId; the fallback attribution when the origin mapping has no hit). */\n provider: string;\n /** Upstream model_id of the top-level Session (paired with provider). */\n modelId: string;\n}\n\n/** Cap on the subagent attribution mapping: over the limit, evicts the oldest by insertion order (an evicted entry falls back to the main Session's Model attribution). */\nexport const ORIGIN_MODELS_MAX = 1000;\n\nexport class UsageRecorder {\n /** Subagent model attribution mapping: origin's last session_id → paired reference (session_id is globally unique). */\n private readonly originModels = new Map<string, { provider: string; modelId: string }>();\n\n constructor(\n private readonly usage: UsageRepo,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n /** Consume one outgoing message; messages other than session_meta / token_usage are a no-op. */\n async record(ctx: UsageContext, msg: OmniMessage): Promise<void> {\n if (isSessionMeta(msg) && msg.origin && msg.origin.length > 0) {\n const originSessionId = msg.origin[msg.origin.length - 1]!;\n // Bounded mapping (avoids unbounded growth over a long-running process): re-inserting refreshes insertion order, over the limit evicts the oldest.\n this.originModels.delete(originSessionId);\n this.originModels.set(originSessionId, {\n provider: msg.payload.provider,\n modelId: msg.payload.model_id,\n });\n if (this.originModels.size > ORIGIN_MODELS_MAX) {\n const oldest = this.originModels.keys().next().value;\n if (oldest !== undefined) this.originModels.delete(oldest);\n }\n return;\n }\n if (!isEventMessage(msg)) return;\n const payload = msg.payload as {\n type?: string;\n request?: { cache_read: number; cache_write: number; output: number; total: number };\n status?: string;\n };\n\n const originSessionId =\n msg.origin && msg.origin.length > 0 ? msg.origin[msg.origin.length - 1]! : null;\n // Empty origin → main session's Model; otherwise look up the mapping, falling back to the main Session's Model (paired) on a miss.\n const ref =\n originSessionId === null\n ? { provider: ctx.provider, modelId: ctx.modelId }\n : (this.originModels.get(originSessionId) ?? {\n provider: ctx.provider,\n modelId: ctx.modelId,\n });\n const now = this.now();\n const base = {\n ts: now.toISOString(),\n date: formatLocalDate(now),\n projectId: ctx.projectId,\n agentId: ctx.agentId,\n sessionId: ctx.sessionId,\n originSessionId,\n provider: ref.provider,\n modelId: ref.modelId,\n };\n\n if (payload.type === \"token_usage\" && payload.request) {\n // A successful request: persist along with tokens (status defaults to completed).\n const r = payload.request;\n this.usage.insert({\n ...base,\n cacheRead: r.cache_read,\n cacheWrite: r.cache_write,\n output: r.output,\n total: r.total,\n });\n return;\n }\n // A failed request (request_end and not completed, usually with no token_usage):\n // persist 0 tokens + status, feeding the \"model success rate\" stat; a successful\n // request is already counted once via the token_usage branch above, not repeated here.\n if (payload.type === \"request_end\" && payload.status && payload.status !== \"completed\") {\n this.usage.insert({\n ...base,\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n total: 0,\n status: payload.status,\n });\n }\n }\n}\n","/**\n * id rules: user_id / project_id / agent_id are semantic ids\n * chosen by their creator at creation time — starting with a lowercase letter,\n * containing only lowercase letters, digits, and underscores. The id doubles as the\n * directory name, so keeping it all-lowercase avoids directory name collisions on\n * case-insensitive filesystems (e.g. macOS) at the source.\n * The hyphen is a **reserved separator**: it only appears at the join point of a\n * non-admin project_id's \"<username>-<suffix>\" concatenation. Usernames never\n * contain a hyphen, so the first hyphen is the ownership boundary — no username can\n * ever be crafted to collide with another user's prefix, keeping namespaces\n * non-overlapping. session_id and temporary workspace ids are still generated by\n * the server (randomHex8).\n */\nimport { randomBytes } from \"node:crypto\";\n\n/** 8-character lowercase hex random string (used for server-generated ids like session_id / temporary workspace). */\nexport function randomHex8(): string {\n return randomBytes(4).toString(\"hex\");\n}\n\n/** General semantic id rule: starts with a lowercase letter, followed by lowercase letters, digits, or underscores only, 2-64 chars (no hyphen). */\nexport const SEMANTIC_ID_PATTERN = /^[a-z][a-z0-9_]{1,63}$/;\n\n/** Username tightens the general rule to 2-32 chars: leaves headroom for the default Project id `<username>-default_project`. */\nexport const USERNAME_PATTERN = /^[a-z][a-z0-9_]{1,31}$/;\n\n/** Suffix segment of a non-admin project_id (after `<username>-`): lowercase letters, digits, and underscores only. */\nexport const PROJECT_SUFFIX_PATTERN = /^[a-z0-9_]+$/;\n\n/** Upper bound on total project_id length (username <=32 + separator + suffix). */\nexport const PROJECT_ID_MAX_LENGTH = 64;\n\n/** Human-readable description of the rule (reused in error messages). */\nexport const SEMANTIC_ID_RULE =\n \"starts with a lowercase letter; lowercase letters, digits and underscores only\";\n","/**\n * Admin user backend: user list / create / reset password / delete.\n *\n * - Create: username is the user_id (^[a-z][a-z0-9_-]{1,31}$); admin sets the initial\n * password, flagged with password_is_initial; a default Project `proj-<username>` is\n * auto-created, rolling back the user row on failure.\n * - Reset password: also flags the password as initial and clears all of the user's\n * login sessions (forcing re-login).\n * - Delete: the built-in admin cannot be deleted; Projects owned by the user are\n * deleted along with it (including data directories), with sessions/memberships/UI\n * preferences cascade-deleted via foreign keys.\n */\nimport type { UserInfo } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { MIN_PASSWORD_LENGTH, toUserInfo } from \"../auth/service.js\";\nimport { hashPassword } from \"../auth/password.js\";\nimport type { AuthSessionsRepo } from \"../db/repos/auth-sessions.js\";\nimport type { ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport { SEMANTIC_ID_RULE, USERNAME_PATTERN } from \"./ids.js\";\nimport type { ProjectService } from \"./project-service.js\";\n\nexport interface AdminServiceDeps {\n users: UsersRepo;\n authSessions: AuthSessionsRepo;\n projects: ProjectsRepo;\n projectService: ProjectService;\n now?: () => Date;\n}\n\nexport class AdminService {\n private readonly now: () => Date;\n\n constructor(private readonly deps: AdminServiceDeps) {\n this.now = deps.now ?? (() => new Date());\n }\n\n listUsers(): UserInfo[] {\n return this.deps.users.list().map(toUserInfo);\n }\n\n async createUser(userId: string, password: string): Promise<UserInfo> {\n if (!USERNAME_PATTERN.test(userId)) {\n throw new HttpError(\n 400,\n \"invalid_user_id\",\n `Username must be 2–32 characters: ${SEMANTIC_ID_RULE}.`,\n );\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"Password must be at least 8 characters.\");\n }\n if (this.deps.users.findById(userId)) {\n throw new HttpError(409, \"user_exists\", `User already exists: ${userId}.`);\n }\n const user: UserRow = {\n userId,\n passwordHash: await hashPassword(password),\n isAdmin: false,\n passwordIsInitial: true,\n createdAt: this.now().toISOString(),\n };\n this.deps.users.insert(user);\n try {\n await this.deps.projectService.provisionInitialProject(user, false);\n } catch (err) {\n // Compensation: roll back the user row if default Project creation fails (e.g. proj-<username> already taken).\n this.deps.users.delete(user.userId);\n throw err;\n }\n return toUserInfo(user);\n }\n\n /** Reset another user's password: flags it as initial and clears all their sessions (prompts a password change on next login). */\n async resetPassword(userId: string, password: string): Promise<void> {\n if (!this.deps.users.findById(userId)) {\n throw new HttpError(404, \"user_not_found\", `User does not exist: ${userId}.`);\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"Password must be at least 8 characters.\");\n }\n this.deps.users.updatePassword(userId, await hashPassword(password), true);\n this.deps.authSessions.deleteByUser(userId);\n }\n\n /** Delete user: the built-in admin cannot be deleted; owned Projects (including data directories) are deleted along with it. */\n async deleteUser(userId: string): Promise<void> {\n const target = this.deps.users.findById(userId);\n if (!target) {\n throw new HttpError(404, \"user_not_found\", `User does not exist: ${userId}.`);\n }\n if (target.isAdmin) {\n throw new HttpError(409, \"cannot_delete_admin\", \"The built-in admin cannot be deleted.\");\n }\n for (const project of this.deps.projects.listByOwner(userId)) {\n await this.deps.projectService.destroyProject(project.projectId);\n }\n this.deps.users.delete(userId); // auth_sessions / project_members / ui_prefs cascade-deleted\n }\n}\n","/**\n * Agent config read/write (config is an editable file).\n *\n * system_config.yaml is edited via yaml's `parseDocument`: only the keys provided in\n * the request are updated, the rest of the file (including comments) is preserved\n * as-is; AGENTS.md is overwritten in full.\n * The vault (agent_state/.vault.toml) is read/written via core's loadAgentVault/saveAgentVault;\n * plaintext values only ever hit disk, and are always masked in responses.\n */\nimport fs from \"node:fs/promises\";\nimport { parseDocument, parse as parseYaml } from \"yaml\";\nimport {\n agentsMdPath,\n agentStateDir,\n agentStateVersion,\n VAULT_VALUE_MAX_LENGTH,\n isValidVaultKey,\n loadAgentVault,\n resetSystemConfigToDefaults,\n saveAgentVault,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport type {\n MCPServerConfig,\n ThinkingLevelName,\n ToolDefinitionConfig,\n} from \"@prismshadow/penguin-core\";\nimport type {\n AgentConfigDto,\n AgentConfigUpdateRequest,\n AgentModelConfigDto,\n AgentCompactionConfigDto,\n VaultEntryInfo,\n VaultResponse,\n VaultUpdateRequest,\n} from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport {\n badRequest,\n optionalBoolean,\n optionalEnum,\n optionalNumber,\n optionalString,\n} from \"../http/validate.js\";\nimport { maskApiKey } from \"./project-config-service.js\";\n\nconst THINKING_LEVELS: readonly ThinkingLevelName[] = [\"none\", \"low\", \"medium\", \"high\", \"xhigh\"];\nconst COMPACTION_MODES = [\"summarize\", \"discard\"] as const;\n\nfunction asRecord(v: unknown): Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : {};\n}\n\nexport interface AgentConfigView {\n agentsMd: string;\n systemConfigYaml: string;\n config: AgentConfigDto;\n stateDir: string;\n}\n\nexport class AgentConfigService {\n constructor(private readonly root: string) {}\n\n /** Whether the Agent exists (determined by the presence of system_config.yaml, matching the CLI's convention). */\n async exists(projectId: string, agentId: string): Promise<boolean> {\n try {\n await fs.access(systemConfigPath(this.root, projectId, agentId));\n return true;\n } catch {\n return false;\n }\n }\n\n async requireExists(projectId: string, agentId: string): Promise<void> {\n if (!(await this.exists(projectId, agentId))) {\n throw new HttpError(404, \"agent_not_found\", \"Agent does not exist.\");\n }\n }\n\n /**\n * Read list-card metadata: name / description + tool count (sum of tools.builtin\n * and tools.mcpServers entries; MCP counted per server). Silently falls back to\n * empty / 0 if the file is corrupt.\n */\n async readCardMeta(\n projectId: string,\n agentId: string,\n ): Promise<{ name?: string; description?: string; toolCount: number; version: number }> {\n try {\n const raw = await fs.readFile(systemConfigPath(this.root, projectId, agentId), \"utf8\");\n const parsed = asRecord(parseYaml(raw));\n const tools = asRecord(parsed.tools);\n const countOf = (v: unknown): number => (Array.isArray(v) ? v.length : 0);\n return {\n ...(typeof parsed.name === \"string\" ? { name: parsed.name } : {}),\n ...(typeof parsed.description === \"string\" ? { description: parsed.description } : {}),\n toolCount: countOf(tools.builtin) + countOf(tools.mcpServers),\n version: agentStateVersion({ version: parsed.version as number | undefined }),\n };\n } catch {\n return { toolCount: 0, version: 1 };\n }\n }\n\n /** Structured config view (matching the edit form's shape) + raw text + AGENTS.md + State path. */\n async getConfig(projectId: string, agentId: string): Promise<AgentConfigView> {\n await this.requireExists(projectId, agentId);\n const yamlPath = systemConfigPath(this.root, projectId, agentId);\n const systemConfigYaml = await fs.readFile(yamlPath, \"utf8\");\n const parsed = asRecord(parseYaml(systemConfigYaml));\n const model = asRecord(parsed.model);\n const compaction = asRecord(parsed.compaction);\n const tools = asRecord(parsed.tools);\n\n let agentsMd = \"\";\n try {\n agentsMd = await fs.readFile(agentsMdPath(this.root, projectId, agentId), \"utf8\");\n } catch {\n // Treat a missing AGENTS.md as an empty file (it normally exists after initialization).\n }\n\n const modelDto: AgentModelConfigDto = {\n ...(typeof model.max_tokens === \"number\" ? { maxTokens: model.max_tokens } : {}),\n ...(typeof model.thinking_level === \"string\"\n ? { thinkingLevel: model.thinking_level as ThinkingLevelName }\n : {}),\n ...(typeof model.timeoutMs === \"number\" ? { timeoutMs: model.timeoutMs } : {}),\n };\n const compactionDto: AgentCompactionConfigDto = {\n ...(typeof compaction.max_context_length === \"number\"\n ? { maxContextLength: compaction.max_context_length }\n : {}),\n ...(typeof compaction.max_session_turns === \"number\"\n ? { maxSessionTurns: compaction.max_session_turns }\n : {}),\n ...(compaction.mode === \"summarize\" || compaction.mode === \"discard\"\n ? { mode: compaction.mode }\n : {}),\n ...(typeof compaction.prompt === \"string\" ? { prompt: compaction.prompt } : {}),\n };\n const config: AgentConfigDto = {\n ...(typeof parsed.name === \"string\" ? { name: parsed.name } : {}),\n ...(typeof parsed.description === \"string\" ? { description: parsed.description } : {}),\n version: agentStateVersion({ version: parsed.version as number | undefined }),\n systemPrompt: typeof parsed.system_prompt === \"string\" ? parsed.system_prompt : \"\",\n ...(typeof parsed.max_turns === \"number\" ? { maxTurns: parsed.max_turns } : {}),\n ...(Object.keys(modelDto).length > 0 ? { model: modelDto } : {}),\n ...(Object.keys(compactionDto).length > 0 ? { compaction: compactionDto } : {}),\n toolsBuiltin: Array.isArray(tools.builtin) ? (tools.builtin as ToolDefinitionConfig[]) : [],\n mcpServers: Array.isArray(tools.mcpServers) ? (tools.mcpServers as MCPServerConfig[]) : [],\n };\n return {\n agentsMd,\n systemConfigYaml,\n config,\n stateDir: agentStateDir(this.root, projectId, agentId),\n };\n }\n\n /**\n * PUT accepts any subset: only the provided keys are updated (parseDocument\n * preserves comments and untouched content); agentsMd is overwritten in full.\n * Numeric validation: >0 or -1; thinkingLevel / mode are validated as enums.\n */\n async updateConfig(\n projectId: string,\n agentId: string,\n req: AgentConfigUpdateRequest,\n ): Promise<void> {\n await this.requireExists(projectId, agentId);\n // Finish all config validation and document changes before writing to disk\n // (if validation fails, AGENTS.md is not written either, avoiding a partial update).\n if (req.config !== undefined) {\n await this.applyConfigUpdate(projectId, agentId, req.config);\n }\n if (req.agentsMd !== undefined) {\n await fs.writeFile(agentsMdPath(this.root, projectId, agentId), req.agentsMd, \"utf8\");\n }\n }\n\n /**\n * Overwrites system_config.yaml with the current code defaults (core's\n * resetSystemConfigToDefaults) — same semantics as updating an installed skill:\n * only the identity fields (name / description / version) survive; the system\n * prompt, max_turns, model/compaction settings and the tool list (incl. MCP\n * servers) become the current defaults. AGENTS.md, skills and the vault are\n * untouched.\n */\n async resetConfig(projectId: string, agentId: string): Promise<void> {\n await this.requireExists(projectId, agentId);\n await resetSystemConfigToDefaults(this.root, projectId, agentId);\n }\n\n private async applyConfigUpdate(\n projectId: string,\n agentId: string,\n config: NonNullable<AgentConfigUpdateRequest[\"config\"]>,\n ): Promise<void> {\n const cfg = config as unknown as Record<string, unknown>;\n const yamlPath = systemConfigPath(this.root, projectId, agentId);\n const doc = parseDocument(await fs.readFile(yamlPath, \"utf8\"));\n\n const setIfProvided = (path: string[], value: unknown): void => {\n if (value !== undefined) doc.setIn(path, value);\n };\n\n setIfProvided([\"name\"], optionalString(cfg, \"name\", { maxLen: 100, label: \"name\" }));\n setIfProvided(\n [\"description\"],\n optionalString(cfg, \"description\", { maxLen: 2000, label: \"description\" }),\n );\n setIfProvided(\n [\"system_prompt\"],\n optionalString(cfg, \"systemPrompt\", { label: \"systemPrompt\" }),\n );\n setIfProvided(\n [\"max_turns\"],\n optionalNumber(cfg, \"maxTurns\", { integer: true, positiveOrMinusOne: true }),\n );\n\n if (cfg.model !== undefined) {\n const model = asRecord(cfg.model);\n setIfProvided(\n [\"model\", \"max_tokens\"],\n optionalNumber(model, \"maxTokens\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided(\n [\"model\", \"thinking_level\"],\n optionalEnum(model, \"thinkingLevel\", THINKING_LEVELS),\n );\n setIfProvided(\n [\"model\", \"timeoutMs\"],\n optionalNumber(model, \"timeoutMs\", { integer: true, positiveOrMinusOne: true }),\n );\n }\n if (cfg.compaction !== undefined) {\n const compaction = asRecord(cfg.compaction);\n setIfProvided(\n [\"compaction\", \"max_context_length\"],\n optionalNumber(compaction, \"maxContextLength\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided(\n [\"compaction\", \"max_session_turns\"],\n optionalNumber(compaction, \"maxSessionTurns\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided([\"compaction\", \"mode\"], optionalEnum(compaction, \"mode\", COMPACTION_MODES));\n setIfProvided([\"compaction\", \"prompt\"], optionalString(compaction, \"prompt\"));\n }\n if (cfg.toolsBuiltin !== undefined) {\n doc.setIn([\"tools\", \"builtin\"], validateToolsBuiltin(cfg.toolsBuiltin));\n }\n if (cfg.mcpServers !== undefined) {\n doc.setIn([\"tools\", \"mcpServers\"], validateMcpServers(cfg.mcpServers));\n }\n\n await fs.writeFile(yamlPath, doc.toString(), \"utf8\");\n }\n\n /** Read the Agent vault (agent_state/.vault.toml): values are always masked, plaintext is never sent to the client. */\n async getVault(projectId: string, agentId: string): Promise<VaultResponse> {\n await this.requireExists(projectId, agentId);\n const vault = await loadAgentVault(this.root, projectId, agentId);\n const entries: VaultEntryInfo[] = Object.entries(vault).map(([key, value]) => ({\n key,\n valueMasked: maskApiKey(value),\n }));\n return { entries };\n }\n\n /**\n * PUT replaces the whole vault table (same semantics as models): keys absent from\n * the body are deleted; omitting value keeps the existing value (a new key must\n * provide a value). Key names are validated against shell environment variable\n * naming rules (same rule as core); deleting everything removes the whole\n * .vault.toml file.\n */\n async updateVault(\n projectId: string,\n agentId: string,\n req: VaultUpdateRequest,\n ): Promise<VaultResponse> {\n await this.requireExists(projectId, agentId);\n const prev = await loadAgentVault(this.root, projectId, agentId);\n\n const seen = new Set<string>();\n const nextVault: Record<string, string> = {};\n for (const entry of req.entries) {\n if (!isValidVaultKey(entry.key)) {\n throw badRequest(\n `Invalid vault key name: ${entry.key} (letters, digits and underscores only, and must not start with a digit).`,\n );\n }\n if (seen.has(entry.key)) {\n throw badRequest(`entries contains a duplicate key: ${entry.key}.`);\n }\n seen.add(entry.key);\n const prevValue = prev[entry.key];\n if (entry.value !== undefined) {\n // Values are injected into the child process environment: an oversized value would\n // make exec spawn fail (E2BIG), so we reject it on write (same limit as core).\n if (entry.value.length > VAULT_VALUE_MAX_LENGTH) {\n throw badRequest(\n `Vault value too long: ${entry.key} (limit ${VAULT_VALUE_MAX_LENGTH} characters).`,\n );\n }\n nextVault[entry.key] = entry.value;\n } else if (prevValue !== undefined) {\n nextVault[entry.key] = prevValue;\n } else {\n throw badRequest(`New key ${entry.key} must provide a value.`);\n }\n }\n\n await saveAgentVault(this.root, projectId, agentId, nextVault);\n return this.getVault(projectId, agentId);\n }\n}\n\nfunction validateToolsBuiltin(value: unknown): ToolDefinitionConfig[] {\n if (!Array.isArray(value)) throw badRequest(\"toolsBuiltin must be an array.\");\n return value.map((item, i) => {\n const t = asRecord(item);\n if (typeof t.name !== \"string\" || t.name.length === 0) {\n throw badRequest(`toolsBuiltin[${i}].name must be a non-empty string.`);\n }\n if (typeof t.description !== \"string\") {\n throw badRequest(`toolsBuiltin[${i}].description must be a string.`);\n }\n if (t.permission !== undefined && t.permission !== \"r\" && t.permission !== \"rw\") {\n throw badRequest(`toolsBuiltin[${i}].permission must be one of r / rw.`);\n }\n if (t.forModel !== undefined && t.forModel !== \"vision\" && t.forModel !== \"text-only\") {\n throw badRequest(`toolsBuiltin[${i}].forModel must be one of vision / text-only.`);\n }\n optionalBoolean(t, \"call_description\", `toolsBuiltin[${i}].call_description`);\n optionalNumber(t, \"timeoutMs\", {\n integer: true,\n positiveOrMinusOne: true,\n label: `toolsBuiltin[${i}].timeoutMs`,\n });\n optionalNumber(t, \"maxOutputLength\", {\n integer: true,\n positiveOrMinusOne: true,\n label: `toolsBuiltin[${i}].maxOutputLength`,\n });\n return t as unknown as ToolDefinitionConfig;\n });\n}\n\nfunction validateMcpServers(value: unknown): MCPServerConfig[] {\n if (!Array.isArray(value)) throw badRequest(\"mcpServers must be an array.\");\n return value.map((item, i) => {\n const s = asRecord(item);\n if (typeof s.name !== \"string\" || s.name.length === 0) {\n throw badRequest(`mcpServers[${i}].name must be a non-empty string.`);\n }\n if (s.config === null || typeof s.config !== \"object\" || Array.isArray(s.config)) {\n throw badRequest(`mcpServers[${i}].config must be an object.`);\n }\n return s as unknown as MCPServerConfig;\n });\n}\n","/**\n * `.project_config.toml` read/write (single hidden config file).\n *\n * Doesn't reuse core's loadProjectConfig/saveProjectConfig (they only keep known\n * fields): reads and writes the complete object directly via smol-toml, preserving\n * extension fields like `name`. credential (api_key / base_url / created_at) is\n * **inlined on the model entry** — there's no longer a supplementary section or\n * secrets file; since the file contains secrets, it's always written with mode\n * 0600. Plaintext only ever hits disk, and is always masked in responses.\n *\n * Model references are **fully split into separate fields**: an entry is\n * stored as two independent fields, `provider` and `model_id`; the `(provider,\n * model_id)` pair is the entry's unique key. `model_id` is the upstream request id,\n * sent to AgentHub verbatim — string concatenation like `<provider>/<id>` is\n * forbidden everywhere in the pipeline. `default_model` / `vision_model` are `{\n * provider, model_id }` paired references (TOML tables).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport {\n GenerativeModel,\n catalogEntryFor,\n defaultProjectConfig,\n projectConfigPath,\n renderProjectConfigToml,\n resolveModelEnv,\n userText,\n} from \"@prismshadow/penguin-core\";\nimport type { LLMOutcome, ModelRef, OmniMessage } from \"@prismshadow/penguin-core\";\nimport type {\n ModelInfo,\n ModelPricingDto,\n ModelRefDto,\n ModelsResponse,\n ModelsUpdateRequest,\n ModelTestRequest,\n ModelTestResponse,\n} from \"../api/types.js\";\nimport { badRequest } from \"../http/validate.js\";\nimport type { PricingRates } from \"./usage-service.js\";\n\ntype RawTable = Record<string, unknown>;\n\n/**\n * API key masking: length <=12 -> `***`, otherwise `first4…last4`; plaintext is\n * never sent to the client. The 12-char threshold: `first4…last4` exposes 8\n * characters, which for a 9-12 character short secret would leak more than half of\n * it, so those are masked in full instead.\n */\nexport function maskApiKey(key: string): string {\n if (key.length <= 12) return \"***\";\n return `${key.slice(0, 4)}…${key.slice(-4)}`;\n}\n\nfunction asTable(v: unknown): RawTable {\n return v !== null && typeof v === \"object\" && !Array.isArray(v) ? (v as RawTable) : {};\n}\n\nfunction asArray(v: unknown): RawTable[] {\n return Array.isArray(v) ? v.map(asTable) : [];\n}\n\nfunction optNum(v: unknown): number | undefined {\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\nfunction optStr(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** Leniently reads a paired reference table (default_model / vision_model); returns undefined on a shape mismatch (including the old string format). */\nfunction optRef(v: unknown): ModelRef | undefined {\n const t = asTable(v);\n const provider = optStr(t.provider);\n const modelId = optStr(t.model_id);\n return provider !== undefined && modelId !== undefined\n ? { provider, model_id: modelId }\n : undefined;\n}\n\n/** Whether an entry matches a paired reference (the entry's provider / model_id fields must be strings). */\nfunction entryMatches(m: RawTable, provider: string, modelId: string): boolean {\n return m.provider === provider && m.model_id === modelId;\n}\n\n/** In-process Map/Set key for a paired reference (\\0-separated to avoid concatenation ambiguity; never persisted, not an id format). */\nfunction refKey(provider: string, modelId: string): string {\n return `${provider}\\0${modelId}`;\n}\n\n/** Display form of a paired reference (for error messages; display only, not a storage format). */\nfunction showRef(provider: string, modelId: string): string {\n return `(provider=${provider}, model_id=${modelId})`;\n}\n\n/**\n * Connectivity probe prompt: asks for one word, so the whole exchange fits in a\n * single-digit output budget. The wording discourages reasoning and the trailing empty\n * <think></think> makes many reasoning models treat their thinking phase as already\n * closed - keeping the probe's tiny budget on actual output instead of burning it on\n * thinking.\n */\nconst PROBE_PROMPT =\n 'ping - reply with the single word \"pong\" and nothing else. Do not think or explain.\\n<think></think>';\n\n/**\n * Speed probe prompt: a one-word answer can't be timed (a compliant model emits 1-3\n * tokens, and the window is then dominated by the final usage chunk's round trip), so\n * speed mode asks for something long enough to run into its raised cap. Counting to 50 is\n * deterministic and needs no knowledge, so every model produces the same token stream at\n * its own decoding rate. Carries the same anti-thinking hint as the connectivity prompt.\n */\nconst SPEED_PROBE_PROMPT =\n \"Count from 1 to 50 as a comma-separated list, and nothing else. Do not think or explain.\\n<think></think>\";\n\nexport class ProjectConfigService {\n constructor(private readonly root: string) {}\n\n private filePath(projectId: string): string {\n return projectConfigPath(this.root, projectId);\n }\n\n /** Reads the raw TOML object; returns an empty object if the file doesn't exist (does not write to disk). */\n async readRaw(projectId: string): Promise<RawTable> {\n let raw: string;\n try {\n raw = await fs.readFile(this.filePath(projectId), \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw err;\n }\n return asTable(parseToml(raw));\n }\n\n /**\n * Writes the whole object to disk: the file inlines secrets like api_key, always\n * written with mode 0600 (the `mode` option only applies at creation time, so\n * chmod is used to enforce it on existing files too — matching core's\n * saveProjectConfig behavior).\n */\n async writeRaw(projectId: string, data: RawTable): Promise<void> {\n const file = this.filePath(projectId);\n await fs.mkdir(path.dirname(file), { recursive: true });\n // Rendering goes through core's single writer: paired references become inline\n // tables, models is placed last — matching the CLI's output format exactly\n // (the same file should never have two formats).\n await fs.writeFile(file, renderProjectConfigToml(data), { encoding: \"utf8\", mode: 0o600 });\n await fs.chmod(file, 0o600);\n }\n\n /**\n * Initial config for a newly created Project: display name + preset built-in\n * model catalog (the default model and all preset entries, sourced from the same\n * core defaultProjectConfig; a gateway model's base_url is already inlined on the\n * entry, with no key); users only need to fill in an API key as needed (leave it\n * blank to fall back to the provider's environment variable).\n */\n async writeInitialConfig(projectId: string, name: string): Promise<void> {\n const preset = defaultProjectConfig();\n await this.writeRaw(projectId, {\n name,\n ...(preset.default_model !== undefined ? { default_model: preset.default_model } : {}),\n models: preset.models,\n });\n }\n\n /**\n * Backfills preset models (for onboarding an existing Project, e.g. the\n * `default_project` shared with the CLI when the first user is onboarded — its\n * directory already existed and never went through `writeInitialConfig`, so it\n * previously had no models and no default model).\n *\n * **Only backfills when there are no models at all**: a Project that already has\n * models configured (via the CLI or edited by the user) is left as-is, and its\n * other fields (name, etc.) are preserved too — existing config is never\n * overwritten.\n */\n async ensurePresetModels(projectId: string): Promise<void> {\n const raw = await this.readRaw(projectId);\n if (asArray(raw.models).length > 0) return;\n const preset = defaultProjectConfig();\n await this.writeRaw(projectId, {\n ...raw,\n // Also reset to the preset default_model if the existing one points at a now-deleted model, to keep the default model valid.\n ...(preset.default_model !== undefined ? { default_model: preset.default_model } : {}),\n models: preset.models,\n });\n }\n\n /** Project display name (the toml's name; returns undefined if unset, the frontend falls back to displaying the id). */\n async getName(projectId: string): Promise<string | undefined> {\n const raw = await this.readRaw(projectId);\n return typeof raw.name === \"string\" ? raw.name : undefined;\n }\n\n /** Paired reference of the default Model; returns undefined if unconfigured (or in the old string format). */\n async getDefaultModelRef(projectId: string): Promise<ModelRef | undefined> {\n const raw = await this.readRaw(projectId);\n return optRef(raw.default_model);\n }\n\n /** Pricing lookup for usage-recorder: the current pricing for this paired reference (undefined if none -> cost is NULL). */\n async getPricing(\n projectId: string,\n provider: string,\n modelId: string,\n ): Promise<PricingRates | undefined> {\n const raw = await this.readRaw(projectId);\n const entry = asArray(raw.models).find((m) => entryMatches(m, provider, modelId));\n const pricing = entry ? asTable(entry.pricing) : {};\n const cacheRead = optNum(pricing.cache_read);\n const cacheWrite = optNum(pricing.cache_write);\n const output = optNum(pricing.output);\n if (cacheRead === undefined && cacheWrite === undefined && output === undefined) {\n return undefined;\n }\n return { cacheRead: cacheRead ?? 0, cacheWrite: cacheWrite ?? 0, output: output ?? 0 };\n }\n\n /**\n * Model connectivity test: the model reference `(provider, modelId)` is submitted\n * as a pair in the request body; sends one minimal request using that model's\n * config (optionally overridden with an unsaved apiKey / baseUrl) — no tools, no\n * system prompt, thinking disabled, a tiny output cap, 20s timeout — just to see\n * whether the endpoint answers. The model id sent to AgentHub is `modelId`\n * itself (the upstream id verbatim; client_type inference follows it).\n *\n * A reasoning-heavy model may ignore the disabled thinking level and burn the\n * whole tiny output cap on thinking (finish_reason=length with no text — AgentHub\n * raises EmptyResponseError, collapsed to a malformed outcome): the endpoint\n * demonstrably streamed model output, which is everything a connectivity test\n * proves, so that case counts as ok too (see probeVerdict).\n *\n * Never throws: the LLM layer collapses auth/parameter/network errors into an\n * `LLMOutcome`, which is translated here into ok / message. Consumes very few\n * Tokens (single-digit output; speed mode spends up to its 64-token cap to have a\n * window worth timing), and writes no Trace and records no usage.\n */\n async testModel(projectId: string, req: ModelTestRequest): Promise<ModelTestResponse> {\n const raw = await this.readRaw(projectId);\n // Testable even if the model isn't in the config yet (validate before saving when adding a custom model): in that case all parameters come from the request body.\n const entry = asArray(raw.models).find((m) => entryMatches(m, req.provider, req.modelId)) ?? {};\n // Always tests against the **current form draft**: checking \"clear\" means the saved key is not fallen back to; an explicit null base URL is treated as cleared.\n const savedKey = optStr(entry.api_key);\n const apiKey = req.clearApiKey ? undefined : (req.apiKey ?? savedKey);\n const savedBaseUrl = optStr(entry.base_url);\n const baseUrl = req.baseUrl === null ? undefined : (req.baseUrl ?? savedBaseUrl);\n const clientType = req.clientType ?? optStr(entry.client_type);\n\n const startedAt = Date.now();\n try {\n // Construction must be inside the try block: the underlying provider SDK can\n // throw during **client construction** itself when a credential is missing\n // (models on the OpenAI protocol need apiKey/OPENAI_API_KEY) — the whole point\n // of a connectivity test is to collapse that kind of failure into\n // `{ ok:false }`; if construction were outside the try, a missing-key test\n // would bubble up as a 500.\n const llm = new GenerativeModel({\n modelId: req.modelId,\n ...(apiKey ? { apiKey } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n ...(clientType ? { clientType } : {}),\n tools: [],\n thinkingLevel: \"none\",\n // Speed mode pairs a raised cap with a prompt that keeps generating (see\n // SPEED_PROBE_PROMPT), so the stream lasts long enough for TTFT/TPS to describe\n // decoding rather than one round trip; the plain connectivity test keeps the\n // single-digit-token budget.\n maxTokens: req.speed ? 64 : 16,\n requestTimeoutMs: 20_000,\n });\n const gen = llm.streamGenerate({\n newMessages: [userText(req.speed ? SPEED_PROBE_PROMPT : PROBE_PROMPT)],\n });\n let sawContent = false;\n let firstContentAt: number | null = null;\n let outputTokens = 0;\n for (;;) {\n const step = await gen.next();\n if (step.done) {\n const verdict = probeVerdict(step.value, sawContent);\n if (!verdict.ok) return verdict;\n const res: ModelTestResponse = { ok: true, latencyMs: Date.now() - startedAt };\n if (firstContentAt !== null) {\n res.ttftMs = firstContentAt - startedAt;\n // Output rate over the streaming window (first content -> stream end), dropped\n // when the sample is too small to mean anything (see probeTps): usage is only\n // reported on completed streams, so thinking-only malformed endings carry TTFT\n // but no rate, and so does a model that answers in a couple of tokens.\n const tps = probeTps(outputTokens, Date.now() - firstContentAt);\n if (tps !== undefined) res.tps = tps;\n }\n return res;\n }\n if (isProbeContent(step.value)) {\n sawContent = true;\n if (firstContentAt === null) firstContentAt = Date.now();\n }\n const p = step.value.payload as { type?: string; request?: { output?: number } };\n if (p.type === \"token_usage\" && typeof p.request?.output === \"number\") {\n outputTokens = p.request.output;\n }\n }\n } catch (err) {\n // Defensive: an unexpected exception during construction/iteration (the LLM layer promises not to throw; this is a fallback).\n return {\n ok: false,\n message: (err instanceof Error ? err.message : String(err)).slice(0, 300),\n };\n }\n }\n\n /**\n * GET models view: masks credential (inline fields), flags the default Model;\n * the group is the entry's `provider` field, looked up in the built-in catalog by\n * the `(provider, model_id)` pair to fill in displayName / envKey (entries outside\n * the catalog are treated as custom models: envKey only has a fallback for the\n * openai protocol). vision follows the TOML annotation when present, otherwise\n * falls back to the catalog annotation (if neither exists, the field is omitted =\n * supported by default).\n */\n async getModels(projectId: string): Promise<ModelsResponse> {\n const raw = await this.readRaw(projectId);\n const defaultRef = optRef(raw.default_model);\n const visionRef = optRef(raw.vision_model);\n const models: ModelInfo[] = asArray(raw.models)\n // An entry is valid only if both provider and model_id are strings (an entry in the old concatenated format lacks provider and is ignored).\n .filter((m) => typeof m.provider === \"string\" && typeof m.model_id === \"string\")\n .map((m) => {\n const provider = m.provider as string;\n const modelId = m.model_id as string;\n const pricing = asTable(m.pricing);\n const pricingDto: ModelPricingDto | undefined =\n optNum(pricing.cache_read) !== undefined ||\n optNum(pricing.cache_write) !== undefined ||\n optNum(pricing.output) !== undefined\n ? {\n cacheRead: optNum(pricing.cache_read) ?? 0,\n cacheWrite: optNum(pricing.cache_write) ?? 0,\n output: optNum(pricing.output) ?? 0,\n }\n : undefined;\n const clientType = optStr(m.client_type);\n const cat = catalogEntryFor(provider, modelId);\n // The env fallback is reported as-is: follows the same rule as\n // AgentHub routing — an explicit client_type takes priority (the openai\n // protocol reads OPENAI_*, independent of the group), otherwise it's\n // auto-routed to a provider client based on model_id; an id that can't be\n // routed has no fallback (no envKey, and AgentHub will reject that id).\n const envKey = resolveModelEnv(modelId, clientType)?.envKey;\n const vision = typeof m.vision === \"boolean\" ? m.vision : cat?.supportsVision;\n // Output cap: TOML annotation only (user-owned; the built-in catalog never presets it).\n const maxTokens = optNum(m.max_tokens);\n // Display name: the explicit TOML field (user-edited) takes priority, then the built-in catalog.\n const displayName = optStr(m.display_name) ?? cat?.displayName;\n // credential is inlined on the entry: a credential block is emitted if either api_key or base_url is present.\n const apiKey = optStr(m.api_key);\n const credBaseUrl = optStr(m.base_url);\n const createdAt = optStr(m.created_at);\n const info: ModelInfo = {\n provider,\n modelId,\n ...(displayName !== undefined ? { displayName } : {}),\n isDefault:\n defaultRef !== undefined &&\n defaultRef.provider === provider &&\n defaultRef.model_id === modelId,\n ...(optNum(m.context_window) !== undefined\n ? { contextWindow: optNum(m.context_window)! }\n : {}),\n ...(clientType ? { clientType } : {}),\n ...(vision !== undefined ? { vision } : {}),\n ...(maxTokens !== undefined ? { maxTokens } : {}),\n ...(envKey ? { envKey } : {}),\n ...(pricingDto ? { pricing: pricingDto } : {}),\n ...(apiKey !== undefined || credBaseUrl !== undefined\n ? {\n credential: {\n ...(apiKey !== undefined ? { apiKeyMasked: maskApiKey(apiKey) } : {}),\n ...(credBaseUrl !== undefined ? { baseUrl: credBaseUrl } : {}),\n ...(createdAt !== undefined ? { createdAt } : {}),\n },\n }\n : {}),\n };\n return info;\n });\n const toDto = (ref: ModelRef): ModelRefDto => ({\n provider: ref.provider,\n modelId: ref.model_id,\n });\n return {\n ...(defaultRef !== undefined ? { defaultModel: toDto(defaultRef) } : {}),\n ...(visionRef !== undefined ? { visionModel: toDto(visionRef) } : {}),\n models,\n };\n }\n\n /**\n * PUT replaces the whole models table: key =\n * `(provider, modelId)`; model entries that no longer appear are deleted along\n * with their inline credential; omitting apiKey keeps the existing value,\n * providing one overwrites it and records created_at, clearApiKey clears it;\n * baseUrl null clears it / omitted keeps it. A key change (either the group or\n * the upstream id changes) is migrated as a pair via `renamedFrom`: credential and\n * unknown fields migrate along with the base entry, and default/vision pointers\n * follow. Other extension fields in the toml (name, etc.) are preserved.\n */\n async updateModels(projectId: string, req: ModelsUpdateRequest): Promise<ModelsResponse> {\n const raw = await this.readRaw(projectId);\n const prevModels = asArray(raw.models);\n\n const seen = new Set<string>();\n const nextModels: RawTable[] = [];\n // Rename mapping (old reference key -> new reference): default model / vision model pointers follow a key change instead of being lost on a full table replacement.\n const renamed = new Map<string, ModelRefDto>();\n for (const entry of req.models) {\n const key = refKey(entry.provider, entry.modelId);\n if (seen.has(key)) {\n throw badRequest(\n `models contains a duplicate model reference: ${showRef(entry.provider, entry.modelId)}.`,\n );\n }\n seen.add(key);\n if (\n entry.renamedFrom !== undefined &&\n !(\n entry.renamedFrom.provider === entry.provider &&\n entry.renamedFrom.modelId === entry.modelId\n )\n ) {\n renamed.set(refKey(entry.renamedFrom.provider, entry.renamedFrom.modelId), {\n provider: entry.provider,\n modelId: entry.modelId,\n });\n }\n\n // Model entry: uses the old entry (the entry for the original reference when\n // the key changed) as the base, preserving unknown fields and inline\n // credential; known fields are replaced wholesale per the request (omitted\n // means removed).\n const prevRef = entry.renamedFrom ?? { provider: entry.provider, modelId: entry.modelId };\n const prev = prevModels.find((m) => entryMatches(m, prevRef.provider, prevRef.modelId)) ?? {};\n const next: RawTable = { ...prev, provider: entry.provider, model_id: entry.modelId };\n delete next.context_window;\n delete next.client_type;\n delete next.vision;\n delete next.max_tokens;\n delete next.pricing;\n delete next.display_name;\n // Leftover key from the old concatenated format (request_model_id): defensively stripped, never written to disk again.\n delete next.request_model_id;\n\n // Display name: **only written to disk when it differs from the built-in\n // catalog (looked up by the paired reference)** — preset models keep the\n // config clean, only user-edited ones (including those not found in the\n // catalog) get written into the TOML.\n const catNew = catalogEntryFor(entry.provider, entry.modelId);\n if (entry.displayName && entry.displayName !== catNew?.displayName) {\n next.display_name = entry.displayName;\n }\n if (entry.contextWindow !== undefined) next.context_window = entry.contextWindow;\n if (entry.clientType) next.client_type = entry.clientType;\n // Treated as supported by default: only written to disk when explicitly annotated (both true/false are kept; false drives a frontend blocking hint).\n if (entry.vision !== undefined) next.vision = entry.vision;\n // Inherit-the-Agent-value by default: only written to disk when explicitly annotated (omitted on a full-table PUT = the annotation is cleared).\n if (entry.maxTokens !== undefined) next.max_tokens = entry.maxTokens;\n if (entry.pricing !== undefined) {\n next.pricing = {\n unit: \"usd_per_mtok\",\n cache_read: entry.pricing.cacheRead,\n cache_write: entry.pricing.cacheWrite,\n output: entry.pricing.output,\n };\n }\n\n // credential is inlined on the entry; added/removed on top of the old value per the request (migrates automatically with the base entry when the key changes).\n if (entry.clearApiKey) {\n delete next.api_key;\n delete next.created_at;\n }\n if (entry.apiKey !== undefined) {\n next.api_key = entry.apiKey;\n next.created_at = new Date().toISOString();\n }\n if (entry.baseUrl === null) delete next.base_url;\n else if (entry.baseUrl !== undefined) next.base_url = entry.baseUrl;\n nextModels.push(next);\n }\n\n // default_model: when provided it must be present in models; when omitted the previous value is kept (the pointer follows a key rename; if it was deleted, it's removed).\n let defaultModel: ModelRefDto | undefined;\n if (req.defaultModel !== undefined) {\n if (!seen.has(refKey(req.defaultModel.provider, req.defaultModel.modelId))) {\n throw badRequest(\n `defaultModel must be included in models: ${showRef(req.defaultModel.provider, req.defaultModel.modelId)}.`,\n );\n }\n defaultModel = req.defaultModel;\n } else {\n const prevRef = optRef(raw.default_model);\n if (prevRef !== undefined) {\n const prevKey = refKey(prevRef.provider, prevRef.model_id);\n const followed = renamed.get(prevKey) ?? {\n provider: prevRef.provider,\n modelId: prevRef.model_id,\n };\n if (seen.has(refKey(followed.provider, followed.modelId))) defaultModel = followed;\n }\n }\n\n // vision_model: same semantics as default_model; additionally must not be annotated vision=false (can't proxy-read images if unsupported).\n const targetOf = (ref: ModelRefDto) =>\n req.models.find((m) => m.provider === ref.provider && m.modelId === ref.modelId);\n let visionModel: ModelRefDto | undefined;\n if (req.visionModel !== undefined) {\n if (!seen.has(refKey(req.visionModel.provider, req.visionModel.modelId))) {\n throw badRequest(\n `visionModel must be included in models: ${showRef(req.visionModel.provider, req.visionModel.modelId)}.`,\n );\n }\n if (targetOf(req.visionModel)?.vision === false) {\n throw badRequest(\n `visionModel must not point to a model annotated as not supporting images: ${showRef(req.visionModel.provider, req.visionModel.modelId)}.`,\n );\n }\n visionModel = req.visionModel;\n } else {\n const prevRef = optRef(raw.vision_model);\n if (prevRef !== undefined) {\n const prevKey = refKey(prevRef.provider, prevRef.model_id);\n const followed = renamed.get(prevKey) ?? {\n provider: prevRef.provider,\n modelId: prevRef.model_id,\n };\n if (seen.has(refKey(followed.provider, followed.modelId))) {\n // The former vision model is now annotated as not supporting images: the annotation takes priority, and the pointer is dropped as invalid.\n if (targetOf(followed)?.vision !== false) visionModel = followed;\n }\n }\n }\n\n const toRaw = (ref: ModelRefDto): RawTable => ({\n provider: ref.provider,\n model_id: ref.modelId,\n });\n const next: RawTable = { ...raw, models: nextModels };\n if (defaultModel !== undefined) next.default_model = toRaw(defaultModel);\n else delete next.default_model;\n if (visionModel !== undefined) next.vision_model = toRaw(visionModel);\n else delete next.vision_model;\n await this.writeRaw(projectId, next);\n return this.getModels(projectId);\n }\n}\n\n/**\n * Whether a streamed message carries genuine model content (thinking or text, partial delta\n * or complete backfill) — the probe's \"the endpoint really answered\" signal. Tool calls and\n * event messages don't count (the probe declares no tools).\n */\nexport function isProbeContent(msg: OmniMessage): boolean {\n const p = msg.payload as { type?: string; thinking?: string; text?: string };\n if (p.type === \"partial_thinking\" || p.type === \"thinking\") return Boolean(p.thinking);\n if (p.type === \"partial_text\" || p.type === \"text\") return Boolean(p.text);\n return false;\n}\n\n/**\n * Probe verdict from the terminal LLM outcome. `completed` always passes. A `malformed`\n * ending after genuine streamed content also passes: the typical case is a reasoning-heavy\n * model that ignores the disabled thinking level and burns the probe's tiny max_tokens\n * entirely on thinking (finish_reason=length -> AgentHub's EmptyResponseError) — the\n * endpoint, credential, and model id all demonstrably work, which is what a connectivity\n * test measures. Everything else (auth/parameter failures, timeouts, malformed with nothing\n * received) fails with the outcome's message.\n */\nexport function probeVerdict(\n outcome: LLMOutcome,\n sawContent: boolean,\n): { ok: true } | { ok: false; message: string } {\n if (outcome.status === \"completed\") return { ok: true };\n if (outcome.status === \"malformed\" && sawContent) return { ok: true };\n const detail = \"message\" in outcome && outcome.message ? outcome.message : outcome.status;\n return { ok: false, message: String(detail).slice(0, 300) };\n}\n\n/**\n * Sample floors below which the streaming window says nothing about decoding rate. The\n * speed probe's 64-token cap clears both by a wide margin, so hitting a floor means the\n * model didn't really stream (a one-word answer, or usage that never arrived).\n */\nconst PROBE_TPS_MIN_TOKENS = 16;\nconst PROBE_TPS_MIN_WINDOW_MS = 100;\n\n/**\n * Output rate (tokens/s) over the probe's streaming window (first content -> stream end),\n * rounded to 1dp; undefined when the sample is too small to be meaningful. A stream's\n * closing usage chunk costs a round trip on its own, so a two-token answer measures network\n * jitter and nothing else — 2 tokens in 30ms reads as 66.7 tok/s and the same model 30ms\n * later reads as 33.3, which the card badges would paint green vs yellow. Callers report\n * TTFT alone rather than a fabricated rate; a malformed ending carries no usage at all and\n * lands here as 0 tokens.\n */\nexport function probeTps(outputTokens: number, windowMs: number): number | undefined {\n if (outputTokens < PROBE_TPS_MIN_TOKENS || windowMs <= PROBE_TPS_MIN_WINDOW_MS) return undefined;\n return Math.round((outputTokens / (windowMs / 1000)) * 10) / 10;\n}\n","/**\n * Agent service.\n *\n * The list is the union of \"DB index ∪ directory scan\": a subdirectory under\n * `<project>/` containing `agent_state/system_config.yaml` is treated as an Agent;\n * unmanaged ones found are backfilled into the DB — this handles Agents created\n * directly via the CLI.\n * Create: generate agent-<8hex>, initialize Agent State via core's `createAgent`,\n * then write name/description into system_config.yaml (parseDocument preserves the\n * template's comments).\n */\nimport fs from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\nimport { HttpError } from \"../http/errors.js\";\nimport {\n agentDir,\n agentsDir,\n agentsMdPath,\n BUILTIN_AGENT_IDS,\n createAgent as coreCreateAgent,\n isValidId,\n loadAgentVault,\n scheduleDir,\n skillsDir,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport type { AgentsRepo } from \"../db/repos/agents.js\";\nimport { SEMANTIC_ID_PATTERN, SEMANTIC_ID_RULE } from \"./ids.js\";\nimport type { AgentConfigService } from \"./agent-config-service.js\";\n\nexport interface AgentListItem {\n agentId: string;\n name?: string;\n description?: string;\n createdAt?: string;\n /** Last config modification time: the later of system_config.yaml / AGENTS.md mtime. */\n updatedAt?: string;\n /** Tool count: number of tools.builtin + tools.mcpServers entries (MCP counted per server). */\n toolCount: number;\n /** Agent State version number (missing field treated as 1). */\n version: number;\n /** Number of vault keys. */\n vaultKeyCount: number;\n /** Number of scheduled tasks (count of .toml files under schedule/, including invalid ones). */\n scheduleCount: number;\n /** Number of installed Skills (count of skills/<name>/ directories that contain a SKILL.md). */\n skillCount: number;\n}\n\nexport class AgentService {\n constructor(\n private readonly root: string,\n private readonly agents: AgentsRepo,\n private readonly agentConfig: AgentConfigService,\n ) {}\n\n /** Union of DB index ∪ directory scan; unmanaged directory Agents are backfilled into the DB. */\n async listAgents(projectId: string): Promise<AgentListItem[]> {\n const known = new Map(this.agents.list(projectId).map((r) => [r.agentId, r]));\n\n let entries: string[] = [];\n try {\n const dirents = await fs.readdir(agentsDir(this.root, projectId), { withFileTypes: true });\n entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);\n } catch {\n // The Project's agents/ directory doesn't exist yet (no Agent directories): return from the DB index only.\n }\n for (const agentId of entries) {\n if (known.has(agentId) || !isValidId(agentId)) continue;\n const configPath = systemConfigPath(this.root, projectId, agentId);\n let createdAt: string;\n try {\n const stat = await fs.stat(configPath);\n createdAt = (stat.birthtime.getTime() > 0 ? stat.birthtime : stat.mtime).toISOString();\n } catch {\n continue; // A directory without system_config.yaml is not an Agent (e.g. a temp folder)\n }\n const row = { projectId, agentId, createdAt };\n this.agents.insertOrIgnore(row);\n known.set(agentId, row);\n }\n\n // Meta reads and mtime stats for each Agent run in parallel (Promise.all preserves the sorted order).\n const sorted = [...known.values()].sort((a, b) =>\n a.createdAt === b.createdAt\n ? a.agentId.localeCompare(b.agentId)\n : a.createdAt < b.createdAt\n ? -1\n : 1,\n );\n return Promise.all(\n sorted.map(async (row) => {\n const [meta, updatedAt, vaultKeyCount, scheduleCount, skillCount] = await Promise.all([\n this.agentConfig.readCardMeta(projectId, row.agentId),\n this.configUpdatedAt(projectId, row.agentId),\n this.vaultKeyCount(projectId, row.agentId),\n this.scheduleCount(projectId, row.agentId),\n this.skillCount(projectId, row.agentId),\n ]);\n return {\n agentId: row.agentId,\n ...meta,\n createdAt: row.createdAt,\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n vaultKeyCount,\n scheduleCount,\n skillCount,\n };\n }),\n );\n }\n\n /** Number of vault keys (falls back to 0 on read failure). */\n private async vaultKeyCount(projectId: string, agentId: string): Promise<number> {\n try {\n return Object.keys(await loadAgentVault(this.root, projectId, agentId)).length;\n } catch {\n return 0;\n }\n }\n\n /** Number of scheduled tasks: count of .toml files under schedule/ (0 if the directory doesn't exist). */\n private async scheduleCount(projectId: string, agentId: string): Promise<number> {\n try {\n const names = await fs.readdir(scheduleDir(this.root, projectId, agentId));\n return names.filter((n) => n.endsWith(\".toml\")).length;\n } catch {\n return 0;\n }\n }\n\n /** Number of installed Skills: count of skills/<name>/ directories containing a SKILL.md (0 if the directory doesn't exist). */\n private async skillCount(projectId: string, agentId: string): Promise<number> {\n const base = skillsDir(this.root, projectId, agentId);\n let dirents: Dirent[];\n try {\n dirents = await fs.readdir(base, { withFileTypes: true });\n } catch {\n return 0;\n }\n const present = await Promise.all(\n dirents\n .filter((d) => d.isDirectory())\n .map(async (d) => {\n try {\n await fs.access(path.join(base, d.name, \"SKILL.md\"));\n return true;\n } catch {\n return false;\n }\n }),\n );\n return present.filter(Boolean).length;\n }\n\n /** Last config modification time: the later of system_config.yaml and AGENTS.md mtime; omitted if neither is readable. */\n private async configUpdatedAt(projectId: string, agentId: string): Promise<string | undefined> {\n const paths = [\n systemConfigPath(this.root, projectId, agentId),\n agentsMdPath(this.root, projectId, agentId),\n ];\n const times = await Promise.all(\n paths.map(async (p) => {\n try {\n return (await fs.stat(p)).mtime.getTime();\n } catch {\n return 0;\n }\n }),\n );\n const max = Math.max(...times);\n return max > 0 ? new Date(max).toISOString() : undefined;\n }\n\n /**\n * Delete an Agent: the sole built-in Agent\n * default_agent (shared with the CLI, the default conversation Agent) cannot be\n * deleted; callers must first drain any active run via manager.abortAgent.\n * The directory is deleted recursively (including Trace), and the DB's\n * agents/sessions index rows are removed along with it; usage records are kept\n * (historical stats are unaffected).\n */\n async deleteAgent(projectId: string, agentId: string): Promise<void> {\n if (BUILTIN_AGENT_IDS.includes(agentId)) {\n throw new HttpError(\n 409,\n \"cannot_delete_builtin_agent\",\n \"Built-in Agents (default_agent) are provisioned with the Project and cannot be deleted from the web.\",\n );\n }\n await fs.rm(agentDir(this.root, projectId, agentId), { recursive: true, force: true });\n this.agents.delete(projectId, agentId);\n }\n\n /**\n * Create an Agent: the id is chosen by the creator (a semantic id, checked for\n * duplicates against both the DB and the directory within the Project — a 409\n * if taken, which naturally also blocks built-in Agent ids) → initialize State →\n * write name/description (name defaults to the id).\n */\n async createAgent(\n projectId: string,\n agentId: string,\n name?: string,\n description?: string,\n ): Promise<AgentListItem> {\n if (!SEMANTIC_ID_PATTERN.test(agentId)) {\n throw new HttpError(\n 400,\n \"invalid_agent_id\",\n `Agent id must be 2–64 characters: ${SEMANTIC_ID_RULE}.`,\n );\n }\n const taken =\n this.agents.exists(projectId, agentId) ||\n (await fs.stat(agentDir(this.root, projectId, agentId)).then(\n () => true,\n () => false,\n ));\n if (taken) {\n throw new HttpError(409, \"agent_exists\", `Agent id is already taken: ${agentId}.`);\n }\n const displayName = name ?? agentId;\n await coreCreateAgent({ root: this.root, projectId, agentId });\n try {\n await this.agentConfig.updateConfig(projectId, agentId, {\n config: { name: displayName, ...(description !== undefined ? { description } : {}) },\n });\n } catch (err) {\n // If initialization fails partway through, clean up the directory: an orphaned\n // directory would make retries with this agent id 409 forever.\n await fs\n .rm(agentDir(this.root, projectId, agentId), { recursive: true, force: true })\n .catch(() => {});\n throw err;\n }\n const createdAt = new Date().toISOString();\n this.agents.insertOrIgnore({ projectId, agentId, createdAt });\n // The init template ships with a default toolset and version number; read back the actual values.\n const meta = await this.agentConfig.readCardMeta(projectId, agentId);\n return {\n agentId,\n name: displayName,\n ...(description !== undefined ? { description } : {}),\n createdAt,\n updatedAt: createdAt,\n toolCount: meta.toolCount,\n version: meta.version,\n vaultKeyCount: 0,\n scheduleCount: 0,\n // Read the real count: coreCreateAgent seeds the default skill set for default_agent.\n skillCount: await this.skillCount(projectId, agentId),\n };\n }\n}\n","/**\n * Benchmark score reading (read-only display): walks `benchmarks/<id>/`, reads\n * `benchmark_config.toml` (title,\n * description, evaluation Model, per-case run count `runs`) and `scoreboard.yaml`\n * (evaluations[], scoreboard v2: each case carries a runs array and a summary).\n * Content is created and refined by benchmark_builder; the server only reads it.\n * Missing or corrupt files always degrade gracefully (title falls back to the\n * directory name, scores come back empty) rather than throwing.\n *\n * The three per-case metrics trust the file's own values; when missing they're\n * computed as the average over the runs array. The old format (no runs at the\n * case level, a single session_id) is parsed as a single run — the server backfills\n * one run entry.\n * Docs: /docs/self-improvement § \"Benchmark storage\".\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport { parse as parseYaml } from \"yaml\";\nimport { benchmarksDir } from \"@prismshadow/penguin-core\";\nimport type {\n BenchmarkCaseScore,\n BenchmarkEvaluation,\n BenchmarkRunScore,\n BenchmarkSummary,\n BenchmarksResponse,\n} from \"../api/types.js\";\n\nfunction asRecord(v: unknown): Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : {};\n}\n\nfunction numberOr(v: unknown): number | undefined {\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\nfunction stringOr(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** Shapes a single run entry: score is the minimum requirement, other fields tolerate being absent; a bad entry returns null and is dropped. */\nfunction toRun(v: unknown): BenchmarkRunScore | null {\n const r = asRecord(v);\n const score = numberOr(r.score);\n if (score === undefined) return null;\n const cost = numberOr(r.cost);\n const durationMs = numberOr(r.duration_ms);\n const sessionId = stringOr(r.session_id);\n return {\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n };\n}\n\n/** Average of a metric across runs; undefined when there's no value at all (never forced to 0). */\nfunction averageOf(runs: BenchmarkRunScore[], pick: (r: BenchmarkRunScore) => number | undefined) {\n const values = runs.map(pick).filter((v): v is number => v !== undefined);\n if (values.length === 0) return undefined;\n return values.reduce((a, b) => a + b, 0) / values.length;\n}\n\n/**\n * Shapes a case-level entry (scoreboard v2): the three metrics trust the file's own\n * values, falling back to an average over runs when missing; the old format (no\n * runs, a single case-level session_id) is backfilled into a single run. case and a\n * score (from the file or derivable from runs) are the minimum requirement,\n * otherwise the entry is dropped.\n */\nfunction toCase(v: unknown): BenchmarkCaseScore | null {\n const cr = asRecord(v);\n const caseId = stringOr(cr.case);\n if (caseId === undefined) return null;\n const parsedRuns = Array.isArray(cr.runs)\n ? cr.runs.map(toRun).filter((r): r is BenchmarkRunScore => r !== null)\n : [];\n const score = numberOr(cr.score) ?? averageOf(parsedRuns, (r) => r.score);\n if (score === undefined) return null;\n const cost = numberOr(cr.cost) ?? averageOf(parsedRuns, (r) => r.cost);\n const durationMs = numberOr(cr.duration_ms) ?? averageOf(parsedRuns, (r) => r.durationMs);\n const sessionId = stringOr(cr.session_id);\n const runs: BenchmarkRunScore[] =\n parsedRuns.length > 0\n ? parsedRuns\n : [\n // The old format is parsed as a single run: the case-level values are that run's raw result.\n {\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n },\n ];\n return {\n case: caseId,\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n runs,\n };\n}\n\n/** Shapes a single evaluation record: time and score are the minimum requirement, other fields (summary, etc.) tolerate being absent. */\nfunction toEvaluation(v: unknown): BenchmarkEvaluation | null {\n const r = asRecord(v);\n const time = r.time instanceof Date ? r.time.toISOString() : r.time;\n const score = numberOr(r.score);\n if (typeof time !== \"string\" || time === \"\" || score === undefined) return null;\n const cases: BenchmarkCaseScore[] = Array.isArray(r.cases)\n ? r.cases.map(toCase).filter((c): c is BenchmarkCaseScore => c !== null)\n : [];\n const summary = stringOr(r.summary);\n // Title and body are separate: summary_title is a one-line\n // conclusion, summary is the body text.\n const summaryTitle = stringOr(r.summary_title);\n // The Model actually used for this evaluation run (paired with provider):\n // charted curves are split into series by model, each with a distinct color.\n const modelId = stringOr(r.model_id);\n const provider = stringOr(r.provider);\n const version = numberOr(r.version);\n const cost = numberOr(r.cost);\n const durationMs = numberOr(r.duration_ms);\n return {\n time,\n ...(summaryTitle !== undefined ? { summaryTitle } : {}),\n ...(summary !== undefined ? { summary } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n score,\n ...(version !== undefined ? { version } : {}),\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n cases,\n };\n}\n\nexport class BenchmarkService {\n constructor(private readonly root: string) {}\n\n async list(projectId: string, agentId: string): Promise<BenchmarksResponse> {\n const dir = benchmarksDir(this.root, projectId, agentId);\n let items: Array<{ name: string; isDir: boolean }>;\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n items = entries.map((e) => ({ name: e.name, isDir: e.isDirectory() }));\n } catch {\n return { benchmarks: [] }; // Doesn't exist when unconfigured.\n }\n const benchmarks: BenchmarkSummary[] = [];\n for (const item of items.filter((i) => i.isDir).sort((a, b) => a.name.localeCompare(b.name))) {\n benchmarks.push(await this.readBenchmark(path.join(dir, item.name), item.name));\n }\n return { benchmarks };\n }\n\n private async readBenchmark(benchDir: string, id: string): Promise<BenchmarkSummary> {\n // benchmark_config.toml: title, description, and per-case run count (falls back\n // to defaults if corrupt). The model isn't part of the config — each evaluation\n // carries the Model actually used for that run.\n let title = id;\n let description: string | undefined;\n let runs: number | undefined;\n try {\n const config = asRecord(\n parseToml(await fs.readFile(path.join(benchDir, \"benchmark_config.toml\"), \"utf8\")),\n );\n if (typeof config.title === \"string\" && config.title !== \"\") title = config.title;\n if (typeof config.description === \"string\" && config.description !== \"\") {\n description = config.description;\n }\n const configRuns = numberOr(config.runs);\n if (configRuns !== undefined && Number.isInteger(configRuns) && configRuns >= 1) {\n runs = configRuns;\n }\n } catch {\n // Missing or corrupt: title falls back to the directory name.\n }\n\n // scoreboard.yaml: evaluations[] is appended over time; bad entries are dropped one by one.\n let evaluations: BenchmarkEvaluation[] = [];\n try {\n const scoreboard = asRecord(\n parseYaml(await fs.readFile(path.join(benchDir, \"scoreboard.yaml\"), \"utf8\")),\n );\n if (Array.isArray(scoreboard.evaluations)) {\n evaluations = scoreboard.evaluations\n .map(toEvaluation)\n .filter((e): e is BenchmarkEvaluation => e !== null);\n }\n } catch {\n // No scores yet.\n }\n\n // Case count: number of case subfolders (the statement/rubric structure isn't validated here).\n let caseCount = 0;\n try {\n const entries = await fs.readdir(benchDir, { withFileTypes: true });\n caseCount = entries.filter((e) => e.isDirectory()).length;\n } catch {\n // Stays at 0.\n }\n\n return {\n id,\n title,\n ...(description !== undefined ? { description } : {}),\n ...(runs !== undefined ? { runs } : {}),\n caseCount,\n evaluations,\n };\n }\n}\n","/**\n * Agent State version snapshots and export/import.\n *\n * A snapshot = `snapshots/v<version>.tar.gz`, packaging `agent_state/` (archive\n * entries are rooted at `agent_state/`), **excluding `.vault.toml`** (secrets never\n * go into a snapshot); if a snapshot for the same version already exists, it isn't\n * repacked. Import goes by the `version` inside the package and keeps the current\n * vault; a snapshot of the current version is automatically taken before import;\n * importing a package version equal to or lower than the current version requires\n * explicit confirmation (otherwise 409).\n * Docs: /docs/self-improvement § \"Snapshots and versions\".\n */\nimport { randomBytes } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport * as tar from \"tar\";\nimport { parse as parseYaml } from \"yaml\";\nimport {\n agentDir,\n agentStateDir,\n agentStateVersion,\n agentVaultPath,\n snapshotsDir,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\n\n/** Vault file name inside a snapshot/import package (used for archive filtering). */\nconst VAULT_BASENAME = \".vault.toml\";\n\nfunction isVaultEntry(entryPath: string): boolean {\n return path.posix.basename(entryPath.replaceAll(\"\\\\\", \"/\")) === VAULT_BASENAME;\n}\n\nexport class SnapshotService {\n constructor(private readonly root: string) {}\n\n /** Current Agent State version number (missing field treated as 1); throws 404 if the Agent doesn't exist. */\n async currentVersion(projectId: string, agentId: string): Promise<number> {\n let raw: string;\n try {\n raw = await fs.readFile(systemConfigPath(this.root, projectId, agentId), \"utf8\");\n } catch {\n throw new HttpError(404, \"agent_not_found\", \"Agent does not exist.\");\n }\n const parsed = parseYaml(raw) as { version?: unknown } | null;\n return agentStateVersion({\n version: typeof parsed?.version === \"number\" ? parsed.version : undefined,\n });\n }\n\n /** Ensures a snapshot exists for the current version (not repacked for the same version), returns the snapshot file path and version number. */\n async ensureSnapshot(\n projectId: string,\n agentId: string,\n ): Promise<{ version: number; file: string }> {\n const version = await this.currentVersion(projectId, agentId);\n const dir = snapshotsDir(this.root, projectId, agentId);\n const file = path.join(dir, `v${version}.tar.gz`);\n try {\n await fs.access(file);\n return { version, file };\n } catch {\n // No snapshot for this version yet: pack it.\n }\n await fs.mkdir(dir, { recursive: true });\n const tmp = `${file}.tmp-${randomBytes(4).toString(\"hex\")}`;\n await tar.create(\n {\n gzip: true,\n cwd: agentDir(this.root, projectId, agentId),\n file: tmp,\n portable: true,\n filter: (p) => !isVaultEntry(p),\n },\n [\"agent_state\"],\n );\n await fs.rename(tmp, file);\n return { version, file };\n }\n\n /** Export: automatically packs a snapshot first if none exists, returns the info needed for download. */\n async exportArchive(\n projectId: string,\n agentId: string,\n ): Promise<{ version: number; file: string; fileName: string }> {\n const { version, file } = await this.ensureSnapshot(projectId, agentId);\n return { version, file, fileName: `${agentId}-v${version}.tar.gz` };\n }\n\n /**\n * Import: validates the package structure and `version`, compares versions\n * (higher than current imports directly, same version or older requires\n * `confirm`), automatically snapshots the current version before import, then\n * replaces `agent_state/` while keeping the current vault.\n */\n async importArchive(\n projectId: string,\n agentId: string,\n archive: Buffer,\n confirm: boolean,\n ): Promise<{ version: number }> {\n const current = await this.currentVersion(projectId, agentId);\n const base = agentDir(this.root, projectId, agentId);\n const staging = path.join(base, `.import-${randomBytes(6).toString(\"hex\")}`);\n await fs.mkdir(staging, { recursive: true });\n try {\n const archiveFile = path.join(staging, \"archive.tar.gz\");\n await fs.writeFile(archiveFile, archive);\n const extractDir = path.join(staging, \"extracted\");\n await fs.mkdir(extractDir, { recursive: true });\n try {\n await tar.extract({\n file: archiveFile,\n cwd: extractDir,\n filter: (p) => !isVaultEntry(p),\n });\n } catch {\n throw badRequest(\"Import failed: not a valid tar.gz snapshot package.\");\n }\n\n // Validation: the package must contain agent_state/system_config.yaml, and version must be valid.\n const configPath = path.join(extractDir, \"agent_state\", \"system_config.yaml\");\n let parsed: unknown;\n try {\n parsed = parseYaml(await fs.readFile(configPath, \"utf8\"));\n } catch {\n throw badRequest(\"Import failed: the package is missing agent_state/system_config.yaml.\");\n }\n if (\n parsed === null ||\n typeof parsed !== \"object\" ||\n typeof (parsed as { system_prompt?: unknown }).system_prompt !== \"string\"\n ) {\n throw badRequest(\"Import failed: the package's system_config.yaml is invalid.\");\n }\n const incomingRaw = (parsed as { version?: unknown }).version;\n if (\n incomingRaw !== undefined &&\n (!Number.isInteger(incomingRaw) || (incomingRaw as number) < 1)\n ) {\n throw badRequest(\"Import failed: the package's version is invalid.\");\n }\n const incoming = agentStateVersion({\n version: typeof incomingRaw === \"number\" ? incomingRaw : undefined,\n });\n if (incoming <= current && !confirm) {\n throw new HttpError(\n 409,\n \"version_conflict\",\n `Package version v${incoming} is not higher than the current v${current}; confirmation is required to import.`,\n );\n }\n\n // Automatically snapshots the current version before import (reused if one already exists for that version), so a mistaken import can be rolled back.\n await this.ensureSnapshot(projectId, agentId);\n\n // Replace agent_state: first merge the current vault into the staging\n // directory to be swapped in (extraction already filtered out any vault\n // inside the package, so no conflict), making the replacement a pure rename\n // swap — once the swap lands, there's no further write that can fail. The\n // vault never goes into a snapshot, so if recovery fails after the swap, the\n // `finally` cleanup of staging would delete the only vault copy with no way\n // to roll back.\n const stateDir = agentStateDir(this.root, projectId, agentId);\n const incomingState = path.join(extractDir, \"agent_state\");\n let vault: Buffer | null = null;\n try {\n vault = await fs.readFile(agentVaultPath(this.root, projectId, agentId));\n } catch {\n // No vault means nothing to preserve.\n }\n if (vault !== null) {\n await fs.writeFile(path.join(incomingState, VAULT_BASENAME), vault, { mode: 0o600 });\n }\n const trash = path.join(staging, \"replaced-agent_state\");\n await fs.rename(stateDir, trash);\n try {\n await fs.rename(incomingState, stateDir);\n } catch (err) {\n await fs.rename(trash, stateDir); // Rollback: restore the old directory if swapping in the new one fails.\n throw err;\n }\n return { version: incoming };\n } finally {\n await fs.rm(staging, { recursive: true, force: true });\n }\n }\n}\n","/**\n * Project service.\n *\n * The single implementation point for authorization rules: `requireProjectAccess`\n * (owner or member, otherwise 404 without leaking existence) and\n * `requireProjectOwner` (owner only; 403 when known to be accessible, 404 when not)\n * are reused by every route; the non-throwing `canAccess` (for error attribution)\n * is likewise just a sibling wrapper around them — all three share the single\n * `resolveAccess` decision, with no second rule set maintained separately.\n * Also handles Project create / list / delete, member authorization, and initial\n * Project provisioning at signup.\n */\nimport fs from \"node:fs/promises\";\nimport { DEFAULT_PROJECT_ID, projectDir, provisionProjectAgents } from \"@prismshadow/penguin-core\";\nimport type { MemberInfo, ProjectRole, ProjectSummary } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { AgentsRepo } from \"../db/repos/agents.js\";\nimport type { ErrorsRepo } from \"../db/repos/errors.js\";\nimport type { MembersRepo } from \"../db/repos/members.js\";\nimport type { ProjectRow, ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { SchedulesRepo } from \"../db/repos/schedules.js\";\nimport type { UsageRepo } from \"../db/repos/usage.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport type { SessionManager } from \"../runtime/session-manager.js\";\nimport {\n PROJECT_ID_MAX_LENGTH,\n PROJECT_SUFFIX_PATTERN,\n SEMANTIC_ID_PATTERN,\n SEMANTIC_ID_RULE,\n} from \"./ids.js\";\nimport type { ProjectConfigService } from \"./project-config-service.js\";\n\n/** Fallback timeout for waiting on runs to settle before deleting a Project. */\nconst ABORT_SETTLE_TIMEOUT_MS = 5000;\n\nasync function dirExists(path: string): Promise<boolean> {\n try {\n const stat = await fs.stat(path);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport interface ProjectServiceDeps {\n root: string;\n users: UsersRepo;\n projects: ProjectsRepo;\n members: MembersRepo;\n agents: AgentsRepo;\n sessions: SessionsRepo;\n usage: UsageRepo;\n errors: ErrorsRepo;\n schedules: SchedulesRepo;\n projectConfig: ProjectConfigService;\n manager: SessionManager;\n}\n\nexport class ProjectService {\n constructor(private readonly deps: ProjectServiceDeps) {}\n\n // —— Authorization rules (single implementation point) ——\n\n /**\n * The **sole** implementation of the owner / member check: returns the row with\n * a role if accessible, otherwise null. `requireProjectAccess` below (throws 404)\n * and `canAccess` (returns boolean) are both just wrappers around it — there's\n * only one copy of the decision rule, since writing it twice would eventually\n * drift out of sync.\n */\n private resolveAccess(\n userId: string,\n projectId: string,\n ): (ProjectRow & { role: ProjectRole }) | null {\n const row = this.deps.projects.findById(projectId);\n if (!row) return null;\n if (row.ownerUserId === userId) return { ...row, role: \"owner\" };\n if (this.deps.members.isMember(projectId, userId)) return { ...row, role: \"member\" };\n return null;\n }\n\n /** Accessible by owner or member; otherwise 404 (does not leak Project existence). */\n requireProjectAccess(userId: string, projectId: string): ProjectRow & { role: ProjectRole } {\n const row = this.resolveAccess(userId, projectId);\n if (!row) {\n throw new HttpError(\n 404,\n \"project_not_found\",\n \"Project does not exist or you do not have access.\",\n );\n }\n return row;\n }\n\n /**\n * The non-throwing version of the same check: used for **error attribution**\n * (app.onError) — that runs on the error-handling path, where throwing another\n * 404 would only break error handling; whether access is granted shouldn't be\n * expressed as an exception there anyway.\n */\n canAccess(userId: string, projectId: string): boolean {\n return this.resolveAccess(userId, projectId) !== null;\n }\n\n /** Owner only: 403 when known accessible as a member; 404 when not accessible. */\n requireProjectOwner(userId: string, projectId: string): ProjectRow {\n const row = this.requireProjectAccess(userId, projectId);\n if (row.role !== \"owner\") {\n throw new HttpError(\n 403,\n \"owner_required\",\n \"Only the Project owner can perform this operation.\",\n );\n }\n return row;\n }\n\n /** List of project_ids accessible to the current user (owned + granted access) (used by workspace-guard). */\n accessibleProjectIds(userId: string): string[] {\n return this.deps.projects.listAccessible(userId).map((p) => p.projectId);\n }\n\n // —— Project lifecycle ——\n\n /** List of owned + granted-access Projects; display names are read from each project_config.toml. */\n async listProjects(userId: string): Promise<ProjectSummary[]> {\n const rows = this.deps.projects.listAccessible(userId);\n return Promise.all(\n rows.map(async (row) => {\n const name = await this.deps.projectConfig.getName(row.projectId);\n return {\n projectId: row.projectId,\n ...(name !== undefined ? { name } : {}),\n role: row.role,\n ownerUserId: row.ownerUserId,\n createdAt: row.createdAt,\n };\n }),\n );\n }\n\n /**\n * Create a Project: the id is chosen by the creator (a semantic id, checked for\n * duplicates against both the DB and the directory — 409 if taken), the initial\n * config is written (display name defaults to the id), and the built-in Agent is\n * initialized.\n * A non-admin's id is forced to be \"<username>-<suffix>\", where the suffix is\n * lowercase letters, digits, and underscores only — the hyphen is a reserved\n * separator, usernames never contain a hyphen, so the first hyphen is the\n * ownership boundary and the prefix can never be crafted from another username;\n * an admin's id contains no hyphen (occupying no user's namespace).\n */\n async createProject(owner: UserRow, projectId: string, name?: string): Promise<ProjectSummary> {\n if (owner.isAdmin) {\n if (!SEMANTIC_ID_PATTERN.test(projectId)) {\n throw new HttpError(\n 400,\n \"invalid_project_id\",\n `Project id must be 2–64 characters: ${SEMANTIC_ID_RULE} (the hyphen is reserved as the user namespace separator).`,\n );\n }\n } else {\n const prefix = `${owner.userId}-`;\n const suffix = projectId.startsWith(prefix) ? projectId.slice(prefix.length) : \"\";\n if (!PROJECT_SUFFIX_PATTERN.test(suffix) || projectId.length > PROJECT_ID_MAX_LENGTH) {\n throw new HttpError(\n 400,\n \"project_id_prefix_required\",\n `Project id must start with ${prefix}, followed by lowercase letters, digits or underscores (at most ${PROJECT_ID_MAX_LENGTH} in total).`,\n );\n }\n }\n if (\n this.deps.projects.findById(projectId) !== null ||\n (await dirExists(projectDir(this.deps.root, projectId)))\n ) {\n throw new HttpError(409, \"project_exists\", `Project id is already taken: ${projectId}.`);\n }\n const displayName = name ?? projectId;\n const createdAt = new Date().toISOString();\n // Insert the DB row first: the primary key is the final arbiter for concurrent\n // creation with the same id (the duplicate check above has an await gap), and a\n // conflict is mapped to 409 with **no cleanup** — the directory belongs to the\n // winner, and cleaning up here would wrongly delete the other side's data.\n try {\n this.deps.projects.insert({ projectId, ownerUserId: owner.userId, createdAt });\n } catch (err) {\n if (err instanceof Error && err.message.includes(\"UNIQUE\")) {\n throw new HttpError(409, \"project_exists\", `Project id is already taken: ${projectId}.`);\n }\n throw err;\n }\n // If file initialization fails, roll back the DB row and clean up the\n // directory: an orphaned directory would make retries with this id 409 forever\n // (a typical scenario: signup failure rolled back the user row, but the\n // <username>-default_project directory was left behind).\n try {\n await fs.mkdir(projectDir(this.deps.root, projectId), { recursive: true });\n await this.deps.projectConfig.writeInitialConfig(projectId, displayName);\n await this.provisionBuiltinAgents(projectId);\n } catch (err) {\n this.deps.projects.delete(projectId);\n await fs\n .rm(projectDir(this.deps.root, projectId), { recursive: true, force: true })\n .catch(() => {});\n throw err;\n }\n return {\n projectId,\n name: displayName,\n role: \"owner\",\n ownerUserId: owner.userId,\n createdAt,\n };\n }\n\n /**\n * Initial Project provisioned at signup:\n * the built-in admin adopts `default_project` (if the directory already exists,\n * it's adopted directly without overwriting existing config — shared with the\n * CLI); other users get `<username>-default_project` created, with display name\n * defaulting to the username.\n */\n async provisionInitialProject(user: UserRow, isAdmin: boolean): Promise<void> {\n if (!isAdmin) {\n await this.createProject(user, `${user.userId}-${DEFAULT_PROJECT_ID}`, user.userId);\n return;\n }\n const projectId = DEFAULT_PROJECT_ID;\n // Initialize the built-in Agent (loaded without overwriting if it already exists); this also ensures the directory exists.\n await this.provisionBuiltinAgents(projectId);\n // Adopting an existing directory doesn't go through writeInitialConfig: preset\n // models and the default model are backfilled instead (only when there are no\n // models at all; a default_project already configured via the CLI is left\n // as-is).\n await this.deps.projectConfig.ensurePresetModels(projectId);\n this.deps.projects.insert({\n projectId,\n ownerUserId: user.userId,\n createdAt: new Date().toISOString(),\n });\n }\n\n /**\n * Delete a Project (owner): default_project is refused; deleting the user's\n * **last accessible Project** is refused too (deleting it would leave the list\n * empty, with no Project to select in the Web client and the page stuck on a\n * skeleton screen — a typical case being a non-first user deleting the initial\n * Project provisioned at signup); active runs are drained first, then the DB and\n * directory are cleared.\n */\n async deleteProject(userId: string, projectId: string): Promise<void> {\n this.requireProjectOwner(userId, projectId);\n if (projectId === DEFAULT_PROJECT_ID) {\n throw new HttpError(\n 409,\n \"cannot_delete_default_project\",\n \"default_project is shared with the CLI and cannot be deleted from the web.\",\n );\n }\n if (this.deps.projects.listAccessible(userId).length <= 1) {\n throw new HttpError(\n 409,\n \"cannot_delete_last_project\",\n \"This is the account's last Project; deleting it would leave no Project available. Create a new Project first.\",\n );\n }\n await this.destroyProject(projectId);\n }\n\n /**\n * The actual deletion (no authorization or protection checks): shared by\n * deleteProject and the cascade cleanup when an admin deletes a user.\n * Abort follow-up (writing the abort event to Trace, etc.) happens\n * asynchronously: waits for runs to settle (capped at 5s) before deleting the\n * directory, to avoid the Trace writer recreating the directory after deletion.\n */\n async destroyProject(projectId: string): Promise<void> {\n const runnings = this.deps.manager.abortProject(projectId);\n if (runnings.length > 0) {\n await Promise.race([\n Promise.allSettled(runnings).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, ABORT_SETTLE_TIMEOUT_MS).unref?.()),\n ]);\n }\n this.deps.projects.delete(projectId); // project_members cascade-deleted\n this.deps.agents.deleteByProject(projectId);\n this.deps.sessions.deleteByProject(projectId);\n this.deps.usage.deleteByProject(projectId);\n this.deps.errors.deleteByProject(projectId);\n this.deps.schedules.deleteByProject(projectId);\n await fs.rm(projectDir(this.deps.root, projectId), { recursive: true, force: true });\n }\n\n // —— Member authorization ——\n\n /** Member list: owner (role=owner) + members. */\n listMembers(userId: string, projectId: string): MemberInfo[] {\n const project = this.requireProjectAccess(userId, projectId);\n const members = this.deps.members.list(projectId);\n return [\n { userId: project.ownerUserId, role: \"owner\", createdAt: project.createdAt },\n ...members.map((m) => ({\n userId: m.userId,\n role: \"member\" as const,\n createdAt: m.createdAt,\n })),\n ];\n }\n\n /** Grant member access (owner): invites by username; 404 if the user doesn't exist, 409 for the owner themself or an existing member. */\n addMember(userId: string, projectId: string, targetUserId: string): MemberInfo {\n const project = this.requireProjectOwner(userId, projectId);\n const target = this.deps.users.findById(targetUserId);\n if (!target) {\n throw new HttpError(404, \"user_not_found\", `User does not exist: ${targetUserId}.`);\n }\n if (target.userId === project.ownerUserId) {\n throw new HttpError(\n 409,\n \"already_owner\",\n \"The owner does not need to grant access to themselves.\",\n );\n }\n if (this.deps.members.isMember(projectId, target.userId)) {\n throw new HttpError(\n 409,\n \"already_member\",\n `${targetUserId} is already a member of this Project.`,\n );\n }\n const createdAt = new Date().toISOString();\n this.deps.members.insert({ projectId, userId: target.userId, createdAt });\n return { userId: target.userId, role: \"member\", createdAt };\n }\n\n /** Revoke member access (owner). */\n removeMember(userId: string, projectId: string, targetUserId: string): void {\n this.requireProjectOwner(userId, projectId);\n if (!this.deps.members.isMember(projectId, targetUserId)) {\n throw new HttpError(404, \"member_not_found\", `This Project has no member: ${targetUserId}.`);\n }\n this.deps.members.delete(projectId, targetUserId);\n }\n\n /**\n * Ensures the Project's built-in Agent exists (the sole built-in Agent\n * default_agent; initialized if the directory is empty, otherwise loaded without\n * overwriting) and indexes it. createdAt increments by 1ms in preset order, so\n * built-in Agents stably sort first; other Agents backfilled by directory\n * scanning are sorted by their own createdAt and are outside the scope of this\n * guarantee.\n */\n private async provisionBuiltinAgents(projectId: string): Promise<void> {\n const agentIds = await provisionProjectAgents({ root: this.deps.root, projectId });\n const base = Date.now();\n agentIds.forEach((agentId, i) => {\n this.deps.agents.insertOrIgnore({\n projectId,\n agentId,\n createdAt: new Date(base + i).toISOString(),\n });\n });\n }\n}\n","/**\n * Session index service.\n *\n * The list is DB index ∪ Trace directory discovery: scans\n * `<agent>/traces/<date>/<session_id>_<index3>.jsonl`; an unmanaged Session (e.g.\n * one started via the CLI) has its first line's session_meta read for\n * (provider, model_id) / workspace, which is backfilled into a DB row\n * (approval_mode defaults, createdAt is taken from the timestamp embedded in\n * session_id).\n * Create: via core's `agent.createSession` (the model reference is always a complete\n * (provider, modelId) pair — both or neither; omitting both falls back to the\n * Project's default reference, 400 if there is none); the new Session is\n * added to session-manager's active table (state idle).\n */\nimport path from \"node:path\";\nimport { open, readdir } from \"node:fs/promises\";\nimport {\n createAgent,\n findLatestTraceFile,\n isSessionMeta,\n parseTraceLines,\n readTraceTolerant,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type { SessionMetaMessage } from \"@prismshadow/penguin-core\";\nimport type {\n ApprovalMode,\n SessionCategory,\n SessionCategoryCounts,\n SessionInfo,\n SessionSource,\n} from \"../api/types.js\";\nimport { HttpError, isMissingCredential, modelCredentialMissing } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\nimport type { SessionRow, SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { SessionManager } from \"../runtime/session-manager.js\";\nimport { asSessionSource } from \"../runtime/session-sources.js\";\nimport type { SessionSources } from \"../runtime/session-sources.js\";\nimport type { ProjectConfigService } from \"./project-config-service.js\";\n\nconst TRACE_FILE_RE = /^(.+)_(\\d{3})\\.jsonl$/;\nconst SESSION_ID_TS_RE = /^session-(\\d{4})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-[0-9a-f]{8}$/;\n\n/** Head window for session_meta reads: generous for a long system prompt, far below a whole multi-MB shard. */\nconst TRACE_HEAD_BYTES = 256 * 1024;\n\n/**\n * Parse a Trace file's head window only. session_meta is the first line core writes to\n * every shard, so a bounded read finds it without pulling the whole file into memory —\n * category filtering / counts may need every Session's source in a single request.\n * The window is cut at its last newline (the tail fragment is incomplete); a first line\n * larger than the whole window falls back to the full tolerant read.\n */\nasync function readTraceHead(filePath: string) {\n const fh = await open(filePath, \"r\");\n let text: string;\n let truncated: boolean;\n try {\n // allocUnsafe: only subarray(0, bytesRead) is ever read, so the uninitialized tail never leaks.\n const { buffer, bytesRead } = await fh.read(\n Buffer.allocUnsafe(TRACE_HEAD_BYTES),\n 0,\n TRACE_HEAD_BYTES,\n 0,\n );\n text = buffer.subarray(0, bytesRead).toString(\"utf8\");\n truncated = bytesRead === TRACE_HEAD_BYTES;\n } finally {\n await fh.close();\n }\n if (!truncated) return parseTraceLines(text);\n const nl = text.lastIndexOf(\"\\n\");\n if (nl === -1) return readTraceTolerant(filePath);\n return parseTraceLines(text.slice(0, nl + 1));\n}\n\n/** Derives creation time from the local timestamp embedded in session_id; returns null if it doesn't match. */\nexport function sessionIdCreatedAt(sessionId: string): string | null {\n const m = SESSION_ID_TS_RE.exec(sessionId);\n if (!m) return null;\n const [, y, mo, d, h, mi, s] = m;\n const date = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nexport interface SessionServiceDeps {\n root: string;\n sessions: SessionsRepo;\n manager: SessionManager;\n projectConfig: ProjectConfigService;\n /** In-process origin registry derived from session_meta (the DB stores no source column). */\n sources: SessionSources;\n}\n\nexport class SessionService {\n constructor(private readonly deps: SessionServiceDeps) {}\n\n /**\n * DB row -> SessionInfo (run status and pending approval count come from session-manager).\n * Async because `source` is derived from session_meta: a registry miss (Session predating\n * this process) falls back to reading the Trace head once (see sourceOf). `traces` is the\n * list flow's one-walk discovery result; without it a miss locates the shard itself.\n */\n async toInfo(\n row: SessionRow,\n hasTrace: boolean,\n traces?: ReadonlyMap<string, TraceLocation>,\n ): Promise<SessionInfo> {\n const source = await this.sourceOf(row, hasTrace, traces);\n return {\n sessionId: row.sessionId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n workspace: row.workspace,\n approvalMode: row.approvalMode,\n ...(row.title !== null ? { title: row.title } : {}),\n ...(source !== undefined ? { source } : {}),\n createdAt: row.createdAt,\n status: this.deps.manager.statusOf(row.sessionId),\n pendingApprovalCount: this.deps.manager.pendingApprovalCount(row.sessionId),\n pendingFollowUpCount: this.deps.manager.pendingFollowUpCount(row.sessionId),\n hasTrace,\n archived: (row.archivedAt ?? null) !== null,\n };\n }\n\n /**\n * A Session's origin, with session_meta as the single source of truth: the in-process\n * registry answers first (populated at creation / subagent registration / adoption);\n * on a miss (a Session created before this process started) the earliest Trace shard's\n * session_meta is read once and cached. A Session with no Trace yet stays unknown and\n * is NOT cached negatively — its meta may appear with the first run.\n */\n private async sourceOf(\n row: SessionRow,\n hasTrace: boolean,\n traces?: ReadonlyMap<string, TraceLocation>,\n ): Promise<SessionSource | undefined> {\n const known = this.deps.sources.get(row.sessionId);\n if (known !== undefined) return known ?? undefined;\n if (!hasTrace) return undefined;\n // Single-session paths carry no discovery map: locate this Session's earliest shard on demand.\n const location =\n traces?.get(row.sessionId) ??\n (await this.discoverTraces(row.projectId, row.agentId)).get(row.sessionId);\n if (!location) return undefined;\n const meta = await this.readTraceMeta(location);\n if (!meta) return undefined; // Unreadable/corrupt Trace: stay unknown, retry on the next list.\n // On-disk values are untrusted: only the exact known origins pass, junk = user-created.\n const source = asSessionSource(meta.payload.source) ?? null;\n this.deps.sources.set(row.sessionId, source);\n return source ?? undefined;\n }\n\n /** Whether this Session already has a Trace record (a Task has been run). */\n async hasTrace(row: SessionRow): Promise<boolean> {\n return (await this.discoverTraces(row.projectId, row.agentId)).has(row.sessionId);\n }\n\n /**\n * The list category of a row: archived wins (an explicit user action), then the\n * origin's bucket, and no/unknown source is `active` — the same precedence the\n * sidebar's partition applies to loaded rows, so server filtering and client\n * rendering can never disagree.\n */\n private async categoryOf(\n row: SessionRow,\n hasTrace: boolean,\n traces?: ReadonlyMap<string, TraceLocation>,\n ): Promise<SessionCategory> {\n if ((row.archivedAt ?? null) !== null) return \"archived\";\n return (await this.sourceOf(row, hasTrace, traces)) ?? \"active\";\n }\n\n /**\n * List: DB ∪ Trace directory discovery, sorted by createdAt descending. Optional\n * `paging` returns just that slice (the sidebar pages with limit+1 to detect \"has\n * more\"); slicing happens before toInfo, so per-request source derivation (lazy\n * Trace-head reads) stays bounded by the page size. Discovery/adoption still scans\n * the whole directory — the union and global ordering need every id.\n *\n * `category` filters to one sidebar bucket **before** paging, so offset/limit page\n * within the category. Filtering needs each walked row's category (a possible\n * Trace-head read per row, cached in the sources registry); without `withCounts`\n * the walk stops as soon as the requested page is complete. `withCounts` classifies\n * every row and returns per-category totals over the whole list — plus the same\n * totals broken down by Workspace path — so the sidebar can label the collapsed\n * folders (and a workspace group can know its own share) without loading them.\n */\n async listSessions(\n projectId: string,\n agentId: string,\n opts: {\n paging?: { offset: number; limit: number };\n category?: SessionCategory;\n withCounts?: boolean;\n } = {},\n ): Promise<{\n sessions: SessionInfo[];\n counts?: SessionCategoryCounts;\n workspaceCounts?: Record<string, SessionCategoryCounts>;\n }> {\n const { paging, category, withCounts } = opts;\n const traces = await this.discoverTraces(projectId, agentId);\n const rows = new Map(\n this.deps.sessions.listByAgent(projectId, agentId).map((r) => [r.sessionId, r]),\n );\n\n // Unmanaged Trace Sessions: backfill an index row by reading the first line's session_meta.\n for (const [sessionId, location] of traces) {\n if (rows.has(sessionId)) continue;\n const discovered = await this.adoptTraceSession(projectId, agentId, sessionId, location);\n if (discovered) rows.set(sessionId, discovered);\n }\n\n const sorted = [...rows.values()].sort(\n (a, b) => b.createdAt.localeCompare(a.createdAt) || b.sessionId.localeCompare(a.sessionId),\n );\n const toPage = (page: SessionRow[]) =>\n Promise.all(page.map((row) => this.toInfo(row, traces.has(row.sessionId), traces)));\n\n // No classification asked for: slice straight away (the pre-category behavior).\n if (category === undefined && !withCounts) {\n return {\n sessions: await toPage(\n paging ? sorted.slice(paging.offset, paging.offset + paging.limit) : sorted,\n ),\n };\n }\n\n const want = paging ? paging.offset + paging.limit : Infinity;\n const counts: SessionCategoryCounts = { active: 0, subagent: 0, schedule: 0, archived: 0 };\n const workspaceCounts: Record<string, SessionCategoryCounts> = {};\n const matched: SessionRow[] = [];\n for (const row of sorted) {\n if (!withCounts && matched.length >= want) break;\n const cat = await this.categoryOf(row, traces.has(row.sessionId), traces);\n counts[cat] += 1;\n if (withCounts) {\n const ws = (workspaceCounts[row.workspace] ??= {\n active: 0,\n subagent: 0,\n schedule: 0,\n archived: 0,\n });\n ws[cat] += 1;\n }\n if ((category === undefined || cat === category) && matched.length < want) matched.push(row);\n }\n const sessions = await toPage(paging ? matched.slice(paging.offset, want) : matched);\n return withCounts ? { sessions, counts, workspaceCounts } : { sessions };\n }\n\n /**\n * Session stats (Agents list card): total count = size of the union of DB index\n * ∪ Trace directory discovery; activity = number of active Sessions per day over\n * the last `days` days (deduplicated count of Sessions created that day or with a\n * Trace record that day; index 0 = earliest, last index = today). Counts only —\n * does not backfill index rows.\n */\n async sessionStats(\n projectId: string,\n agentId: string,\n days: number,\n ): Promise<{ sessionCount: number; activity: number[] }> {\n const all = new Set<string>();\n const byDate = new Map<string, Set<string>>();\n const mark = (date: string, sessionId: string): void => {\n all.add(sessionId);\n const set = byDate.get(date) ?? new Set<string>();\n set.add(sessionId);\n byDate.set(date, set);\n };\n\n // Trace directory: the date directory name is the local date (yyyy-mm-dd) that core uses when writing to disk.\n const dir = tracesDir(this.deps.root, projectId, agentId);\n for (const dateDir of await listDirsSafe(dir)) {\n for (const file of await listFilesSafe(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (match) mark(dateDir, match[1]!);\n }\n }\n // DB index: the creation day also counts as active (a Session that hasn't run a Task yet produces no Trace).\n for (const row of this.deps.sessions.listByAgent(projectId, agentId)) {\n const created = new Date(row.createdAt);\n if (Number.isNaN(created.getTime())) all.add(row.sessionId);\n else mark(localDate(created), row.sessionId);\n }\n\n const activity: number[] = [];\n const now = new Date();\n for (let i = days - 1; i >= 0; i--) {\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i);\n activity.push(byDate.get(localDate(d))?.size ?? 0);\n }\n return { sessionCount: all.size, activity };\n }\n\n /**\n * Create a Session: the model reference is a complete `(provider, modelId)` pair.\n * Half a reference is a client error, never something to resolve — the missing half\n * is never guessed, since a guessed provider would send an entry's credential to a\n * vendor nobody named. Omitting both falls back to the Project's default reference\n * (400 prompting to configure a model first if there is none). `workspace` is already\n * validated by the route guard. The new Session is added to the active table\n * (idle).\n */\n async createSession(args: {\n projectId: string;\n agentId: string;\n /** Upstream id of the session's model; always paired with provider. Omit both for the Project's default reference. */\n modelId?: string;\n /** The provider group for `modelId`; always paired with modelId, never inferred. */\n provider?: string;\n workspace?: string;\n approvalMode?: ApprovalMode;\n /** Session source marker (schedule when triggered by a scheduled task; defaults to user-created). */\n source?: \"schedule\";\n }): Promise<SessionInfo> {\n if ((args.modelId === undefined) !== (args.provider === undefined)) {\n throw badRequest(\n \"modelId and provider must be given together as a (provider, modelId) pair: specify both, or neither to use the Project's default model.\",\n );\n }\n let modelId: string;\n let provider: string;\n if (args.modelId !== undefined && args.provider !== undefined) {\n modelId = args.modelId;\n provider = args.provider;\n } else {\n // The guard above leaves only \"both omitted\" here: fall back to the Project default.\n const def = await this.deps.projectConfig.getDefaultModelRef(args.projectId);\n if (def === undefined) {\n throw new HttpError(\n 400,\n \"no_default_model\",\n \"This Project has no default model yet. Add a model on the Models page and set it as the default first.\",\n );\n }\n modelId = def.model_id;\n provider = def.provider;\n }\n const agent = await createAgent({\n root: this.deps.root,\n projectId: args.projectId,\n agentId: args.agentId,\n });\n let session;\n try {\n session = await agent.createSession({\n modelId,\n provider,\n ...(args.workspace !== undefined ? { workspaceDir: args.workspace } : {}),\n // The origin is also recorded in core session_meta (Trace), not just the index row.\n ...(args.source !== undefined ? { source: args.source } : {}),\n });\n } catch (err) {\n // A missing credential is its own category (the frontend shows localized text\n // by code); other core errors (the pair naming no configured entry, Workspace\n // not existing, etc.) are collapsed to 400 — the guard already blocks most cases.\n if (isMissingCredential(err)) throw modelCredentialMissing(modelId);\n throw new HttpError(\n 400,\n \"session_create_failed\",\n err instanceof Error ? err.message : String(err),\n );\n }\n // The origin is derived from the just-created core Session's session_meta (the single\n // source of truth) rather than echoing args.source back: what the registry serves is\n // exactly what the Trace will record.\n const metaMsg = session.metaMessage;\n this.deps.sources.set(\n session.sessionId,\n isSessionMeta(metaMsg) ? (asSessionSource(metaMsg.payload.source) ?? null) : null,\n );\n const row: SessionRow = {\n sessionId: session.sessionId,\n projectId: args.projectId,\n agentId: args.agentId,\n provider: session.provider,\n modelId: session.modelId,\n workspace: session.workspaceDir,\n approvalMode: args.approvalMode ?? \"allow-all\",\n title: null,\n createdAt: new Date().toISOString(),\n };\n this.deps.sessions.insert(row);\n this.deps.manager.adopt(row, session);\n return this.toInfo(row, false);\n }\n\n /**\n * Absolute path of a Session's **latest** Trace file (the current context shard);\n * undefined when no Trace exists. Costs a directory walk, so only the single-session\n * GET includes it in the DTO (see SessionInfo.tracePath) — the web's `/model` switch\n * hands it to the new session's `[model_switch_from]` block so the model can read the\n * source history itself when it needs it.\n */\n async latestTracePath(row: SessionRow): Promise<string | undefined> {\n const located = await findLatestTraceFile(\n tracesDir(this.deps.root, row.projectId, row.agentId),\n row.sessionId,\n );\n return located?.path;\n }\n\n /**\n * One walk over the Trace directory: session_id → its **earliest** shard (the shard\n * whose head carries the original session_meta). Discovery (which Sessions have\n * records) and the meta-read location come out of a single pass, so classifying every\n * row (`counts=1`) costs one directory walk total instead of one per Session.\n */\n private async discoverTraces(\n projectId: string,\n agentId: string,\n ): Promise<Map<string, TraceLocation>> {\n const dir = tracesDir(this.deps.root, projectId, agentId);\n const out = new Map<string, TraceLocation>();\n for (const dateDir of await listDirsSafe(dir)) {\n for (const file of await listFilesSafe(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match) continue;\n const sessionId = match[1]!;\n const index = Number(match[2]);\n const cur = out.get(sessionId);\n if (!cur || index < cur.index) {\n out.set(sessionId, { path: path.join(dir, dateDir, file), index });\n }\n }\n }\n return out;\n }\n\n /**\n * session_meta from a located Trace shard head; null when unreadable or it has no\n * meta. Shared by adoption backfill and lazy `source` resolution.\n */\n private async readTraceMeta(location: TraceLocation): Promise<SessionMetaMessage | null> {\n let messages;\n try {\n messages = await readTraceHead(location.path);\n } catch {\n return null; // Corrupt file: skip (does not block the list)\n }\n return messages.find(isSessionMeta) ?? null;\n }\n\n /** Adopts a Session that exists only in the Trace directory: reads session_meta from the first line of the earliest index file. */\n private async adoptTraceSession(\n projectId: string,\n agentId: string,\n sessionId: string,\n location: TraceLocation,\n ): Promise<SessionRow | null> {\n const meta = await this.readTraceMeta(location);\n if (!meta) return null;\n // An older Trace version's session_meta lacks provider (the model reference\n // wasn't split into separate fields yet): no backward compat, skip adoption\n // (core will give a clear error on resume; the product hasn't launched yet, so\n // old data can simply be deleted and recreated).\n if (typeof meta.payload.provider !== \"string\") return null;\n // The adoption read already has the meta in hand: record the origin (single source of\n // truth); on-disk values are narrowed — junk counts as user-created.\n this.deps.sources.set(sessionId, asSessionSource(meta.payload.source) ?? null);\n const row: SessionRow = {\n sessionId,\n projectId,\n agentId,\n provider: meta.payload.provider,\n modelId: meta.payload.model_id,\n workspace: meta.payload.workspace,\n // The approval mode for an unmanaged Session (started via the CLI) isn't in the Trace, so it's backfilled with the default value.\n approvalMode: \"allow-all\",\n title: null,\n createdAt: sessionIdCreatedAt(sessionId) ?? meta.timestamp,\n };\n // Idempotent backfill: concurrent list calls may discover the same Session for the first time simultaneously (consistent with AgentsRepo's convention).\n this.deps.sessions.insertOrIgnore(row);\n return row;\n }\n}\n\n/** A located Trace shard of one Session: absolute path plus its shard index. */\ninterface TraceLocation {\n path: string;\n index: number;\n}\n\n/** Local date as yyyy-mm-dd (matches the Trace date directory convention: core's internal formatLocalDate, not publicly exported). */\nfunction localDate(d: Date): string {\n const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`);\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\nasync function listDirsSafe(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nasync function listFilesSafe(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n","/**\n * Trace service.\n *\n * History messages: all of the Session's index files concatenated in order\n * (readTraceTolerant, tolerating a truncated last line), containing only the\n * complete messages and events that were actually written to Trace (naturally\n * excluding partial_*); in-flight increments are continued by SSE.\n * Performance analysis is derived from a single Trace file: nearest-neighbor\n * pairing of request_begin/end, tool call duration pairing, reconnect / compaction\n * counts, and Token trend.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n agentsDir,\n parseUserSteeringText,\n readTraceTolerant,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport type {\n AgentTracesResponse,\n RequestSpan,\n ToolCallSpan,\n TraceAnalysisResponse,\n TraceEventsResponse,\n TraceFileInfo,\n TraceModelSegment,\n TraceTaskStats,\n TraceToolSpan,\n UsageTrendPointInTrace,\n} from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\n\nconst TRACE_FILE_RE = /^(.+)_(\\d{3})\\.jsonl$/;\n\n/** Recursion depth cap for sub-session expansion (run_subagent depth is already constrained by the SDK; this is just a defensive backstop against cycles). */\nconst MAX_SUBAGENT_DEPTH = 4;\n\ninterface LocatedFile {\n path: string;\n date: string;\n index: number;\n}\n\n/**\n * A **direct sub-session pointer** (the `subagent` event in the parent Trace) ->\n * the sub-session's Session id. The pointer only\n * records the Session id; the sub-session's Agent is located within the Project by\n * its Trace file.\n */\nfunction subagentPointer(msg: OmniMessage): string | null {\n if (msg.type !== \"event_msg\") return null;\n const p = msg.payload as { type?: string; session_id?: unknown };\n if (p.type !== \"subagent\" || typeof p.session_id !== \"string\" || p.session_id === \"\") {\n return null;\n }\n return p.session_id;\n}\n\nasync function listDirs(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nasync function listFiles(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nexport class TraceService {\n constructor(private readonly root: string) {}\n\n /** All of this Session's Trace files (sorted by index ascending). */\n private async locateAll(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<LocatedFile[]> {\n const dir = tracesDir(this.root, projectId, agentId);\n const out: LocatedFile[] = [];\n for (const dateDir of await listDirs(dir)) {\n for (const file of await listFiles(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match || match[1] !== sessionId) continue;\n out.push({ path: path.join(dir, dateDir, file), date: dateDir, index: Number(match[2]) });\n }\n }\n return out.sort((a, b) => a.index - b.index);\n }\n\n /** Deletes all of this Session's Trace files (called when the Session is deleted). */\n async deleteSessionTraces(projectId: string, agentId: string, sessionId: string): Promise<void> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n for (const file of files) {\n await fs.rm(file.path, { force: true });\n }\n }\n\n /**\n * History messages: all index files concatenated in order, with sub-sessions\n * **expanded in place**.\n *\n * The parent Trace only records a `subagent` pointer event at the spawn point\n * (recording just the child Session id; the content lives in the child\n * Session's own Trace). Here the pointer is used to locate the child Trace\n * within the Project, read it recursively, and splice the child messages —\n * tagged with an origin chain — back in at the pointer's position, so that when\n * the session is reopened, the frontend can re-attach the sub-session to the\n * run_subagent tool card via origin (the child Trace's first `session_meta`,\n * once given an origin, takes the same shape as what's forwarded over the live\n * stream). When expansion succeeds, the pointer event itself is no longer\n * emitted; when the child Trace is missing (deleted), the pointer event is kept\n * so API consumers can still know it existed.\n */\n async readMessages(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<OmniMessage[]> {\n return this.readMessagesExpanded(projectId, agentId, sessionId, {\n index: null,\n ancestry: new Set([sessionId]),\n depth: 0,\n });\n }\n\n /**\n * A Project-wide session location index (sessionId -> agentId): built by\n * scanning every Agent's traces directory. Built lazily the first time a\n * subagent pointer is encountered, then reused across the whole readMessages\n * call — rescanning per pointer would blow up into tens of thousands of readdir\n * calls under multiple sub-sessions plus recursive expansion.\n */\n private async buildSessionIndex(projectId: string): Promise<Map<string, string>> {\n const index = new Map<string, string>();\n for (const agentId of await listDirs(agentsDir(this.root, projectId))) {\n const dir = tracesDir(this.root, projectId, agentId);\n for (const dateDir of await listDirs(dir)) {\n for (const file of await listFiles(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (match && !index.has(match[1]!)) index.set(match[1]!, agentId);\n }\n }\n }\n return index;\n }\n\n private async readMessagesExpanded(\n projectId: string,\n agentId: string,\n sessionId: string,\n ctx: { index: Map<string, string> | null; ancestry: Set<string>; depth: number },\n ): Promise<OmniMessage[]> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const out: OmniMessage[] = [];\n for (const file of files) {\n for (const msg of await readTraceTolerant(file.path)) {\n // The depth cap guards against runaway recursion; ancestry guards against a\n // cyclic pointer (a tampered Trace pointing to itself/an ancestor is not expanded).\n const childSid = ctx.depth < MAX_SUBAGENT_DEPTH ? subagentPointer(msg) : null;\n if (!childSid || ctx.ancestry.has(childSid)) {\n out.push(msg);\n continue;\n }\n ctx.index ??= await this.buildSessionIndex(projectId);\n const childAgent = ctx.index.get(childSid);\n let nested: OmniMessage[] = [];\n if (childAgent) {\n ctx.ancestry.add(childSid);\n nested = await this.readMessagesExpanded(projectId, childAgent, childSid, {\n ...ctx,\n depth: ctx.depth + 1,\n });\n ctx.ancestry.delete(childSid);\n }\n // Child Trace missing (deleted): keep the pointer event, since the sub-session's content can't be recovered.\n if (nested.length === 0) {\n out.push(msg);\n continue;\n }\n for (const m of nested) out.push({ ...m, origin: [childSid, ...(m.origin ?? [])] });\n }\n }\n return out;\n }\n\n /** List of Trace files (index / date / size / mtime). */\n async listTraceFiles(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<TraceFileInfo[]> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const out: TraceFileInfo[] = [];\n for (const file of files) {\n const stat = await fs.stat(file.path);\n out.push({\n index: file.index,\n date: file.date,\n sizeBytes: stat.size,\n mtime: stat.mtime.toISOString(),\n });\n }\n return out;\n }\n\n /** Reads events from the Trace file at the given index, paginated by line (for loading large files in pages). */\n async readEvents(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n offset: number,\n limit: number,\n ): Promise<TraceEventsResponse> {\n const messages = await this.readFileByIndex(projectId, agentId, sessionId, index);\n return {\n events: messages.slice(offset, offset + limit),\n offset,\n limit,\n total: messages.length,\n };\n }\n\n /** Performance analysis: derived from a single Trace file. */\n async analyze(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n ): Promise<TraceAnalysisResponse> {\n const messages = await this.readFileByIndex(projectId, agentId, sessionId, index);\n\n const requests: RequestSpan[] = [];\n let openRequest: RequestSpan | null = null;\n const toolCalls: ToolCallSpan[] = [];\n const openToolCalls = new Map<string, ToolCallSpan>();\n let reconnectCount = 0;\n let compactionCount = 0;\n const usageTrend: UsageTrendPointInTrace[] = [];\n // Timeline (serial-duration estimation): Trace records completion times; model\n // messages are produced\n // serially (autoregressive decoding), so each segment's start = the previous\n // event's time (the request's first segment = request_begin); a tool's\n // approval/execution runs in parallel with model decoding, on its own lane;\n // prevSerialTs is cleared after request_end, and the next request_begin\n // restarts the count (which presumes all of the previous round's\n // tool_call_output have already come back).\n const modelSegments: TraceModelSegment[] = [];\n const toolSpans: TraceToolSpan[] = [];\n const openSpansById = new Map<string, TraceToolSpan>();\n let prevSerialTs: string | null = null;\n // Task grouping: one user turn contains multiple Request rounds (the Agent\n // loop sends another round each time it calls a tool); the turn ends once the\n // model produces only text with no further tool call. Consecutive Requests are\n // merged into one Task on this basis, and each Task gets its own independent\n // timeline — different Tasks can be far apart in time (the user is thinking or\n // has stepped away), and sharing one timeline would leave large gaps.\n // Compaction forms its own turn: both compaction_begin/compaction_end break a\n // continuation, so the compaction request becomes its own Task, and the\n // request that resumes after compaction starts yet another Task.\n let taskIndex = -1;\n let continuation = false; // The previous round's Request called a tool -> the next request_begin continues the same Task\n let sawToolCallThisRequest = false;\n // Compaction interval (compaction_begin..compaction_end): the compaction\n // request's request_begin/request_end and token_usage all fall inside it (see\n // core context-engine's summarize flow), which is used to exclude the\n // compaction request entirely from TPS — matching the same convention as\n // compactionActive in the Chat page's task-stats.\n let compactionActive = false;\n // Token / duration totals per Task (computed server-side over the whole file:\n // frontend events are fetched in pages, so summing them there would be\n // mismatched).\n const taskStats = new Map<number, TraceTaskStats>();\n const ensureTask = (ti: number): TraceTaskStats => {\n let t = taskStats.get(ti);\n if (t === undefined) {\n t = {\n taskIndex: ti,\n messageFrom: -1,\n messageTo: -1,\n startTs: \"\",\n endTs: \"\",\n tokens: { cacheRead: 0, cacheWrite: 0, output: 0 },\n llmMs: 0,\n };\n taskStats.set(ti, t);\n }\n return t;\n };\n\n /**\n * Which turn each message belongs to: **decided definitively in one\n * sequential pass**, not left for the frontend to guess by timestamp.\n *\n * Timestamp boundaries can't be pulled apart — the same millisecond can\n * contain \"the previous turn's last reply, compaction_begin, the compaction\n * prompt, and the next turn's request_begin\" all at once, so assigning by\n * time would inevitably misfile this turn's reply into the next turn.\n *\n * Rule (a turn = one user turn; `request_end` is the end of some Request\n * within a turn):\n * - The **starting marker** of a new turn: the main session's user Prompt\n * (outside compaction), or compaction_begin (compaction forms its own\n * turn). Messages after the marker and before that turn's first\n * `request_begin` (subsequent images from a multi-image send, the\n * compaction prompt) are always held pending, waiting for\n * `request_begin` to settle the new taskIndex before the whole span is\n * assigned at once — they belong to the **new** turn, not the tail of\n * the previous one.\n * - Other messages belong to the current taskIndex: tool output and\n * approval decisions arriving after request_end still belong to this\n * turn (they're the results of tools this turn's Request initiated).\n */\n const msgTask: number[] = new Array<number>(messages.length).fill(-1);\n /** The pending new turn's starting point (message index); settled once request_begin determines the taskIndex. */\n let pendingFrom: number | null = null;\n\n for (let mi = 0; mi < messages.length; mi++) {\n const msg = messages[mi]!;\n const p = msg.payload as Record<string, unknown> & { type?: string };\n // The timeline only looks at the main session (a Trace itself never contains origin messages; this is a defensive skip).\n const hasOrigin = msg.origin !== undefined && msg.origin.length > 0;\n\n // Starting marker of a new turn: the main session's user Prompt (outside\n // compaction) -> a new user turn; compaction_begin -> a compaction turn\n // (compaction forms its own turn). A single send can be \"text + multiple\n // images\" = multiple messages; only the **first** one counts (once\n // pendingFrom is set, it's not changed again), otherwise the turn's start\n // would shift to the last image.\n // Mid-run steering (`[user_steering]`-wrapped user text, delivered between turns of a\n // running Task): never a turn starter — same exclusion idea as the compaction summary —\n // and it forces the next request to be a continuation (a steering-only continuation\n // turn has no preceding tool call, but it is still the same Task).\n const isSteeringText =\n !hasOrigin &&\n msg.type === \"model_msg\" &&\n p.type === \"text\" &&\n p.role === \"user\" &&\n typeof p.text === \"string\" &&\n parseUserSteeringText(p.text) !== null;\n if (isSteeringText) continuation = true;\n const startsUserTurn =\n !hasOrigin &&\n !compactionActive &&\n !isSteeringText &&\n msg.type === \"model_msg\" &&\n ((p.type === \"text\" && p.role === \"user\") || p.type === \"image_url\");\n const startsCompactionTurn =\n !hasOrigin && msg.type === \"event_msg\" && p.type === \"compaction_begin\";\n if (startsUserTurn || startsCompactionTurn) {\n if (pendingFrom === null) pendingFrom = mi;\n // A user Prompt **always starts a new turn**: judging continuation solely\n // by \"did the previous turn call a tool\" isn't enough — if the previous\n // turn ended in timeout/malformed (given up after exhausting retries),\n // retryable would leave continuation at true, and this new message would\n // get merged into that failed turn, smearing the two turns' messages /\n // Tokens / TPS / duration together.\n if (startsUserTurn) continuation = false;\n }\n // A main-session message that isn't pending belongs to the current turn immediately (taskIndex < 0 = before the first request_begin, e.g. session_meta).\n if (!hasOrigin && pendingFrom === null) msgTask[mi] = taskIndex;\n\n if (msg.type === \"event_msg\") {\n if (p.type === \"request_begin\") {\n if (!hasOrigin) {\n prevSerialTs = msg.timestamp;\n if (!continuation) taskIndex++; // Not a continuation -> a new Task\n sawToolCallThisRequest = false;\n }\n // Settle taskIndex before opening the span: the span belongs directly to\n // the current Task. Nearest-neighbor pairing: if the previous begin was\n // never closed (process exited mid-run), the span is left open.\n openRequest = { beginTs: msg.timestamp, taskIndex };\n if (compactionActive) openRequest.compaction = true;\n requests.push(openRequest);\n if (!hasOrigin) {\n const t = ensureTask(taskIndex);\n if (compactionActive) t.compaction = true; // This turn is a compaction turn\n // This turn's duration starts at the first request_begin. It doesn't\n // use the timestamp of the user Prompt / compaction summary or other\n // user text: `[context_summary]` is created during compaction but only\n // written to disk on the next run, so resuming the next day would\n // stretch the first turn out by a whole day for no reason; the Prompt\n // to request-dispatch gap is only ever milliseconds anyway.\n if (t.startTs === \"\") t.startTs = msg.timestamp;\n // The new turn's taskIndex is only settled here: the pending span\n // (user Prompt / multiple images / compaction prompt) is assigned in\n // full to **this** turn — they're the start of the new turn, not the\n // tail of the previous one.\n if (pendingFrom !== null) {\n for (let k = pendingFrom; k < mi; k++) {\n if (messages[k]!.origin === undefined) msgTask[k] = taskIndex;\n }\n pendingFrom = null;\n }\n msgTask[mi] = taskIndex;\n }\n } else if (p.type === \"approval_decision\") {\n if (!hasOrigin && typeof p.tool_call_id === \"string\") {\n const span = openSpansById.get(p.tool_call_id);\n if (span && span.approvalTs === undefined) {\n span.approvalTs = msg.timestamp;\n if (typeof p.decision === \"string\") span.decision = p.decision;\n // Approval wait time is subtracted out of the LLM generation\n // duration: core does `await approve(tc)` inside the streaming loop,\n // so the entire manual wait sits between request_begin and\n // request_end (see RequestSpan.approvalWaitMs). Without subtracting\n // it, \"5s generation + 55s approval wait\" would show 100 tok/s as 8 tok/s.\n if (openRequest) {\n const wait = Date.parse(msg.timestamp) - Date.parse(span.callTs);\n if (Number.isFinite(wait) && wait > 0) {\n openRequest.approvalWaitMs = (openRequest.approvalWaitMs ?? 0) + wait;\n }\n }\n }\n }\n } else if (p.type === \"request_end\") {\n const status = typeof p.status === \"string\" ? p.status : undefined;\n // timeout/malformed is automatically reconnected by core within the same\n // run (context-engine's retry loop); the resent Request still belongs to\n // **the same user turn**: it must continue the turn, otherwise a single\n // timeout would split that turn's Tokens/duration/TPS across two Tasks.\n const retryable = status === \"timeout\" || status === \"malformed\";\n if (!hasOrigin) {\n prevSerialTs = null;\n continuation = sawToolCallThisRequest || retryable;\n }\n if (retryable) reconnectCount++;\n if (openRequest) {\n openRequest.endTs = msg.timestamp;\n const dur = Date.parse(msg.timestamp) - Date.parse(openRequest.beginTs);\n if (Number.isFinite(dur)) {\n openRequest.durationMs = dur;\n openRequest.activeMs = Math.max(0, dur - (openRequest.approvalWaitMs ?? 0));\n }\n if (status !== undefined) openRequest.status = status;\n // TPS denominator: accumulated per the turn a Request belongs to. A\n // compaction request counts too — it belongs to **its own compaction\n // turn** (compaction forms its own turn), so it neither pollutes a\n // user turn's TPS, nor does the compaction turn fail to report its own\n // generation speed accurately. A failed retry's duration is counted as\n // well — it belongs to the same turn as the retry that eventually\n // succeeded, and \"how long this turn took to produce these tokens\"\n // should include the retries by definition.\n if (!hasOrigin && openRequest.activeMs !== undefined) {\n ensureTask(openRequest.taskIndex).llmMs += openRequest.activeMs;\n }\n openRequest = null;\n }\n } else if (p.type === \"compaction_begin\") {\n compactionCount++;\n // Compaction forms its own turn: otherwise, if the previous turn called\n // a tool, continuation would still be true and the compaction request\n // would get merged into the previous Task.\n if (!hasOrigin) {\n continuation = false;\n compactionActive = true;\n }\n } else if (p.type === \"compaction_end\") {\n // Both ends of compaction break a continuation. This closing one can't\n // be skipped: if the compaction request itself exhausts its retries and\n // ends in timeout, the retryable check above would mark it as \"continued\",\n // and without clearing it here, the next user turn after compaction\n // would get merged into this compaction Task.\n if (!hasOrigin) {\n continuation = false;\n compactionActive = false;\n }\n } else if (p.type === \"token_usage\") {\n const request = p.request as\n | { total?: number; cache_read?: number; cache_write?: number; output?: number }\n | undefined;\n const session = p.session as { total?: number } | undefined;\n usageTrend.push({\n ts: msg.timestamp,\n requestTotal: request?.total ?? 0,\n sessionTotal: session?.total ?? 0,\n });\n if (!hasOrigin) {\n const t = ensureTask(taskIndex);\n // Cumulative usage for this turn (a running total): those tokens were\n // actually paid for, so the cost can't be dropped. `tokens.output` also\n // doubles as the numerator for output TPS — compaction's output\n // belongs to **its own compaction turn** (compaction forms its own\n // turn), so a user turn's TPS isn't polluted by it, while the\n // compaction turn can still accurately report \"how fast the summary\n // was generated\".\n t.tokens.cacheRead += request?.cache_read ?? 0;\n t.tokens.cacheWrite += request?.cache_write ?? 0;\n t.tokens.output += request?.output ?? 0;\n if (!compactionActive) {\n // The context snapshot only takes non-compaction Requests: tokens\n // consumed by compaction aren't the post-compaction context\n // footprint. A later write overwrites an earlier one -> this\n // naturally leaves behind the snapshot of the Task's **last**\n // non-compaction Request = the context footprint at the end of this\n // turn. Accumulating would be wrong: each Request's input carries\n // the entire history afresh (see TraceTaskStats).\n t.context = {\n cacheRead: request?.cache_read ?? 0,\n cacheWrite: request?.cache_write ?? 0,\n output: request?.output ?? 0,\n };\n }\n }\n }\n continue;\n }\n if (msg.type !== \"model_msg\") continue;\n // Model serial segments: assistant-side thinking/text/tool_call (a user input sent instantaneously occupies no segment).\n if (\n !hasOrigin &&\n prevSerialTs !== null &&\n (p.type === \"thinking\" ||\n p.type === \"tool_call\" ||\n (p.type === \"text\" && p.role === \"assistant\"))\n ) {\n const segment: TraceModelSegment = {\n kind: p.type === \"thinking\" ? \"thinking\" : p.type === \"tool_call\" ? \"tool_call\" : \"text\",\n startTs: prevSerialTs,\n endTs: msg.timestamp,\n taskIndex,\n };\n if (p.type === \"tool_call\" && typeof p.tool_call_id === \"string\") {\n segment.toolCallId = p.tool_call_id;\n if (typeof p.name === \"string\") segment.name = p.name;\n }\n modelSegments.push(segment);\n prevSerialTs = msg.timestamp;\n }\n if (p.type === \"tool_call\" && typeof p.tool_call_id === \"string\") {\n if (!hasOrigin) sawToolCallThisRequest = true; // This turn called a tool -> the next turn continues the same Task\n const callStop = typeof p.stop_reason === \"string\" ? p.stop_reason : undefined;\n const span: ToolCallSpan = {\n toolCallId: p.tool_call_id,\n name: typeof p.name === \"string\" ? p.name : \"\",\n startTs: msg.timestamp,\n };\n if (callStop !== undefined) span.stopReason = callStop;\n openToolCalls.set(p.tool_call_id, span);\n toolCalls.push(span);\n // The timeline lane only accepts calls that \"will actually be executed\":\n // an interrupt-compensation tool_call (stop_reason other than completed)\n // never gets an approval/output, and putting it on a lane would render as\n // a phantom \"executing\" state spanning the whole timeline — so it's\n // skipped outright.\n if (!hasOrigin && (callStop === undefined || callStop === \"completed\")) {\n const timeline: TraceToolSpan = {\n toolCallId: p.tool_call_id,\n name: typeof p.name === \"string\" ? p.name : \"\",\n callTs: msg.timestamp,\n taskIndex,\n };\n openSpansById.set(p.tool_call_id, timeline);\n toolSpans.push(timeline);\n }\n } else if (p.type === \"tool_call_output\" && typeof p.tool_call_id === \"string\") {\n const span = openToolCalls.get(p.tool_call_id);\n if (span && span.endTs === undefined) {\n span.endTs = msg.timestamp;\n const dur = Date.parse(msg.timestamp) - Date.parse(span.startTs);\n if (Number.isFinite(dur)) span.durationMs = dur;\n if (typeof p.stop_reason === \"string\") span.stopReason = p.stop_reason;\n }\n const timeline = openSpansById.get(p.tool_call_id);\n if (timeline && timeline.outputTs === undefined) {\n timeline.outputTs = msg.timestamp;\n if (typeof p.stop_reason === \"string\") timeline.stopReason = p.stop_reason;\n }\n }\n }\n\n // A pending span that never got a request_begin (interrupted right after the\n // user sent it / the process exited): it's a turn that never got to run,\n // and forms its own turn — reattaching it to the previous turn would smear\n // two separate user sends together.\n if (pendingFrom !== null) {\n taskIndex++;\n for (let k = pendingFrom; k < messages.length; k++) {\n if (messages[k]!.origin === undefined) msgTask[k] = taskIndex;\n }\n ensureTask(taskIndex);\n }\n\n // Each turn's message index range and end-of-turn time are always derived from\n // the per-message assignment done above (same source, so they never disagree\n // with each other). Messages before the first request_begin (session_meta)\n // have taskIndex -1 and are assigned to the first turn, otherwise they'd have\n // nowhere to sit on the page. The turn duration's **starting point** isn't\n // decided here — it was already settled at request_begin (duration only looks\n // at LLM requests; timestamps of the user Prompt / compaction summary or other\n // user text don't participate, see TraceTaskStats.startTs).\n const firstTask = [...taskStats.keys()].sort((a, b) => a - b)[0];\n for (let k = 0; k < messages.length; k++) {\n let ti = msgTask[k]!;\n if (ti < 0) {\n if (firstTask === undefined) continue;\n ti = firstTask;\n msgTask[k] = ti;\n }\n const t = ensureTask(ti);\n if (t.messageFrom < 0 || k < t.messageFrom) t.messageFrom = k;\n if (k > t.messageTo) t.messageTo = k;\n // session_meta is only **listed** in the first turn, and doesn't count\n // toward the end-of-turn time: it's metadata written when the session was\n // created, and its timestamp has nothing to do with this turn (it also gets\n // rewritten verbatim at the start of a new file after compaction splits the file).\n if (messages[k]!.type === \"session_meta\") continue;\n const ts = messages[k]!.timestamp;\n if (t.endTs === \"\" || ts > t.endTs) t.endTs = ts;\n }\n\n const tasks = [...taskStats.values()].sort((a, b) => a.taskIndex - b.taskIndex);\n // Total elapsed time = **the sum of each turn's duration**, matching exactly\n // the scope shown per-turn below (**including compaction turns** — their wall\n // clock time genuinely elapsed, each turn's card has its own duration, and the\n // overall total is their sum, so the numbers must add up). It is not \"last\n // message timestamp minus first message timestamp\": that would be the whole\n // file's wall-clock span, counting in the gaps **between** turns (the user\n // thinking, stepping out for coffee, coming back the next day) — none of which\n // is time the Agent spent working. A degenerate turn with no Request has an\n // empty startTs and counts as 0.\n // Note this uses a different convention from the Session's cumulative elapsed\n // time on the Chat page: that one only accumulates user turns (compaction\n // after a turn ends doesn't count toward the turn).\n const elapsedMs = tasks.reduce((sum, t) => {\n const span = Date.parse(t.endTs) - Date.parse(t.startTs);\n return sum + (Number.isFinite(span) ? Math.max(0, span) : 0);\n }, 0);\n return {\n elapsedMs,\n requests,\n tasks,\n toolCalls,\n modelSegments,\n toolSpans,\n reconnectCount,\n compactionCount,\n usageTrend,\n };\n }\n\n /** Level-by-level browsing (newest first): Agent -> date -> Session -> Trace files. */\n async agentTraces(projectId: string, agentId: string): Promise<AgentTracesResponse> {\n const dir = tracesDir(this.root, projectId, agentId);\n const dates = (await listDirs(dir)).sort().reverse();\n const out: AgentTracesResponse = { dates: [] };\n for (const date of dates) {\n const bySession = new Map<string, { index: number; sizeBytes: number }[]>();\n for (const file of await listFiles(path.join(dir, date))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match) continue;\n const sessionId = match[1]!;\n const stat = await fs.stat(path.join(dir, date, file));\n const files = bySession.get(sessionId) ?? [];\n files.push({ index: Number(match[2]), sizeBytes: stat.size });\n bySession.set(sessionId, files);\n }\n if (bySession.size === 0) continue;\n out.dates.push({\n date,\n // session_id embeds a timestamp, so reverse lexicographic order is reverse chronological order.\n sessions: [...bySession.entries()]\n .sort((a, b) => b[0].localeCompare(a[0]))\n .map(([sessionId, files]) => ({\n sessionId,\n files: files.sort((a, b) => a.index - b.index),\n })),\n });\n }\n return out;\n }\n\n private async readFileByIndex(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n ): Promise<OmniMessage[]> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const file = files.find((f) => f.index === index);\n if (!file) {\n throw new HttpError(\n 404,\n \"trace_not_found\",\n `This Session has no Trace file with index ${index}.`,\n );\n }\n return readTraceTolerant(file.path);\n }\n}\n","/**\n * Usage statistics query.\n *\n * Cost is **computed in real time**: usage_records only stores Tokens (pricing may\n * be added later), so at query time each Model's cost is converted using the\n * current Project's configured pricing — the repo returns raw Token totals broken\n * down by `(provider, model_id)` paired reference, and this service looks up each\n * reference's price once and folds it into cost / hasUncosted (if a Model has no\n * pricing, its consumption is excluded from cost and hasUncosted is flagged).\n * Summary cards (today / last 7 days / cumulative), grouped aggregation (date /\n * agent / model / session, with the session dimension supporting agentId drill-down\n * filtering), and a 30-day trend.\n * Server-side error statistics (error_records) ride along on the same response:\n * the statistics center fetches everything in one request, and filters are\n * naturally shared; unattributed errors (login failures, process crashes, and other\n * errors with no Project context) are visible only to admins, see the ErrorsRepo\n * file header.\n */\nimport type {\n UsageBucket,\n UsageErrors,\n UsageGroupBy,\n UsageGroupRow,\n UsageResponse,\n} from \"../api/types.js\";\nimport type { ErrorFilter, ErrorsRepo } from \"../db/repos/errors.js\";\nimport type {\n UsageRepo,\n UsageModelSums,\n UsageGroupModelSums,\n UsageFilter,\n} from \"../db/repos/usage.js\";\nimport { formatLocalDate, localDateMinusDays } from \"../internal/dates.js\";\n\n/** Number of most-recent entries kept in the error detail table. */\nconst ERROR_RECENT_N = 20;\n\n/** The three pricing buckets (usd_per_mtok convention), returned by the pricing lookup callback. */\nexport interface PricingRates {\n cacheRead: number;\n cacheWrite: number;\n output: number;\n}\n\nexport type PricingLookup = (\n projectId: string,\n provider: string,\n modelId: string,\n) => Promise<PricingRates | undefined>;\n\nexport interface UsageQuery {\n from?: string;\n to?: string;\n groupBy: UsageGroupBy;\n /** Top-level filter: view by Agent (also used for groupBy=session drill-down). */\n agentId?: string;\n /** Top-level filter: view by Model (paired with modelId; the dropdown always sends them as a pair). */\n provider?: string;\n modelId?: string;\n /** Whether to include unattributed errors: admin only (the route passes user.isAdmin), defaults to false. */\n includeGlobalErrors?: boolean;\n}\n\n/** Cost formula: sum of the three buckets, in USD per million Tokens. */\nfunction costOf(sums: UsageModelSums, rates: PricingRates): number {\n return (\n (sums.cacheRead * rates.cacheRead +\n sums.cacheWrite * rates.cacheWrite +\n sums.output * rates.output) /\n 1e6\n );\n}\n\n/** In-process Map key for a paired reference (\\0-separated, the same style as session-manager's agentKey; never persisted). */\nfunction refKey(provider: string, modelId: string): string {\n return `${provider}\\0${modelId}`;\n}\n\nexport class UsageService {\n constructor(\n private readonly usage: UsageRepo,\n private readonly errors: ErrorsRepo,\n private readonly lookupPricing: PricingLookup,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n async query(projectId: string, q: UsageQuery): Promise<UsageResponse> {\n const today = formatLocalDate(this.now());\n // Top-level filter: agent + model (the cost center switches views by agent/model; the model filter is always sent as a pair).\n const base: UsageFilter = {};\n if (q.agentId !== undefined) base.agentId = q.agentId;\n if (q.provider !== undefined) base.provider = q.provider;\n if (q.modelId !== undefined) base.modelId = q.modelId;\n\n const win = (from?: string, to?: string): UsageFilter => ({\n ...base,\n ...(from !== undefined ? { from } : {}),\n ...(to !== undefined ? { to } : {}),\n });\n\n const todayRows = this.usage.bucketByModel(projectId, win(today, today));\n const last7dRows = this.usage.bucketByModel(projectId, win(localDateMinusDays(this.now(), 6)));\n const totalRows = this.usage.bucketByModel(projectId, win(q.from, q.to));\n const groupRows = this.usage.groupsByModel(projectId, q.groupBy, win(q.from, q.to));\n // Fixed 30-day window; affected by the agent/model filter.\n const trendFrom = localDateMinusDays(this.now(), 29);\n const trendRows = this.usage.groupsByModel(projectId, \"date\", win(trendFrom));\n // Agent call-count chart: not affected by the agent filter (shows all agents), but still affected by the date + model filter.\n const agentRows = this.usage.groupsByModel(projectId, \"agent\", {\n ...(q.provider !== undefined ? { provider: q.provider } : {}),\n ...(q.modelId !== undefined ? { modelId: q.modelId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n });\n // Model success-rate chart: not affected by the model filter (shows all models), but still affected by the date + agent filter.\n const statusRows = this.usage.statusByModel(projectId, {\n ...(q.agentId !== undefined ? { agentId: q.agentId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n });\n // Error statistics: likewise not affected by the model filter (HTTP / process errors have no Model dimension), but still affected by the date + agent filter.\n const errorFilter: ErrorFilter = {\n ...(q.agentId !== undefined ? { agentId: q.agentId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n // Unattributed errors are visible only to admins (regular members only see errors within their own Project, see the ErrorsRepo file header).\n ...(q.includeGlobalErrors === true ? { includeGlobal: true } : {}),\n };\n\n // Each paired reference that occurs is looked up for its current price only once.\n const rates = new Map<string, PricingRates | undefined>();\n const allRefs = new Map<string, { provider: string; modelId: string }>();\n for (const r of [...todayRows, ...last7dRows, ...totalRows, ...groupRows, ...trendRows]) {\n allRefs.set(refKey(r.provider, r.modelId), { provider: r.provider, modelId: r.modelId });\n }\n for (const [key, ref] of allRefs) {\n rates.set(key, await this.lookupPricing(projectId, ref.provider, ref.modelId));\n }\n\n const byAgentMap = new Map<string, { requests: number; total: number }>();\n for (const r of agentRows) {\n const acc = byAgentMap.get(r.key) ?? { requests: 0, total: 0 };\n acc.requests += r.requests;\n acc.total += r.total;\n byAgentMap.set(r.key, acc);\n }\n\n return {\n summary: {\n today: this.foldBucket(todayRows, rates),\n last7d: this.foldBucket(last7dRows, rates),\n total: this.foldBucket(totalRows, rates),\n },\n groupBy: q.groupBy,\n groups: this.foldGroups(groupRows, rates, q.groupBy),\n trend: this.foldTrend(trendRows, rates),\n byAgent: [...byAgentMap.entries()]\n .map(([agentId, v]) => ({ agentId, requests: v.requests, total: v.total }))\n .sort((a, b) => b.requests - a.requests),\n success: statusRows.sort((a, b) => b.total - a.total),\n errors: this.foldErrors(projectId, errorFilter),\n agentIds: this.usage.distinctAgentIds(projectId),\n models: this.usage.distinctModels(projectId),\n };\n }\n\n /** Error statistics: summary info (total / unexpected / most common error code) + the last N entries, all filtered by the selected range. */\n private foldErrors(projectId: string, f: ErrorFilter): UsageErrors {\n const { total, unexpected } = this.errors.summary(projectId, f);\n return {\n total,\n unexpected,\n topCode: this.errors.topCode(projectId, f),\n recent: this.errors.recent(projectId, f, ERROR_RECENT_N),\n };\n }\n\n private foldBucket(\n rows: UsageModelSums[],\n rates: Map<string, PricingRates | undefined>,\n ): UsageBucket {\n let total = 0;\n let requests = 0;\n let cost: number | null = null;\n let hasUncosted = false;\n for (const r of rows) {\n total += r.total;\n requests += r.requests;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) cost = (cost ?? 0) + costOf(r, rate);\n else hasUncosted = true;\n }\n return { total, requests, cost, hasUncosted };\n }\n\n private foldGroups(\n rows: UsageGroupModelSums[],\n rates: Map<string, PricingRates | undefined>,\n groupBy: UsageGroupBy,\n ): UsageGroupRow[] {\n // The model dimension folds by paired reference (a shared model_id name across providers is split into separate rows); other dimensions fold by their group key.\n const keyOf = (r: UsageGroupModelSums): string =>\n groupBy === \"model\" ? refKey(r.provider, r.key) : r.key;\n const byKey = new Map<string, UsageGroupRow>();\n for (const r of rows) {\n const acc = byKey.get(keyOf(r)) ?? {\n key: r.key,\n ...(groupBy === \"model\" ? { provider: r.provider } : {}),\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n total: 0,\n requests: 0,\n cost: null as number | null,\n hasUncosted: false,\n };\n acc.cacheRead += r.cacheRead;\n acc.cacheWrite += r.cacheWrite;\n acc.output += r.output;\n acc.total += r.total;\n acc.requests += r.requests;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) acc.cost = (acc.cost ?? 0) + costOf(r, rate);\n else acc.hasUncosted = true;\n byKey.set(keyOf(r), acc);\n }\n const out = [...byKey.values()];\n // The date dimension sorts by key descending (most recent first); other dimensions sort by total Token count descending.\n if (groupBy === \"date\") out.sort((a, b) => b.key.localeCompare(a.key));\n else out.sort((a, b) => b.total - a.total);\n return out;\n }\n\n private foldTrend(\n rows: UsageGroupModelSums[],\n rates: Map<string, PricingRates | undefined>,\n ): UsageResponse[\"trend\"] {\n const byDate = new Map<\n string,\n { total: number; cacheRead: number; cacheWrite: number; output: number; cost: number | null }\n >();\n for (const r of rows) {\n const acc = byDate.get(r.key) ?? {\n total: 0,\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n cost: null,\n };\n acc.total += r.total;\n acc.cacheRead += r.cacheRead;\n acc.cacheWrite += r.cacheWrite;\n acc.output += r.output;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) acc.cost = (acc.cost ?? 0) + costOf(r, rate);\n byDate.set(r.key, acc);\n }\n return [...byDate.entries()]\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([date, v]) => ({\n date,\n total: v.total,\n cacheRead: v.cacheRead,\n cacheWrite: v.cacheWrite,\n output: v.output,\n cost: v.cost,\n }));\n }\n}\n","/**\n * Workspace HTML preview served from a separate origin.\n *\n * `GET /preview/:token/*` sits outside `/api` and outside the auth middleware: the\n * preview origin never receives the session cookie, so the signed token in the path is\n * the only credential. Path-based (rather than the query-parameter `files/content`\n * endpoint) so a page's relative subresources — `app.js`, `style.css`, images — resolve\n * against the document and actually load.\n *\n * Because the boundary is now the origin itself, the response does NOT carry the CSP\n * sandbox: the page gets a normal origin with working storage and cookies, which is what\n * lets third-party embeds run. That only stays safe while the host check below holds —\n * this same process also answers on the App origin, and serving Agent-written HTML there\n * would be a same-origin XSS with full API access.\n *\n * Design: design/specs/05-ARCHITECTURE.md § \"Workspace 文件预览\".\n */\nimport { Hono } from \"hono\";\nimport type { SessionsRepo } from \"../../db/repos/sessions.js\";\nimport type { WorkspaceFilesService } from \"../../services/workspace-files-service.js\";\nimport type { PreviewTokenSigner } from \"../../services/preview-token.js\";\nimport { hostOnly, requestAuthority } from \"../../services/preview-token.js\";\n\nexport interface PreviewDeps {\n sessionsRepo: SessionsRepo;\n workspaceFiles: WorkspaceFilesService;\n previewTokens: PreviewTokenSigner;\n}\n\n/** Directory-style requests resolve to index.html, matching ordinary static hosting. */\nconst DIRECTORY_INDEX = \"index.html\";\n\nexport function previewRoutes(deps: PreviewDeps) {\n const app = new Hono();\n\n app.get(\"/:token/*\", async (c) => {\n const token = c.req.param(\"token\");\n const payload = token ? deps.previewTokens.verify(token) : null;\n // A bad or expired token and a wrong host both answer 404 with no detail: this\n // endpoint is unauthenticated, so it should not confirm what exists.\n if (!payload) return c.text(\"Not found\", 404);\n\n // The host binding is the load-bearing check — see the file header.\n const authority = requestAuthority(c.req.url, c.req.header(\"host\"));\n if (hostOnly(authority).toLowerCase() !== payload.host.toLowerCase()) {\n return c.text(\"Not found\", 404);\n }\n\n const row = deps.sessionsRepo.findById(payload.sessionId);\n if (!row) return c.text(\"Not found\", 404);\n\n // Everything after `/preview/<token>/` is the Workspace-relative path; the service\n // re-resolves it against the Workspace and rejects `..` and symlink escapes.\n const prefix = `/preview/${token}/`;\n const raw = c.req.path.startsWith(prefix) ? c.req.path.slice(prefix.length) : \"\";\n let rel: string;\n try {\n rel = decodeURIComponent(raw);\n } catch {\n return c.text(\"Not found\", 404);\n }\n if (rel === \"\" || rel.endsWith(\"/\")) rel += DIRECTORY_INDEX;\n\n let file;\n try {\n file = await deps.workspaceFiles.read(row.workspace, rel);\n } catch {\n return c.text(\"Not found\", 404);\n }\n\n return new Response(new Uint8Array(file.data), {\n status: 200,\n headers: {\n \"Content-Type\": file.contentType,\n \"X-Content-Type-Options\": \"nosniff\",\n // Without this, third-party requests made by the page leak the token-bearing\n // URL through Referer — a risk that only exists because this origin exists to\n // let third-party embeds run.\n \"Referrer-Policy\": \"no-referrer\",\n // Previews are per-token and short-lived; never let a shared cache keep them.\n \"Cache-Control\": \"no-store\",\n },\n });\n });\n\n return app;\n}\n","/**\n * Server runtime config (ServerConfig) — parsed from environment variables.\n *\n * The data root directory is shared with the SDK / CLI (`resolveRoot()`:\n * PENGUIN_HOME or ~/.penguin/data); the SQLite index database defaults to\n * `<root>/web.db` (overridable via PENGUIN_WEB_DB, tests use \":memory:\").\n * In production, the SPA is served statically once the frontend build output\n * directory (PENGUIN_WEB_DIST, the bundled web-dist/, or ../web/dist) is\n * detected to exist.\n * Docs: /docs/configuration § \"Environment variables\".\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_SERVER_PORT, resolveRoot } from \"@prismshadow/penguin-core\";\n\nexport interface ServerConfig {\n /** Local data root directory (shared with the SDK/CLI). */\n root: string;\n /** HTTP listen address and port (defaults to 127.0.0.1:7364, deliberately avoiding common ports like 3000/8080). */\n host: string;\n port: number;\n /** SQLite database path; \":memory:\" for test injection. */\n dbPath: string;\n /** Frontend static assets directory; whether it's enabled is decided by checking existence when the app is assembled. */\n webDist: string;\n /**\n * Origin that serves Workspace HTML previews (PENGUIN_PREVIEW_ORIGIN), e.g.\n * `https://preview.example.com`. It must differ from the App origin by **hostname** —\n * cookies ignore ports, so a second port would still share the session cookie. Unset\n * is the norm locally: the loopback counterpart (`127.0.0.1` <-> `localhost`) is\n * derived per request instead. See design § \"Workspace 文件预览\".\n */\n previewOrigin: string | null;\n /** Login session validity period (7 days). */\n authSessionTtlMs: number;\n /** Sliding renewal threshold: if the remaining validity is below this value when validation succeeds, it's renewed to the full TTL (renews under 6 days). */\n authSessionRenewMs: number;\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Default frontend build output directory, first match wins:\n * - `<this package>/web-dist`: npm package layout — the release workflow copies the built\n * web assets into the published package, so an `npm install` gets the Web UI too;\n * - `<this package>/../web/dist`: monorepo layout (resolves the same whether running\n * from src or dist), also the fallback when neither exists.\n */\nfunction defaultWebDist(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const bundled = path.resolve(here, \"..\", \"web-dist\");\n if (fs.existsSync(bundled)) return bundled;\n return path.resolve(here, \"..\", \"..\", \"web\", \"dist\");\n}\n\n/**\n * Validates PENGUIN_PREVIEW_ORIGIN into a bare origin, or throws. An unparseable value\n * is a hard failure rather than a silent fallback: falling back would quietly serve\n * previews same-origin, which is the configuration this variable exists to avoid.\n */\nfunction normalizePreviewOrigin(raw: string | undefined): string | null {\n if (!raw || raw.trim() === \"\") return null;\n let url: URL;\n try {\n url = new URL(raw.trim());\n } catch {\n throw new Error(`Invalid PENGUIN_PREVIEW_ORIGIN=${raw} (expected an absolute origin)`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new Error(`Invalid PENGUIN_PREVIEW_ORIGIN=${raw} (only http/https are supported)`);\n }\n return url.origin;\n}\n\n/** Parses server config from environment variables (PORT / HOST / PENGUIN_HOME / PENGUIN_WEB_DIST / PENGUIN_WEB_DB / PENGUIN_PREVIEW_ORIGIN). */\nexport function resolveServerConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const root = env.PENGUIN_HOME ?? resolveRoot();\n // An empty PORT string is treated as unset (the common `.env` case of an empty\n // `PORT=`): Number(\"\") === 0 would pass the range check and bind to a random\n // port; this matches the CLI's resolvePort convention.\n const port = Number(env.PORT || DEFAULT_SERVER_PORT);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new Error(`Invalid port configuration PORT=${env.PORT}`);\n }\n return {\n root,\n host: env.HOST ?? \"127.0.0.1\",\n port,\n dbPath: env.PENGUIN_WEB_DB ?? path.join(root, \"web.db\"),\n webDist: env.PENGUIN_WEB_DIST ?? defaultWebDist(),\n previewOrigin: normalizePreviewOrigin(env.PENGUIN_PREVIEW_ORIGIN),\n authSessionTtlMs: 7 * DAY_MS,\n authSessionRenewMs: 6 * DAY_MS,\n };\n}\n"],"mappings":"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","names":["fs","path","Hono","mapRow","mapRow","mapRow","randomBytes","deps","randomBytes","deps","app","Hono","getCookie","deps","app","Hono","getCookie","Hono","randomBytes","timingSafeEqual","app","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","createHash","Hono","isValidId","ms","path","path","isValidId","deps","app","Hono","createHash","Hono","deps","app","Hono","fs","path","Hono","app","Hono","deps","path","fs","fs","Hono","deps","app","Hono","fs","Hono","deps","app","Hono","fs","path","Hono","deps","app","Hono","path","fs","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","fs","path","Hono","fs","fs","fs","path","fs","path","deps","app","Hono","fs","path","fs","path","isSessionMeta","path","path","fs","deps","isSessionMeta","path","createHash","userText","deps","createHash","userText","deps","isEventMessage","isSessionMeta","isSessionMeta","originSessionId","isEventMessage","randomBytes","deps","fs","fs","path","parseToml","userText","fs","parseToml","path","userText","next","THINKING_LEVELS","fs","config","path","fs","path","agentsDir","agentsMdPath","isValidId","loadAgentVault","scheduleDir","skillsDir","systemConfigPath","fs","agentsDir","isValidId","systemConfigPath","loadAgentVault","scheduleDir","skillsDir","path","agentsMdPath","fs","path","parseToml","parseYaml","asRecord","config","randomBytes","fs","path","parseYaml","agentDir","agentStateDir","agentStateVersion","systemConfigPath","path","fs","systemConfigPath","parseYaml","agentStateVersion","randomBytes","agentDir","agentStateDir","fs","path","fs","deps","path","readdir","createAgent","findLatestTraceFile","isSessionMeta","tracesDir","deps","tracesDir","path","createAgent","isSessionMeta","findLatestTraceFile","readdir","fs","path","agentsDir","readTraceTolerant","tracesDir","TRACE_FILE_RE","fs","tracesDir","path","agentsDir","readTraceTolerant","refKey","Hono","deps","app","Hono","config","deps","app","Hono","fs","CONTENT_TYPES","path","fs","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/app.ts","../src/db/database.ts","../src/db/schema.ts","../src/db/repos/agents.ts","../src/db/repos/auth-sessions.ts","../src/db/repos/errors.ts","../src/db/repos/members.ts","../src/db/repos/projects.ts","../src/db/repos/goals.ts","../src/db/repos/schedules.ts","../src/db/repos/sessions.ts","../src/db/repos/ui-prefs.ts","../src/db/repos/usage.ts","../src/db/repos/users.ts","../src/auth/middleware.ts","../src/http/errors.ts","../src/auth/service.ts","../src/auth/password.ts","../src/http/routes/admin.ts","../src/http/validate.ts","../src/http/routes/auth.ts","../src/http/routes/me.ts","../src/services/preview-token.ts","../src/http/routes/events.ts","../src/http/sse.ts","../src/http/routes/projects.ts","../src/http/routes/members.ts","../src/http/routes/models.ts","../src/http/routes/vault.ts","../src/http/routes/schedules.ts","../src/runtime/schedule-file.ts","../src/runtime/schedule-store.ts","../src/http/routes/benchmarks.ts","../src/http/routes/skills.ts","../src/http/routes/agent-transfer.ts","../src/http/routes/agents.ts","../src/http/settle.ts","../src/http/routes/dirs.ts","../src/http/routes/agent-config.ts","../src/http/routes/agent-traces.ts","../src/http/routes/usage.ts","../src/http/routes/sessions.ts","../src/services/workspace-guard.ts","../src/services/workspace-files-service.ts","../src/http/routes/version.ts","../src/runtime/channel.ts","../src/internal/dates.ts","../src/runtime/error-recorder.ts","../src/runtime/session-manager.ts","../src/runtime/approvals.ts","../src/runtime/live-tail.ts","../src/runtime/session-sources.ts","../src/runtime/stream-error-watcher.ts","../src/runtime/scheduler.ts","../src/runtime/title-generator.ts","../src/runtime/usage-recorder.ts","../src/services/ids.ts","../src/services/admin-service.ts","../src/services/agent-config-service.ts","../src/services/project-config-service.ts","../src/services/agent-service.ts","../src/services/benchmark-service.ts","../src/services/snapshot-service.ts","../src/services/project-service.ts","../src/services/session-service.ts","../src/services/trace-service.ts","../src/services/update-check-service.ts","../src/services/usage-service.ts","../src/http/routes/preview.ts","../src/config.ts"],"sourcesContent":["/**\n * Server startup entry point: dotenv → config → assembly → listen\n * → graceful shutdown.\n *\n * SIGINT / SIGTERM: interrupt all active runs (pending approvals converge to deny), wait\n * ≤5s for wrap-up, then close HTTP and SQLite. Tests never go through this file\n * (injected via app.request() instead).\n * There's also a process-level error fallback (uncaughtException / unhandledRejection):\n * persist + log, with the fatal one still shutting down per existing semantics (see the\n * comment below).\n */\nimport { config as loadDotenv } from \"dotenv\";\nimport { serve } from \"@hono/node-server\";\nimport { buildAppDeps, createApp } from \"./app.js\";\nimport { resolveServerConfig } from \"./config.js\";\nimport { loopbackHostRoles } from \"./services/preview-token.js\";\n\nloadDotenv({ quiet: true });\n\nconst config = resolveServerConfig();\nconst deps = buildAppDeps(config);\nconst app = createApp(deps);\n\n// Built-in admin seed (idempotent): creates admin (initial password penguin-2026) and adopts default_project when the users table is empty.\nawait deps.authService.seedAdmin();\n\n// Schedule scheduler: startup reconciliation (missed, don't backfill) + periodic scan; only active while the server is running.\nawait deps.scheduler.start();\n\n// Goal mode runs only in SessionManager memory: a hard crash (SIGKILL, power loss) can leave\n// goal_state rows stuck `active` with no runner behind them. Reconcile them to `aborted` now —\n// nothing is running yet, so any `active` row is a crash orphan — so the chat banner never\n// restores a phantom \"running\" goal. GOAL.yaml on disk stays `active` as the resume point.\ndeps.goalsRepo.abortOrphanedActive();\n\n// On a loopback bind the App is canonicalized onto one name (`localhost`) and its\n// counterpart is reserved for previews, so advertise the canonical name — the other one\n// only 302s back here for App routes (see the canonical-host guard in app.ts).\nconst appHost = loopbackHostRoles(config.host)?.app ?? config.host;\nconst server = serve({ fetch: app.fetch, hostname: config.host, port: config.port }, (info) => {\n console.log(`penguin-server started: http://${appHost}:${info.port}`);\n console.log(`Data root: ${config.root}`);\n console.log(`SQLite: ${config.dbPath}`);\n});\n\n/**\n * Second loopback listener so the preview origin is actually reachable.\n *\n * Workspace HTML previews are served from the loopback counterpart of the host the App\n * is used on (`127.0.0.1` <-> `localhost`, see design § \"Workspace 文件预览\"). On most\n * systems `localhost` resolves to `::1` first, so a server bound only to `127.0.0.1`\n * would leave every preview URL refusing connections. Binding `::1` as well closes that\n * gap. Failure is non-fatal — the App keeps working, previews just fall back.\n */\nconst ipv6Loopback =\n config.host === \"127.0.0.1\" || config.host === \"localhost\"\n ? serve({ fetch: app.fetch, hostname: \"::1\", port: config.port })\n : null;\nipv6Loopback?.on(\"error\", (err: NodeJS.ErrnoException) => {\n console.warn(\n `[server] IPv6 loopback listener unavailable (${err.code ?? err.message}); previews via localhost may not resolve.`,\n );\n});\n\nlet shuttingDown = false;\nasync function shutdown(signal: string, exitCode = 0): Promise<void> {\n if (shuttingDown) return;\n shuttingDown = true;\n console.log(`Received ${signal}, shutting down…`);\n deps.scheduler.stop();\n await deps.manager.shutdown(5000);\n deps.channels.dispose();\n ipv6Loopback?.close();\n server.close(() => {\n deps.db.close();\n process.exit(exitCode);\n });\n // Fallback: a long-lived SSE connection may block the close callback, so force exit after 1s.\n setTimeout(() => process.exit(exitCode), 1000).unref();\n}\n\nprocess.on(\"SIGINT\", () => void shutdown(\"SIGINT\"));\nprocess.on(\"SIGTERM\", () => void shutdown(\"SIGTERM\"));\n\n// Process-level error fallback: once a background\n// fire-and-forget promise (title generation, Session drive, etc.) throws, the error\n// reaches the process without passing through any catch — persist it first for a\n// record, then handle each case according to its nature.\nprocess.on(\"uncaughtException\", (err) => {\n console.error(`[server] Uncaught exception: ${err.stack ?? err.message}`);\n deps.errors.record({ source: \"process\", err, code: \"uncaught_exception\" });\n // From this point the process state can't be trusted (the error was never converged\n // by any catch): don't swallow it — wrap up per existing shutdown semantics and exit\n // with a nonzero code (equivalent to Node's default crash exit, just with an extra\n // persist and graceful wrap-up).\n // Must exit even if shutdown itself errors — never let \"caught a fatal error\" turn\n // into \"the process limps along in a broken state\".\n void shutdown(\"uncaughtException\", 1).catch(() => process.exit(1));\n});\nprocess.on(\"unhandledRejection\", (reason) => {\n const err = reason instanceof Error ? reason : new Error(String(reason));\n console.error(`[server] Unhandled promise rejection: ${err.stack ?? err.message}`);\n deps.errors.record({ source: \"process\", err, code: \"unhandled_rejection\" });\n // Unlike uncaughtException, this **doesn't** exit: a rejected promise is a localized\n // failure of some background task, and the process state isn't compromised; dragging\n // down the entire service for it (Node's default behavior) isn't worth it — persist +\n // log, then keep serving.\n});\n","/**\n * Hono app assembly: middleware + route mounting + static hosting +\n * error handling.\n *\n * `createApp(deps)` is pure assembly (does not listen on a port): tests inject requests via\n * `app.request()`; `buildAppDeps(config)` assembles all services from config (test doubles\n * like SessionLoader can be injected). The startup entry point is in index.ts.\n */\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ServerConfig } from \"./config.js\";\nimport { openDatabase } from \"./db/database.js\";\nimport { AgentsRepo } from \"./db/repos/agents.js\";\nimport { AuthSessionsRepo } from \"./db/repos/auth-sessions.js\";\nimport { ErrorsRepo } from \"./db/repos/errors.js\";\nimport { MembersRepo } from \"./db/repos/members.js\";\nimport { ProjectsRepo } from \"./db/repos/projects.js\";\nimport { GoalsRepo } from \"./db/repos/goals.js\";\nimport { SchedulesRepo } from \"./db/repos/schedules.js\";\nimport { SessionsRepo } from \"./db/repos/sessions.js\";\nimport { UiPrefsRepo } from \"./db/repos/ui-prefs.js\";\nimport { UsageRepo } from \"./db/repos/usage.js\";\nimport { UsersRepo } from \"./db/repos/users.js\";\nimport type { UserRow } from \"./db/repos/users.js\";\nimport { authMiddleware, jsonOnlyWrites } from \"./auth/middleware.js\";\nimport type { AppEnv } from \"./auth/middleware.js\";\nimport { AuthService } from \"./auth/service.js\";\nimport { handleError, HttpError, errorBody } from \"./http/errors.js\";\nimport { adminUsersRoutes } from \"./http/routes/admin.js\";\nimport { authRoutes } from \"./http/routes/auth.js\";\nimport { meRoutes } from \"./http/routes/me.js\";\nimport { eventsRoutes, userChannelKey } from \"./http/routes/events.js\";\nimport { projectsRoutes } from \"./http/routes/projects.js\";\nimport { membersRoutes } from \"./http/routes/members.js\";\nimport { modelsRoutes } from \"./http/routes/models.js\";\nimport { vaultRoutes } from \"./http/routes/vault.js\";\nimport { scheduleRoutes } from \"./http/routes/schedules.js\";\nimport { benchmarksRoutes } from \"./http/routes/benchmarks.js\";\nimport { agentSkillsRoutes, skillLibraryRoutes } from \"./http/routes/skills.js\";\nimport { agentTransferRoutes } from \"./http/routes/agent-transfer.js\";\nimport { agentsRoutes } from \"./http/routes/agents.js\";\nimport { dirsRoutes } from \"./http/routes/dirs.js\";\nimport { agentConfigRoutes } from \"./http/routes/agent-config.js\";\nimport { agentTracesRoutes } from \"./http/routes/agent-traces.js\";\nimport { usageRoutes } from \"./http/routes/usage.js\";\nimport { agentSessionsRoutes, sessionsRoutes } from \"./http/routes/sessions.js\";\nimport { versionRoutes } from \"./http/routes/version.js\";\nimport { ChannelHub } from \"./runtime/channel.js\";\nimport { ErrorRecorder } from \"./runtime/error-recorder.js\";\nimport { createCoreSessionLoader, SessionManager } from \"./runtime/session-manager.js\";\nimport { SessionSources } from \"./runtime/session-sources.js\";\nimport type { SessionLoader } from \"./runtime/session-manager.js\";\nimport { Scheduler } from \"./runtime/scheduler.js\";\nimport { TitleGenerator } from \"./runtime/title-generator.js\";\nimport type { TitleNotifier } from \"./runtime/title-generator.js\";\nimport { UsageRecorder } from \"./runtime/usage-recorder.js\";\nimport { AdminService } from \"./services/admin-service.js\";\nimport { AgentConfigService } from \"./services/agent-config-service.js\";\nimport { AgentService } from \"./services/agent-service.js\";\nimport { BenchmarkService } from \"./services/benchmark-service.js\";\nimport { SnapshotService } from \"./services/snapshot-service.js\";\nimport { ProjectConfigService } from \"./services/project-config-service.js\";\nimport { ProjectService } from \"./services/project-service.js\";\nimport { SessionService } from \"./services/session-service.js\";\nimport { TraceService } from \"./services/trace-service.js\";\nimport { UpdateCheckService } from \"./services/update-check-service.js\";\nimport { UsageService } from \"./services/usage-service.js\";\nimport { WorkspaceFilesService } from \"./services/workspace-files-service.js\";\nimport {\n createPreviewTokenSigner,\n hostOnly,\n loopbackHostRoles,\n requestAuthority,\n} from \"./services/preview-token.js\";\nimport type { PreviewTokenSigner } from \"./services/preview-token.js\";\nimport { previewRoutes } from \"./http/routes/preview.js\";\n\n/** Request body size limit (tasks may carry data: images): 20MB. */\nconst MAX_BODY_BYTES = 20 * 1024 * 1024;\n\nexport interface AppDeps {\n config: ServerConfig;\n db: DatabaseSync;\n sessionsRepo: SessionsRepo;\n prefsRepo: UiPrefsRepo;\n authService: AuthService;\n adminService: AdminService;\n projectService: ProjectService;\n projectConfigService: ProjectConfigService;\n agentService: AgentService;\n agentConfigService: AgentConfigService;\n sessionService: SessionService;\n traceService: TraceService;\n usageService: UsageService;\n /** GitHub latest-release lookup for the web UI's update reminder (cached, fail-soft). */\n updateCheck: UpdateCheckService;\n workspaceFiles: WorkspaceFilesService;\n /** Signs/verifies short-lived Workspace preview tokens (separate preview origin). */\n previewTokens: PreviewTokenSigner;\n benchmarks: BenchmarkService;\n snapshots: SnapshotService;\n schedulesRepo: SchedulesRepo;\n goalsRepo: GoalsRepo;\n errorsRepo: ErrorsRepo;\n scheduler: Scheduler;\n channels: ChannelHub;\n manager: SessionManager;\n /** Session-origin registry derived from session_meta (single source of truth; no DB column). */\n sessionSources: SessionSources;\n /** Error persistence (shared by app.onError and various background capture points; the process-level fallback is in index.ts). */\n errors: ErrorRecorder;\n /** Request log output (minimal one-liner); tests inject a noop. */\n log: (line: string) => void;\n}\n\nexport interface BuildDepsOverrides {\n /** Test double: session-manager's underlying loader (avoids the real LLM/SDK path). */\n loader?: SessionLoader;\n /** Test double: Session title generator (avoids real LLM requests). */\n titles?: TitleNotifier;\n /** Test double: update-check service with a stubbed fetch/clock (avoids real network calls). */\n updateCheck?: UpdateCheckService;\n log?: (line: string) => void;\n now?: () => Date;\n}\n\n/** Assemble all services from config (shared by production and tests; tests pass dbPath=\":memory:\" and a temp root). */\nexport function buildAppDeps(config: ServerConfig, overrides: BuildDepsOverrides = {}): AppDeps {\n const db = openDatabase(config.dbPath);\n const log = overrides.log ?? ((line: string) => console.log(line));\n\n const usersRepo = new UsersRepo(db);\n const authSessionsRepo = new AuthSessionsRepo(db);\n const projectsRepo = new ProjectsRepo(db);\n const membersRepo = new MembersRepo(db);\n const agentsRepo = new AgentsRepo(db);\n const sessionsRepo = new SessionsRepo(db);\n const usageRepo = new UsageRepo(db);\n const errorsRepo = new ErrorsRepo(db);\n const prefsRepo = new UiPrefsRepo(db);\n const schedulesRepo = new SchedulesRepo(db);\n const goalsRepo = new GoalsRepo(db);\n\n const projectConfigService = new ProjectConfigService(config.root);\n const agentConfigService = new AgentConfigService(config.root);\n const agentService = new AgentService(config.root, agentsRepo, agentConfigService);\n const traceService = new TraceService(config.root);\n const workspaceFiles = new WorkspaceFilesService();\n // Per-process secret: preview tokens are short-lived, so losing them on restart is\n // harmless and there is nothing to persist or rotate.\n const previewTokens = createPreviewTokenSigner();\n const benchmarks = new BenchmarkService(config.root);\n const snapshots = new SnapshotService(config.root);\n const usageService = new UsageService(\n usageRepo,\n errorsRepo,\n (projectId, provider, modelId) => projectConfigService.getPricing(projectId, provider, modelId),\n overrides.now ?? (() => new Date()),\n );\n const updateCheck =\n overrides.updateCheck ?? new UpdateCheckService(overrides.now ? { now: overrides.now } : {});\n\n // Channel idle reclamation skips active Sessions (running/compacting can go a long time\n // without a publish, e.g. while waiting for approval).\n // manager is created after channels: use a lazy predicate (managerRef is assigned by the\n // time the sweep timer fires).\n let managerRef: SessionManager | undefined;\n const channels = new ChannelHub({\n isActive: (key) => managerRef !== undefined && managerRef.statusOf(key) !== \"idle\",\n });\n const recorder = new UsageRecorder(usageRepo, overrides.now ?? (() => new Date()));\n const errors = new ErrorRecorder(errorsRepo, overrides.now ?? (() => new Date()));\n const titles =\n overrides.titles ??\n new TitleGenerator({ sessions: sessionsRepo, channels, recorder, errors, log });\n // Session-origin registry: session_meta is the single source of truth (no DB column);\n // shared by the manager (subagent registration), the loader (self-heal rebuild) and\n // SessionService (creation / adoption / lazy list resolution).\n const sessionSources = new SessionSources();\n const manager = new SessionManager({\n sessions: sessionsRepo,\n channels,\n loader: overrides.loader ?? createCoreSessionLoader(config.root, sessionSources),\n sources: sessionSources,\n recorder,\n errors,\n titles,\n log,\n goals: goalsRepo,\n });\n managerRef = manager;\n\n const projectService = new ProjectService({\n root: config.root,\n users: usersRepo,\n projects: projectsRepo,\n members: membersRepo,\n agents: agentsRepo,\n sessions: sessionsRepo,\n usage: usageRepo,\n errors: errorsRepo,\n schedules: schedulesRepo,\n goals: goalsRepo,\n projectConfig: projectConfigService,\n manager,\n });\n const authService = new AuthService({\n users: usersRepo,\n authSessions: authSessionsRepo,\n provisionInitialProject: (user, isAdmin) =>\n projectService.provisionInitialProject(user, isAdmin),\n sessionTtlMs: config.authSessionTtlMs,\n sessionRenewMs: config.authSessionRenewMs,\n ...(overrides.now ? { now: overrides.now } : {}),\n });\n const adminService = new AdminService({\n users: usersRepo,\n authSessions: authSessionsRepo,\n projects: projectsRepo,\n projectService,\n ...(overrides.now ? { now: overrides.now } : {}),\n });\n const sessionService = new SessionService({\n root: config.root,\n sessions: sessionsRepo,\n manager,\n projectConfig: projectConfigService,\n sources: sessionSources,\n });\n // Schedule scheduler: active only while the server is running. Only\n // assembled here; start() is called in index.ts (tests drive it via tickOnce, no real timer).\n const scheduler = new Scheduler({\n root: config.root,\n repo: schedulesRepo,\n projects: projectsRepo,\n sessions: sessionsRepo,\n runner: manager,\n sessionCreator: sessionService,\n errors,\n notify: (userId, event) => {\n channels.get(userChannelKey(userId)).publish(event, \"server_event\");\n },\n ...(overrides.now ? { now: () => overrides.now!().getTime() } : {}),\n });\n\n return {\n config,\n db,\n sessionsRepo,\n prefsRepo,\n authService,\n adminService,\n projectService,\n projectConfigService,\n agentService,\n agentConfigService,\n sessionService,\n traceService,\n usageService,\n updateCheck,\n workspaceFiles,\n previewTokens,\n benchmarks,\n snapshots,\n schedulesRepo,\n goalsRepo,\n errorsRepo,\n scheduler,\n channels,\n manager,\n sessionSources,\n errors,\n log,\n };\n}\n\n/** Assembles the Hono app (does not listen on a port). */\nexport function createApp(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n // Error recording is layered in a lambda wrapping onError: handleError stays a\n // pure function with unchanged behavior (HttpError is mapped as-is, unknown\n // exceptions are logged with a stack trace and collapsed to 500), and recording\n // to the DB is just a side-effect layered on top.\n app.onError((err, c) => {\n const projectId = attributedProjectId(c, deps);\n deps.errors.record({\n source: \"http\",\n err,\n ...(projectId !== undefined ? { ctx: { projectId } } : {}),\n });\n return handleError(err, c);\n });\n app.notFound((c) => c.json(errorBody(\"not_found\", \"Endpoint does not exist.\"), 404));\n\n // Request logging: a minimal one-liner (method path status ms).\n app.use(\"*\", async (c, next) => {\n const start = performance.now();\n await next();\n const ms = Math.round(performance.now() - start);\n deps.log(`${c.req.method} ${c.req.path} ${c.res.status} ${ms}ms`);\n });\n\n // Canonical-host guard (loopback binds only): the App is served on one loopback name and\n // previews on its counterpart, but the SAME process answers on both. Without this, Agent-\n // written preview HTML on the preview host could call /api same-origin and — if a session\n // cookie had ever been set on that host — act as the user. So the preview host serves ONLY\n // /preview/*: /api answers 401 (it never sets or honors a cookie there, closing both the\n // login and the stale-cookie paths), and everything else 302s to the canonical App host.\n // See design § \"Workspace 文件预览\". Off when PENGUIN_PREVIEW_ORIGIN is set: previews then\n // use that origin rather than the loopback counterpart, so 127.0.0.1 is an ordinary App\n // access point and must not be locked down — deployments enforce the equivalent at the\n // reverse proxy (route only /preview/* to the App on the preview origin).\n const previewRoles = deps.config.previewOrigin ? null : loopbackHostRoles(deps.config.host);\n if (previewRoles) {\n app.use(\"*\", async (c, next) => {\n const host = hostOnly(requestAuthority(c.req.url, c.req.header(\"host\"))).toLowerCase();\n if (host === previewRoles.preview && !c.req.path.startsWith(\"/preview/\")) {\n if (c.req.path.startsWith(\"/api/\")) {\n throw new HttpError(401, \"unauthorized\", \"The API is not served on the preview host.\");\n }\n const url = new URL(c.req.url);\n url.hostname = previewRoles.app;\n return c.redirect(url.toString(), 302);\n }\n await next();\n });\n }\n\n // API common defenses: request body size cap (20MB) and write-request Content-Type (one of the CSRF MVP defenses).\n app.use(\"/api/*\", async (c, next) => {\n const contentLength = Number(c.req.header(\"content-length\") ?? 0);\n if (contentLength > MAX_BODY_BYTES) {\n throw new HttpError(413, \"payload_too_large\", \"Request body exceeds the 20MB limit.\");\n }\n await next();\n });\n app.use(\"/api/*\", jsonOnlyWrites);\n\n // Public routes (no login required).\n app.route(\"/api/auth\", authRoutes(deps));\n\n // Protected routes: cookie -> auth_session -> user.\n const auth = authMiddleware(deps.authService);\n app.use(\"/api/*\", auth);\n app.route(\"/api/me\", meRoutes(deps));\n app.route(\"/api/version\", versionRoutes(deps));\n app.route(\"/api/admin/users\", adminUsersRoutes(deps));\n app.route(\"/api/events\", eventsRoutes(deps));\n // Skill library listing: readable once logged in, not nested under a Project prefix.\n app.route(\"/api/skills\", skillLibraryRoutes());\n app.route(\"/api/projects\", projectsRoutes(deps));\n app.route(\"/api/projects/:projectId/members\", membersRoutes(deps));\n app.route(\"/api/projects/:projectId/models\", modelsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents\", agentsRoutes(deps));\n app.route(\"/api/projects/:projectId/dirs\", dirsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/config\", agentConfigRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/vault\", vaultRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/schedules\", scheduleRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/benchmarks\", benchmarksRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/skills\", agentSkillsRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId\", agentTransferRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/traces\", agentTracesRoutes(deps));\n app.route(\"/api/projects/:projectId/agents/:agentId/sessions\", agentSessionsRoutes(deps));\n app.route(\"/api/projects/:projectId/usage\", usageRoutes(deps));\n app.route(\"/api/sessions\", sessionsRoutes(deps));\n\n // Workspace HTML preview on the separate preview origin: deliberately outside /api and\n // outside the auth middleware — that origin never receives the session cookie, so the\n // signed token in the path is the only credential. Mounted before static hosting so the\n // SPA fallback cannot swallow it. See design § \"Workspace 文件预览\".\n app.route(\"/preview\", previewRoutes(deps));\n\n // Static hosting (production): serves the frontend build output when webDist exists, with SPA fallback to index.html.\n if (fs.existsSync(deps.config.webDist)) {\n registerStaticRoutes(app, deps.config.webDist);\n }\n\n return app;\n}\n\n/**\n * The Project an error is attributed to: only\n * attributed when the URL has a `:projectId` **and** the requester genuinely has\n * access to that Project; otherwise recorded as unattributed (`project_id IS\n * NULL`, visible only to admins).\n *\n * onError also has to handle requests that **haven't passed permission checks\n * yet** — a 401 from being logged out, a 404 from not being a member, both get\n * recorded here. Attributing directly from the URL parameter would let anyone\n * (not necessarily a member of that Project, or even logged in) pick a projectId\n * and hammer it repeatedly to pollute another user's Project with error stats.\n * Traces that can't be attributed simply fall into the admin view (unattributed\n * errors are only visible to admins by design anyway), which is exactly where\n * unauthorized probing belongs.\n *\n * Two defenses here, because this code runs on the error-handling path:\n * - `c.var.user`'s static type is non-null, but authMiddleware never sets it\n * **before** throwing the 401 when logged out, so at runtime it may actually be\n * undefined — it can only be read safely, never destructured directly.\n * - Exceptions are swallowed entirely: throwing here would break onError itself\n * (possibly recursively); any judgment failure falls back to unattributed.\n */\nfunction attributedProjectId(c: Context<AppEnv>, deps: AppDeps): string | undefined {\n try {\n const projectId = c.req.param(\"projectId\");\n if (projectId === undefined) return undefined;\n const user = c.get(\"user\") as UserRow | undefined;\n if (user === undefined) return undefined;\n return deps.projectService.canAccess(user.userId, projectId) ? projectId : undefined;\n } catch {\n return undefined;\n }\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".json\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".ico\": \"image/x-icon\",\n \".map\": \"application/json\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".woff2\": \"font/woff2\",\n};\n\n/** Minimal static file server (avoiding an extra dependency): path traversal protection + SPA fallback. */\nfunction registerStaticRoutes(app: Hono<AppEnv>, webDist: string): void {\n app.get(\"*\", async (c) => {\n const reqPath = decodeURIComponent(c.req.path);\n if (reqPath.startsWith(\"/api/\")) {\n return c.json(errorBody(\"not_found\", \"Endpoint does not exist.\"), 404);\n }\n const rel = reqPath.replace(/^\\/+/, \"\");\n const resolved = path.resolve(webDist, rel === \"\" ? \"index.html\" : rel);\n // Guard against path traversal: once resolved, it must still be inside webDist.\n const base = path.resolve(webDist);\n const target =\n resolved === base || resolved.startsWith(base + path.sep)\n ? resolved\n : path.join(base, \"index.html\");\n let file = target;\n try {\n const stat = await fsp.stat(file);\n if (stat.isDirectory()) file = path.join(file, \"index.html\");\n await fsp.access(file);\n } catch {\n file = path.join(base, \"index.html\"); // SPA fallback\n }\n let content: Buffer;\n try {\n content = await fsp.readFile(file);\n } catch {\n return c.json(errorBody(\"not_found\", \"Resource does not exist.\"), 404);\n }\n const type = CONTENT_TYPES[path.extname(file).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(new Uint8Array(content), {\n status: 200,\n headers: { \"Content-Type\": type },\n });\n });\n}\n","/**\n * SQLite connection & initialization (node:sqlite DatabaseSync).\n *\n * Single process, single writer: a synchronous API is sufficient and avoids a connection\n * pool; WAL mode and foreign key constraints are enabled. Table-creation SQL runs on open\n * (idempotent), with no migration branches (product not yet released).\n */\nimport { mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { SCHEMA_SQL } from \"./schema.js\";\n\n// Fetch the runtime module via process.getBuiltinModule (node >=22.3): avoids static\n// resolution of `node:sqlite` by bundlers/vite (some tools' builtin lists don't yet\n// recognize this experimental module).\nconst sqlite = process.getBuiltinModule(\"node:sqlite\");\n\n/** Open (creating if necessary) the database: ensure the parent directory exists, set PRAGMAs, run table creation. */\nexport function openDatabase(dbPath: string): DatabaseSync {\n if (dbPath !== \":memory:\") {\n mkdirSync(path.dirname(dbPath), { recursive: true });\n }\n const db = new sqlite.DatabaseSync(dbPath);\n db.exec(\"PRAGMA journal_mode = WAL;\");\n db.exec(\"PRAGMA foreign_keys = ON;\");\n db.exec(SCHEMA_SQL);\n return db;\n}\n","/**\n * SQLite table-creation SQL.\n *\n * SQLite stores only indexes and aggregates: users / login sessions / Project authorization /\n * Agent & Session indexes / usage summaries / error records / UI preferences. Agent State,\n * Trace, and Workspace still follow the local directory-based storage rules.\n * Product not yet released: no migration branches — everything is CREATE IF NOT EXISTS, formed once.\n */\n\nexport const SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS users (\n user_id TEXT PRIMARY KEY, -- semantic id doubles as login name: ^[a-z][a-z0-9_-]{1,31}$\n password_hash TEXT NOT NULL, -- scrypt$N$r$p$salt$hash(base64)\n is_admin INTEGER NOT NULL DEFAULT 0, -- 1 for the built-in admin (seeded at startup)\n password_is_initial INTEGER NOT NULL DEFAULT 0, -- 1=initial password (seeded/admin-set); cleared once the user changes it\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS auth_sessions (\n token_hash TEXT PRIMARY KEY, -- sha256(token) hex; the cookie stores only the raw token\n user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL -- 7-day sliding renewal (topped up when <6 days remain)\n);\nCREATE TABLE IF NOT EXISTS projects (\n project_id TEXT PRIMARY KEY, -- directory name doubles as id; display name lives in project_config.toml\n owner_user_id TEXT NOT NULL REFERENCES users(user_id),\n created_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS project_members ( -- member grants only; owners are not stored here\n project_id TEXT NOT NULL REFERENCES projects(project_id) ON DELETE CASCADE,\n user_id TEXT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,\n created_at TEXT NOT NULL,\n PRIMARY KEY (project_id, user_id)\n);\nCREATE TABLE IF NOT EXISTS agents ( -- index only; name/description live in system_config.yaml\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (project_id, agent_id)\n);\nCREATE TABLE IF NOT EXISTS sessions (\n session_id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n provider TEXT NOT NULL, -- provider group of the session model (paired with model_id as a model reference)\n model_id TEXT NOT NULL, -- upstream model id (sent to AgentHub as-is; never concatenate <provider>/<id>)\n workspace TEXT NOT NULL,\n approval_mode TEXT NOT NULL DEFAULT 'allow-all', -- allow-all|deny-all|read-only|always-ask\n title TEXT, -- auto-generated by the model after the first exchange; NULL=not yet (frontend shows \"New chat\")\n archived_at TEXT, -- archive time; NULL=not archived (shown by default; archived ones move under \"Archived\")\n created_at TEXT NOT NULL\n); -- session origin is NOT stored: session_meta in the Trace is the single source of truth (see runtime/session-sources.ts)\nCREATE TABLE IF NOT EXISTS usage_records (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts TEXT NOT NULL,\n date TEXT NOT NULL, -- local date yyyy-mm-dd (aggregation key; same convention as Trace date directories)\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n session_id TEXT NOT NULL, -- top-level Session (child-session usage counts toward its main Session)\n origin_session_id TEXT, -- immediate source child Session (last of the origin chain); NULL=main session\n provider TEXT NOT NULL, -- provider group: paired with model_id as the attribution key; always GROUP BY provider, model_id\n model_id TEXT NOT NULL, -- upstream model id (paired with provider; same model_id across providers aggregates separately)\n cache_read INTEGER NOT NULL,\n cache_write INTEGER NOT NULL,\n output INTEGER NOT NULL,\n total INTEGER NOT NULL, -- from token_usage.request (one row per Request)\n status TEXT NOT NULL DEFAULT 'completed' -- request outcome: completed=success (with tokens); others=failure (0 tokens, for success rate)\n); -- cost is not stored: computed at query time from current pricing\nCREATE INDEX IF NOT EXISTS idx_usage_project_date ON usage_records(project_id, date);\nCREATE INDEX IF NOT EXISTS idx_usage_session ON usage_records(session_id);\nCREATE TABLE IF NOT EXISTS error_records ( -- server-side error capture (the Costs page's \"Errors\" tab)\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts TEXT NOT NULL,\n date TEXT NOT NULL, -- local date yyyy-mm-dd (same convention as usage_records)\n project_id TEXT, -- nullable: sign-in/registration and process-level errors have no Project context\n agent_id TEXT,\n session_id TEXT,\n source TEXT NOT NULL, -- http | session | usage | title | subagent | process | llm | environment | schedule\n kind TEXT NOT NULL, -- expected (HttpError, business 4xx) | unexpected (500/runtime)\n code TEXT NOT NULL, -- HttpError.code / internal / session_run_failed / ...\n status INTEGER, -- HTTP status code; NULL for non-HTTP sources\n message TEXT NOT NULL -- truncated to 500 chars (no stack stored: stacks go to logs only)\n);\nCREATE INDEX IF NOT EXISTS idx_error_project_date ON error_records(project_id, date);\nCREATE TABLE IF NOT EXISTS schedule_state ( -- schedule runtime state (files are declarative intent; the system never writes back)\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n name TEXT NOT NULL, -- file name (without .toml) doubles as the identifier\n creator_user_id TEXT, -- creator (recorded on API create; hand-edited file reconciliation falls back to the Project owner)\n start_at_ms INTEGER NOT NULL, -- defines identity: a start_at change counts as a new task instance, resetting fire state\n def_hash TEXT NOT NULL, -- file content fingerprint: any change clears the invalid flag (the file takes effect again after edits)\n last_slot_ms INTEGER, -- latest due slot already consumed (advances on fire or skip; no re-fire across restarts)\n last_fired_at TEXT, -- last actual fire time (for display)\n fired_once INTEGER NOT NULL DEFAULT 0, -- one-shot task has fired\n missed INTEGER NOT NULL DEFAULT 0, -- one-shot task was missed (flagged by startup/registration reconciliation; not backfilled)\n invalid_reason TEXT, -- invalidation reason (e.g. the bound Session was deleted); NULL=healthy\n PRIMARY KEY (project_id, agent_id, name)\n);\nCREATE TABLE IF NOT EXISTS goal_state ( -- goal-mode runtime state (GOAL.yaml on disk is the model-facing protocol; this row is what the UI reads)\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- one row per goal run; a Session may run goals repeatedly, latest row wins for display\n session_id TEXT NOT NULL,\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n objective TEXT NOT NULL,\n status TEXT NOT NULL, -- active | complete | blocked | budget_limited | aborted (aborted is server-side only; on-disk status stays active)\n budget INTEGER NOT NULL, -- token budget; -1 = unlimited\n used INTEGER NOT NULL DEFAULT 0, -- uncached input + output, refreshed per round (mirrors the runner's accounting)\n rounds INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_goal_session ON goal_state(session_id);\nCREATE TABLE IF NOT EXISTS ui_prefs (\n user_id TEXT PRIMARY KEY REFERENCES users(user_id) ON DELETE CASCADE,\n prefs_json TEXT NOT NULL -- {theme?, lastProjectId?, ...} free-form JSON\n);\n`;\n","/**\n * agents table repo: Agent index; name/description live in system_config.yaml.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface AgentRow {\n projectId: string;\n agentId: string;\n createdAt: string;\n}\n\nexport class AgentsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n /** Idempotent insert: backfills an untracked Agent discovered via directory scan; shared with explicit creation. */\n insertOrIgnore(row: AgentRow): void {\n this.db\n .prepare(\"INSERT OR IGNORE INTO agents (project_id, agent_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.agentId, row.createdAt);\n }\n\n exists(projectId: string, agentId: string): boolean {\n const r = this.db\n .prepare(\"SELECT 1 AS x FROM agents WHERE project_id = ? AND agent_id = ?\")\n .get(projectId, agentId);\n return r !== undefined;\n }\n\n list(projectId: string): AgentRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM agents WHERE project_id = ? ORDER BY created_at ASC, agent_id ASC\")\n .all(projectId);\n return rows.map((r) => ({\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n createdAt: r.created_at as string,\n }));\n }\n\n delete(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM agents WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM agents WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * auth_sessions table repo (server-side sessions backing the HttpOnly cookie).\n *\n * Stores only the sha256(token) hex hash; the raw token appears only in the cookie.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface AuthSessionRow {\n tokenHash: string;\n userId: string;\n createdAt: string;\n expiresAt: string;\n}\n\nexport class AuthSessionsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: AuthSessionRow): void {\n this.db\n .prepare(\n \"INSERT INTO auth_sessions (token_hash, user_id, created_at, expires_at) VALUES (?, ?, ?, ?)\",\n )\n .run(row.tokenHash, row.userId, row.createdAt, row.expiresAt);\n }\n\n findByTokenHash(tokenHash: string): AuthSessionRow | null {\n const r = this.db.prepare(\"SELECT * FROM auth_sessions WHERE token_hash = ?\").get(tokenHash);\n if (!r) return null;\n return {\n tokenHash: r.token_hash as string,\n userId: r.user_id as string,\n createdAt: r.created_at as string,\n expiresAt: r.expires_at as string,\n };\n }\n\n /** Sliding renewal: update the expiration time. */\n touch(tokenHash: string, expiresAt: string): void {\n this.db\n .prepare(\"UPDATE auth_sessions SET expires_at = ? WHERE token_hash = ?\")\n .run(expiresAt, tokenHash);\n }\n\n delete(tokenHash: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE token_hash = ?\").run(tokenHash);\n }\n\n /** Opportunistically clean up expired sessions (called during login/validation). */\n deleteExpired(nowIso: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE expires_at < ?\").run(nowIso);\n }\n\n /** Clear all sessions for a user (forces re-login after an admin resets the password). */\n deleteByUser(userId: string): void {\n this.db.prepare(\"DELETE FROM auth_sessions WHERE user_id = ?\").run(userId);\n }\n}\n","/**\n * error_records table repo: one row per error the server catches.\n *\n * Key difference from usage_records: **all attribution columns are nullable** — errors\n * from the login/register endpoints have no Project, and process-level catch-alls\n * (uncaughtException) don't even have a request. These **unattributed errors are visible\n * only to admins** (`ErrorFilter.includeGlobal`, defaults to false): they represent other\n * users' login failures, misdirected Session access, and process crashes (the message may\n * contain internal paths and variable values), so surfacing them in any regular member's\n * statistics center would be a cross-tenant information leak — regular members see only\n * their own Project's errors. Admins still see the category that most needs visibility.\n *\n * **Row cap** (MAX_ROWS): errors often come in storms (an API scan producing a wall of\n * 404s, a tool failing repeatedly in a loop), and an uncapped table would blow up disk\n * usage. So the insert path enforces the cap: check capacity every PRUNE_EVERY inserts\n * (insertion sits on the error-handling path, so we avoid COUNT on every call), and when\n * over the limit, evict the oldest rows in ascending id order. The excess is computed via\n * an **exact COUNT**, not an approximation like `id <= MAX(id) - :max` — after\n * deleteByProject removes rows, id and row count diverge, and the approximation would\n * wrongly delete still-valid data within the cap. The first line of defense is\n * ErrorRecorder's short-window deduplication.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/** Row cap: once exceeded, oldest rows are evicted in ascending id order (see file header). */\nexport const MAX_ROWS = 20000;\n\n/** Check capacity every N inserts (see file header: insertion sits on the error-handling path, so we avoid COUNT on every call). */\nexport const PRUNE_EVERY = 200;\n\n/** Capacity parameters (default to the two constants above; tests inject small values to exercise the eviction path). */\nexport interface ErrorsRepoLimits {\n maxRows?: number;\n pruneEvery?: number;\n}\n\nexport interface ErrorRecordInsert {\n ts: string;\n date: string;\n projectId: string | null;\n agentId: string | null;\n sessionId: string | null;\n source: string;\n /** expected (HttpError, business 4xx) | unexpected (500 / unforeseen runtime error). */\n kind: string;\n code: string;\n status: number | null;\n message: string;\n}\n\n/** Generic filter: date range + agent (errors have no Model dimension, so no model filter). */\nexport interface ErrorFilter {\n from?: string;\n to?: string;\n agentId?: string;\n /** Whether to include unattributed errors (`project_id IS NULL`): admins only, defaults to false (see file header). */\n includeGlobal?: boolean;\n}\n\n/** Total error count and how many are unexpected (stats for the statistics center's error panel). */\nexport interface ErrorSummary {\n total: number;\n unexpected: number;\n}\n\n/** Occurrence count for one source · code pair (the error panel's \"most common\" metric). */\nexport interface ErrorCodeCount {\n source: string;\n code: string;\n kind: string;\n count: number;\n}\n\n/** One error summary row (a row in the error panel's table). */\nexport interface ErrorItem {\n ts: string;\n source: string;\n code: string;\n kind: string;\n message: string;\n}\n\nexport class ErrorsRepo {\n private readonly maxRows: number;\n private readonly pruneEvery: number;\n /** Insert count since the last capacity check (see file header: avoids COUNT on every call). */\n private sinceCheck = 0;\n\n constructor(\n private readonly db: DatabaseSync,\n limits: ErrorsRepoLimits = {},\n ) {\n this.maxRows = limits.maxRows ?? MAX_ROWS;\n this.pruneEvery = limits.pruneEvery ?? PRUNE_EVERY;\n }\n\n insert(r: ErrorRecordInsert): void {\n this.db\n .prepare(\n `INSERT INTO error_records\n (ts, date, project_id, agent_id, session_id, source, kind, code, status, message)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n r.ts,\n r.date,\n r.projectId,\n r.agentId,\n r.sessionId,\n r.source,\n r.kind,\n r.code,\n r.status,\n r.message,\n );\n if (++this.sinceCheck >= this.pruneEvery) {\n this.sinceCheck = 0;\n this.pruneOverflow();\n }\n }\n\n /** Capacity enforcement (see file header): compute the exact excess via COUNT, then delete the oldest rows in ascending id order. */\n private pruneOverflow(): void {\n const row = this.db.prepare(\"SELECT COUNT(*) AS n FROM error_records\").get()!;\n const excess = (row.n as number) - this.maxRows;\n if (excess <= 0) return;\n this.db\n .prepare(\n `DELETE FROM error_records WHERE id IN (\n SELECT id FROM error_records ORDER BY id ASC LIMIT :excess\n )`,\n )\n .run({ excess });\n }\n\n /** WHERE fragment and named params: this Project (admins additionally get unattributed errors), plus optional date/agent filter. */\n private conds(\n projectId: string,\n f: ErrorFilter,\n ): { where: string; params: Record<string, string> } {\n // Unattributed errors (login failures, process crashes, ...) are visible only to admins; otherwise it's a cross-tenant leak — see file header.\n const conds = [\n f.includeGlobal === true ? \"(project_id = :pid OR project_id IS NULL)\" : \"project_id = :pid\",\n ];\n const params: Record<string, string> = { pid: projectId };\n if (f.from !== undefined) {\n conds.push(\"date >= :from\");\n params.from = f.from;\n }\n if (f.to !== undefined) {\n conds.push(\"date <= :to\");\n params.to = f.to;\n }\n if (f.agentId !== undefined) {\n // Filtering by Agent naturally leaves only that Agent's errors (HTTP / process-level errors have no agent_id).\n conds.push(\"agent_id = :agentId\");\n params.agentId = f.agentId;\n }\n return { where: conds.join(\" AND \"), params };\n }\n\n /** Total count + how many are unexpected. */\n summary(projectId: string, f: ErrorFilter = {}): ErrorSummary {\n const { where, params } = this.conds(projectId, f);\n const row = this.db\n .prepare(\n `SELECT COUNT(*) AS total,\n COALESCE(SUM(CASE WHEN kind = 'unexpected' THEN 1 ELSE 0 END), 0) AS unexpected\n FROM error_records WHERE ${where}`,\n )\n .get(params)!;\n return { total: row.total as number, unexpected: row.unexpected as number };\n }\n\n /** The most frequent source · code (ties broken by code's lexicographic order); null if there are no errors. */\n topCode(projectId: string, f: ErrorFilter = {}): ErrorCodeCount | null {\n const { where, params } = this.conds(projectId, f);\n const row = this.db\n .prepare(\n `SELECT source, code, kind, COUNT(*) AS count\n FROM error_records WHERE ${where}\n GROUP BY source, code, kind\n ORDER BY count DESC, code ASC\n LIMIT 1`,\n )\n .get(params);\n if (!row) return null;\n return {\n source: row.source as string,\n code: row.code as string,\n kind: row.kind as string,\n count: row.count as number,\n };\n }\n\n /** The most recent `limit` entries (reverse chronological order). */\n recent(projectId: string, f: ErrorFilter = {}, limit = 20): ErrorItem[] {\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT ts, source, code, kind, message\n FROM error_records WHERE ${where}\n ORDER BY id DESC LIMIT :limit`,\n )\n .all({ ...params, limit });\n return rows.map((r) => ({\n ts: r.ts as string,\n source: r.source as string,\n code: r.code as string,\n kind: r.kind as string,\n message: r.message as string,\n }));\n }\n\n /** Cascading cleanup on Agent deletion (unattributed errors carry no agent_id and are unaffected). */\n deleteByAgent(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM error_records WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n /** Cascading cleanup on Project deletion (unattributed errors belong to no Project and are unaffected). */\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM error_records WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * Repo for the project_members table: only member\n * authorization relationships — the owner is never in this table.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface MemberRow {\n projectId: string;\n userId: string;\n createdAt: string;\n}\n\nexport class MembersRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: MemberRow): void {\n this.db\n .prepare(\"INSERT INTO project_members (project_id, user_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.userId, row.createdAt);\n }\n\n isMember(projectId: string, userId: string): boolean {\n const r = this.db\n .prepare(\"SELECT 1 AS x FROM project_members WHERE project_id = ? AND user_id = ?\")\n .get(projectId, userId);\n return r !== undefined;\n }\n\n list(projectId: string): MemberRow[] {\n const rows = this.db\n .prepare(\n \"SELECT project_id, user_id, created_at FROM project_members WHERE project_id = ? ORDER BY created_at ASC\",\n )\n .all(projectId);\n return rows.map((r) => ({\n projectId: r.project_id as string,\n userId: r.user_id as string,\n createdAt: r.created_at as string,\n }));\n }\n\n delete(projectId: string, userId: string): void {\n this.db\n .prepare(\"DELETE FROM project_members WHERE project_id = ? AND user_id = ?\")\n .run(projectId, userId);\n }\n}\n","/**\n * Repo for the projects table: an index of ownership\n * relationships; the display name lives in project_config.toml.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ProjectRole } from \"../../api/types.js\";\n\nexport interface ProjectRow {\n projectId: string;\n ownerUserId: string;\n createdAt: string;\n}\n\nexport interface AccessibleProjectRow extends ProjectRow {\n role: ProjectRole;\n}\n\nfunction mapRow(r: Record<string, unknown>): ProjectRow {\n return {\n projectId: r.project_id as string,\n ownerUserId: r.owner_user_id as string,\n createdAt: r.created_at as string,\n };\n}\n\nexport class ProjectsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: ProjectRow): void {\n this.db\n .prepare(\"INSERT INTO projects (project_id, owner_user_id, created_at) VALUES (?, ?, ?)\")\n .run(row.projectId, row.ownerUserId, row.createdAt);\n }\n\n findById(projectId: string): ProjectRow | null {\n const r = this.db.prepare(\"SELECT * FROM projects WHERE project_id = ?\").get(projectId);\n return r ? mapRow(r) : null;\n }\n\n /** All Projects (used by the scheduler's reconciliation scan), ascending by creation time. */\n listAll(): ProjectRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM projects ORDER BY created_at ASC, project_id ASC\")\n .all();\n return rows.map((r) => mapRow(r as Record<string, unknown>));\n }\n\n /** Projects owned by or shared with the current user (including their role), ascending by creation time. */\n listAccessible(userId: string): AccessibleProjectRow[] {\n const rows = this.db\n .prepare(\n `SELECT p.project_id, p.owner_user_id, p.created_at,\n CASE WHEN p.owner_user_id = :uid THEN 'owner' ELSE 'member' END AS role\n FROM projects p\n WHERE p.owner_user_id = :uid\n OR EXISTS (SELECT 1 FROM project_members m\n WHERE m.project_id = p.project_id AND m.user_id = :uid)\n ORDER BY p.created_at ASC, p.project_id ASC`,\n )\n .all({ uid: userId });\n return rows.map((r) => ({\n ...mapRow(r),\n role: r.role as ProjectRole,\n }));\n }\n\n /** All Projects owned by a given user (used for cascading cleanup when an admin deletes a user). */\n listByOwner(userId: string): ProjectRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM projects WHERE owner_user_id = ? ORDER BY created_at ASC\")\n .all(userId);\n return rows.map(mapRow);\n }\n\n delete(projectId: string): void {\n this.db.prepare(\"DELETE FROM projects WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * Repo for goal-mode runtime state: one row per goal run, keyed by autoincrement id (a\n * Session may run goals repeatedly; the latest row is what the UI shows). The on-disk\n * GOAL.yaml is the model-facing protocol; this table is the server-side record the banner\n * restores from and the terminal outcome lands in (including the server-only `aborted`,\n * which never appears in the file — the on-disk status stays `active` for resuming).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport type GoalRowStatus = \"active\" | \"complete\" | \"blocked\" | \"budget_limited\" | \"aborted\";\n\nexport interface GoalStateRow {\n id: number;\n sessionId: string;\n projectId: string;\n agentId: string;\n objective: string;\n status: GoalRowStatus;\n budget: number;\n used: number;\n rounds: number;\n createdAt: string;\n updatedAt: string;\n}\n\nfunction mapRow(r: Record<string, unknown>): GoalStateRow {\n return {\n id: Number(r.id),\n sessionId: r.session_id as string,\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n objective: r.objective as string,\n status: r.status as GoalRowStatus,\n budget: Number(r.budget),\n used: Number(r.used),\n rounds: Number(r.rounds),\n createdAt: r.created_at as string,\n updatedAt: r.updated_at as string,\n };\n}\n\nexport class GoalsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n /** Register a new goal run (status active, counters at zero); returns the new row id. */\n create(args: {\n sessionId: string;\n projectId: string;\n agentId: string;\n objective: string;\n budget: number;\n }): number {\n const now = new Date().toISOString();\n const result = this.db\n .prepare(\n `INSERT INTO goal_state\n (session_id, project_id, agent_id, objective, status, budget, used, rounds, created_at, updated_at)\n VALUES (?, ?, ?, ?, 'active', ?, 0, 0, ?, ?)`,\n )\n .run(args.sessionId, args.projectId, args.agentId, args.objective, args.budget, now, now);\n return Number(result.lastInsertRowid);\n }\n\n /** Per-round progress refresh (round just started; `used` mirrors the runner's accounting so far). */\n progress(id: number, rounds: number, used: number): void {\n this.db\n .prepare(\"UPDATE goal_state SET rounds = ?, used = ?, updated_at = ? WHERE id = ?\")\n .run(rounds, used, new Date().toISOString(), id);\n }\n\n /** Record the terminal outcome. */\n finish(id: number, status: GoalRowStatus, rounds: number, used: number): void {\n this.db\n .prepare(\n \"UPDATE goal_state SET status = ?, rounds = ?, used = ?, updated_at = ? WHERE id = ?\",\n )\n .run(status, rounds, used, new Date().toISOString(), id);\n }\n\n /** The Session's most recent goal run (what the chat page's banner restores from); null if it never ran one. */\n latestForSession(sessionId: string): GoalStateRow | null {\n const r = this.db\n .prepare(\"SELECT * FROM goal_state WHERE session_id = ? ORDER BY id DESC LIMIT 1\")\n .get(sessionId);\n return r ? mapRow(r as Record<string, unknown>) : null;\n }\n\n deleteBySession(sessionId: string): void {\n this.db.prepare(\"DELETE FROM goal_state WHERE session_id = ?\").run(sessionId);\n }\n\n deleteByAgent(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM goal_state WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM goal_state WHERE project_id = ?\").run(projectId);\n }\n\n /**\n * Startup reconciliation: a goal runs only in SessionManager memory, so a hard crash\n * (SIGKILL, power loss) leaves its row stuck `active` with no runner behind it — the banner\n * would then restore a forever-\"running\" goal. Called once at boot, before the server accepts\n * connections (nothing can be running yet), so every remaining `active` row is an orphan:\n * mark them `aborted`. The on-disk GOAL.yaml is left `active` as the documented resume point.\n * Returns the number of rows reconciled.\n */\n abortOrphanedActive(): number {\n const result = this.db\n .prepare(\"UPDATE goal_state SET status = 'aborted', updated_at = ? WHERE status = 'active'\")\n .run(new Date().toISOString());\n return Number(result.changes);\n }\n}\n","/**\n * Repo for schedule runtime state: intent and state are separate — the file is\n * declarative intent, this table only records runtime state such as\n * \"fired before / last fired / missed / disabled\" plus the creator.\n *\n * Identity rule: a change to `start_at` is treated as a new task instance\n * (registerOrSync resets the trigger state); a change to the file content fingerprint\n * only clears the disabled flag (the file becomes effective again after reconciliation).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface ScheduleStateRow {\n projectId: string;\n agentId: string;\n name: string;\n creatorUserId: string | null;\n startAtMs: number;\n defHash: string;\n lastSlotMs: number | null;\n lastFiredAt: string | null;\n firedOnce: boolean;\n missed: boolean;\n invalidReason: string | null;\n}\n\nfunction mapRow(r: Record<string, unknown>): ScheduleStateRow {\n return {\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n name: r.name as string,\n creatorUserId: (r.creator_user_id as string | null) ?? null,\n startAtMs: Number(r.start_at_ms),\n defHash: r.def_hash as string,\n lastSlotMs: r.last_slot_ms === null ? null : Number(r.last_slot_ms),\n lastFiredAt: (r.last_fired_at as string | null) ?? null,\n firedOnce: Number(r.fired_once) === 1,\n missed: Number(r.missed) === 1,\n invalidReason: (r.invalid_reason as string | null) ?? null,\n };\n}\n\nexport class SchedulesRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n find(projectId: string, agentId: string, name: string): ScheduleStateRow | null {\n const r = this.db\n .prepare(\"SELECT * FROM schedule_state WHERE project_id = ? AND agent_id = ? AND name = ?\")\n .get(projectId, agentId, name);\n return r ? mapRow(r as Record<string, unknown>) : null;\n }\n\n listByAgent(projectId: string, agentId: string): ScheduleStateRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM schedule_state WHERE project_id = ? AND agent_id = ? ORDER BY name\")\n .all(projectId, agentId);\n return rows.map((r) => mapRow(r as Record<string, unknown>));\n }\n\n /**\n * Register or sync a task's runtime state, returning the synced row plus a `fresh`\n * flag:\n * - Insert if it doesn't exist (creator is only persisted at this point; a hand-edited\n * file gets registered via reconciliation, with creator falling back to the Project\n * owner);\n * - A change to `start_at` resets the trigger state (a new task instance);\n * - Otherwise, a change to the file fingerprint only clears the disabled flag.\n * `fresh` = this call was an insert or reset — the scheduler only establishes its\n * \"missed, don't backfill\" baseline at this moment; afterward, last_slot being NULL\n * only means \"no scheduled time has been consumed yet\", and it must fire normally once\n * reached.\n */\n registerOrSync(args: {\n projectId: string;\n agentId: string;\n name: string;\n startAtMs: number;\n defHash: string;\n creatorUserId: string | null;\n }): { row: ScheduleStateRow; fresh: boolean } {\n const existing = this.find(args.projectId, args.agentId, args.name);\n let fresh = false;\n if (!existing) {\n this.db\n .prepare(\n `INSERT INTO schedule_state\n (project_id, agent_id, name, creator_user_id, start_at_ms, def_hash)\n VALUES (?, ?, ?, ?, ?, ?)`,\n )\n .run(\n args.projectId,\n args.agentId,\n args.name,\n args.creatorUserId,\n args.startAtMs,\n args.defHash,\n );\n fresh = true;\n } else if (existing.startAtMs !== args.startAtMs) {\n this.db\n .prepare(\n `UPDATE schedule_state\n SET start_at_ms = ?, def_hash = ?, last_slot_ms = NULL, last_fired_at = NULL,\n fired_once = 0, missed = 0, invalid_reason = NULL\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(args.startAtMs, args.defHash, args.projectId, args.agentId, args.name);\n fresh = true;\n } else if (existing.defHash !== args.defHash) {\n this.db\n .prepare(\n `UPDATE schedule_state SET def_hash = ?, invalid_reason = NULL\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(args.defHash, args.projectId, args.agentId, args.name);\n }\n const row = this.find(args.projectId, args.agentId, args.name);\n if (!row) throw new Error(\"Failed to read back schedule_state after registration\");\n return { row, fresh };\n }\n\n /** Advance the consumed scheduled time (advances whether triggered or skipped; restarts don't re-trigger). */\n markSlot(projectId: string, agentId: string, name: string, slotMs: number): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET last_slot_ms = ?\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(slotMs, projectId, agentId, name);\n }\n\n /** Record an actual send (also sets fired_once for a one-shot task). */\n markFired(\n projectId: string,\n agentId: string,\n name: string,\n firedAt: string,\n oneShot: boolean,\n ): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET last_fired_at = ?, fired_once = CASE WHEN ? THEN 1 ELSE fired_once END\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(firedAt, oneShot ? 1 : 0, projectId, agentId, name);\n }\n\n /** Missed marker for a one-shot task (the scheduled time had already passed at startup/registration reconciliation; missed means not backfilled). */\n markMissed(projectId: string, agentId: string, name: string): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET missed = 1\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(projectId, agentId, name);\n }\n\n /** Mark as disabled (e.g. the bound Session was deleted); cleared via registerOrSync after the file is modified. */\n markInvalid(projectId: string, agentId: string, name: string, reason: string): void {\n this.db\n .prepare(\n `UPDATE schedule_state SET invalid_reason = ?\n WHERE project_id = ? AND agent_id = ? AND name = ?`,\n )\n .run(reason, projectId, agentId, name);\n }\n\n /** Deleting the file removes the task: clears its runtime state. */\n delete(projectId: string, agentId: string, name: string): void {\n this.db\n .prepare(\"DELETE FROM schedule_state WHERE project_id = ? AND agent_id = ? AND name = ?\")\n .run(projectId, agentId, name);\n }\n\n /** Reconciliation cleanup: deletes state rows under this Agent that aren't in the current file list, returning the removed names. */\n deleteMissing(projectId: string, agentId: string, presentNames: string[]): string[] {\n const rows = this.listByAgent(projectId, agentId);\n const present = new Set(presentNames);\n const removed: string[] = [];\n for (const row of rows) {\n if (!present.has(row.name)) {\n this.delete(projectId, agentId, row.name);\n removed.push(row.name);\n }\n }\n return removed;\n }\n\n deleteByAgent(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM schedule_state WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM schedule_state WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * sessions table repo:\n * Session index, approval mode, and auto-generated title; Session-level routes use this to look up project ownership.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ApprovalMode } from \"../../api/types.js\";\n\nexport interface SessionRow {\n sessionId: string;\n projectId: string;\n agentId: string;\n /** Provider group of the session's model (pairs with `modelId` to form the model reference). */\n provider: string;\n /** Upstream model_id of the session's model (sent as-is to AgentHub; never concatenated). */\n modelId: string;\n workspace: string;\n approvalMode: ApprovalMode;\n /** Auto-generated session title; NULL = not yet generated (frontend shows \"New Conversation\"). */\n title: string | null;\n /** Archive timestamp, ISO; NULL = not archived (omitting on insert defaults to NULL). */\n archivedAt?: string | null;\n // The Session origin (schedule / subagent) is deliberately NOT a row field: core\n // session_meta in the Trace is the single source of truth (runtime/session-sources.ts).\n createdAt: string;\n}\n\nfunction mapRow(r: Record<string, unknown>): SessionRow {\n return {\n sessionId: r.session_id as string,\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n provider: r.provider as string,\n modelId: r.model_id as string,\n workspace: r.workspace as string,\n approvalMode: r.approval_mode as ApprovalMode,\n title: (r.title as string | null) ?? null,\n archivedAt: (r.archived_at as string | null) ?? null,\n createdAt: r.created_at as string,\n };\n}\n\nexport class SessionsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: SessionRow): void {\n this.db\n .prepare(\n `INSERT INTO sessions (session_id, project_id, agent_id, provider, model_id, workspace, approval_mode, title, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n row.sessionId,\n row.projectId,\n row.agentId,\n row.provider,\n row.modelId,\n row.workspace,\n row.approvalMode,\n row.title,\n row.createdAt,\n );\n }\n\n /** Idempotent insert: used when Trace directory discovery backfills a row (concurrent listing discovering the same Session no longer triggers a UNIQUE violation). */\n insertOrIgnore(row: SessionRow): void {\n this.db\n .prepare(\n `INSERT OR IGNORE INTO sessions (session_id, project_id, agent_id, provider, model_id, workspace, approval_mode, title, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n row.sessionId,\n row.projectId,\n row.agentId,\n row.provider,\n row.modelId,\n row.workspace,\n row.approvalMode,\n row.title,\n row.createdAt,\n );\n }\n\n findById(sessionId: string): SessionRow | null {\n const r = this.db.prepare(\"SELECT * FROM sessions WHERE session_id = ?\").get(sessionId);\n return r ? mapRow(r) : null;\n }\n\n listByAgent(projectId: string, agentId: string): SessionRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM sessions WHERE project_id = ? AND agent_id = ?\")\n .all(projectId, agentId);\n return rows.map(mapRow);\n }\n\n listByProject(projectId: string): SessionRow[] {\n const rows = this.db.prepare(\"SELECT * FROM sessions WHERE project_id = ?\").all(projectId);\n return rows.map(mapRow);\n }\n\n updateApprovalMode(sessionId: string, mode: ApprovalMode): void {\n this.db\n .prepare(\"UPDATE sessions SET approval_mode = ? WHERE session_id = ?\")\n .run(mode, sessionId);\n }\n\n updateTitle(sessionId: string, title: string): void {\n this.db.prepare(\"UPDATE sessions SET title = ? WHERE session_id = ?\").run(title, sessionId);\n }\n\n /**\n * Writes only if the title is still NULL. Subagent-session registration and Trace\n * directory discovery backfill can race on the same row, and both are insert-only:\n * whichever inserts first determines the title. Discovery backfill can only supply NULL,\n * so this method fills the title back in without overwriting an existing one (including\n * a user rename or an already-generated title).\n */\n updateTitleIfNull(sessionId: string, title: string): void {\n this.db\n .prepare(\"UPDATE sessions SET title = ? WHERE session_id = ? AND title IS NULL\")\n .run(title, sessionId);\n }\n\n /** Archive / unarchive (archivedAt = ISO or NULL). */\n setArchived(sessionId: string, archivedAt: string | null): void {\n this.db\n .prepare(\"UPDATE sessions SET archived_at = ? WHERE session_id = ?\")\n .run(archivedAt, sessionId);\n }\n\n /** Self-healing: after rebuilding a broken Session with no Trace, update the primary key to the new id. */\n replaceId(oldSessionId: string, newSessionId: string): void {\n this.db\n .prepare(\"UPDATE sessions SET session_id = ? WHERE session_id = ?\")\n .run(newSessionId, oldSessionId);\n }\n\n deleteByAgent(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM sessions WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM sessions WHERE project_id = ?\").run(projectId);\n }\n\n deleteById(sessionId: string): void {\n this.db.prepare(\"DELETE FROM sessions WHERE session_id = ?\").run(sessionId);\n }\n}\n","/**\n * ui_prefs table repo (UI preferences): free-form JSON storage.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport class UiPrefsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n /** Returns the raw JSON string; null if never set. */\n get(userId: string): string | null {\n const r = this.db.prepare(\"SELECT prefs_json FROM ui_prefs WHERE user_id = ?\").get(userId);\n return r ? (r.prefs_json as string) : null;\n }\n\n set(userId: string, prefsJson: string): void {\n this.db\n .prepare(\n `INSERT INTO ui_prefs (user_id, prefs_json) VALUES (?, ?)\n ON CONFLICT(user_id) DO UPDATE SET prefs_json = excluded.prefs_json`,\n )\n .run(userId, prefsJson);\n }\n}\n","/**\n * usage_records table repo:\n * one row per token_usage (per-request bucket). Stores Token counts only, not cost —\n * cost is computed on the fly by usage-service against current pricing at query time,\n * so every aggregation is broken down by the `(provider, model_id)` pair and returns\n * raw Token sums (a model_id shared across providers is aggregated separately; never concatenated).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { UsageGroupBy } from \"../../api/types.js\";\n\nexport interface UsageRecordInsert {\n ts: string;\n date: string;\n projectId: string;\n agentId: string;\n sessionId: string;\n originSessionId: string | null;\n /** Provider group (pairs with modelId to form the attribution key). */\n provider: string;\n /** Upstream model id (pairs with provider). */\n modelId: string;\n cacheRead: number;\n cacheWrite: number;\n output: number;\n total: number;\n /** Request outcome; defaults to completed (success, carries tokens). Failed requests are stored with 0 tokens + status, for success-rate calculations. */\n status?: string;\n}\n\n/** Generic filter: date range + agent / model dimensions (cost center top bar switches by agent/model). */\nexport interface UsageFilter {\n from?: string;\n to?: string;\n agentId?: string;\n /** Provider filter paired with modelId (the frontend dropdown always sends them together). */\n provider?: string;\n modelId?: string;\n}\n\n/**\n * Raw request success-rate counts for a single Model (paired reference).\n * `total` is **the success-rate denominator**: all requests minus aborted — the user\n * clicking \"stop\" is not a model failure, and counting it would make the success rate\n * drop every time stop is pressed. `aborted` is counted separately for display.\n */\nexport interface UsageStatusCount {\n provider: string;\n modelId: string;\n completed: number;\n total: number;\n aborted: number;\n failed: number;\n timeout: number;\n malformed: number;\n}\n\n/** Raw Token sums for a single Model (paired reference) — the smallest unit for cost conversion. */\nexport interface UsageModelSums {\n provider: string;\n modelId: string;\n cacheRead: number;\n cacheWrite: number;\n output: number;\n total: number;\n requests: number;\n}\n\n/** Raw Token sums by group key x Model. */\nexport interface UsageGroupModelSums extends UsageModelSums {\n key: string;\n}\n\n/** groupBy dimension -> column name allowlist (prevents injection; only these four columns can be group keys). */\nconst GROUP_COLUMNS: Record<UsageGroupBy, string> = {\n date: \"date\",\n agent: \"agent_id\",\n model: \"model_id\",\n session: \"session_id\",\n};\n\nconst SUM_COLUMNS = `COALESCE(SUM(cache_read), 0) AS cache_read,\n COALESCE(SUM(cache_write), 0) AS cache_write,\n COALESCE(SUM(output), 0) AS output,\n COALESCE(SUM(total), 0) AS total,\n COUNT(*) AS requests`;\n\nfunction toSums(r: Record<string, unknown>): UsageModelSums {\n return {\n provider: r.provider as string,\n modelId: r.model_id as string,\n cacheRead: r.cache_read as number,\n cacheWrite: r.cache_write as number,\n output: r.output as number,\n total: r.total as number,\n requests: r.requests as number,\n };\n}\n\nexport class UsageRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(r: UsageRecordInsert): void {\n this.db\n .prepare(\n `INSERT INTO usage_records\n (ts, date, project_id, agent_id, session_id, origin_session_id, provider, model_id,\n cache_read, cache_write, output, total, status)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n r.ts,\n r.date,\n r.projectId,\n r.agentId,\n r.sessionId,\n r.originSessionId,\n r.provider,\n r.modelId,\n r.cacheRead,\n r.cacheWrite,\n r.output,\n r.total,\n r.status ?? \"completed\",\n );\n }\n\n /** WHERE fragment (project + optional date/agent/model) plus named params. */\n private conds(\n projectId: string,\n f: UsageFilter,\n ): { where: string; params: Record<string, string> } {\n const conds = [\"project_id = :pid\"];\n const params: Record<string, string> = { pid: projectId };\n if (f.from !== undefined) {\n conds.push(\"date >= :from\");\n params.from = f.from;\n }\n if (f.to !== undefined) {\n conds.push(\"date <= :to\");\n params.to = f.to;\n }\n if (f.agentId !== undefined) {\n conds.push(\"agent_id = :agentId\");\n params.agentId = f.agentId;\n }\n if (f.provider !== undefined) {\n conds.push(\"provider = :provider\");\n params.provider = f.provider;\n }\n if (f.modelId !== undefined) {\n conds.push(\"model_id = :modelId\");\n params.modelId = f.modelId;\n }\n return { where: conds.join(\" AND \"), params };\n }\n\n /** Sums (broken down by paired reference): date range + optional agent/model filter. */\n bucketByModel(projectId: string, f: UsageFilter = {}): UsageModelSums[] {\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT provider, model_id, ${SUM_COLUMNS}\n FROM usage_records WHERE ${where}\n GROUP BY provider, model_id`,\n )\n .all(params);\n return rows.map(toSums);\n }\n\n /** Grouped aggregation (group key x paired reference breakdown): date range + optional agent/model filter. */\n groupsByModel(\n projectId: string,\n groupBy: UsageGroupBy,\n f: UsageFilter = {},\n ): UsageGroupModelSums[] {\n const col = GROUP_COLUMNS[groupBy];\n const { where, params } = this.conds(projectId, f);\n const rows = this.db\n .prepare(\n `SELECT ${col} AS key, provider, model_id, ${SUM_COLUMNS}\n FROM usage_records WHERE ${where}\n GROUP BY ${col}, provider, model_id`,\n )\n .all(params);\n return rows.map((r) => ({ key: r.key as string, ...toSums(r) }));\n }\n\n /**\n * Raw success-rate counts per Model (paired reference) (completed / non-aborted requests):\n * powers the cost center's \"Model Success Rate\" chart. The denominator excludes aborted\n * (user-initiated interruption); failure breakdowns (failed / timeout / malformed) are\n * also returned for hover display. Unknown statuses aren't broken out but still count\n * toward the denominator (conservative: anything non-completed counts as a failure).\n */\n statusByModel(projectId: string, f: UsageFilter = {}): UsageStatusCount[] {\n const { where, params } = this.conds(projectId, f);\n const count = (status: string) =>\n `COALESCE(SUM(CASE WHEN status = '${status}' THEN 1 ELSE 0 END), 0) AS ${status}`;\n const rows = this.db\n .prepare(\n `SELECT provider, model_id,\n ${count(\"completed\")},\n ${count(\"aborted\")},\n ${count(\"failed\")},\n ${count(\"timeout\")},\n ${count(\"malformed\")},\n COALESCE(SUM(CASE WHEN status <> 'aborted' THEN 1 ELSE 0 END), 0) AS total\n FROM usage_records WHERE ${where} GROUP BY provider, model_id`,\n )\n .all(params);\n return rows.map((r) => ({\n provider: r.provider as string,\n modelId: r.model_id as string,\n completed: r.completed as number,\n total: r.total as number,\n aborted: r.aborted as number,\n failed: r.failed as number,\n timeout: r.timeout as number,\n malformed: r.malformed as number,\n }));\n }\n\n /** Distinct agent_id values seen for this Project (for filter dropdowns). */\n distinctAgentIds(projectId: string): string[] {\n const rows = this.db\n .prepare(\n \"SELECT DISTINCT agent_id AS v FROM usage_records WHERE project_id = ? ORDER BY agent_id\",\n )\n .all(projectId);\n return rows.map((r) => r.v as string);\n }\n\n /** Distinct Model paired references seen for this Project (for filter dropdowns). */\n distinctModels(projectId: string): Array<{ provider: string; modelId: string }> {\n const rows = this.db\n .prepare(\n `SELECT DISTINCT provider, model_id FROM usage_records\n WHERE project_id = ? ORDER BY provider, model_id`,\n )\n .all(projectId);\n return rows.map((r) => ({ provider: r.provider as string, modelId: r.model_id as string }));\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM usage_records WHERE project_id = ?\").run(projectId);\n }\n}\n","/**\n * users table repo: pure SQL wrapper, no business rules.\n * user_id is the login name (a semantic id, specified at creation, immutable).\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\nexport interface UserRow {\n userId: string;\n passwordHash: string;\n isAdmin: boolean;\n /** Still using the initial password (seeded / set by an admin); cleared to 0 once the user changes it. */\n passwordIsInitial: boolean;\n createdAt: string;\n}\n\nfunction mapRow(r: Record<string, unknown>): UserRow {\n return {\n userId: r.user_id as string,\n passwordHash: r.password_hash as string,\n isAdmin: (r.is_admin as number) === 1,\n passwordIsInitial: (r.password_is_initial as number) === 1,\n createdAt: r.created_at as string,\n };\n}\n\nexport class UsersRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n insert(row: UserRow): void {\n this.db\n .prepare(\n \"INSERT INTO users (user_id, password_hash, is_admin, password_is_initial, created_at) VALUES (?, ?, ?, ?, ?)\",\n )\n .run(\n row.userId,\n row.passwordHash,\n row.isAdmin ? 1 : 0,\n row.passwordIsInitial ? 1 : 0,\n row.createdAt,\n );\n }\n\n findById(userId: string): UserRow | null {\n const r = this.db.prepare(\"SELECT * FROM users WHERE user_id = ?\").get(userId);\n return r ? mapRow(r) : null;\n }\n\n /** All users (for the admin user backend), ordered by creation time ascending. */\n list(): UserRow[] {\n const rows = this.db.prepare(\"SELECT * FROM users ORDER BY created_at ASC, user_id ASC\").all();\n return rows.map(mapRow);\n }\n\n count(): number {\n const r = this.db.prepare(\"SELECT COUNT(*) AS n FROM users\").get();\n return (r?.n as number) ?? 0;\n }\n\n /** Update the password hash; isInitial marks whether the password was set by someone else (seed / admin). */\n updatePassword(userId: string, passwordHash: string, isInitial: boolean): void {\n this.db\n .prepare(\"UPDATE users SET password_hash = ?, password_is_initial = ? WHERE user_id = ?\")\n .run(passwordHash, isInitial ? 1 : 0, userId);\n }\n\n /** Used by admin user deletion and account-creation compensation paths (owned Projects must be cleaned up first). */\n delete(userId: string): void {\n this.db.prepare(\"DELETE FROM users WHERE user_id = ?\").run(userId);\n }\n}\n","/**\n * Auth middleware: cookie -> auth_session ->\n * user injected into c.var.\n *\n * Accessing a protected API while logged out -> 401 `{error:{code:\"unauthorized\"}}`.\n * CSRF (MVP): SameSite=Lax cookie + write requests only accept\n * `Content-Type: application/json` (an HTML form can't forge that Content-Type),\n * see the README security notes.\n */\nimport type { MiddlewareHandler } from \"hono\";\nimport { getCookie } from \"hono/cookie\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { UserRow } from \"../db/repos/users.js\";\nimport type { AuthService } from \"./service.js\";\n\n/** Session cookie name. */\nexport const SESSION_COOKIE = \"penguin_session\";\n\n/** Hono env: variables injected by the auth middleware. */\nexport type AppEnv = {\n Variables: {\n user: UserRow;\n };\n};\n\n/** Gets the current user (available after authMiddleware). */\nexport function currentUser(c: { var: { user: UserRow } }): UserRow {\n return c.var.user;\n}\n\nexport function authMiddleware(auth: AuthService): MiddlewareHandler<AppEnv> {\n return async (c, next) => {\n const token = getCookie(c, SESSION_COOKIE);\n const user = token ? auth.authenticate(token) : null;\n if (!user) {\n throw new HttpError(401, \"unauthorized\", \"Not signed in or the sign-in has expired.\");\n }\n c.set(\"user\", user);\n await next();\n };\n}\n\nconst WRITE_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * Content-Type defense for write requests: a write request with a Content-Type\n * other than application/json is rejected (a request with no Content-Type and an\n * empty body is let through — an HTML form always carries a form-type Content-Type).\n */\nexport const jsonOnlyWrites: MiddlewareHandler = async (c, next) => {\n if (WRITE_METHODS.has(c.req.method)) {\n const contentType = c.req.header(\"content-type\");\n if (contentType && !contentType.toLowerCase().startsWith(\"application/json\")) {\n throw new HttpError(\n 415,\n \"unsupported_media_type\",\n \"Write requests only accept application/json.\",\n );\n }\n }\n await next();\n};\n","/**\n * Unified HTTP error: `{error: {code, message}}` response body,\n * with message in English.\n *\n * Routes and services express business errors via throw HttpError; app-level onError\n * uniformly converges these into a JSON response, with unknown errors converged to 500\n * (never leaking internal details to the client).\n */\nimport type { Context } from \"hono\";\nimport type { ErrorBody } from \"../api/types.js\";\n\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = \"HttpError\";\n }\n}\n\nexport function errorBody(code: string, message: string): ErrorBody {\n return { error: { code, message } };\n}\n\n/**\n * Model missing a credential: the provider SDK throws this at **client-construction\n * time** (hit by both creating a Session and resuming a Session); the original message\n * is full of environment variable names, meaningless to a user — uniformly replaced\n * with a single actionable sentence. The frontend produces localized text by code\n * (message is only a fallback); see web's lib/api-error.ts.\n */\nexport function isMissingCredential(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /missing credentials|api[_ ]?key/i.test(message);\n}\n\nexport function modelCredentialMissing(modelId: string): HttpError {\n return new HttpError(\n 400,\n \"model_credential_missing\",\n `Model ${modelId} has no API key yet. Configure it on the Models page first.`,\n );\n}\n\n/** app.onError handler: maps HttpError through as-is; everything else is logged and converged to 500. */\nexport function handleError(err: Error, c: Context): Response {\n if (err instanceof HttpError) {\n return c.json(errorBody(err.code, err.message), err.status as 400);\n }\n // Unknown error: print the stack for diagnosis, but never expose details externally.\n console.error(`[server] Unhandled exception: ${err.stack ?? err.message}`);\n return c.json(errorBody(\"internal\", \"Internal server error.\"), 500);\n}\n","/**\n * Auth service: built-in admin seeding /\n * login / logout / password change / session validation.\n *\n * - No open registration: on startup, if there are no users at all, the built-in\n * admin `admin` is seeded (initial password penguin-2026), and it adopts\n * `default_project`; all other users are created by an admin via the user\n * backend (admin-service).\n * - An initial password (whether seeded or set by an admin) is flagged with\n * password_is_initial, which the frontend uses to prompt for a password change soon.\n * - Sessions: a 32-byte random token, with only its sha256 hash stored in the DB;\n * valid for 7 days, with sliding renewal once less than 6 days remain.\n */\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type { UserInfo } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { AuthSessionsRepo } from \"../db/repos/auth-sessions.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport { hashPassword, verifyPassword } from \"./password.js\";\n\nexport const MIN_PASSWORD_LENGTH = 8;\n\n/** Built-in admin: user_id and initial password (matches the README and login-page hint). */\nexport const ADMIN_USER_ID = \"admin\";\nexport const ADMIN_INITIAL_PASSWORD = \"penguin-2026\";\n\nfunction sha256Hex(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function toUserInfo(row: UserRow): UserInfo {\n return {\n userId: row.userId,\n isAdmin: row.isAdmin,\n passwordIsInitial: row.passwordIsInitial,\n createdAt: row.createdAt,\n };\n}\n\nexport interface AuthServiceDeps {\n users: UsersRepo;\n authSessions: AuthSessionsRepo;\n /** Provisions the initial Project at signup (injected by project-service, to avoid a circular dependency). */\n provisionInitialProject: (user: UserRow, isAdmin: boolean) => Promise<void>;\n sessionTtlMs: number;\n sessionRenewMs: number;\n now?: () => Date;\n}\n\nexport class AuthService {\n private readonly now: () => Date;\n\n constructor(private readonly deps: AuthServiceDeps) {\n this.now = deps.now ?? (() => new Date());\n }\n\n /**\n * Startup seeding (idempotent): creates the built-in admin and adopts\n * default_project when the users table is empty; if the initial Project fails,\n * the user row is rolled back and the server retries on next startup.\n */\n async seedAdmin(): Promise<void> {\n if (this.deps.users.count() > 0) return;\n const user: UserRow = {\n userId: ADMIN_USER_ID,\n passwordHash: await hashPassword(ADMIN_INITIAL_PASSWORD),\n isAdmin: true,\n passwordIsInitial: true,\n createdAt: this.now().toISOString(),\n };\n this.deps.users.insert(user);\n try {\n await this.deps.provisionInitialProject(user, true);\n } catch (err) {\n this.deps.users.delete(user.userId);\n throw err;\n }\n }\n\n async login(userId: string, password: string): Promise<{ user: UserInfo; token: string }> {\n const row = this.deps.users.findById(userId);\n const ok = row !== null && (await verifyPassword(password, row.passwordHash));\n if (!row || !ok) {\n throw new HttpError(401, \"invalid_credentials\", \"Incorrect username or password.\");\n }\n this.deps.authSessions.deleteExpired(this.now().toISOString());\n return { user: toUserInfo(row), token: this.issueSession(row.userId) };\n }\n\n /** Self password change (user settings): validates the old password, and on success clears the initial-password flag; the current session remains valid. */\n async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void> {\n const row = this.deps.users.findById(userId);\n if (!row || !(await verifyPassword(oldPassword, row.passwordHash))) {\n throw new HttpError(400, \"password_mismatch\", \"Current password is incorrect.\");\n }\n if (newPassword.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"Password must be at least 8 characters.\");\n }\n this.deps.users.updatePassword(userId, await hashPassword(newPassword), false);\n }\n\n logout(token: string): void {\n this.deps.authSessions.delete(sha256Hex(token));\n }\n\n /** Validates the cookie token: returns null if expired/unknown; sliding renewal once less than 6 days remain. */\n authenticate(token: string): UserRow | null {\n const tokenHash = sha256Hex(token);\n const session = this.deps.authSessions.findByTokenHash(tokenHash);\n if (!session) return null;\n const now = this.now();\n const expiresAt = Date.parse(session.expiresAt);\n if (!(expiresAt > now.getTime())) {\n this.deps.authSessions.delete(tokenHash);\n return null;\n }\n if (expiresAt - now.getTime() < this.deps.sessionRenewMs) {\n this.deps.authSessions.touch(\n tokenHash,\n new Date(now.getTime() + this.deps.sessionTtlMs).toISOString(),\n );\n }\n return this.deps.users.findById(session.userId);\n }\n\n private issueSession(userId: string): string {\n const token = randomBytes(32).toString(\"base64url\");\n const now = this.now();\n this.deps.authSessions.insert({\n tokenHash: sha256Hex(token),\n userId,\n createdAt: now.toISOString(),\n expiresAt: new Date(now.getTime() + this.deps.sessionTtlMs).toISOString(),\n });\n return token;\n }\n}\n","/**\n * Password hashing (slow-hash storage).\n *\n * Uses node:crypto's scrypt (built-in, no extra dependency, meets the same\n * slow-hash requirement as bcrypt/argon2). Storage format:\n * `scrypt$N$r$p$<salt b64>$<hash b64>` — parameters are stored alongside the hash,\n * so old hashes remain verifiable after future parameter tuning; comparison uses\n * timingSafeEqual to guard against timing side-channels.\n */\nimport { randomBytes, scrypt, timingSafeEqual } from \"node:crypto\";\n\nconst SCRYPT_N = 16384;\nconst SCRYPT_R = 8;\nconst SCRYPT_P = 1;\nconst SALT_BYTES = 16;\nconst KEY_BYTES = 64;\n\nfunction scryptAsync(\n password: string,\n salt: Buffer,\n keyLen: number,\n n: number,\n r: number,\n p: number,\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n scrypt(password, salt, keyLen, { N: n, r, p, maxmem: 128 * 1024 * 1024 }, (err, key) => {\n if (err) reject(err);\n else resolve(key);\n });\n });\n}\n\n/** Generates a password hash in `scrypt$N$r$p$salt$hash` format. */\nexport async function hashPassword(password: string): Promise<string> {\n const salt = randomBytes(SALT_BYTES);\n const key = await scryptAsync(password, salt, KEY_BYTES, SCRYPT_N, SCRYPT_R, SCRYPT_P);\n return [\n \"scrypt\",\n String(SCRYPT_N),\n String(SCRYPT_R),\n String(SCRYPT_P),\n salt.toString(\"base64\"),\n key.toString(\"base64\"),\n ].join(\"$\");\n}\n\n/** Verifies a password; returns false if the stored string has an invalid format (never throws, so the login path can uniformly treat it as a credential error). */\nexport async function verifyPassword(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(\"$\");\n if (parts.length !== 6 || parts[0] !== \"scrypt\") return false;\n const n = Number(parts[1]);\n const r = Number(parts[2]);\n const p = Number(parts[3]);\n if (!Number.isInteger(n) || !Number.isInteger(r) || !Number.isInteger(p)) return false;\n let salt: Buffer;\n let expected: Buffer;\n try {\n salt = Buffer.from(parts[4]!, \"base64\");\n expected = Buffer.from(parts[5]!, \"base64\");\n } catch {\n return false;\n }\n if (salt.length === 0 || expected.length === 0) return false;\n let actual: Buffer;\n try {\n actual = await scryptAsync(password, salt, expected.length, n, r, p);\n } catch {\n return false;\n }\n return actual.length === expected.length && timingSafeEqual(actual, expected);\n}\n","/**\n * Admin user-backend routes: only the built-in admin can use these (403 for non-admins).\n * GET|POST /api/admin/users, POST /api/admin/users/:userId/password, DELETE /api/admin/users/:userId.\n */\nimport { Hono } from \"hono\";\nimport type { AdminUserCreateResponse, AdminUsersResponse } from \"../../api/types.js\";\nimport { HttpError } from \"../errors.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { pathParam, readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function adminUsersRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.use(\"*\", async (c, next) => {\n if (!c.var.user.isAdmin) {\n throw new HttpError(403, \"admin_required\", \"Only an admin can perform this operation.\");\n }\n await next();\n });\n\n app.get(\"/\", (c) => {\n return c.json({ users: deps.adminService.listUsers() } satisfies AdminUsersResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const password = requireString(body, \"password\", { label: \"password\" });\n const user = await deps.adminService.createUser(userId, password);\n return c.json({ user } satisfies AdminUserCreateResponse, 201);\n });\n\n app.post(\"/:userId/password\", async (c) => {\n const body = await readJson(c);\n const password = requireString(body, \"password\", { label: \"password\" });\n await deps.adminService.resetPassword(pathParam(c, \"userId\"), password);\n return c.body(null, 204);\n });\n\n app.delete(\"/:userId\", async (c) => {\n await deps.adminService.deleteUser(pathParam(c, \"userId\"));\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Hand-rolled request body validation helpers (fields follow TypeScript types;\n * this adds a runtime safety net).\n *\n * No validation library: each helper checks one basic shape, throwing a 400 HttpError on failure.\n */\nimport type { Context } from \"hono\";\nimport { isValidId } from \"@prismshadow/penguin-core\";\nimport { HttpError } from \"./errors.js\";\n\nexport function badRequest(message: string): HttpError {\n return new HttpError(400, \"bad_request\", message);\n}\n\n/**\n * Get a path parameter (under sub-route mounting, hono infers string | undefined; the\n * route guarantees presence at runtime — treat a defensive missing value as 404).\n */\nexport function pathParam(c: Context, name: string): string {\n const v = c.req.param(name);\n if (v === undefined || v === \"\") {\n throw new HttpError(404, \"not_found\", \"Missing path parameter.\");\n }\n return v;\n}\n\n/**\n * Get a path parameter and validate the id (alphanumeric, underscore, and hyphen\n * only, to prevent path traversal). Hono decodes URL-encoded `%2F` into a single path\n * parameter; an id containing `/` or `..` passed straight into path construction could\n * escape the resource directory (cross-Project privilege escalation). So validate right\n * after reading the value — any invalid id is rejected with 404 (not leaking resource\n * existence), before any service-layer or path-construction code runs.\n */\nexport function requireValidId(c: Context, name: string): string {\n const v = pathParam(c, name);\n if (!isValidId(v)) {\n throw new HttpError(404, \"not_found\", \"Resource does not exist or you do not have access.\");\n }\n return v;\n}\n\n/** Parse a positive-integer path parameter (e.g. Trace file index). */\nexport function positiveIntParam(c: Context, name: string): number {\n // Match digits only: Number.parseInt would accept trailing garbage (\"12abc\" -> 12).\n const raw = pathParam(c, name);\n if (!/^\\d+$/.test(raw)) throw badRequest(`${name} must be a positive integer.`);\n const v = Number.parseInt(raw, 10);\n // isSafeInteger (not isInteger) also rejects overlong indices like \"99999999999999999999\"\n // that would otherwise parse to an imprecise float (1e20).\n if (!Number.isSafeInteger(v) || v < 1) throw badRequest(`${name} must be a positive integer.`);\n return v;\n}\n\n/** Parse Trace pagination query params: offset >= 0 (default 0), limit 1-1000 (default 200). */\nexport function paginationQuery(c: Context): { offset: number; limit: number } {\n const offset = Number.parseInt(c.req.query(\"offset\") ?? \"0\", 10);\n const limit = Number.parseInt(c.req.query(\"limit\") ?? \"200\", 10);\n if (!Number.isInteger(offset) || offset < 0)\n throw badRequest(\"offset must be a non-negative integer.\");\n if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {\n throw badRequest(\"limit must be an integer between 1 and 1000.\");\n }\n return { offset, limit };\n}\n\n/**\n * Parse OPTIONAL list paging query params: both absent = null (caller returns the full\n * list — the pre-paging behavior stays intact for existing callers). When paging, `limit`\n * is required (1-1000) and `offset` optional (>= 0, default 0) — an offset alone would\n * silently return the full list shifted, which no caller ever means.\n */\nexport function optionalPagingQuery(c: Context): { offset: number; limit: number } | null {\n const rawLimit = c.req.query(\"limit\");\n const rawOffset = c.req.query(\"offset\");\n if (rawLimit === undefined && rawOffset === undefined) return null;\n if (rawLimit === undefined) throw badRequest(\"offset requires limit.\");\n const limit = Number.parseInt(rawLimit, 10);\n if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {\n throw badRequest(\"limit must be an integer between 1 and 1000.\");\n }\n const offset = Number.parseInt(rawOffset ?? \"0\", 10);\n if (!Number.isInteger(offset) || offset < 0) {\n throw badRequest(\"offset must be a non-negative integer.\");\n }\n return { offset, limit };\n}\n\n/** Read the JSON request body (parse failure / non-object -> 400). */\nexport async function readJson(c: Context): Promise<Record<string, unknown>> {\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n throw badRequest(\"Request body must be valid JSON.\");\n }\n if (body === null || typeof body !== \"object\" || Array.isArray(body)) {\n throw badRequest(\"Request body must be a JSON object.\");\n }\n return body as Record<string, unknown>;\n}\n\nexport interface StringRule {\n minLen?: number;\n maxLen?: number;\n pattern?: RegExp;\n /** Display name for the field in error messages (defaults to key). */\n label?: string;\n}\n\nexport function requireString(\n obj: Record<string, unknown>,\n key: string,\n rule: StringRule = {},\n): string {\n const v = obj[key];\n const label = rule.label ?? key;\n if (typeof v !== \"string\") throw badRequest(`${label} must be a string.`);\n if (rule.minLen !== undefined && v.length < rule.minLen) {\n throw badRequest(`${label} must be at least ${rule.minLen} characters.`);\n }\n if (rule.maxLen !== undefined && v.length > rule.maxLen) {\n throw badRequest(`${label} must be at most ${rule.maxLen} characters.`);\n }\n if (rule.pattern !== undefined && !rule.pattern.test(v)) {\n throw badRequest(`${label} has an invalid format.`);\n }\n return v;\n}\n\nexport function optionalString(\n obj: Record<string, unknown>,\n key: string,\n rule: StringRule = {},\n): string | undefined {\n if (obj[key] === undefined) return undefined;\n return requireString(obj, key, rule);\n}\n\nexport function requireEnum<T extends string>(\n obj: Record<string, unknown>,\n key: string,\n values: readonly T[],\n label = key,\n): T {\n const v = obj[key];\n if (typeof v !== \"string\" || !(values as readonly string[]).includes(v)) {\n throw badRequest(`${label} must be one of ${values.join(\" / \")}.`);\n }\n return v as T;\n}\n\nexport function optionalEnum<T extends string>(\n obj: Record<string, unknown>,\n key: string,\n values: readonly T[],\n label = key,\n): T | undefined {\n if (obj[key] === undefined) return undefined;\n return requireEnum(obj, key, values, label);\n}\n\nexport interface NumberRule {\n /** Require positive or -1 (Agent runtime parameter convention: >0 active, -1 disabled). */\n positiveOrMinusOne?: boolean;\n /** Require non-negative. */\n nonNegative?: boolean;\n /** Require integer. */\n integer?: boolean;\n label?: string;\n}\n\nexport function optionalNumber(\n obj: Record<string, unknown>,\n key: string,\n rule: NumberRule = {},\n): number | undefined {\n const v = obj[key];\n if (v === undefined) return undefined;\n const label = rule.label ?? key;\n if (typeof v !== \"number\" || !Number.isFinite(v)) throw badRequest(`${label} must be a number.`);\n if (rule.integer && !Number.isInteger(v)) throw badRequest(`${label} must be an integer.`);\n if (rule.positiveOrMinusOne && !(v > 0 || v === -1)) {\n throw badRequest(`${label} must be greater than 0 or equal to -1.`);\n }\n if (rule.nonNegative && v < 0) throw badRequest(`${label} must not be negative.`);\n return v;\n}\n\nexport function optionalBoolean(\n obj: Record<string, unknown>,\n key: string,\n label = key,\n): boolean | undefined {\n const v = obj[key];\n if (v === undefined) return undefined;\n if (typeof v !== \"boolean\") throw badRequest(`${label} must be a boolean.`);\n return v;\n}\n\n/** Validate a yyyy-mm-dd query parameter (defaults to undefined). */\nexport function optionalDateParam(value: string | undefined, label: string): string | undefined {\n if (value === undefined || value === \"\") return undefined;\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value);\n // The shape check alone accepts impossible dates (2026-13-40, 2026-02-30); verify it's a real\n // calendar day by round-tripping through UTC (which never rolls over into an adjacent month).\n if (m) {\n const [, y, mo, d] = m;\n const dt = new Date(`${value}T00:00:00Z`);\n if (\n !Number.isNaN(dt.getTime()) &&\n dt.getUTCFullYear() === Number(y) &&\n dt.getUTCMonth() + 1 === Number(mo) &&\n dt.getUTCDate() === Number(d)\n ) {\n return value;\n }\n }\n throw badRequest(`${label} must be a valid date in YYYY-MM-DD format.`);\n}\n","/**\n * Auth routes: POST /api/auth/login | logout.\n * No self-registration: users are created by an admin in the user backend (/api/admin/users).\n * Login issues a cookie session; logout deletes the server-side session and clears the cookie.\n */\nimport { Hono } from \"hono\";\nimport { deleteCookie, getCookie, setCookie } from \"hono/cookie\";\nimport type { AuthResponse } from \"../../api/types.js\";\nimport { SESSION_COOKIE } from \"../../auth/middleware.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Session cookie attributes: HttpOnly, SameSite=Lax, 7 days. */\nfunction cookieOptions(c: { req: { header(name: string): string | undefined } }) {\n return {\n httpOnly: true,\n sameSite: \"Lax\" as const,\n path: \"/\",\n maxAge: 7 * 24 * 60 * 60,\n // Add Secure when the reverse proxy declares https.\n ...(c.req.header(\"x-forwarded-proto\") === \"https\" ? { secure: true } : {}),\n };\n}\n\nexport function authRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.post(\"/login\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const password = requireString(body, \"password\", { label: \"password\" });\n const { user, token } = await deps.authService.login(userId, password);\n setCookie(c, SESSION_COOKIE, token, cookieOptions(c));\n return c.json({ user } satisfies AuthResponse);\n });\n\n app.post(\"/logout\", (c) => {\n const token = getCookie(c, SESSION_COOKIE);\n if (token) deps.authService.logout(token);\n deleteCookie(c, SESSION_COOKIE, { path: \"/\" });\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Current-user routes: GET /api/me, PUT /api/me/password, GET|PUT /api/me/prefs.\n * ui_prefs is free-form JSON (theme / lastProjectId / credentialGuideSeen, etc.): GET reads\n * it whole, PUT shallow-merges (PATCH semantics) — several independent writers each write\n * their own fields without clobbering each other.\n */\nimport { Hono } from \"hono\";\nimport type { MeResponse, PrefsResponse, UiPrefs } from \"../../api/types.js\";\nimport { toUserInfo } from \"../../auth/service.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { readJson, requireString } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { resolvePreviewTarget } from \"../../services/preview-token.js\";\n\nexport function meRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n // previewIsolated depends on the host this request came in on, so it is computed\n // here rather than stored: the same server answers on 127.0.0.1, localhost and\n // possibly a LAN address, and only the first two have a loopback counterpart.\n const target = resolvePreviewTarget(\n c.req.url,\n c.req.header(\"host\"),\n deps.config.previewOrigin,\n deps.config,\n );\n return c.json({\n user: toUserInfo(c.var.user),\n previewIsolated: target !== null,\n } satisfies MeResponse);\n });\n\n // Self-service password change (user settings): validates the old password; on success, the initial-password prompt disappears from GET /api/me.\n app.put(\"/password\", async (c) => {\n const body = await readJson(c);\n const oldPassword = requireString(body, \"oldPassword\", { label: \"oldPassword\" });\n const newPassword = requireString(body, \"newPassword\", { label: \"newPassword\" });\n await deps.authService.changePassword(c.var.user.userId, oldPassword, newPassword);\n return c.body(null, 204);\n });\n\n app.get(\"/prefs\", (c) => {\n const raw = deps.prefsRepo.get(c.var.user.userId);\n let prefs: UiPrefs = {};\n if (raw !== null) {\n try {\n prefs = JSON.parse(raw) as UiPrefs;\n } catch {\n prefs = {}; // Corrupted prefs fall back to an empty object\n }\n }\n return c.json({ prefs } satisfies PrefsResponse);\n });\n\n // PATCH semantics: the request body is **shallow-merged** into existing prefs, not a\n // full replace. prefs has several independent writers (lastProjectId /\n // credentialGuideSeen, etc., each writing their own field); a full replace would wipe\n // out each other's fields — e.g. writing lastProjectId when switching Projects would\n // clear credentialGuideSeen, breaking the \"show onboarding once ever\" guarantee.\n app.put(\"/prefs\", async (c) => {\n const body = await readJson(c);\n const raw = deps.prefsRepo.get(c.var.user.userId);\n let current: UiPrefs = {};\n if (raw !== null) {\n try {\n current = JSON.parse(raw) as UiPrefs;\n } catch {\n current = {}; // Corrupted prefs fall back to an empty object (consistent with GET).\n }\n }\n const merged = { ...current, ...(body as UiPrefs) };\n deps.prefsRepo.set(c.var.user.userId, JSON.stringify(merged));\n return c.json({ prefs: merged } satisfies PrefsResponse);\n });\n\n return app;\n}\n","/**\n * Signed tokens for Workspace HTML preview on a separate origin.\n *\n * The preview origin deliberately differs from the App origin (see\n * design/specs/05-ARCHITECTURE.md § \"Workspace 文件预览\"), so it never receives the\n * session cookie — cookies are keyed by host and ignore port, which is why the two\n * must differ by hostname and not merely by port. Authorization therefore travels in\n * the URL as a short-lived HMAC token instead.\n *\n * The token binds three things: the Session whose Workspace may be read, the host the\n * preview must be served from, and an expiry. Binding the host is what keeps this from\n * becoming a same-origin XSS hole — the same process also answers on the App origin, so\n * without that check `/preview/<token>/index.html` would happily execute Agent-written\n * HTML with the App's cookies. See previewRoutes for the enforcement.\n *\n * The signing secret is generated per process: tokens are short-lived anyway, so losing\n * them across a restart costs nothing and there is no secret to persist or rotate.\n */\nimport { createHmac, randomBytes, timingSafeEqual } from \"node:crypto\";\n\n/** Preview tokens live just long enough to open a page and let it pull its subresources. */\nexport const PREVIEW_TOKEN_TTL_MS = 10 * 60 * 1000;\n\nexport interface PreviewTokenPayload {\n /** Session whose Workspace subtree the token grants read access to. */\n sessionId: string;\n /** Host (no port) the preview must be served from; anything else is refused. */\n host: string;\n /** Expiry, epoch milliseconds. */\n expiresAt: number;\n}\n\n/** Wire form: `<base64url(json)>.<base64url(hmac)>`. */\ntype Signer = {\n sign(payload: PreviewTokenPayload): string;\n verify(token: string): PreviewTokenPayload | null;\n};\n\nfunction b64url(buf: Buffer): string {\n return buf.toString(\"base64url\");\n}\n\nexport function createPreviewTokenSigner(secret: Buffer = randomBytes(32)): Signer {\n const mac = (body: string): Buffer => createHmac(\"sha256\", secret).update(body).digest();\n\n return {\n sign(payload) {\n const body = b64url(Buffer.from(JSON.stringify(payload), \"utf8\"));\n return `${body}.${b64url(mac(body))}`;\n },\n\n verify(token) {\n const dot = token.indexOf(\".\");\n if (dot <= 0 || dot === token.length - 1) return null;\n const body = token.slice(0, dot);\n const provided = Buffer.from(token.slice(dot + 1), \"base64url\");\n const expected = mac(body);\n // Length check first: timingSafeEqual throws on a length mismatch.\n if (provided.length !== expected.length || !timingSafeEqual(provided, expected)) return null;\n\n let payload: PreviewTokenPayload;\n try {\n payload = JSON.parse(\n Buffer.from(body, \"base64url\").toString(\"utf8\"),\n ) as PreviewTokenPayload;\n } catch {\n return null;\n }\n if (\n typeof payload?.sessionId !== \"string\" ||\n typeof payload?.host !== \"string\" ||\n typeof payload?.expiresAt !== \"number\"\n ) {\n return null;\n }\n if (Date.now() >= payload.expiresAt) return null;\n return payload;\n },\n };\n}\n\nexport type PreviewTokenSigner = ReturnType<typeof createPreviewTokenSigner>;\n\n/** Host header without its port (IPv6 literals keep their brackets). */\nexport function hostOnly(hostHeader: string): string {\n const trimmed = hostHeader.trim();\n if (trimmed.startsWith(\"[\")) {\n const end = trimmed.indexOf(\"]\");\n return end === -1 ? trimmed : trimmed.slice(0, end + 1);\n }\n const colon = trimmed.lastIndexOf(\":\");\n return colon === -1 ? trimmed : trimmed.slice(0, colon);\n}\n\n/**\n * The loopback counterpart of the host the App is being used on: `127.0.0.1` and\n * `localhost` are distinct hosts for cookie purposes, so serving previews from the\n * other one isolates them with no extra port, no DNS, and nothing to configure.\n * Returns null for any other host (LAN IP, real domain) — those need an explicit\n * PENGUIN_PREVIEW_ORIGIN, and the caller degrades to the same-origin sandbox.\n */\nexport function loopbackCounterpart(host: string): string | null {\n const h = host.toLowerCase();\n if (h === \"127.0.0.1\") return \"localhost\";\n if (h === \"localhost\") return \"127.0.0.1\";\n // ::1 shares localhost's cookie jar semantics poorly across browsers; send it to the\n // IPv4 literal, which is unambiguous.\n if (h === \"[::1]\") return \"127.0.0.1\";\n return null;\n}\n\n/**\n * When the server is bound to a loopback host, the App is canonicalized onto one loopback\n * name and previews are served from the other. Fixing the assignment — rather than serving\n * previews from the counterpart of whichever name the caller happens to type — is what\n * keeps the preview host free of a session cookie. The same process answers on both names,\n * so if the App could be reached (and logged into) on the preview name too, Agent-written\n * preview HTML would call `/api` same-origin and regain a full App session. `app` is the\n * only name that serves the App and sets/accepts the session cookie; `preview` serves\n * `/preview/*` and nothing else (enforced by the canonical-host guard in app.ts).\n *\n * Null for non-loopback binds (wildcard, LAN IP, a bare `::1`): they can't offer a loopback\n * preview the IPv6 companion listener actually serves, so they must set\n * PENGUIN_PREVIEW_ORIGIN and otherwise fall back to the same-origin sandbox.\n */\nexport function loopbackHostRoles(bindHost: string): { app: string; preview: string } | null {\n const h = bindHost.trim().toLowerCase();\n if (h !== \"127.0.0.1\" && h !== \"localhost\") return null;\n const app = \"localhost\";\n const preview = loopbackCounterpart(app);\n if (!preview) return null;\n return { app, preview };\n}\n\n/**\n * The host:port this request was addressed to. Prefers the Host header (what the browser\n * actually sent, and what decides the origin), falling back to the request URL's\n * authority — some server runtimes and test harnesses build a Request from a full URL\n * without materializing a Host header.\n */\nexport function requestAuthority(requestUrl: string, hostHeader: string | undefined): string {\n const header = (hostHeader ?? \"\").trim();\n if (header !== \"\") return header;\n try {\n return new URL(requestUrl).host;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Where previews for this request should be served from: the configured origin when\n * PENGUIN_PREVIEW_ORIGIN is set, otherwise the loopback counterpart of the host the\n * caller is using. Null when neither applies — or when the configured origin's host is\n * the request's own host (no boundary) — and the caller degrades to the same-origin\n * sandbox rather than silently serving unisolated content.\n *\n * The port comes from **this server's own binding**, never from the request. Those differ\n * in development: the SPA is served by Vite on its own port and only proxies `/api`, so a\n * preview URL built from the browser's current port would point at a port where nothing\n * serves `/preview` — connection refused, or a Vite 404. In production the two are the\n * same and this is a no-op. Only a loopback-name bind (`127.0.0.1` / `localhost`) offers a\n * loopback preview — it is the bind where the IPv6 companion listener runs and where the\n * App is canonicalized (see `loopbackHostRoles`); any other bind (wildcard, LAN IP) has no\n * reachable counterpart and falls back.\n */\nexport function resolvePreviewTarget(\n requestUrl: string,\n hostHeader: string | undefined,\n configuredOrigin: string | null,\n serverBind: { host: string; port: number },\n): { origin: string; host: string } | null {\n const requestHost = hostOnly(requestAuthority(requestUrl, hostHeader)).toLowerCase();\n if (configuredOrigin) {\n const url = new URL(configuredOrigin);\n // A configured origin that lands on the very host this request is using is no\n // boundary at all: \"isolated\" previews would be same-origin with the App, and the\n // allow-same-origin iframe the UI mounts for them would hand Agent HTML the App's\n // origin. Such a misconfiguration degrades to null — previewIsolated reports false\n // and the redirect falls back to the same-origin sandbox — instead of claiming an\n // isolation that does not exist. Host-only comparison on purpose: cookies ignore\n // ports, so a port difference would not separate anything either.\n if (url.hostname.toLowerCase() === requestHost) return null;\n return { origin: url.origin, host: url.hostname };\n }\n if (requestHost === \"\") return null;\n // The loopback counterpart differs from the request host by construction, so the\n // same-host guard above is only needed for the configured branch.\n const counterpart = loopbackCounterpart(requestHost);\n if (!counterpart) return null;\n if (!loopbackHostRoles(serverBind.host)) return null;\n\n let protocol: string;\n try {\n protocol = new URL(requestUrl).protocol;\n } catch {\n protocol = \"http:\";\n }\n const suffix =\n (protocol === \"http:\" && serverBind.port === 80) ||\n (protocol === \"https:\" && serverBind.port === 443)\n ? \"\"\n : `:${serverBind.port}`;\n return { origin: `${protocol}//${counterpart}${suffix}`, host: counterpart };\n}\n","/**\n * User-level server event stream: GET /api/events (SSE user channel).\n * Carries cross-Session notifications (reserved for automated tasks); sends a `hello` handshake event on connect.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { sseEndpoint } from \"../sse.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** The user channel's key in ChannelHub. */\nexport function userChannelKey(userId: string): string {\n return `user:${userId}`;\n}\n\nexport function eventsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n const channel = deps.channels.get(userChannelKey(c.var.user.userId));\n return sseEndpoint(c, channel, { initialEvents: [{ type: \"hello\" }] });\n });\n\n return app;\n}\n","/**\n * SSE endpoint adapter:\n * writes the runtime/channel event stream as an SSE response, shared by both the Session\n * channel and the user channel.\n *\n * - Response headers: text/event-stream, no-cache, `X-Accel-Buffering: no` (disables\n * buffering on reverse proxies);\n * - Heartbeat: writes a `: ping` comment line every 20s; the connection is torn down on write failure;\n * - Replay protocol: a fresh subscription without Last-Event-ID does not replay the buffer\n * (history is served by the messages endpoint) — it only sends the initial events the\n * caller supplied (pending approvals / hello). With a Last-Event-ID that hits the buffer,\n * replay resumes from there; on a miss, `resync_required` is sent first, then the\n * connection continues.\n * Docs: /docs/server-api § \"Delivery Guarantees\".\n */\nimport type { Context } from \"hono\";\nimport { streamSSE } from \"hono/streaming\";\nimport type { ServerEvent } from \"../api/types.js\";\nimport type { Channel, ChannelEvent, ChannelListener } from \"../runtime/channel.js\";\n\nconst HEARTBEAT_MS = 20_000;\n\nexport interface SseEndpointOptions {\n /** Initial server events to send privately after the subscription is established (in order): pending-approval replay / user channel hello. */\n initialEvents?: ServerEvent[];\n}\n\n/** Stream out a Channel as an SSE response. */\nexport function sseEndpoint(c: Context, channel: Channel, opts: SseEndpointOptions = {}): Response {\n const lastEventIdHeader = c.req.header(\"Last-Event-ID\");\n c.header(\"X-Accel-Buffering\", \"no\");\n c.header(\"Cache-Control\", \"no-cache\");\n\n return streamSSE(c, async (stream) => {\n let closed = false;\n let finish: () => void = () => {};\n const done = new Promise<void>((resolve) => {\n finish = () => {\n if (closed) return;\n closed = true;\n resolve();\n };\n });\n\n // Write serialization: SSE events must be written fully and in order; any write failure tears down the connection.\n let chain: Promise<void> = Promise.resolve();\n const enqueue = (write: () => Promise<unknown>): void => {\n chain = chain\n .then(async () => {\n if (!closed) await write();\n })\n .catch(() => finish());\n };\n const listener: ChannelListener = (evt: ChannelEvent) => {\n enqueue(() =>\n stream.writeSSE({\n data: evt.data,\n // Event id is an opaque string generated by the channel (`<epoch>-<seq>`), passed through as-is.\n id: evt.id,\n ...(evt.event !== undefined ? { event: evt.event } : {}),\n }),\n );\n };\n\n const unsubscribe = channel.subscribe(listener);\n // Subscribe first (to avoid dropping events in a race with broadcasts), then replay\n // synchronously — event order: buffered replay (or resync_required) -> initial events\n // (task_state snapshot / pending approvals / hello) -> live stream.\n if (lastEventIdHeader !== undefined) {\n const replay = channel.replayAfter(lastEventIdHeader);\n if (!replay.hit) {\n const resync: ServerEvent = { type: \"resync_required\" };\n channel.sendTo(listener, resync, \"server_event\");\n } else {\n for (const evt of replay.events) listener(evt);\n }\n }\n for (const event of opts.initialEvents ?? []) {\n channel.sendTo(listener, event, \"server_event\");\n }\n\n const heartbeat = setInterval(() => {\n enqueue(() => stream.write(\": ping\\n\\n\"));\n }, HEARTBEAT_MS);\n\n stream.onAbort(() => finish());\n try {\n await done;\n } finally {\n clearInterval(heartbeat);\n unsubscribe();\n }\n });\n}\n","/**\n * Project routes: GET|POST /api/projects, DELETE /api/projects/:p.\n */\nimport { Hono } from \"hono\";\nimport type { ProjectCreateResponse, ProjectsResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { optionalString, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function projectsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projects = await deps.projectService.listProjects(c.var.user.userId);\n return c.json({ projects } satisfies ProjectsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const projectId = requireString(body, \"projectId\", { label: \"projectId\" });\n const name = optionalString(body, \"name\", { minLen: 1, maxLen: 100, label: \"name\" });\n const project = await deps.projectService.createProject(c.var.user, projectId, name);\n return c.json({ project } satisfies ProjectCreateResponse, 201);\n });\n\n app.delete(\"/:projectId\", async (c) => {\n // Defensive id validation (FD-4): deleteProject constructs the project directory path and recursively deletes it.\n await deps.projectService.deleteProject(c.var.user.userId, requireValidId(c, \"projectId\"));\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Member authorization routes:\n * GET|POST /api/projects/:p/members, DELETE /api/projects/:p/members/:userId.\n * Reading requires access; adding/removing is owner-only (validated inside the service).\n */\nimport { Hono } from \"hono\";\nimport type { MemberAddResponse, MembersResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { pathParam, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function membersRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", (c) => {\n // Defensive id validation (FD-4).\n const members = deps.projectService.listMembers(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n );\n return c.json({ members } satisfies MembersResponse);\n });\n\n app.post(\"/\", async (c) => {\n const body = await readJson(c);\n const userId = requireString(body, \"userId\", { label: \"userId\" });\n const member = deps.projectService.addMember(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n userId,\n );\n return c.json({ member } satisfies MemberAddResponse, 201);\n });\n\n app.delete(\"/:userId\", (c) => {\n deps.projectService.removeMember(\n c.var.user.userId,\n requireValidId(c, \"projectId\"),\n pathParam(c, \"userId\"),\n );\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Model & credential config routes:\n * GET|PUT /api/projects/:p/models, POST /api/projects/:p/models/test (the model reference\n * `(provider, modelId)` is sent as a pair in the request body, avoiding URL-encoding\n * issues). Any member can read (api_key is masked); only the owner can modify or test.\n */\nimport { Hono } from \"hono\";\nimport type {\n ModelRefDto,\n ModelsUpdateRequest,\n ModelTestRequest,\n ModelUpdateEntry,\n ServerEvent,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/**\n * Live unlock for auth-dead composers: after a models/credential update, publish\n * `credentials_updated` to every EXISTING channel of this Project's Sessions (`peek` —\n * never creates channels: a tab that isn't subscribed learns the same fact from the models\n * response's `updatedAt` when it next loads).\n */\nfunction publishCredentialsUpdated(deps: AppDeps, projectId: string): void {\n const event: ServerEvent = { type: \"credentials_updated\" };\n for (const row of deps.sessionsRepo.listByProject(projectId)) {\n deps.channels.peek(row.sessionId)?.publish(event, \"server_event\");\n }\n}\n\n/** Validate a paired reference object ({ provider, modelId }); shape mismatch throws 400. */\nfunction parseRef(value: unknown, label: string): ModelRefDto {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw badRequest(`${label} must be a { provider, modelId } object.`);\n }\n const r = value as Record<string, unknown>;\n return {\n provider: requireString(r, \"provider\", { minLen: 1, maxLen: 64, label: `${label}.provider` }),\n modelId: requireString(r, \"modelId\", { minLen: 1, maxLen: 200, label: `${label}.modelId` }),\n };\n}\n\n/** Validate the PUT request body and shape it into a ModelsUpdateRequest (rejects any shape errors). */\nfunction parseModelsUpdate(body: Record<string, unknown>): ModelsUpdateRequest {\n if (!Array.isArray(body.models)) throw badRequest(\"models must be an array.\");\n const models: ModelUpdateEntry[] = body.models.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`models[${i}] must be an object.`);\n }\n const m = item as Record<string, unknown>;\n const entry: ModelUpdateEntry = {\n provider: requireString(m, \"provider\", {\n minLen: 1,\n maxLen: 64,\n label: `models[${i}].provider`,\n }),\n modelId: requireString(m, \"modelId\", {\n minLen: 1,\n maxLen: 200,\n label: `models[${i}].modelId`,\n }),\n };\n if (m.displayName !== undefined) {\n if (typeof m.displayName !== \"string\" || m.displayName.length > 100) {\n throw badRequest(`models[${i}].displayName must be a string of at most 100 characters.`);\n }\n if (m.displayName) entry.displayName = m.displayName;\n }\n // A key change (either the provider group or the upstream id) goes through renamedFrom's paired old reference; unknown fields are ignored.\n if (m.renamedFrom !== undefined) {\n entry.renamedFrom = parseRef(m.renamedFrom, `models[${i}].renamedFrom`);\n }\n if (m.contextWindow !== undefined) {\n if (typeof m.contextWindow !== \"number\" || !(m.contextWindow > 0)) {\n throw badRequest(`models[${i}].contextWindow must be a positive number.`);\n }\n entry.contextWindow = m.contextWindow;\n }\n if (m.clientType !== undefined) {\n if (typeof m.clientType !== \"string\" || m.clientType.length > 64) {\n throw badRequest(`models[${i}].clientType must be a string of at most 64 characters.`);\n }\n // An empty string is treated as \"unspecified\", leaving AgentHub to infer it from modelId.\n if (m.clientType) entry.clientType = m.clientType;\n }\n if (m.vision !== undefined) {\n if (typeof m.vision !== \"boolean\") {\n throw badRequest(`models[${i}].vision must be a boolean.`);\n }\n entry.vision = m.vision;\n }\n if (m.maxTokens !== undefined) {\n if (typeof m.maxTokens !== \"number\" || !Number.isInteger(m.maxTokens) || m.maxTokens <= 0) {\n throw badRequest(`models[${i}].maxTokens must be a positive integer.`);\n }\n entry.maxTokens = m.maxTokens;\n }\n if (m.pricing !== undefined) {\n const p = m.pricing as Record<string, unknown>;\n if (p === null || typeof p !== \"object\" || Array.isArray(p)) {\n throw badRequest(`models[${i}].pricing must be an object.`);\n }\n for (const key of [\"cacheRead\", \"cacheWrite\", \"output\"] as const) {\n const v = p[key];\n if (typeof v !== \"number\" || !Number.isFinite(v) || v < 0) {\n throw badRequest(`models[${i}].pricing.${key} must be a non-negative number.`);\n }\n }\n entry.pricing = {\n cacheRead: p.cacheRead as number,\n cacheWrite: p.cacheWrite as number,\n output: p.output as number,\n };\n }\n if (m.apiKey !== undefined) {\n if (typeof m.apiKey !== \"string\" || m.apiKey.length === 0) {\n throw badRequest(`models[${i}].apiKey must be a non-empty string.`);\n }\n entry.apiKey = m.apiKey;\n }\n if (m.clearApiKey !== undefined) {\n if (typeof m.clearApiKey !== \"boolean\") {\n throw badRequest(`models[${i}].clearApiKey must be a boolean.`);\n }\n entry.clearApiKey = m.clearApiKey;\n }\n if (m.baseUrl !== undefined) {\n if (m.baseUrl !== null && typeof m.baseUrl !== \"string\") {\n throw badRequest(`models[${i}].baseUrl must be a string or null.`);\n }\n entry.baseUrl = m.baseUrl as string | null;\n }\n return entry;\n });\n const req: ModelsUpdateRequest = { models };\n if (body.defaultModel !== undefined) {\n req.defaultModel = parseRef(body.defaultModel, \"defaultModel\");\n }\n if (body.visionModel !== undefined) {\n req.visionModel = parseRef(body.visionModel, \"visionModel\");\n }\n return req;\n}\n\nexport function modelsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4).\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.projectConfigService.getModels(projectId));\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const req = parseModelsUpdate(await readJson(c));\n const res = await deps.projectConfigService.updateModels(projectId, req);\n // Effective-value semantics (mirrors the vault route): no hot swap into a Task already\n // in flight, but every cached runtime in this Project is invalidated — the next Task on\n // any of its Sessions re-resumes and reads the new api_key / base_url. Without this, a\n // key edit would not reach a loaded Session until the 30-minute idle sweep or a restart.\n deps.manager.invalidateProjectRuntimes(projectId);\n // Live unlock: open tabs clear their auth-dead composer immediately (no reload needed).\n publishCredentialsUpdated(deps, projectId);\n return c.json(res);\n });\n\n // Connectivity test (owner): the model reference `(provider, modelId)` is sent as a pair\n // in the request body; sends one minimal request using that model's config. May include\n // not-yet-saved apiKey / baseUrl / clientType — when the model isn't in the config yet\n // (adding a custom model), everything is taken from the request body.\n app.post(\"/test\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const body = await readJson(c);\n const req: ModelTestRequest = {\n provider: requireString(body, \"provider\", { minLen: 1, maxLen: 64 }),\n modelId: requireString(body, \"modelId\", { minLen: 1, maxLen: 200 }),\n };\n if (body.apiKey !== undefined) {\n if (typeof body.apiKey !== \"string\") throw badRequest(\"apiKey must be a string.\");\n if (body.apiKey) req.apiKey = body.apiKey;\n }\n if (body.clearApiKey !== undefined) {\n if (typeof body.clearApiKey !== \"boolean\") throw badRequest(\"clearApiKey must be a boolean.\");\n req.clearApiKey = body.clearApiKey;\n }\n if (body.speed !== undefined) {\n if (typeof body.speed !== \"boolean\") throw badRequest(\"speed must be a boolean.\");\n req.speed = body.speed;\n }\n // null = explicit clear (test against the draft, don't fall back to the stored value); empty string is treated as null.\n if (body.baseUrl !== undefined) {\n if (body.baseUrl !== null && typeof body.baseUrl !== \"string\") {\n throw badRequest(\"baseUrl must be a string or null.\");\n }\n req.baseUrl = body.baseUrl ? body.baseUrl : null;\n }\n if (body.clientType !== undefined) {\n if (typeof body.clientType !== \"string\") throw badRequest(\"clientType must be a string.\");\n if (body.clientType) req.clientType = body.clientType;\n }\n return c.json(await deps.projectConfigService.testModel(projectId, req));\n });\n\n return app;\n}\n","/**\n * Vault environment variable routes:\n * GET|PUT /api/projects/:p/agents/:a/vault (Agent-level, agent_state/.vault.toml).\n * Any member can read (values masked); only the owner can modify; 404 if the Agent doesn't exist.\n */\nimport { Hono } from \"hono\";\nimport type { VaultEntryUpdate, VaultUpdateRequest } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Validate the PUT request body and shape it into a VaultUpdateRequest (semantic checks like key-name rules live in the service layer). */\nfunction parseVaultUpdate(body: Record<string, unknown>): VaultUpdateRequest {\n if (!Array.isArray(body.entries)) throw badRequest(\"entries must be an array.\");\n const entries: VaultEntryUpdate[] = body.entries.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`entries[${i}] must be an object.`);\n }\n const e = item as Record<string, unknown>;\n const entry: VaultEntryUpdate = {\n key: requireString(e, \"key\", { minLen: 1, maxLen: 200, label: `entries[${i}].key` }),\n };\n if (e.value !== undefined) {\n if (typeof e.value !== \"string\" || e.value.length === 0) {\n throw badRequest(`entries[${i}].value must be a non-empty string.`);\n }\n entry.value = e.value;\n }\n return entry;\n });\n return { entries };\n}\n\nexport function vaultRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.agentConfigService.getVault(projectId, agentId));\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const req = parseVaultUpdate(await readJson(c));\n const res = await deps.agentConfigService.updateVault(projectId, agentId, req);\n // Effective-value semantics: no hot swap into a Task already in flight, but every\n // runtime built before this update is invalidated — the next Task on any Session\n // of this Agent re-resumes and picks up the new vault values.\n deps.manager.invalidateAgentRuntimes(projectId, agentId);\n return c.json(res);\n });\n\n return app;\n}\n","/**\n * Schedule routes:\n * GET|POST /api/projects/:p/agents/:a/schedules\n * GET|PUT|DELETE /api/projects/:p/agents/:a/schedules/:name (name is the file name)\n * Any member can read; only the owner can modify. The file is declarative intent:\n * POST/PUT fully replace the file, validation always goes through parseScheduleFile\n * (same rules as hand-edited files), and writes take effect immediately via reconciliation.\n */\nimport { createHash } from \"node:crypto\";\nimport { Hono } from \"hono\";\nimport { isValidId } from \"@prismshadow/penguin-core\";\nimport type { ScheduleItem, ScheduleStatus, SchedulesResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { HttpError } from \"../errors.js\";\nimport {\n badRequest,\n optionalString,\n readJson,\n requireString,\n requireValidId,\n} from \"../validate.js\";\nimport type { ScheduleDefinition } from \"../../runtime/schedule-file.js\";\nimport {\n latestSlotAt,\n nextSlotAfter,\n parseScheduleFile,\n slotInWindow,\n} from \"../../runtime/schedule-file.js\";\nimport type { ScheduleStateRow } from \"../../db/repos/schedules.js\";\nimport {\n deleteScheduleFile,\n readScheduleFile,\n serializeSchedule,\n validateScheduleModelRef,\n writeScheduleFile,\n} from \"../../runtime/schedule-store.js\";\n\n/** Validate and shape the POST/PUT request body into file fields (semantic validation is left to parseScheduleFile). */\nfunction parseUpsertBody(body: Record<string, unknown>): {\n prompt: string;\n enabled: boolean;\n startAt: string;\n period?: string;\n endAt?: string;\n sessionId?: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n} {\n if (typeof body.enabled !== \"boolean\") throw badRequest(\"enabled must be a boolean.\");\n const prompt = requireString(body, \"prompt\", { minLen: 1, maxLen: 100_000 });\n const startAt = requireString(body, \"startAt\", { minLen: 1, maxLen: 100 });\n const period = optionalString(body, \"period\", { minLen: 1, maxLen: 20 });\n const endAt = optionalString(body, \"endAt\", { minLen: 1, maxLen: 100 });\n const sessionId = optionalString(body, \"sessionId\", { minLen: 1, maxLen: 200 });\n const workspace = optionalString(body, \"workspace\", { minLen: 1, maxLen: 4096 });\n const modelId = optionalString(body, \"modelId\", { minLen: 1, maxLen: 200 });\n const provider = optionalString(body, \"provider\", { minLen: 1, maxLen: 64 });\n return {\n prompt,\n enabled: body.enabled,\n startAt,\n ...(period !== undefined ? { period } : {}),\n ...(endAt !== undefined ? { endAt } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n };\n}\n\n/** Next scheduled fire time: none when disabled/invalid/done/missed; an undigested due slot counts as-is. */\nfunction nextFireAt(\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n nowMs: number,\n): string | undefined {\n if (!def.enabled || state.invalidReason !== null) return undefined;\n if (def.periodMs === undefined && (state.firedOnce || state.missed)) return undefined;\n const due = latestSlotAt(def, nowMs);\n if (\n due !== null &&\n slotInWindow(def, due) &&\n (state.lastSlotMs === null || due > state.lastSlotMs)\n ) {\n return new Date(due).toISOString();\n }\n const next = nextSlotAfter(def, nowMs);\n return next !== null ? new Date(next).toISOString() : undefined;\n}\n\n/** Displayed status precedence: invalid > done/missed (one-shot) > expired > enabled flag. */\nfunction statusOf(def: ScheduleDefinition, state: ScheduleStateRow, nowMs: number): ScheduleStatus {\n if (state.invalidReason !== null) return \"invalid\";\n if (def.periodMs === undefined && state.firedOnce) return \"done\";\n if (def.periodMs === undefined && state.missed) return \"missed\";\n if (def.endAtMs !== undefined && nowMs > def.endAtMs) return \"expired\";\n return def.enabled ? \"active\" : \"disabled\";\n}\n\nfunction toItem(\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n queued: boolean,\n nowMs: number,\n): ScheduleItem {\n const next = nextFireAt(def, state, nowMs);\n return {\n name: def.name,\n prompt: def.prompt,\n enabled: def.enabled,\n startAt: def.startAt,\n ...(def.period !== undefined ? { period: def.period } : {}),\n ...(def.endAt !== undefined ? { endAt: def.endAt } : {}),\n ...(def.sessionId !== undefined ? { sessionId: def.sessionId } : {}),\n ...(def.workspace !== undefined ? { workspace: def.workspace } : {}),\n ...(def.modelId !== undefined ? { modelId: def.modelId } : {}),\n ...(def.provider !== undefined ? { provider: def.provider } : {}),\n status: statusOf(def, state, nowMs),\n ...(state.invalidReason !== null ? { invalidReason: state.invalidReason } : {}),\n ...(next !== undefined ? { nextFireAt: next } : {}),\n ...(state.lastFiredAt !== null ? { lastFiredAt: state.lastFiredAt } : {}),\n queued,\n ...(state.creatorUserId !== null ? { creatorUserId: state.creatorUserId } : {}),\n };\n}\n\n/** Schedule name in the path: same character rules as directories/files, validated before any path construction. */\nfunction requireScheduleName(raw: string | undefined): string {\n if (!raw || !isValidId(raw)) throw badRequest(\"Invalid schedule name.\");\n return raw;\n}\n\nexport function scheduleRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const { entries, invalid } = await deps.scheduler.listAgent(projectId, agentId);\n const nowMs = Date.now();\n const res: SchedulesResponse = {\n schedules: entries.map((e) => toItem(e.def, e.state, e.queued, nowMs)),\n invalidFiles: invalid,\n };\n return c.json(res);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const name = requireScheduleName(requireString(body, \"name\", { minLen: 1, maxLen: 100 }));\n if (await readScheduleFile(deps.config.root, projectId, agentId, name)) {\n throw new HttpError(409, \"schedule_exists\", `Schedule already exists: ${name}`);\n }\n await upsert(deps, c.var.user.userId, projectId, agentId, name, body);\n return c.json(await readItem(deps, projectId, agentId, name), 201);\n });\n\n app.get(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n const item = await readItem(deps, projectId, agentId, name);\n return c.json(item);\n });\n\n app.put(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n if (!(await readScheduleFile(deps.config.root, projectId, agentId, name))) {\n throw new HttpError(404, \"schedule_not_found\", `Schedule does not exist: ${name}`);\n }\n const body = await readJson(c);\n await upsert(deps, c.var.user.userId, projectId, agentId, name, body);\n return c.json(await readItem(deps, projectId, agentId, name));\n });\n\n app.delete(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n const name = requireScheduleName(c.req.param(\"name\"));\n const removed = await deleteScheduleFile(deps.config.root, projectId, agentId, name);\n if (!removed)\n throw new HttpError(404, \"schedule_not_found\", `Schedule does not exist: ${name}`);\n deps.scheduler.dropEntry(projectId, agentId, name);\n return c.body(null, 204);\n });\n\n return app;\n}\n\n/** Write + register creator + reconcile immediately (API changes take effect right away). */\nasync function upsert(\n deps: AppDeps,\n userId: string,\n projectId: string,\n agentId: string,\n name: string,\n body: Record<string, unknown>,\n): Promise<void> {\n const fields = parseUpsertBody(body);\n const raw = serializeSchedule(fields);\n const parsed = parseScheduleFile(name, raw);\n if (!parsed.ok) throw badRequest(`Invalid schedule configuration: ${parsed.error}`);\n // At save time, verify the (provider, modelId) pair names a configured model (same rules as reconciliation) so we never persist a broken file.\n // The pairing rule itself is enforced by parseScheduleFile above, which rejects half a reference.\n const refError = await validateScheduleModelRef(deps.config.root, projectId, parsed.def);\n if (refError !== null) throw badRequest(`Invalid schedule configuration: ${refError}`);\n await writeScheduleFile(deps.config.root, projectId, agentId, name, raw);\n // Creator attribution: the API writer is the creator (falls back to the Project owner only for hand-edited files).\n deps.schedulesRepo.registerOrSync({\n projectId,\n agentId,\n name,\n startAtMs: parsed.def.startAtMs,\n defHash: createHash(\"sha1\").update(raw).digest(\"hex\"),\n creatorUserId: userId,\n });\n await deps.scheduler.reconcileAgent(projectId, agentId);\n}\n\nasync function readItem(\n deps: AppDeps,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<ScheduleItem> {\n const { entries, invalid } = await deps.scheduler.listAgent(projectId, agentId);\n const entry = entries.find((e) => e.def.name === name);\n if (entry) return toItem(entry.def, entry.state, entry.queued, Date.now());\n const bad = invalid.find((i) => i.name === name);\n if (bad) throw badRequest(`Invalid schedule file: ${bad.error}`);\n throw new HttpError(404, \"schedule_not_found\", `Schedule does not exist: ${name}`);\n}\n","/**\n * Schedule file parsing, validation, and trigger-time computation.\n *\n * `agent_state/schedule/<name>.toml` is declarative intent; the system never writes it\n * back. This module does pure parsing and pure time math only: an invalid file returns\n * an error (the scheduler skips it and records the error); runtime state (fired /\n * missed / disabled) doesn't live here — it belongs to SQLite (db/repos/schedules.ts).\n * Docs: /docs/configuration § \"Schedules\".\n */\nimport { parse as parseToml } from \"smol-toml\";\n\n/** `period` lower bound: below 5 minutes is treated as an invalid file (guards against runaway high-frequency tasks). */\nexport const MIN_PERIOD_MS = 5 * 60_000;\n\n/** A parsed schedule definition (the filename minus `.toml` is its identity). */\nexport interface ScheduleDefinition {\n name: string;\n /** The Prompt to send (required). */\n prompt: string;\n /** Enabled switch; disabled by default. */\n enabled: boolean;\n /** Original text of the first trigger time (for API echo, preserving the written form). */\n startAt: string;\n /** First trigger time (epoch ms). */\n startAtMs: number;\n /** Original text of the end time. */\n endAt?: string;\n /** Original text of the trigger period (e.g. `30m`, for API echo); undefined means a one-shot task. */\n period?: string;\n /** Trigger period (ms); undefined means a one-shot task. */\n periodMs?: number;\n /** End time (epoch ms); no more triggers once past it. */\n endAtMs?: number;\n /** The target Session to bind to; defaults to creating a new Session each time. */\n sessionId?: string;\n /** Workspace for new-Session mode (same semantics as manually starting a session; auto-creates a temp directory if unspecified). */\n workspace?: string;\n /** Model for new-Session mode (upstream id, always paired with provider; omit both for the Project's default reference). */\n modelId?: string;\n /**\n * Vendor grouping for `model_id`; always present when `model_id` is (the pairing rule\n * is enforced here). Whether that pair names a configured model is validated by the\n * caller against config at reconciliation/save time — this module does pure parsing\n * and never touches config.\n */\n provider?: string;\n}\n\nexport type ScheduleParseResult =\n { ok: true; def: ScheduleDefinition } | { ok: false; error: string };\n\n/** Parse a fixed interval in `30m` / `12h` / `7d` form; returns null if invalid. */\nexport function parsePeriod(raw: string): number | null {\n const m = /^(\\d+)([mhd])$/.exec(raw.trim());\n if (!m) return null;\n const n = Number(m[1]);\n if (!Number.isInteger(n) || n <= 0) return null;\n const unit = m[2] === \"m\" ? 60_000 : m[2] === \"h\" ? 3_600_000 : 86_400_000;\n return n * unit;\n}\n\n/** Parse an ISO 8601 instant into epoch ms plus the original text for echo; returns null if invalid (smol-toml's date values are also accepted). */\nfunction parseInstant(value: unknown): { ms: number; raw: string } | null {\n if (value instanceof Date) {\n const ms = value.getTime();\n return Number.isNaN(ms) ? null : { ms, raw: value.toISOString() };\n }\n if (typeof value !== \"string\") return null;\n const ms = Date.parse(value);\n return Number.isNaN(ms) ? null : { ms, raw: value };\n}\n\n/**\n * Parse and validate a schedule file. A field with the wrong type invalidates the whole\n * file (the baseline for hand-edit tolerance is to never let bad config reach the\n * scheduler); unknown keys are ignored (forward compatibility).\n */\nexport function parseScheduleFile(name: string, raw: string): ScheduleParseResult {\n let parsed: unknown;\n try {\n parsed = parseToml(raw);\n } catch (err) {\n return {\n ok: false,\n error: `Failed to parse TOML: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n if (parsed === null || typeof parsed !== \"object\")\n return { ok: false, error: \"Content is not a TOML table\" };\n const t = parsed as Record<string, unknown>;\n\n const prompt = t[\"prompt\"];\n if (typeof prompt !== \"string\" || prompt.trim() === \"\") {\n return { ok: false, error: \"Missing required field prompt\" };\n }\n const enabled = t[\"enabled\"] === undefined ? false : t[\"enabled\"];\n if (typeof enabled !== \"boolean\") return { ok: false, error: \"enabled must be a boolean\" };\n\n const startAt = parseInstant(t[\"start_at\"]);\n if (startAt === null)\n return { ok: false, error: \"start_at is missing or not a valid ISO 8601 instant\" };\n\n let period: string | undefined;\n let periodMs: number | undefined;\n if (t[\"period\"] !== undefined) {\n if (typeof t[\"period\"] !== \"string\") return { ok: false, error: \"period must be a string\" };\n const ms = parsePeriod(t[\"period\"]);\n if (ms === null) return { ok: false, error: \"period must look like 30m / 12h / 7d\" };\n if (ms < MIN_PERIOD_MS) return { ok: false, error: \"period is below the 5m minimum\" };\n period = t[\"period\"].trim();\n periodMs = ms;\n }\n\n let endAt: { ms: number; raw: string } | undefined;\n if (t[\"end_at\"] !== undefined) {\n const parsedEnd = parseInstant(t[\"end_at\"]);\n if (parsedEnd === null) return { ok: false, error: \"end_at is not a valid ISO 8601 instant\" };\n if (parsedEnd.ms <= startAt.ms)\n return { ok: false, error: \"end_at must be later than start_at\" };\n endAt = parsedEnd;\n }\n\n let sessionId: string | undefined;\n if (t[\"session_id\"] !== undefined) {\n if (typeof t[\"session_id\"] !== \"string\" || t[\"session_id\"] === \"\") {\n return { ok: false, error: \"session_id must be a non-empty string\" };\n }\n sessionId = t[\"session_id\"];\n }\n let workspace: string | undefined;\n if (t[\"workspace\"] !== undefined) {\n if (typeof t[\"workspace\"] !== \"string\" || t[\"workspace\"] === \"\") {\n return { ok: false, error: \"workspace must be a non-empty string\" };\n }\n workspace = t[\"workspace\"];\n }\n let modelId: string | undefined;\n if (t[\"model_id\"] !== undefined) {\n if (typeof t[\"model_id\"] !== \"string\" || t[\"model_id\"] === \"\") {\n return { ok: false, error: \"model_id must be a non-empty string\" };\n }\n modelId = t[\"model_id\"];\n }\n let provider: string | undefined;\n if (t[\"provider\"] !== undefined) {\n if (typeof t[\"provider\"] !== \"string\" || t[\"provider\"] === \"\") {\n return { ok: false, error: \"provider must be a non-empty string\" };\n }\n provider = t[\"provider\"];\n }\n // Target conflicts are reported first: when session_id is set the whole model reference\n // is out of place, so complaining about its shape would point at the wrong fix.\n if (\n sessionId !== undefined &&\n (workspace !== undefined || modelId !== undefined || provider !== undefined)\n ) {\n return {\n ok: false,\n error: \"Pick one target: workspace and provider / model_id are only for new-Session mode\",\n };\n }\n // A model reference is always a complete (provider, model_id) pair: neither half is\n // ever inferred from the other, so a file carrying only one of them is invalid rather\n // than something to resolve later. Omit both to run on the Project's default model.\n // A file written before this rule existed (model_id alone) therefore parses as\n // invalid: it is listed in invalidFiles and skipped, never scheduled.\n if ((modelId === undefined) !== (provider === undefined)) {\n return {\n ok: false,\n error:\n \"provider and model_id must be given together (a model reference is always a pair); omit both to use the Project's default model\",\n };\n }\n\n return {\n ok: true,\n def: {\n name,\n prompt,\n enabled,\n startAt: startAt.raw,\n startAtMs: startAt.ms,\n ...(period !== undefined ? { period } : {}),\n ...(periodMs !== undefined ? { periodMs } : {}),\n ...(endAt !== undefined ? { endAt: endAt.raw, endAtMs: endAt.ms } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n },\n };\n}\n\n/**\n * Step from `start_at` by `period` and return the most recent scheduled time not later\n * than `nowMs`; null if `start_at` hasn't been reached yet. A one-shot task's only slot\n * is `start_at` itself.\n */\nexport function latestSlotAt(def: ScheduleDefinition, nowMs: number): number | null {\n if (nowMs < def.startAtMs) return null;\n if (def.periodMs === undefined) return def.startAtMs;\n const k = Math.floor((nowMs - def.startAtMs) / def.periodMs);\n return def.startAtMs + k * def.periodMs;\n}\n\n/** Whether a scheduled slot still falls within the `[start_at, end_at]` window (always true if there's no end_at). */\nexport function slotInWindow(def: ScheduleDefinition, slotMs: number): boolean {\n return def.endAtMs === undefined || slotMs <= def.endAtMs;\n}\n\n/**\n * The next scheduled time strictly after `nowMs` (used to display \"next trigger\");\n * for a one-shot task this only has a value while start_at hasn't been reached, and\n * returns null once past end_at.\n */\nexport function nextSlotAfter(def: ScheduleDefinition, nowMs: number): number | null {\n let next: number;\n if (nowMs < def.startAtMs) {\n next = def.startAtMs;\n } else if (def.periodMs === undefined) {\n return null;\n } else {\n const k = Math.floor((nowMs - def.startAtMs) / def.periodMs) + 1;\n next = def.startAtMs + k * def.periodMs;\n }\n return slotInWindow(def, next) ? next : null;\n}\n","/**\n * Schedule file access: `agent_state/schedule/<name>.toml`, where\n * the filename (a semantic name) is the identity. Reads are fault-tolerant (an invalid\n * file is recorded as an error and skipped by the caller); writes only go through the\n * API routes (the system never rewrites existing file content — PUT is a full-file\n * replacement expressing user intent).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { stringify as stringifyToml } from \"smol-toml\";\nimport { loadProjectConfig, resolveModelRef, scheduleDir } from \"@prismshadow/penguin-core\";\nimport type { ScheduleDefinition } from \"./schedule-file.js\";\nimport { parseScheduleFile, type ScheduleParseResult } from \"./schedule-file.js\";\n\nexport interface ScheduleFileEntry {\n name: string;\n raw: string;\n parsed: ScheduleParseResult;\n}\n\n/** List all schedule files for this Agent (a missing directory is treated as empty). */\nexport async function listScheduleFiles(\n root: string,\n projectId: string,\n agentId: string,\n): Promise<ScheduleFileEntry[]> {\n const dir = scheduleDir(root, projectId, agentId);\n let names: string[];\n try {\n names = await fs.readdir(dir);\n } catch {\n return [];\n }\n const entries: ScheduleFileEntry[] = [];\n for (const file of names.sort()) {\n if (!file.endsWith(\".toml\")) continue;\n const name = file.slice(0, -\".toml\".length);\n let raw: string;\n try {\n raw = await fs.readFile(path.join(dir, file), \"utf8\");\n } catch {\n continue; // Deleted during reconciliation: revisit next round.\n }\n entries.push({ name, raw, parsed: parseScheduleFile(name, raw) });\n }\n return entries;\n}\n\nexport async function readScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<ScheduleFileEntry | null> {\n const file = path.join(scheduleDir(root, projectId, agentId), `${name}.toml`);\n try {\n const raw = await fs.readFile(file, \"utf8\");\n return { name, raw, parsed: parseScheduleFile(name, raw) };\n } catch {\n return null;\n }\n}\n\n/** Serialize API fields into file content (validation uniformly goes through parseScheduleFile, avoiding two sets of rules). */\nexport function serializeSchedule(fields: {\n prompt: string;\n enabled: boolean;\n startAt: string;\n period?: string;\n endAt?: string;\n sessionId?: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n}): string {\n const table: Record<string, unknown> = {\n prompt: fields.prompt,\n enabled: fields.enabled,\n start_at: fields.startAt,\n ...(fields.period !== undefined ? { period: fields.period } : {}),\n ...(fields.endAt !== undefined ? { end_at: fields.endAt } : {}),\n ...(fields.sessionId !== undefined ? { session_id: fields.sessionId } : {}),\n ...(fields.workspace !== undefined ? { workspace: fields.workspace } : {}),\n ...(fields.provider !== undefined ? { provider: fields.provider } : {}),\n ...(fields.modelId !== undefined ? { model_id: fields.modelId } : {}),\n };\n return `${stringifyToml(table)}\\n`;\n}\n\n/**\n * Config check for a schedule's model reference (shared by save and reconciliation):\n * the reference is a complete `(provider, model_id)` pair and must name an entry in the\n * Project config — nothing is inferred, so a pair that matches no entry is simply an\n * error. Returns an error message (no such model / half a reference / config read\n * failure), or null when the pair is configured (or there is no model reference at all).\n */\nexport async function validateScheduleModelRef(\n root: string,\n projectId: string,\n def: Pick<ScheduleDefinition, \"modelId\" | \"provider\">,\n): Promise<string | null> {\n if (def.modelId === undefined && def.provider === undefined) return null;\n // parseScheduleFile already rejects half a reference, so this only guards callers that\n // build a definition by hand — the missing half is reported, never guessed.\n if (def.modelId === undefined || def.provider === undefined) {\n return \"provider and model_id must be given together (a model reference is always a pair); omit both to use the Project's default model\";\n }\n try {\n const cfg = await loadProjectConfig(root, projectId);\n resolveModelRef(cfg, def.modelId, def.provider);\n return null;\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n}\n\n/** Write a schedule file to disk (full-file replacement for POST/PUT). */\nexport async function writeScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n raw: string,\n): Promise<void> {\n const dir = scheduleDir(root, projectId, agentId);\n await fs.mkdir(dir, { recursive: true });\n await fs.writeFile(path.join(dir, `${name}.toml`), raw, \"utf8\");\n}\n\n/** Delete a schedule file; returns false if it doesn't exist. */\nexport async function deleteScheduleFile(\n root: string,\n projectId: string,\n agentId: string,\n name: string,\n): Promise<boolean> {\n const file = path.join(scheduleDir(root, projectId, agentId), `${name}.toml`);\n try {\n await fs.unlink(file);\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Benchmark scoring routes:\n * GET /api/projects/:p/agents/:a/benchmarks (any member, read-only)\n * Returns the Agent's Benchmark list (title/description from benchmark_config.toml)\n * along with the evaluations[] from scoreboard.yaml.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { requireValidId } from \"../validate.js\";\n\nexport function benchmarksRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(await deps.benchmarks.list(projectId, agentId));\n });\n\n return app;\n}\n","/**\n * Skill library & Agent-installed-Skills routes:\n * GET /api/skills # library groups & metadata (any logged-in user)\n * GET|POST /api/projects/:p/agents/:a/skills # installed list / install from library (any member)\n * DELETE /api/projects/:p/agents/:a/skills/:name # uninstall (any member)\n * Installing writes the library's SKILL.md verbatim to agent_state/skills/<name>/;\n * reinstalling overwrites with the library content (i.e. an update). The scope is small\n * enough to skip a service layer — routes call core's disk-writing functions directly.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport {\n installSkill,\n listInstalledSkills,\n removeSkill,\n skillsDir,\n} from \"@prismshadow/penguin-core\";\nimport { librarySkill, loadSkillGroups } from \"@prismshadow/penguin-skills\";\nimport type { LibrarySkill, SkillMetadata } from \"@prismshadow/penguin-skills\";\nimport type {\n AgentSkillsResponse,\n SkillLibraryResponse,\n SkillMetadataItem,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { HttpError } from \"../errors.js\";\nimport { badRequest, readJson, requireValidId } from \"../validate.js\";\n\n/**\n * Strips the content off a LibrarySkill: the API only sends metadata; the full body is\n * written to disk on install and read by the model on demand. The optional short\n * description (shortDescription(Zh)) and custom icon (icon.svg source) are conditionally\n * passed through — both the library side (LibrarySkill) and the installed side (core\n * InstalledSkill) carry these fields.\n */\nfunction toMetadataItem(skill: SkillMetadata & { icon?: string }): SkillMetadataItem {\n return {\n name: skill.name,\n description: skill.description,\n ...(skill.shortDescription !== undefined ? { shortDescription: skill.shortDescription } : {}),\n ...(skill.shortDescriptionZh !== undefined\n ? { shortDescriptionZh: skill.shortDescriptionZh }\n : {}),\n ...(skill.icon !== undefined ? { icon: skill.icon } : {}),\n version: skill.version,\n updated: skill.updated,\n };\n}\n\n/** Library listing response: the files are the source of truth — read and parse the library directory fresh on every request (files are small, requests infrequent, no caching needed). */\nfunction libraryResponse(): SkillLibraryResponse {\n return {\n groups: loadSkillGroups().map((group) => ({\n id: group.id,\n title: group.title,\n ...(group.titleZh !== undefined ? { titleZh: group.titleZh } : {}),\n skills: group.skills.map(toMetadataItem),\n })),\n };\n}\n\n/** Validate the POST request body: names must be a non-empty array of strings. */\nfunction parseInstallNames(body: Record<string, unknown>): string[] {\n if (!Array.isArray(body.names) || body.names.length === 0) {\n throw badRequest(\"names must be a non-empty array.\");\n }\n return body.names.map((v, i) => {\n if (typeof v !== \"string\" || v.length === 0) {\n throw badRequest(`names[${i}] must be a non-empty string.`);\n }\n return v;\n });\n}\n\n/** GET /api/skills: Skill library groups & metadata (any logged-in user; no Project check). */\nexport function skillLibraryRoutes(): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n app.get(\"/\", (c) => c.json(libraryResponse()));\n return app;\n}\n\n/** /api/projects/:p/agents/:a/skills: read, install, and uninstall are all Project-member operations. */\nexport function agentSkillsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n const listResponse = async (\n projectId: string,\n agentId: string,\n ): Promise<AgentSkillsResponse> => ({\n skills: (await listInstalledSkills(deps.config.root, projectId, agentId)).map(toMetadataItem),\n });\n\n app.get(\"/\", async (c) => {\n // Defensive id validation happens before any path construction (FD-4: prevents path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(await listResponse(projectId, agentId));\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const names = parseInstallNames(await readJson(c));\n // Verify all names up front before writing anything: if any name isn't in the library, reject the whole request rather than leaving a half-installed state.\n const skills: LibrarySkill[] = names.map((name) => {\n const skill = librarySkill(name);\n if (!skill) throw new HttpError(404, \"unknown_skill\", `Skill is not in the library: ${name}`);\n return skill;\n });\n for (const skill of skills) {\n await installSkill(deps.config.root, projectId, agentId, skill);\n }\n return c.json(await listResponse(projectId, agentId), 201);\n });\n\n app.delete(\"/:name\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const name = requireValidId(c, \"name\");\n // Installed-check uses the same criterion as listInstalledSkills: skills/<name>/SKILL.md exists.\n const file = path.join(skillsDir(deps.config.root, projectId, agentId), name, \"SKILL.md\");\n try {\n await fs.access(file);\n } catch {\n throw new HttpError(404, \"not_found\", `Skill is not installed: ${name}`);\n }\n await removeSkill(deps.config.root, projectId, agentId, name);\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Agent State export/import routes:\n * GET /api/projects/:p/agents/:a/export (any member; auto-packages if no snapshot exists, downloads tar.gz)\n * POST /api/projects/:p/agents/:a/import (owner only; version conflicts require a confirm flag)\n */\nimport fs from \"node:fs/promises\";\nimport { Hono } from \"hono\";\nimport type { AgentImportResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { badRequest, readJson, requireString, requireValidId } from \"../validate.js\";\n\n/** Import archive size cap: aligned with the global request body limit (stays within 20MB after base64). */\nconst MAX_ARCHIVE_BYTES = 14 * 1024 * 1024;\n\nexport function agentTransferRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/export\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const { file, fileName } = await deps.snapshots.exportArchive(projectId, agentId);\n const bytes = await fs.readFile(file);\n return new Response(new Uint8Array(bytes), {\n headers: {\n \"Content-Type\": \"application/gzip\",\n \"Content-Disposition\": `attachment; filename=\"${fileName}\"`,\n },\n });\n });\n\n app.post(\"/import\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const dataBase64 = requireString(body, \"dataBase64\", { minLen: 1, maxLen: 20 * 1024 * 1024 });\n const confirm = body.confirm === true;\n let archive: Buffer;\n try {\n archive = Buffer.from(dataBase64, \"base64\");\n } catch {\n throw badRequest(\"dataBase64 is not valid base64.\");\n }\n if (archive.byteLength === 0) throw badRequest(\"Import package is empty.\");\n if (archive.byteLength > MAX_ARCHIVE_BYTES)\n throw badRequest(\"Import package exceeds the 14MB limit.\");\n const { version } = await deps.snapshots.importArchive(projectId, agentId, archive, confirm);\n const res: AgentImportResponse = { version };\n return c.json(res);\n });\n\n return app;\n}\n","/**\n * Agent routes:\n * GET|POST /api/projects/:p/agents, DELETE /:agentId (owner only).\n * The list is the union of DB entries and directory scan results, including active\n * Session count, total Session count, and config last-modified time.\n */\nimport { Hono } from \"hono\";\nimport type { AgentCreateResponse, AgentsResponse, AgentSummary } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { settleWithin } from \"../settle.js\";\nimport { optionalString, readJson, requireString, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Window size in days for the card's activity sparkline (last 30 days, including today). */\nconst ACTIVITY_DAYS = 30;\n\nexport function agentsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4): don't rely on the implicit invariant that requireProjectAccess always runs before path construction.\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const items = await deps.agentService.listAgents(projectId);\n const agents: AgentSummary[] = await Promise.all(\n items.map(async (item) => {\n const stats = await deps.sessionService.sessionStats(\n projectId,\n item.agentId,\n ACTIVITY_DAYS,\n );\n return {\n ...item,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, item.agentId),\n sessionCount: stats.sessionCount,\n sessionActivity: stats.activity,\n };\n }),\n );\n return c.json({ agents } satisfies AgentsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const body = await readJson(c);\n const agentId = requireString(body, \"agentId\", { label: \"agentId\" });\n const name = optionalString(body, \"name\", { minLen: 1, maxLen: 100, label: \"name\" });\n const description = optionalString(body, \"description\", {\n maxLen: 2000,\n label: \"description\",\n });\n const item = await deps.agentService.createAgent(projectId, agentId, name, description);\n const agent: AgentSummary = {\n ...item,\n activeSessionCount: 0,\n sessionCount: 0,\n sessionActivity: Array.from({ length: ACTIVITY_DAYS }, () => 0),\n };\n return c.json({ agent } satisfies AgentCreateResponse, 201);\n });\n\n app.delete(\"/:agentId\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n // Deletion is a Project-level management operation: owner only.\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n // Mark as deleting and converge active runs (beginAgentDeletion): any new Task during\n // this window gets 409, preventing the race where a new task recreates the directory\n // and revives the Agent between abort and rm. Abort cleanup writes the Trace\n // asynchronously; wait for it to finish before removing the directory, and clear the\n // deleting flag once deletion completes (success or failure).\n const runnings = deps.manager.beginAgentDeletion(projectId, agentId);\n try {\n await settleWithin(runnings, 5000);\n await deps.agentService.deleteAgent(projectId, agentId);\n deps.sessionsRepo.deleteByAgent(projectId, agentId);\n // Per-Agent runtime state keyed on the now-removed sessions/agent: drop it so nothing is\n // orphaned (session ids are never reused, so a leftover row is dead weight). Usage records\n // are deliberately kept — historical stats survive Agent deletion (see deleteAgent).\n deps.goalsRepo.deleteByAgent(projectId, agentId);\n deps.schedulesRepo.deleteByAgent(projectId, agentId);\n deps.errorsRepo.deleteByAgent(projectId, agentId);\n } finally {\n deps.manager.endAgentDeletion(projectId, agentId);\n }\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Convergent wait for delete routes (currently used by agents DELETE; sessions DELETE\n * uses the same inline pattern and could later be unified onto this): waits for aborted\n * runs to wind down, up to ms milliseconds; returns whether all of them settled within\n * the window. The timer is unref'd so it never blocks process exit.\n */\nexport async function settleWithin(promises: Promise<unknown>[], ms: number): Promise<boolean> {\n if (promises.length === 0) return true;\n return Promise.race([\n Promise.allSettled(promises).then(() => true),\n new Promise<boolean>((resolve) => setTimeout(() => resolve(false), ms).unref?.()),\n ]);\n}\n","/**\n * Server directory browsing:\n * GET /api/projects/:p/dirs?path=<absolute>.\n *\n * Lets the user interactively pick a Workspace directory when creating a Session via\n * advanced mode. Defaults to the home directory of the account running the service, and\n * can be browsed all the way up to the root `/` — reachability is governed by OS file\n * permissions; the server no longer restricts browsing to within the Project directory\n * tree (same convention as workspace-guard). Lists subdirectories only, not files.\n *\n * `projectId` remains the authorization anchor: the caller must have access to that Project.\n */\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { DirListResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { HttpError } from \"../errors.js\";\nimport { requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function dirsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n\n // Default starting point: home directory; an explicit path must be absolute (the frontend always sends back the realpath result).\n const raw = c.req.query(\"path\");\n const target = raw && raw.trim() ? raw.trim() : os.homedir();\n if (!path.isAbsolute(target)) {\n throw new HttpError(400, \"dir_not_absolute\", \"Directory must be an absolute path.\");\n }\n\n let real: string;\n try {\n real = await fs.realpath(target);\n } catch {\n throw new HttpError(\n 404,\n \"dir_not_found\",\n `Directory does not exist or is inaccessible: ${target}.`,\n );\n }\n const stat = await fs.stat(real);\n if (!stat.isDirectory()) {\n throw new HttpError(400, \"not_a_dir\", \"Not a directory.\");\n }\n\n let dirents: import(\"node:fs\").Dirent[] = [];\n try {\n dirents = await fs.readdir(real, { withFileTypes: true });\n } catch {\n // No read permission: return an empty list instead of an error, so the user can still navigate back up.\n dirents = [];\n }\n const entries = dirents\n .filter((d) => d.isDirectory())\n .map((d) => ({ name: d.name, path: path.join(real, d.name) }))\n .sort((a, b) => a.name.localeCompare(b.name));\n\n const parent = path.dirname(real);\n return c.json({\n path: real,\n parent: parent === real ? null : parent,\n entries,\n } satisfies DirListResponse);\n });\n\n return app;\n}\n","/**\n * Agent config routes (reads/writes system_config.yaml and AGENTS.md):\n * GET|PUT /api/projects/:p/agents/:a/config, plus POST …/config/reset to adopt the\n * current default config (keeps only name/description/version — the config-side\n * analogue of a skill update). Members can read and write (unrestricted).\n */\nimport { Hono } from \"hono\";\nimport type { AgentConfigResponse, AgentConfigUpdateRequest } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, optionalString, readJson, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function agentConfigRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const view = await deps.agentConfigService.getConfig(projectId, agentId);\n return c.json({\n ...view,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId),\n } satisfies AgentConfigResponse);\n });\n\n app.put(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const body = await readJson(c);\n const req: AgentConfigUpdateRequest = {};\n const agentsMd = optionalString(body, \"agentsMd\", { label: \"agentsMd\" });\n if (agentsMd !== undefined) req.agentsMd = agentsMd;\n if (body.config !== undefined) {\n if (body.config === null || typeof body.config !== \"object\" || Array.isArray(body.config)) {\n throw badRequest(\"config must be an object.\");\n }\n req.config = body.config as AgentConfigUpdateRequest[\"config\"];\n }\n // Fine-grained validation (numeric ranges / enums) is done inside agent-config-service.\n await deps.agentConfigService.updateConfig(projectId, agentId, req);\n const view = await deps.agentConfigService.getConfig(projectId, agentId);\n return c.json({\n ...view,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId),\n } satisfies AgentConfigResponse);\n });\n\n // Overwrite system_config.yaml with the current defaults (see AgentConfigService.resetConfig\n // for the exact semantics); same authorization as PUT, and responds like GET/PUT with the\n // fresh config so the client can refresh in place.\n app.post(\"/reset\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.resetConfig(projectId, agentId);\n const view = await deps.agentConfigService.getConfig(projectId, agentId);\n return c.json({\n ...view,\n activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId),\n } satisfies AgentConfigResponse);\n });\n\n return app;\n}\n","/**\n * Agent-level Trace browsing routes:\n * - GET /api/projects/:p/agents/:a/traces — drills down Agent -> date -> Session -> index (reverse order);\n * - GET /api/projects/:p/agents/:a/traces/:sessionId/:index (including /analysis, /download) —\n * read-only Trace detail endpoints (FD-3): locate the Trace file directly by\n * (projectId, agentId, sessionId), without depending on the sessions table for\n * tracking — any entry visible in the directory tree (subagent child Sessions,\n * CLI-created Sessions) can be opened and read; access is enforced by requireProjectAccess.\n * - POST /api/projects/:p/agents/:a/traces/import — uploads a Trace JSONL file (owner\n * only, mirroring the Agent snapshot import); the file names itself via its\n * session_meta and always becomes index 001 of a new Session — a session id the\n * Agent already has is rejected with 409 trace_session_exists.\n */\nimport { Hono } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { TraceImportResponse } from \"../../api/types.js\";\nimport {\n badRequest,\n paginationQuery,\n positiveIntParam,\n readJson,\n requireString,\n requireValidId,\n} from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Import file size cap: aligned with the snapshot import (stays within the 20MB body limit after base64). */\nconst MAX_TRACE_BYTES = 14 * 1024 * 1024;\n\nexport function agentTracesRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validation happens before any path construction (FD-4: prevents agentId path traversal for cross-Project privilege escalation).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return c.json(await deps.traceService.agentTraces(projectId, agentId));\n });\n\n app.get(\"/:sessionId/:index\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const sessionId = requireValidId(c, \"sessionId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const index = positiveIntParam(c, \"index\");\n const { offset, limit } = paginationQuery(c);\n return c.json(\n await deps.traceService.readEvents(projectId, agentId, sessionId, index, offset, limit),\n );\n });\n\n app.get(\"/:sessionId/:index/analysis\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const sessionId = requireValidId(c, \"sessionId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const index = positiveIntParam(c, \"index\");\n return c.json(await deps.traceService.analyze(projectId, agentId, sessionId, index));\n });\n\n // Raw-file download (any member, like the snapshot export): the file is served verbatim\n // as an attachment, so what's downloaded can be re-imported byte-compatibly.\n app.get(\"/:sessionId/:index/download\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const sessionId = requireValidId(c, \"sessionId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const index = positiveIntParam(c, \"index\");\n const bytes = await deps.traceService.readFileRaw(projectId, agentId, sessionId, index);\n const fileName = `${sessionId}_${String(index).padStart(3, \"0\")}.jsonl`;\n return new Response(new Uint8Array(bytes), {\n headers: {\n \"Content-Type\": \"application/x-ndjson\",\n \"Content-Disposition\": `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n \"X-Content-Type-Options\": \"nosniff\",\n },\n });\n });\n\n // Trace file upload (owner only, mirroring the Agent snapshot import): the route checks the\n // transport shape (base64, size); the content itself — JSONL, leading session_meta, a\n // filename-safe session_id — is validated by the service right where the path is built.\n app.post(\"/import\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectOwner(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const dataBase64 = requireString(body, \"dataBase64\", { minLen: 1, maxLen: 20 * 1024 * 1024 });\n const bytes = Buffer.from(dataBase64, \"base64\");\n if (bytes.byteLength === 0) throw badRequest(\"Import file is empty.\");\n if (bytes.byteLength > MAX_TRACE_BYTES) {\n throw badRequest(\"Import file exceeds the 14MB limit.\");\n }\n const res: TraceImportResponse = await deps.traceService.importTraceFile(\n projectId,\n agentId,\n bytes.toString(\"utf8\"),\n );\n return c.json(res);\n });\n\n return app;\n}\n","/**\n * Usage statistics routes:\n * GET /api/projects/:p/usage?from&to&groupBy&agentId&provider&modelId\n * (model filter is paired: provider and modelId are given together).\n */\nimport { Hono } from \"hono\";\nimport type { UsageGroupBy } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, optionalDateParam, requireValidId } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nconst GROUP_BYS: readonly UsageGroupBy[] = [\"date\", \"agent\", \"model\", \"session\"];\n\nexport function usageRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Defensive id validation (FD-4).\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const groupByRaw = c.req.query(\"groupBy\") ?? \"date\";\n if (!(GROUP_BYS as readonly string[]).includes(groupByRaw)) {\n throw badRequest(`groupBy must be one of ${GROUP_BYS.join(\" / \")}.`);\n }\n const from = optionalDateParam(c.req.query(\"from\"), \"from\");\n const to = optionalDateParam(c.req.query(\"to\"), \"to\");\n const agentId = c.req.query(\"agentId\");\n const provider = c.req.query(\"provider\");\n const modelId = c.req.query(\"modelId\");\n return c.json(\n await deps.usageService.query(projectId, {\n groupBy: groupByRaw as UsageGroupBy,\n // Unattributed errors (login failures, process crashes, etc. with no Project\n // context) are visible only to admins: requireProjectAccess only guarantees\n // \"is a member of this Project\" — a regular member seeing another tenant's errors\n // would be a cross-tenant information leak.\n includeGlobalErrors: c.var.user.isAdmin,\n ...(from !== undefined ? { from } : {}),\n ...(to !== undefined ? { to } : {}),\n ...(agentId !== undefined && agentId !== \"\" ? { agentId } : {}),\n ...(provider !== undefined && provider !== \"\" ? { provider } : {}),\n ...(modelId !== undefined && modelId !== \"\" ? { modelId } : {}),\n }),\n );\n });\n\n return app;\n}\n","/**\n * Session routes.\n *\n * Two entry groups:\n * - Agent-level: GET|POST /api/projects/:p/agents/:a/sessions (list including run state / create);\n * - Session-level: /api/sessions/:sessionId/* (no projectId; looks up project_id via the\n * sessions index, then goes through requireProjectAccess; 404 if the index has no such Session).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport { imageUrlMessage, scratchpadDir, userText } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage, ThinkingLevelName } from \"@prismshadow/penguin-core\";\nimport type {\n ApprovalMode,\n FilesStatResponse,\n GoalResponse,\n MessagesLiveTail,\n MessagesResponse,\n ServerEvent,\n SessionCategory,\n SessionCreateResponse,\n SessionResponse,\n SessionsResponse,\n RetryNowResponse,\n TaskCreateResponse,\n} from \"../../api/types.js\";\nimport { PREVIEW_TOKEN_TTL_MS, resolvePreviewTarget } from \"../../services/preview-token.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { SessionRow } from \"../../db/repos/sessions.js\";\nimport { assertWorkspaceAllowed } from \"../../services/workspace-guard.js\";\nimport { HttpError } from \"../errors.js\";\nimport { sseEndpoint } from \"../sse.js\";\nimport {\n badRequest,\n optionalEnum,\n optionalPagingQuery,\n optionalString,\n paginationQuery,\n pathParam,\n positiveIntParam,\n readJson,\n requireEnum,\n requireValidId,\n} from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { MAX_UPLOAD_BYTES } from \"../../services/workspace-files-service.js\";\n\n/** Max title length for manual renames: looser than the auto-generated 30-char limit, to accommodate users' own organizing conventions. */\nconst SESSION_TITLE_MAX = 120;\n\n/** Max path count and per-path length for a single files/stat check (message file-card candidates never exceed this scale). */\nconst STAT_MAX_PATHS = 100;\nconst STAT_MAX_PATH_LEN = 512;\n\nconst APPROVAL_MODES: readonly ApprovalMode[] = [\n \"allow-all\",\n \"deny-all\",\n \"read-only\",\n \"always-ask\",\n];\n\n/** The five valid per-turn thinking level names (TaskCreateRequest.thinkingLevel). */\nconst THINKING_LEVELS: readonly ThinkingLevelName[] = [\"none\", \"low\", \"medium\", \"high\", \"xhigh\"];\n\n/** Accepted `category` query values of the list endpoint (SessionCategory, spelled out for validation). */\nconst SESSION_CATEGORIES: readonly SessionCategory[] = [\n \"active\",\n \"subagent\",\n \"schedule\",\n \"archived\",\n];\n\n/** Validate Prompt input parts: text or image (data: / http(s) URL). */\nfunction parseTaskInput(body: Record<string, unknown>): OmniMessage[] {\n const input = body.input;\n if (!Array.isArray(input) || input.length === 0) {\n throw badRequest(\"input must be an array with at least one item.\");\n }\n return input.map((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`input[${i}] must be an object.`);\n }\n const part = item as Record<string, unknown>;\n if (part.type === \"text\") {\n if (typeof part.text !== \"string\" || part.text.length === 0) {\n throw badRequest(`input[${i}].text must be a non-empty string.`);\n }\n return userText(part.text);\n }\n if (part.type === \"image_url\") {\n const url = part.imageUrl;\n if (\n typeof url !== \"string\" ||\n !(url.startsWith(\"data:\") || url.startsWith(\"http://\") || url.startsWith(\"https://\"))\n ) {\n throw badRequest(`input[${i}].imageUrl only supports data: or http(s) URLs.`);\n }\n return imageUrlMessage(url);\n }\n throw badRequest(`input[${i}].type must be one of text / image_url.`);\n });\n}\n\n/**\n * Validate the optional `goal` field of a task request: absent = a regular task (null);\n * present = goal mode with a token budget (a positive integer, or -1/omitted = unlimited).\n * The input text is the objective — skills ride the text itself as a `[use_skills]` block,\n * exactly like a regular task's message.\n */\nfunction parseGoalField(body: Record<string, unknown>): { budget: number } | null {\n const goal = body.goal;\n if (goal === undefined) return null;\n if (goal === null || typeof goal !== \"object\" || Array.isArray(goal)) {\n throw badRequest(\"goal must be an object.\");\n }\n const budget = (goal as Record<string, unknown>).budget;\n if (\n budget !== undefined &&\n (typeof budget !== \"number\" || !Number.isInteger(budget) || (budget <= 0 && budget !== -1))\n ) {\n throw badRequest(\"goal.budget must be a positive integer, or -1 for unlimited.\");\n }\n return { budget: (budget as number | undefined) ?? -1 };\n}\n\n/** Agent-level entry: /api/projects/:p/agents/:a/sessions. */\nexport function agentSessionsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n app.get(\"/\", async (c) => {\n // Id validity is checked before any path is constructed (FD-4: guards against agentId path traversal across Projects).\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n // Optional paging (absent = full list, the pre-paging contract): the sidebar requests\n // limit+1 and shows limit, detecting \"has more\" without a response-envelope change.\n const paging = optionalPagingQuery(c);\n // Optional category filter (paging then applies within the category) and per-category\n // totals — the sidebar loads active rows only and labels the collapsed folders from counts.\n const rawCategory = c.req.query(\"category\");\n if (rawCategory !== undefined && !SESSION_CATEGORIES.includes(rawCategory as SessionCategory)) {\n throw badRequest(`category must be one of ${SESSION_CATEGORIES.join(\" / \")}.`);\n }\n const rawCounts = c.req.query(\"counts\");\n if (rawCounts !== undefined && rawCounts !== \"1\") throw badRequest(\"counts only accepts 1.\");\n const { sessions, counts, workspaceCounts } = await deps.sessionService.listSessions(\n projectId,\n agentId,\n {\n ...(paging ? { paging } : {}),\n ...(rawCategory !== undefined ? { category: rawCategory as SessionCategory } : {}),\n ...(rawCounts !== undefined ? { withCounts: true } : {}),\n },\n );\n return c.json({\n sessions,\n ...(counts ? { counts } : {}),\n ...(workspaceCounts ? { workspaceCounts } : {}),\n } satisfies SessionsResponse);\n });\n\n app.post(\"/\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const body = await readJson(c);\n const modelId = optionalString(body, \"modelId\", { minLen: 1, label: \"modelId\" });\n const provider = optionalString(body, \"provider\", { minLen: 1, label: \"provider\" });\n // Model reference is submitted as a pair — both or neither. Neither half is ever\n // inferred from the other, so half a reference is rejected here instead of being\n // resolved (core does the same validation; this catches it early). Omitting both\n // falls back to the Project's default model.\n if ((modelId === undefined) !== (provider === undefined)) {\n throw badRequest(\n \"modelId and provider must be given together as a model reference pair: specify both, or neither to use the Project's default model.\",\n );\n }\n const approvalMode = optionalEnum(body, \"approvalMode\", APPROVAL_MODES);\n let workspace = optionalString(body, \"workspace\", { minLen: 1, label: \"workspace\" });\n if (workspace !== undefined) {\n // An explicitly specified Workspace must be an existing directory (never auto-created); reachability is determined by file permissions.\n workspace = await assertWorkspaceAllowed({ workspace });\n }\n const session = await deps.sessionService.createSession({\n projectId,\n agentId,\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(approvalMode !== undefined ? { approvalMode } : {}),\n });\n return c.json({ session } satisfies SessionCreateResponse, 201);\n });\n\n return app;\n}\n\n/** Session-level entry point: /api/sessions/:sessionId/*. */\nexport function sessionsRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n /** Look up ownership and check access (404 if the index has no such Session, or access is denied — never leaking existence). */\n const resolveSession = (c: Context<AppEnv>): SessionRow => {\n const sessionId = c.req.param(\"sessionId\");\n const row = sessionId ? deps.sessionsRepo.findById(sessionId) : null;\n if (!row) {\n throw new HttpError(\n 404,\n \"session_not_found\",\n \"Session does not exist or you do not have access.\",\n );\n }\n try {\n deps.projectService.requireProjectAccess(c.var.user.userId, row.projectId);\n } catch {\n throw new HttpError(\n 404,\n \"session_not_found\",\n \"Session does not exist or you do not have access.\",\n );\n }\n return row;\n };\n\n app.get(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n const hasTrace = await deps.sessionService.hasTrace(row);\n const info = await deps.sessionService.toInfo(row, hasTrace);\n // Single-session GET only: the latest Trace file's absolute path (a directory walk per\n // call — too costly for list rows). The web's /model switch hands it to the new session's\n // [model_switch_from] block so the model can read the source history itself.\n const tracePath = hasTrace ? await deps.sessionService.latestTracePath(row) : undefined;\n return c.json({\n session: { ...info, ...(tracePath !== undefined ? { tracePath } : {}) },\n } satisfies SessionResponse);\n });\n\n app.patch(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const approvalMode = optionalEnum(body, \"approvalMode\", APPROVAL_MODES);\n const archivedRaw = (body as Record<string, unknown>).archived;\n const archived = typeof archivedRaw === \"boolean\" ? archivedRaw : undefined;\n const titleRaw = (body as Record<string, unknown>).title;\n let title: string | undefined;\n if (titleRaw !== undefined) {\n if (typeof titleRaw !== \"string\") {\n throw new HttpError(400, \"invalid_title\", \"title must be a string.\");\n }\n title = titleRaw.trim();\n if (!title || title.length > SESSION_TITLE_MAX) {\n throw new HttpError(\n 400,\n \"invalid_title\",\n `title must be 1–${SESSION_TITLE_MAX} characters.`,\n );\n }\n }\n if (approvalMode === undefined && archived === undefined && title === undefined) {\n throw new HttpError(\n 400,\n \"no_update\",\n \"No updatable field provided (approvalMode / archived / title).\",\n );\n }\n let updated: SessionRow = { ...row };\n if (title !== undefined) {\n // Manual renaming takes priority over auto-generation: TitleGenerator only persists a title while it's still NULL.\n deps.sessionsRepo.updateTitle(row.sessionId, title);\n updated = { ...updated, title };\n }\n if (approvalMode !== undefined) {\n // Takes effect immediately: a running approve callback re-reads the DB on every decision.\n deps.sessionsRepo.updateApprovalMode(row.sessionId, approvalMode);\n updated = { ...updated, approvalMode };\n }\n if (archived !== undefined) {\n const at = archived ? new Date().toISOString() : null;\n deps.sessionsRepo.setArchived(row.sessionId, at);\n updated = { ...updated, archivedAt: at };\n }\n const hasTrace = await deps.sessionService.hasTrace(updated);\n return c.json({\n session: await deps.sessionService.toInfo(updated, hasTrace),\n } satisfies SessionResponse);\n });\n\n app.delete(\"/:sessionId\", async (c) => {\n const row = resolveSession(c);\n // Mark as being deleted and converge active runs (beginSessionDeletion): new\n // Tasks/compactions are always rejected with 409 during this window\n // (assertSessionNotDeleting), preventing the race where a new task recreates the\n // entry and Trace after abort but before the files are deleted, reviving an\n // already-deleted Session. Interrupt cleanup writes the Trace asynchronously, so we\n // wait for it to finish (≤5s cap) before deleting the files and index row; the\n // being-deleted marker is cleared once deletion finishes (success or failure).\n const runnings = deps.manager.beginSessionDeletion(row.sessionId);\n try {\n if (runnings.length > 0) {\n await Promise.race([\n Promise.allSettled(runnings).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, 5000).unref?.()),\n ]);\n }\n await deps.traceService.deleteSessionTraces(row.projectId, row.agentId, row.sessionId);\n // The session-level scratchpad (model temp files + input images saved to disk for image-unsupported models) is deleted along with the session.\n await fs.rm(\n path.join(scratchpadDir(deps.config.root, row.projectId, row.agentId), row.sessionId),\n { recursive: true, force: true },\n );\n deps.sessionsRepo.deleteById(row.sessionId);\n deps.goalsRepo.deleteBySession(row.sessionId);\n // Drop the derived-origin entry along with the Session (bulk Agent/Project deletion\n // may leave stale entries; session ids are never reused, so they are never matched).\n deps.sessionSources.delete(row.sessionId);\n } finally {\n deps.manager.endSessionDeletion(row.sessionId);\n }\n return c.body(null, 204);\n });\n\n // Session scratchpad files (e.g. input images saved to disk for image-unsupported\n // models): read by filename, so the conversation UI can render a message's\n // \"[attached image: <path>]\" attachment line back into an image. Restricted to this\n // session's own scratchpad directory (the filename must not contain a path\n // separator, blocking traversal); filenames include a timestamp and are globally\n // unique, so the response is marked immutable and long-cacheable.\n app.get(\"/:sessionId/scratchpad/:fileName\", async (c) => {\n const row = resolveSession(c);\n const fileName = c.req.param(\"fileName\") ?? \"\";\n if (!/^[A-Za-z0-9._-]+$/.test(fileName) || fileName.includes(\"..\")) {\n throw new HttpError(404, \"file_not_found\", \"File does not exist.\");\n }\n const filePath = path.join(\n scratchpadDir(deps.config.root, row.projectId, row.agentId),\n row.sessionId,\n fileName,\n );\n let bytes: Buffer;\n try {\n bytes = await fs.readFile(filePath);\n } catch {\n throw new HttpError(404, \"file_not_found\", \"File does not exist.\");\n }\n const MIME_BY_EXT: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n };\n const mime = MIME_BY_EXT[path.extname(fileName).toLowerCase()] ?? \"application/octet-stream\";\n return c.body(new Uint8Array(bytes), 200, {\n \"content-type\": mime,\n \"cache-control\": \"private, max-age=31536000, immutable\",\n });\n });\n\n app.get(\"/:sessionId/messages\", async (c) => {\n const row = resolveSession(c);\n // Live tail (running/compacting sessions only): capture the channel cursor and the\n // open-fragment snapshot together, synchronously — no await between the two, and both\n // BEFORE the trace read starts. That ordering is what makes the client contract safe\n // (see MessagesLiveTail in api/types.ts): every published event with id <= cursor is\n // already reflected in `fragments`, and partial_* messages never reach the Trace, so\n // the client may drop its buffered partials at/or before the cursor and seed from\n // `fragments` without loss or duplication. Complete messages are never dropped by the\n // cursor — the client's overlap dedup against `messages` decides for them — so a\n // complete message whose trace append is still in flight when the read starts is not\n // lost either.\n let live: MessagesLiveTail | undefined;\n if (deps.manager.statusOf(row.sessionId) !== \"idle\") {\n live = {\n cursor: deps.channels.get(row.sessionId).lastEventId,\n fragments: deps.manager.liveFragments(row.sessionId),\n };\n }\n const messages = await deps.traceService.readMessages(\n row.projectId,\n row.agentId,\n row.sessionId,\n );\n return c.json({ messages, ...(live !== undefined ? { live } : {}) } satisfies MessagesResponse);\n });\n\n app.get(\"/:sessionId/stream\", (c) => {\n const row = resolveSession(c);\n const channel = deps.channels.get(row.sessionId);\n // FD-1: the first event of every new subscription (including reconnects and resync\n // rebuilds) is always a snapshot of the current running state — the frontend treats\n // this as authoritative, eliminating input-area lockup or premature Task closure\n // caused by a stale running/idle in the list; followed by replaying all still-pending\n // approval requests.\n const initialEvents: ServerEvent[] = [\n {\n type: \"task_state\",\n state: deps.manager.statusOf(row.sessionId),\n queued: deps.manager.pendingFollowUpCount(row.sessionId),\n },\n ...deps.manager.pendingApprovals(row.sessionId).map((p) => ({\n type: \"approval_request\" as const,\n toolCall: p.toolCall,\n ...(p.origin !== undefined ? { origin: p.origin } : {}),\n })),\n ];\n return sseEndpoint(c, channel, { initialEvents });\n });\n\n app.post(\"/:sessionId/tasks\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const goal = parseGoalField(body);\n // Per-turn thinking level (optional): validated against the five names; omitted follows\n // the session's default. In goal mode it rides every round of the goal; a queued\n // follow-up keeps its level for its auto-start.\n const thinkingLevel = optionalEnum(body, \"thinkingLevel\", THINKING_LEVELS);\n if (goal) {\n // Goal mode: the input must be plain non-empty text (its marker-stripped text becomes\n // the objective, re-injected every round — images have no place in the protocol).\n const input = parseTaskInput(body);\n const text = input\n .filter((m) => (m.payload as { type?: string }).type === \"text\")\n .map((m) => (m.payload as { text: string }).text)\n .join(\"\\n\")\n .trim();\n if (!text || input.some((m) => (m.payload as { type?: string }).type !== \"text\")) {\n throw badRequest(\"goal mode requires text-only input (the objective).\");\n }\n const { sessionId } = await deps.manager.startGoal(row.sessionId, {\n input,\n budget: goal.budget,\n ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),\n });\n return c.json({ sessionId } satisfies TaskCreateResponse, 202);\n }\n const input = parseTaskInput(body);\n // Follow-up queue: with queueIfBusy, a busy session enqueues the input instead of 409\n // (auto-starts as an ordinary next task once idle; the response says which happened).\n const queueIfBusy = body.queueIfBusy === true;\n // 202: the Task executes on the server, decoupled from the SSE connection; sessionId is the current actual id (the new id after self-heal).\n const { sessionId, queued } = await deps.manager.startTask(row.sessionId, input, {\n ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),\n queueIfBusy,\n });\n return c.json({ sessionId, queued } satisfies TaskCreateResponse, 202);\n });\n\n // Mid-run steering: queue a user message for the running Task; core delivers it between\n // turns as a standalone `[user_steering]` user message (the model sees it without the loop\n // being interrupted). 409 not_running when no Task is in progress — the frontend then falls\n // back to a normal task POST.\n app.post(\"/:sessionId/steer\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const text = typeof body.text === \"string\" ? body.text.trim() : \"\";\n if (!text) throw badRequest(\"text must be a non-empty string.\");\n deps.manager.steer(row.sessionId, text);\n return c.body(null, 202);\n });\n\n // The Session's most recent goal run (for restoring the chat page's goal banner on load).\n app.get(\"/:sessionId/goal\", (c) => {\n const row = resolveSession(c);\n const g = deps.goalsRepo.latestForSession(row.sessionId);\n return c.json({\n goal: g\n ? {\n objective: g.objective,\n status: g.status,\n budget: g.budget,\n used: g.used,\n rounds: g.rounds,\n updatedAt: g.updatedAt,\n }\n : null,\n } satisfies GoalResponse);\n });\n\n app.post(\"/:sessionId/approvals/:toolCallId\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const decision = requireEnum(body, \"decision\", [\"allow\", \"deny\"] as const);\n const ok = deps.manager.decideApproval(row.sessionId, pathParam(c, \"toolCallId\"), decision);\n if (!ok) {\n throw new HttpError(\n 404,\n \"approval_not_found\",\n \"Approval does not exist or has already been decided.\",\n );\n }\n return c.body(null, 204);\n });\n\n app.post(\"/:sessionId/abort\", (c) => {\n const row = resolveSession(c);\n const aborted = deps.manager.abortTask(row.sessionId);\n // No Task in progress → 204 no-op; interrupt was triggered → 202 (wrap-up is completed by the SDK's \"interrupt cleanup\").\n return c.body(null, aborted ? 202 : 204);\n });\n\n // \"Retry now\" on the reconnect countdown: skip the remaining backoff wait and fire the\n // next retry immediately (attempt counter unchanged). Benign either way — 200 with\n // skipped:false when no reconnect wait is in progress, so a timing race (the wait\n // elapsed just before the click) never surfaces as an error.\n app.post(\"/:sessionId/retry-now\", (c) => {\n const row = resolveSession(c);\n const skipped = deps.manager.retryNow(row.sessionId);\n return c.json({ skipped } satisfies RetryNowResponse);\n });\n\n app.post(\"/:sessionId/compact\", async (c) => {\n const row = resolveSession(c);\n const { sessionId } = await deps.manager.startCompact(row.sessionId);\n return c.json({ sessionId } satisfies TaskCreateResponse, 202);\n });\n\n // —— Workspace file browsing (Files tab) ——\n\n app.get(\"/:sessionId/files\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n return c.json(await deps.workspaceFiles.list(row.workspace, rel));\n });\n\n app.get(\"/:sessionId/files/content\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n const download = c.req.query(\"download\") === \"1\";\n // Sandboxed top-level preview (\"open in a new tab\" for html): the document keeps its REAL\n // content type but carries a CSP sandbox WITHOUT allow-same-origin — it renders and runs\n // fully in an opaque origin, so agent-generated markup cannot reach this origin's cookies\n // or API. The request itself still authenticates (top-level GET sends the Lax cookie).\n const preview = !download && c.req.query(\"preview\") === \"1\";\n const { data, fileName, contentType, scriptable } = await deps.workspaceFiles.read(\n row.workspace,\n rel,\n );\n const disposition = download ? \"attachment\" : \"inline\";\n // Same-origin XSS defense: html/svg inline previews are always returned as plain\n // text (Workspace files may be Agent-generated and untrusted); downloads\n // (attachment) keep the real content type, and sandboxed previews keep it under the\n // CSP above. Paired with nosniff to prevent MIME sniffing from undoing this.\n const effectiveType =\n !download && scriptable && !preview ? \"text/plain; charset=utf-8\" : contentType;\n return new Response(new Uint8Array(data), {\n status: 200,\n headers: {\n \"Content-Type\": effectiveType,\n \"Content-Disposition\": `${disposition}; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n \"X-Content-Type-Options\": \"nosniff\",\n ...(preview && scriptable\n ? {\n \"Content-Security-Policy\":\n \"sandbox allow-scripts allow-popups allow-modals allow-forms\",\n }\n : {}),\n },\n });\n });\n\n // \"Open in a new tab\" for Workspace HTML: mints a token and redirects to the separate\n // preview origin (see design § \"Workspace 文件预览\").\n //\n // A redirect rather than a JSON endpoint the UI fetches, because the alternative is\n // worse on two counts: opening the tab after an await trips popup blockers, and a\n // window opened by script keeps an `opener` handle back to the App — exactly the\n // reference this design exists to deny. A plain link with rel=\"noopener noreferrer\"\n // has neither problem.\n //\n // Minting on GET is safe: a cross-site request can make the browser follow the\n // redirect, but the response is opaque to the initiating page, so no token leaks — and\n // what it would grant is a preview of the victim's own file.\n //\n // With no usable preview origin (the App is reached on something other than a loopback\n // name and PENGUIN_PREVIEW_ORIGIN is unset), this falls back to the sandboxed\n // same-origin preview: the page still renders, but storage and third-party embeds do\n // not. The UI flags that ahead of time via `previewIsolated` on /api/me.\n app.get(\"/:sessionId/files/preview-redirect\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n // Validate existence + containment while the caller is still authenticated, so a bad\n // path fails here rather than as an opaque 404 from the unauthenticated preview origin.\n // A stat, not a read: the file itself is fetched later, on the preview origin — reading\n // it here (up to 50MB) only to discard the bytes would be wasted work on every click.\n const [exists] = await deps.workspaceFiles.statExisting(row.workspace, [rel]);\n if (!exists) throw new HttpError(404, \"file_not_found\", \"File does not exist.\");\n\n const target = resolvePreviewTarget(\n c.req.url,\n c.req.header(\"host\"),\n deps.config.previewOrigin,\n deps.config,\n );\n if (!target) {\n return c.redirect(\n `/api/sessions/${row.sessionId}/files/content?path=${encodeURIComponent(rel)}&preview=1`,\n 302,\n );\n }\n\n const token = deps.previewTokens.sign({\n sessionId: row.sessionId,\n host: target.host,\n expiresAt: Date.now() + PREVIEW_TOKEN_TTL_MS,\n });\n const encoded = rel.split(\"/\").map(encodeURIComponent).join(\"/\");\n return c.redirect(`${target.origin}/preview/${token}/${encoded}`, 302);\n });\n\n // Bulk existence check (message file cards list only files that actually exist):\n // path-confinement resolution shares the same logic as files/content\n // (WorkspaceFilesService.statExisting reuses resolveRead); out-of-bounds or\n // resolution failures count as not-existing, always 200 — existence itself is the\n // question being answered, and a 4xx would only leak confinement details.\n app.post(\"/:sessionId/files/stat\", async (c) => {\n const row = resolveSession(c);\n const body = await readJson(c);\n const paths = body.paths;\n if (\n !Array.isArray(paths) ||\n paths.length > STAT_MAX_PATHS ||\n !paths.every((p) => typeof p === \"string\" && p.length <= STAT_MAX_PATH_LEN)\n ) {\n throw badRequest(\n `paths must be an array of strings (≤${STAT_MAX_PATHS} items, each ≤${STAT_MAX_PATH_LEN} characters).`,\n );\n }\n const existing = await deps.workspaceFiles.statExisting(row.workspace, paths as string[]);\n return c.json({ existing } satisfies FilesStatResponse);\n });\n\n app.put(\"/:sessionId/files/content\", async (c) => {\n const row = resolveSession(c);\n const rel = c.req.query(\"path\") ?? \"\";\n const body = await readJson(c);\n if (typeof body.dataBase64 !== \"string\") {\n throw badRequest(\"dataBase64 must be a base64 string.\");\n }\n const data = Buffer.from(body.dataBase64, \"base64\");\n if (data.length > MAX_UPLOAD_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"Uploaded file exceeds the 14MB limit.\");\n }\n await deps.workspaceFiles.write(row.workspace, rel, data);\n return c.body(null, 204);\n });\n\n app.get(\"/:sessionId/traces\", async (c) => {\n const row = resolveSession(c);\n const files = await deps.traceService.listTraceFiles(row.projectId, row.agentId, row.sessionId);\n return c.json({ files });\n });\n\n app.get(\"/:sessionId/traces/:index\", async (c) => {\n const row = resolveSession(c);\n const index = positiveIntParam(c, \"index\");\n const { offset, limit } = paginationQuery(c);\n return c.json(\n await deps.traceService.readEvents(\n row.projectId,\n row.agentId,\n row.sessionId,\n index,\n offset,\n limit,\n ),\n );\n });\n\n app.get(\"/:sessionId/traces/:index/analysis\", async (c) => {\n const row = resolveSession(c);\n const index = positiveIntParam(c, \"index\");\n return c.json(\n await deps.traceService.analyze(row.projectId, row.agentId, row.sessionId, index),\n );\n });\n\n return app;\n}\n","/**\n * Workspace validation.\n *\n * When a user explicitly specifies a Workspace: after realpath normalization\n * (resolving `..` and symlinks), it's required to be an **existing directory**\n * (never auto-created). The directory location is not constrained to the\n * Project directory — a Workspace can be any path on the server, with actual\n * reachability governed by the file permissions of the OS account running the\n * service.\n * When no Workspace is specified, this module isn't involved (the SDK creates its\n * own temporary directory).\n */\nimport fs from \"node:fs/promises\";\nimport { HttpError } from \"../http/errors.js\";\n\n/**\n * Validates and returns the normalized (realpath) Workspace path.\n *\n * @throws 400 workspace_not_found: the path doesn't exist, isn't readable, or isn't a directory.\n */\nexport async function assertWorkspaceAllowed(args: { workspace: string }): Promise<string> {\n let ws: string;\n try {\n ws = await fs.realpath(args.workspace);\n } catch {\n throw new HttpError(\n 400,\n \"workspace_not_found\",\n `Workspace does not exist or is inaccessible: ${args.workspace}. Specify an existing directory, or leave it empty to use a temporary directory.`,\n );\n }\n const stat = await fs.stat(ws);\n if (!stat.isDirectory()) {\n throw new HttpError(\n 400,\n \"workspace_not_found\",\n `Workspace is not a directory: ${args.workspace}.`,\n );\n }\n return ws;\n}\n","/**\n * Workspace file browsing: list directory / read\n * file (preview & download) / write file (upload). Security: a relative path, once\n * resolved, must stay inside the Workspace — a logical prefix check plus a realpath\n * check against the nearest existing ancestor (guards against `..` and symlink escapes).\n */\nimport fs from \"node:fs/promises\";\nimport { constants as fsc } from \"node:fs\";\nimport path from \"node:path\";\nimport type { WorkspaceFilesResponse } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\n\n/** Per-file read cap (a safety limit since preview/download reads the whole file into memory). */\nconst MAX_READ_BYTES = 50 * 1024 * 1024;\n/** Upload cap (stays within the 20MB request body limit even after base64 encoding). */\nexport const MAX_UPLOAD_BYTES = 14 * 1024 * 1024;\n\nconst CONTENT_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".htm\": \"text/html; charset=utf-8\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".md\": \"text/markdown; charset=utf-8\",\n \".json\": \"application/json\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".ts\": \"text/plain; charset=utf-8\",\n \".tsx\": \"text/plain; charset=utf-8\",\n \".py\": \"text/plain; charset=utf-8\",\n \".sh\": \"text/plain; charset=utf-8\",\n \".yaml\": \"text/plain; charset=utf-8\",\n \".yml\": \"text/plain; charset=utf-8\",\n \".toml\": \"text/plain; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".csv\": \"text/plain; charset=utf-8\",\n \".log\": \"text/plain; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".pdf\": \"application/pdf\",\n};\n\nexport interface WorkspaceFileContent {\n data: Buffer;\n fileName: string;\n contentType: string;\n /** Types whose same-origin inline rendering would execute scripts (html/svg): inline preview must fall back to plain text. */\n scriptable: boolean;\n}\n\nexport class WorkspaceFilesService {\n /** Canonical path (realpath) of the Workspace root; 404 if it doesn't exist. */\n private async realBase(workspace: string): Promise<string> {\n try {\n return await fs.realpath(path.resolve(workspace));\n } catch {\n throw new HttpError(404, \"workspace_missing\", \"This Session's Workspace no longer exists.\");\n }\n }\n\n /**\n * Lexical containment check: whether target is inside base (including equal to\n * base). Uses path.relative rather than prefix concatenation, so it works when\n * base is the filesystem root (\"/\" concatenated with sep would produce a \"//\"\n * prefix that no subpath could ever match); only a full \"..\" segment is\n * compared, so a legitimate name like \"..foo\" isn't mistakenly rejected.\n */\n private isInside(target: string, base: string): boolean {\n const rel = path.relative(base, target);\n return rel !== \"..\" && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);\n }\n\n /** Lexical prefix check (a relative path, once resolved, must still be inside the Workspace); returns the absolute target path. */\n private lexicalTarget(base: string, rel: string): string {\n if (rel.includes(\"\\0\")) throw badRequest(\"path is invalid.\");\n const target = path.resolve(base, rel === \"\" ? \".\" : rel);\n if (!this.isInside(target, base)) {\n throw badRequest(\"path must be inside the Workspace.\");\n }\n return target;\n }\n\n private assertInside(real: string, realBase: string): void {\n if (!this.isInside(real, realBase)) {\n throw badRequest(\"path must be inside the Workspace.\");\n }\n }\n\n /**\n * Read-path resolution: realpath the entire path (following all symlinks to get\n * a link-free canonical path), then check containment and **perform IO on the\n * canonical path** — since the canonical path contains no symlink segments at\n * all, this eliminates check-then-use TOCTOU escapes (an out-of-bounds symlink\n * is already resolved and rejected at the realpath step).\n */\n private async resolveRead(workspace: string, rel: string): Promise<string> {\n const realBase = await this.realBase(workspace);\n const target = this.lexicalTarget(path.resolve(workspace), rel);\n let canonical: string;\n try {\n canonical = await fs.realpath(target);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new HttpError(404, \"path_not_found\", \"File does not exist.\");\n }\n throw err;\n }\n this.assertInside(canonical, realBase);\n return canonical;\n }\n\n /**\n * Write-path resolution: realpaths the parent directory (whose canonical path\n * has no symlink segments) and checks containment, then appends the final\n * segment as the file name. When the parent directory is missing, it is safely\n * created (uploading a folder needs to preserve directory structure): first the\n * nearest **existing** ancestor is found and its canonical path checked against\n * the Workspace — this exposes it if a middle segment was preset as a symlink\n * pointing outside; the missing segments are then created recursively beneath it\n * (a brand-new directory can never be a symlink), followed by a second realpath\n * check after creation. The actual write opens with O_NOFOLLOW (refusing to\n * follow a symlink at the final segment), blocking the sandbox-escape pattern of\n * \"Agent presets a symlink -> an upload is used as leverage to overwrite a file\n * outside the sandbox\". Returns the canonical parent directory + file name.\n */\n private async resolveWriteParent(\n workspace: string,\n rel: string,\n ): Promise<{ dir: string; name: string }> {\n const realBase = await this.realBase(workspace);\n const target = this.lexicalTarget(path.resolve(workspace), rel);\n const name = path.basename(target);\n if (name === \"\" || name === \".\" || name === \"..\") throw badRequest(\"path must be a file path.\");\n const parent = path.dirname(target);\n let canonicalParent: string;\n try {\n canonicalParent = await fs.realpath(parent);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n let probe = parent;\n while (true) {\n try {\n this.assertInside(await fs.realpath(probe), realBase);\n break;\n } catch (probeErr) {\n if ((probeErr as NodeJS.ErrnoException).code !== \"ENOENT\") throw probeErr;\n const up = path.dirname(probe);\n if (up === probe) throw badRequest(\"path is invalid.\");\n probe = up;\n }\n }\n await fs.mkdir(parent, { recursive: true });\n canonicalParent = await fs.realpath(parent);\n }\n this.assertInside(canonicalParent, realBase);\n return { dir: canonicalParent, name };\n }\n\n /**\n * Batch existence check (a message's file card lists only files that actually\n * exist): each item goes through the same containment resolution as reading\n * (resolveRead); out-of-bounds, resolution failure, missing Workspace, or an\n * irregular file are all treated as non-existent — the card scenario only asks\n * \"can this be opened\", and throwing a 4xx would only add frontend branches while\n * leaking containment details. Returns the deduplicated existing items in input order.\n */\n async statExisting(workspace: string, rels: string[]): Promise<string[]> {\n const unique = [...new Set(rels)];\n const exists = await Promise.all(\n unique.map(async (rel) => {\n try {\n const stat = await fs.stat(await this.resolveRead(workspace, rel));\n return stat.isFile();\n } catch {\n return false;\n }\n }),\n );\n return unique.filter((_, i) => exists[i]);\n }\n\n /** List a directory: dirs come first, each group sorted by name; kind follows the symlink target (consistent with read behavior). */\n async list(workspace: string, rel: string): Promise<WorkspaceFilesResponse> {\n const dir = await this.resolveRead(workspace, rel);\n let dirents;\n try {\n dirents = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new HttpError(404, \"path_not_found\", \"Directory does not exist.\");\n }\n if ((err as NodeJS.ErrnoException).code === \"ENOTDIR\") {\n throw badRequest(\"path is not a directory.\");\n }\n throw err;\n }\n const entries = await Promise.all(\n dirents.map(async (d) => {\n let sizeBytes = 0;\n let mtime = \"\";\n // Dirent doesn't report the target type for a symlink, so stat (following the link) is used to determine dir/file.\n let isDir = d.isDirectory();\n try {\n const stat = await fs.stat(path.join(dir, d.name));\n sizeBytes = stat.size;\n mtime = stat.mtime.toISOString();\n isDir = stat.isDirectory();\n } catch {\n // A dangling symlink or similar: keep the entry, with size/time left at defaults.\n }\n return {\n name: d.name,\n kind: isDir ? (\"dir\" as const) : (\"file\" as const),\n sizeBytes,\n mtime,\n };\n }),\n );\n entries.sort((a, b) =>\n a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === \"dir\" ? -1 : 1,\n );\n return { path: rel, entries };\n }\n\n /** Read a file (preview/download): IO on the canonical path (resolveRead has already eliminated symlink escapes). */\n async read(workspace: string, rel: string): Promise<WorkspaceFileContent> {\n const file = await this.resolveRead(workspace, rel);\n let stat;\n try {\n stat = await fs.stat(file);\n } catch {\n throw new HttpError(404, \"path_not_found\", \"File does not exist.\");\n }\n if (stat.isDirectory()) throw badRequest(\"path is a directory.\");\n if (stat.size > MAX_READ_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"File exceeds the 50MB read limit.\");\n }\n const data = await fs.readFile(file);\n const ext = path.extname(file).toLowerCase();\n return {\n data,\n fileName: path.basename(file),\n contentType: CONTENT_TYPES[ext] ?? \"application/octet-stream\",\n scriptable: ext === \".html\" || ext === \".htm\" || ext === \".svg\",\n };\n }\n\n /**\n * Write a file (upload, overwriting a same-named one). If the parent directory\n * is missing, it's automatically created under sandbox checks (preserving\n * directory structure for folder uploads); the final segment is opened with\n * O_NOFOLLOW, refusing to follow a symlink to write outside the Workspace\n * (together with resolveWriteParent's canonical-parent check, this blocks\n * sandbox escapes).\n */\n async write(workspace: string, rel: string, data: Buffer): Promise<void> {\n if (rel === \"\" || rel.endsWith(\"/\")) throw badRequest(\"path must be a file path.\");\n if (data.length > MAX_UPLOAD_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"Uploaded file exceeds the 14MB limit.\");\n }\n const { dir, name } = await this.resolveWriteParent(workspace, rel);\n const file = path.join(dir, name);\n // Windows has no O_NOFOLLOW (the `?? 0` below erases it), so the atomic ELOOP guard never\n // fires there — refuse a final-segment symlink via lstat instead. Best effort (a link\n // created between this check and the open wins the race), but it closes the practical\n // \"preset a symlink, overwrite an outside file by upload\" escape; POSIX keeps the atomic\n // open-time guarantee.\n if (process.platform === \"win32\") {\n const st = await fs.lstat(file).catch(() => null);\n if (st?.isSymbolicLink()) throw badRequest(\"path must not be a symlink.\");\n }\n // O_NOFOLLOW: open reports ELOOP if the final segment is a symlink, refusing to use it as leverage to overwrite a file outside the sandbox.\n const flags = fsc.O_WRONLY | fsc.O_CREAT | fsc.O_TRUNC | (fsc.O_NOFOLLOW ?? 0);\n let handle;\n try {\n handle = await fs.open(file, flags, 0o644);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ELOOP\") throw badRequest(\"path must not be a symlink.\");\n if (code === \"ENOENT\")\n throw new HttpError(404, \"path_not_found\", \"Parent directory does not exist.\");\n if (code === \"EISDIR\") throw badRequest(\"path is a directory.\");\n throw err;\n }\n try {\n await handle.writeFile(data);\n } finally {\n await handle.close();\n }\n }\n}\n","/**\n * Version routes: the running release identity, the update-check reminder, and the\n * admin-only self-update.\n *\n * GET /api/version -> {version, buildDate} from core's VERSION / BUILD_DATE\n * GET /api/version/update-check -> UpdateCheckService (fail-soft, cached, opt-out env;\n * ?force=1 bypasses the cache for the manual check)\n * POST /api/version/update -> admin only: re-runs the CLI as `penguin update --yes`\n *\n * How the self-update works: `penguin server|web` exports PENGUIN_CLI_ENTRY (its own entry\n * script path) before importing this server, and this route re-runs that script as\n * `node <entry> update --yes` — the CLI's update command owns all install-kind detection,\n * download and replacement logic (packages/cli/src/commands/update.ts). A server started\n * any other way (tests, a custom embedding) has no CLI to run and reports \"unsupported\".\n * SECURITY: the spawned argv is a fixed literal list; nothing from the request flows into\n * the command, its arguments, or its environment.\n *\n * Interpreting the CLI's outcome: `penguin update` exits non-zero only when an upgrade was\n * attempted and failed; refusals (source checkout, unrecognized layout, Windows) print a\n * message and exit 0. Exit codes alone therefore cannot separate \"updated\" from\n * \"unsupported\", so the spawn forces PENGUIN_LANG=en and classifies by the refusal\n * messages' stable English fragments (packages/cli/src/i18n.ts, `update` section). That\n * matching is reliable here because the spawned CLI and this server ship in lockstep from\n * the same install — the strings can never be from a different release than this code.\n */\nimport { spawn } from \"node:child_process\";\nimport { BUILD_DATE, VERSION } from \"@prismshadow/penguin-core\";\nimport { Hono } from \"hono\";\nimport type { UpdateRunResponse, VersionResponse } from \"../../api/types.js\";\nimport { HttpError } from \"../errors.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/** Only the tail of the update output is kept (installer logs can be long). */\nconst OUTPUT_CAP = 64 * 1024;\n/** The whole update (download + install) must finish within this window. */\nconst UPDATE_TIMEOUT_MS = 10 * 60 * 1000;\n\n/**\n * English fragments of the CLI refusal messages (packages/cli/src/i18n.ts `update.*`),\n * one per refusal reason. `needsYes` / `cancelled` are omitted: `--yes` makes the\n * confirmation gate proceed unconditionally, so neither can occur here.\n */\nconst REFUSAL_MARKERS = [\n \"runs from a source checkout\", // sourceCheckout\n \"Cannot tell how this penguin was installed\", // unknownInstall\n \"could not be identified\", // npmUnknownManager\n \"does not run on Windows\", // windowsUnsupported\n \"cannot run your package manager for you\", // windowsGlobalInstall\n];\n\n/**\n * Maps the finished CLI run to an UpdateRunResponse. Exported for unit tests (tests never\n * spawn anything real). Exit 0 with a refusal message is \"unsupported\"; any other exit 0\n * is \"updated\" — including the CLI's \"already on the latest version\", because that means\n * the *install* is current and only a restart of this (older, in-memory) process is\n * missing, which is exactly what needsRestart says.\n */\nexport function classifyUpdateRun(exitCode: number, output: string): UpdateRunResponse {\n if (exitCode !== 0) return { status: \"failed\", output, needsRestart: false };\n if (REFUSAL_MARKERS.some((marker) => output.includes(marker))) {\n return { status: \"unsupported\", output, needsRestart: false };\n }\n return { status: \"updated\", output, needsRestart: true };\n}\n\n/** Runs `node <cliEntry> update --yes` to completion and classifies the outcome. */\nfunction runSelfUpdate(cliEntry: string): Promise<UpdateRunResponse> {\n return new Promise((resolve) => {\n // PENGUIN_LANG=en pins the CLI's output language so classifyUpdateRun's markers match\n // regardless of the deployment's configured CLI language.\n const child = spawn(process.execPath, [cliEntry, \"update\", \"--yes\"], {\n env: { ...process.env, PENGUIN_LANG: \"en\" },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let output = \"\";\n const append = (chunk: Buffer) => {\n output = (output + chunk.toString(\"utf8\")).slice(-OUTPUT_CAP);\n };\n child.stdout.on(\"data\", append);\n child.stderr.on(\"data\", append);\n\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n // A SIGTERM-ignoring child would otherwise leave this request hanging forever.\n setTimeout(() => child.kill(\"SIGKILL\"), 10_000).unref();\n }, UPDATE_TIMEOUT_MS);\n timer.unref();\n\n child.on(\"error\", (err) => {\n clearTimeout(timer);\n resolve({\n status: \"failed\",\n output: `${output}\\n[failed to run the update command: ${err.message}]`.trim(),\n needsRestart: false,\n });\n });\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n if (timedOut) {\n resolve({\n status: \"failed\",\n output:\n `${output}\\n[the update run was killed after ${UPDATE_TIMEOUT_MS / 60_000} minutes]`.trim(),\n needsRestart: false,\n });\n return;\n }\n resolve(classifyUpdateRun(code ?? -1, output.trim()));\n });\n });\n}\n\nexport function versionRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n // Two admins clicking \"update\" at once must not race two installers over the same\n // install dir: concurrent requests share the one in-flight run (and its result).\n let inflightRun: Promise<UpdateRunResponse> | null = null;\n\n app.get(\"/\", (c) => {\n return c.json({ version: VERSION, buildDate: BUILD_DATE } satisfies VersionResponse);\n });\n\n app.get(\"/update-check\", async (c) => {\n // ?force=1 (the web's manual \"check for updates\" action) bypasses the TTL cache;\n // see UpdateCheckService.check for why a user-initiated press-through is acceptable.\n return c.json(await deps.updateCheck.check(c.req.query(\"force\") === \"1\"));\n });\n\n app.post(\"/update\", async (c) => {\n if (!c.var.user.isAdmin) {\n throw new HttpError(403, \"admin_required\", \"Only an admin can perform this operation.\");\n }\n const cliEntry = process.env.PENGUIN_CLI_ENTRY;\n if (!cliEntry) {\n return c.json({\n status: \"unsupported\",\n reason: \"not_launched_via_cli\",\n output: \"\",\n needsRestart: false,\n } satisfies UpdateRunResponse);\n }\n inflightRun ??= runSelfUpdate(cliEntry).finally(() => {\n inflightRun = null;\n });\n return c.json(await inflightRun);\n });\n\n return app;\n}\n","/**\n * SSE event channel.\n *\n * The Session channel and the user channel share this implementation:\n * - Event id is an opaque string `<epoch>-<seq>`: epoch is a random short string\n * generated when each Channel instance is created, seq is a monotonically increasing\n * integer within the channel. epoch necessarily changes when the channel is\n * recycled/recreated or the process restarts, so a stale Last-Event-ID always misses\n * and falls through to resync — this prevents a silent false-hit event loss when the\n * new epoch's event count happens to exceed the old id;\n * - A bounded ring buffer (most recent 10,000 entries or 8MB, whichever comes first,\n * evicting the oldest on overflow) serves replay-on-reconnect via `Last-Event-ID`;\n * an evicted/unknown id is handled by the caller sending `resync_required`;\n * - Unicast (sendTo) is used for one-off replay at subscribe time (pending approvals /\n * resync / hello): it consumes a seq number but doesn't enter the buffer or get\n * broadcast — if that subscriber later reconnects with this id, the hit check is\n * still safe (seq is monotonic).\n *\n * This module only handles event numbering / buffering / dispatch, not HTTP — SSE\n * output is adapted at the routing layer.\n * Docs: /docs/server-api § \"Delivery Guarantees\".\n */\nimport { randomUUID } from \"node:crypto\";\n\n/** A numbered channel event; `id` is `<epoch>-<seq>`, `data` is serialized single-line JSON. */\nexport interface ChannelEvent {\n id: string;\n /** SSE event name; omitted (OmniMessage) means no `event:` line. */\n event?: string;\n data: string;\n}\n\nexport type ChannelListener = (evt: ChannelEvent) => void;\n\nexport interface ChannelOptions {\n maxBufferCount?: number;\n maxBufferBytes?: number;\n}\n\n// Sized so a mid-stream reconnect during a fast large-code reply still hits replay instead of\n// resync_required: the server publishes one event per provider delta (a 240KB reply ≈ 5k events\n// / 1.4MB), and 1000 events covered only ~6s of such a stream — every longer blip forced a full\n// client-side history rebuild.\nconst DEFAULT_MAX_COUNT = 10_000;\nconst DEFAULT_MAX_BYTES = 8 * 1024 * 1024;\n\n/** Buffered entry: seq is stored separately so hit checks never need to parse the string id. */\ninterface BufferedEvent {\n seq: number;\n evt: ChannelEvent;\n}\n\nexport class Channel {\n /** Channel epoch: generated at instance creation, prefixed onto event ids (necessarily changes after recycle/recreate or restart). */\n readonly epoch: string = randomUUID().slice(0, 8);\n private nextSeq = 1;\n private buffer: BufferedEvent[] = [];\n private bufferBytes = 0;\n /** Max seq among evicted events (0 means never evicted): lower bound for hit checks. */\n private lastEvictedSeq = 0;\n private readonly listeners = new Set<ChannelListener>();\n private readonly maxCount: number;\n private readonly maxBytes: number;\n /** Timestamp of last activity (publish/subscription change), used for idle-reclaim checks. */\n lastActivityMs = Date.now();\n\n constructor(opts: ChannelOptions = {}) {\n this.maxCount = opts.maxBufferCount ?? DEFAULT_MAX_COUNT;\n this.maxBytes = opts.maxBufferBytes ?? DEFAULT_MAX_BYTES;\n }\n\n get subscriberCount(): number {\n return this.listeners.size;\n }\n\n /**\n * Id of the most recently assigned event (`<epoch>-<seq>`; seq 0 when none was assigned\n * yet). Unicast (sendTo) seqs count too — the value is a position marker, not a buffer\n * lookup key. Used as the live-tail cursor on GET /messages: captured in the same\n * synchronous tick as the fragment snapshot, it tells the client which buffered events\n * the snapshot already covers.\n */\n get lastEventId(): string {\n return `${this.epoch}-${this.nextSeq - 1}`;\n }\n\n /** Broadcast an event: number it, buffer it (evicting the oldest), notify all subscribers. */\n publish(data: unknown, event?: string): ChannelEvent {\n const entry = this.makeEvent(data, event);\n this.buffer.push(entry);\n this.bufferBytes += entry.evt.data.length;\n while (\n this.buffer.length > 0 &&\n (this.buffer.length > this.maxCount || this.bufferBytes > this.maxBytes)\n ) {\n const evicted = this.buffer.shift()!;\n this.bufferBytes -= evicted.evt.data.length;\n this.lastEvictedSeq = Math.max(this.lastEvictedSeq, evicted.seq);\n }\n for (const listener of this.listeners) listener(entry.evt);\n return entry.evt;\n }\n\n /** Unicast an event to a single subscriber: consumes a seq but doesn't buffer or broadcast (used for replay at subscribe time). */\n sendTo(listener: ChannelListener, data: unknown, event?: string): ChannelEvent {\n const entry = this.makeEvent(data, event);\n listener(entry.evt);\n return entry.evt;\n }\n\n subscribe(listener: ChannelListener): () => void {\n this.listeners.add(listener);\n this.lastActivityMs = Date.now();\n return () => {\n this.listeners.delete(listener);\n this.lastActivityMs = Date.now();\n };\n }\n\n /**\n * Compute replay from a Last-Event-ID (`<epoch>-<seq>`): a mismatched epoch (channel\n * recycled/recreated, process restarted, or malformed id) always misses; a matching\n * epoch hits the buffer (if no events after that seq have been evicted and the seq was\n * indeed assigned by this channel) and returns the buffered events after it; otherwise\n * miss (the caller should send `resync_required` first).\n */\n replayAfter(lastEventId: string): { hit: boolean; events: ChannelEvent[] } {\n const sep = lastEventId.lastIndexOf(\"-\");\n if (sep <= 0) return { hit: false, events: [] };\n const epoch = lastEventId.slice(0, sep);\n const seq = Number.parseInt(lastEventId.slice(sep + 1), 10);\n if (epoch !== this.epoch || !Number.isInteger(seq) || seq < 0) {\n return { hit: false, events: [] };\n }\n const hit = seq >= this.lastEvictedSeq && seq < this.nextSeq;\n if (!hit) return { hit: false, events: [] };\n return { hit: true, events: this.buffer.filter((e) => e.seq > seq).map((e) => e.evt) };\n }\n\n private makeEvent(data: unknown, event?: string): BufferedEvent {\n this.lastActivityMs = Date.now();\n const serialized = typeof data === \"string\" ? data : JSON.stringify(data);\n const seq = this.nextSeq++;\n const evt: ChannelEvent = { id: `${this.epoch}-${seq}`, data: serialized };\n if (event !== undefined) evt.event = event;\n return { seq, evt };\n }\n}\n\nconst DEFAULT_IDLE_MS = 30 * 60 * 1000;\nconst SWEEP_INTERVAL_MS = 60 * 1000;\n\nexport interface ChannelHubOptions {\n idleMs?: number;\n /**\n * Active check: keys for which this returns true are excluded from idle reclaim (app\n * assembly injects `manager.statusOf(key) !== \"idle\"`, so a running/compacting\n * Session channel is never reclaimed no matter how long since its last publish; a\n * user channel key looks like `user:<id>` and is always considered active).\n */\n isActive?: (key: string) => boolean;\n}\n\n/**\n * Channel collection: lazily created by key (Session id or `user:<user_id>`);\n * a channel whose Session is idle and has had no subscribers for over 30 minutes is\n * reclaimed, releasing its buffer as well.\n */\nexport class ChannelHub {\n private readonly channels = new Map<string, Channel>();\n private readonly timer: NodeJS.Timeout;\n private readonly idleMs: number;\n private readonly isActive: (key: string) => boolean;\n\n constructor(opts: ChannelHubOptions = {}) {\n this.idleMs = opts.idleMs ?? DEFAULT_IDLE_MS;\n this.isActive = opts.isActive ?? (() => false);\n this.timer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);\n this.timer.unref?.();\n }\n\n get(key: string): Channel {\n let ch = this.channels.get(key);\n if (!ch) {\n ch = new Channel();\n this.channels.set(key, ch);\n }\n return ch;\n }\n\n peek(key: string): Channel | undefined {\n return this.channels.get(key);\n }\n\n /** Reclaim idle channels (skips active Sessions: no reclaim even without a publish while awaiting approval); `now` is injectable for tests. */\n sweep(now: number = Date.now()): void {\n for (const [key, ch] of this.channels) {\n if (this.isActive(key)) continue;\n if (ch.subscriberCount === 0 && now - ch.lastActivityMs > this.idleMs) {\n this.channels.delete(key);\n }\n }\n }\n\n dispose(): void {\n clearInterval(this.timer);\n this.channels.clear();\n }\n}\n","/**\n * Local date helpers (same convention as core's Trace date directories: local timezone\n * yyyy-mm-dd). Shared by the usage_records.date aggregation key and stats windows\n * (today / last 7 days / last 30 days).\n */\n\n/** Format a time as a local `yyyy-mm-dd` (4-digit year, zero-padded 2-digit month/day). */\nexport function formatLocalDate(date: Date): string {\n const year = date.getFullYear().toString().padStart(4, \"0\");\n const month = (date.getMonth() + 1).toString().padStart(2, \"0\");\n const day = date.getDate().toString().padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/** Subtract N days from a local date (used for the start of the last-7-days / last-30-days windows). */\nexport function localDateMinusDays(date: Date, days: number): string {\n const d = new Date(date);\n d.setDate(d.getDate() - days);\n return formatLocalDate(d);\n}\n","/**\n * Error persistence: errors caught on the server are all\n * written to error_records through here, for display on the stats dashboard. Shape\n * mirrors usage-recorder — persist only raw facts, leave aggregation to query time.\n *\n * **The classification (kind) criterion is \"does a human need to step in\"**, not where\n * the error originated:\n *\n * - `expected`: anticipated by the system, has a defined handling path, part of normal\n * operation, no human needed — HTTP business errors (`HttpError`, mostly 4xx); LLM\n * `timeout` / `malformed` (the engine already reconnects and retries); tool execution\n * `failed` / `timeout` (the error is fed back to the model, and the Agent adjusts on\n * its own).\n * - `unexpected`: shouldn't happen, usually a bug or a config/environment fault,\n * **needs a human** — internal errors converged to 500; process crashes; runtime\n * errors escaping from background tasks (Session drive / usage persistence / title\n * generation / subagent registration); LLM `failed` (not retryable: auth failure,\n * invalid params, etc.).\n * - User-initiated actions **are not errors** and are never recorded: request/tool\n * `aborted` (user clicked \"stop\", or denied a tool).\n *\n * Determination: HTTP sources are inferred automatically from `HttpError` (preserving\n * existing behavior); other sources must pass `kind` explicitly at the capture site.\n * The frontend highlights unexpected by default; expected is still recorded without\n * losing information.\n *\n * Sources cover HTTP, Session drive, LLM requests, Environment (tool execution), usage\n * persistence, title generation, subagent registration, and process-level fallback;\n * among these, `llm` / `environment` errors are not expressed via throw (core converges\n * them into the message stream instead), and are fished out by stream-error-watcher from\n * the Session output stream.\n *\n * **This recorder never throws**: it's hooked onto app.onError, and throwing from\n * within it would turn error handling into infinite recursion; if persistence itself\n * fails (disk full / DB already closed, etc.), it's fine to drop that one record.\n *\n * **Short-window dedup (DEDUP_WINDOW_MS)**: error storms are the norm — someone scanning\n * the API produces a wall of 404s, or a tool fails repeatedly in a loop. Persisting each\n * one both write-amplifies and floods the table, and makes the dashboard's \"most recent\n * 20\" all the same error. So the same `(source, code, Project)` is persisted at most\n * once per window; repeats within the window are **dropped outright** (not persisted);\n * only an actual persist refreshes the timestamp, so a sustained storm leaves a steady\n * one record per window instead of being suppressed indefinitely.\n * **Tradeoff**: aggregate counts therefore **underestimate** — a storm of the same error\n * only counts once, check the logs for true frequency; in exchange, a single error storm\n * doesn't drown out error_records or the stats dashboard. The second line of defense is\n * ErrorsRepo's capacity cap. The dedup table (lastSeen) must stay bounded: past\n * DEDUP_KEYS_MAX, expired entries are cleared first, and if still over the limit the\n * whole table is cleared — better to miss some dedup than let it grow unbounded across\n * different error codes.\n */\nimport { formatLocalDate } from \"../internal/dates.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { ErrorsRepo } from \"../db/repos/errors.js\";\n\n/** Capture-site source (maps one-to-one to error_records.source). */\nexport type ErrorSource =\n | \"http\"\n | \"session\"\n | \"llm\"\n | \"environment\"\n | \"usage\"\n | \"title\"\n | \"subagent\"\n | \"process\"\n | \"schedule\";\n\n/** Error classification: see file header — the criterion is \"does a human need to step in\". */\nexport type ErrorKind = \"expected\" | \"unexpected\";\n\n/** Attribution context (all optional: the login endpoint has no Project, and process-level fallback has no request at all). */\nexport interface ErrorContext {\n projectId?: string;\n agentId?: string;\n sessionId?: string;\n}\n\nexport interface ErrorRecordArgs {\n source: ErrorSource;\n /** The caught error (unknown: the value caught may not be an Error; failures from the message stream pass the reason text directly). */\n err: unknown;\n ctx?: ErrorContext;\n /** Semantic code (required for non-HTTP sources, e.g. session_run_failed); defaults to HttpError.code. */\n code?: string;\n /** HTTP status code; leave empty for non-HTTP sources. */\n status?: number;\n /** Explicit classification (see file header); defaults to inferring from `HttpError` — HTTP sources rely on this, other sources should pass it explicitly. */\n kind?: ErrorKind;\n}\n\n/** Message truncation length (keep only a readable summary; the full stack is still logged). */\nexport const MESSAGE_MAX = 500;\n\n/** Short-window dedup window: the same (source, code, Project) is persisted at most once per window (see the file header's tradeoff). */\nexport const DEDUP_WINDOW_MS = 2000;\n\n/** Cap on dedup table keys (bounded; over the limit, expired entries are cleared first, and if still over, the whole table is cleared). */\nexport const DEDUP_KEYS_MAX = 1000;\n\nfunction messageOf(err: unknown): string {\n const raw = err instanceof Error ? err.message : String(err);\n return raw.length > MESSAGE_MAX ? raw.slice(0, MESSAGE_MAX) : raw;\n}\n\nexport class ErrorRecorder {\n /** Dedup table: `source \\0 code \\0 projectId` → timestamp of the last **persist** (see file header). */\n private readonly lastSeen = new Map<string, number>();\n\n constructor(\n private readonly errors: ErrorsRepo,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n /** Record an error (synchronous, fails silently; same-window duplicates are dropped outright, see file header). */\n record(args: ErrorRecordArgs): void {\n try {\n const http = args.err instanceof HttpError ? args.err : null;\n const now = this.now();\n const projectId = args.ctx?.projectId ?? null;\n const code = args.code ?? http?.code ?? \"internal\";\n // Short-window dedup: coarse-grained to \"same kind of error for the same Project\"; repeats within the window aren't persisted.\n if (this.deduped(`${args.source}\\0${code}\\0${projectId ?? \"\"}`, now.getTime())) return;\n this.errors.insert({\n ts: now.toISOString(),\n date: formatLocalDate(now),\n projectId,\n agentId: args.ctx?.agentId ?? null,\n sessionId: args.ctx?.sessionId ?? null,\n source: args.source,\n // Explicit classification takes priority; otherwise infer from HttpError (business error = expected, else unexpected).\n kind: args.kind ?? (http ? \"expected\" : \"unexpected\"),\n code,\n // Unexpected errors from HTTP sources are converged to 500 externally (matches handleError's response).\n status: args.status ?? http?.status ?? (args.source === \"http\" ? 500 : null),\n message: messageOf(args.err),\n });\n } catch {\n // See file header: if the recorder itself errors, dropping this one record is the only option — never rethrow.\n }\n }\n\n /** true if a same-kind error was already recorded within the window (drop it); otherwise register this persist timestamp and keep the dedup table bounded. */\n private deduped(key: string, nowMs: number): boolean {\n const last = this.lastSeen.get(key);\n if (last !== undefined && nowMs - last < DEDUP_WINDOW_MS) return true;\n this.lastSeen.set(key, nowMs);\n if (this.lastSeen.size > DEDUP_KEYS_MAX) this.evict(nowMs);\n return false;\n }\n\n /** Keep the dedup table bounded (see file header): clear expired entries first; if still over the limit (hundreds/thousands of distinct error codes erupting at once), clear it entirely. */\n private evict(nowMs: number): void {\n for (const [key, at] of this.lastSeen) {\n if (nowMs - at >= DEDUP_WINDOW_MS) this.lastSeen.delete(key);\n }\n if (this.lastSeen.size > DEDUP_KEYS_MAX) this.lastSeen.clear();\n }\n}\n\n/** Minimal dependency a capture site needs on the recorder (tests inject a fake; structurally matches SessionManager's UsageRecorderLike). */\nexport type ErrorSink = Pick<ErrorRecorder, \"record\">;\n","/**\n * Active Session runtime.\n *\n * Responsibilities:\n * - get-or-resume-or-heal: use it directly on an active-table hit; with a Trace,\n * recover via `agent.resumeSession`; a stale Session that was created but never run\n * and survived a process restart (no Trace) **self-heals** — recreated via\n * createSession using the index row's workspace/modelId, yielding a new session_id\n * and updating the index's primary key; the Task response body always returns the\n * current actual id;\n * - Vault effectiveness: a vault update bumps the Agent's config generation\n * (invalidateAgentRuntimes); runtimes built earlier are discarded on their next\n * idle access and re-resumed, so the next Task always runs with current values;\n * - Per-Session mutual exclusion: only one Task/compaction may be in progress at a\n * time;\n * - run/compact drive: consumes the output stream in the background, publishing each\n * message to the SSE channel and handing it to usage-recorder for persistence;\n * on completion (including errors) resets to idle and pushes a `task_state` server\n * event;\n * - Approval registration and interrupt convergence: each approval decision re-reads\n * approval_mode from the DB (takes effect immediately); an interrupt first\n * converges pending approvals to deny, then aborts.\n *\n * The underlying implementation of get-or-resume-or-heal is injected via\n * `SessionLoader`: production uses the core SDK (createCoreSessionLoader), tests inject\n * a fake Session (issuing no real LLM requests).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n createAgent,\n findLatestTraceFile,\n goalFinishedOf,\n goalTokenDelta,\n isGoalRoundInput,\n isSessionMeta,\n stripLeadingMarkerBlocks,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type {\n ApproveFn,\n CompactAvailability,\n OmniMessage,\n SessionMetaPayload,\n SessionTitleResult,\n TextPayload,\n ThinkingLevelName,\n} from \"@prismshadow/penguin-core\";\nimport type { ServerEvent, SessionStatus } from \"../api/types.js\";\nimport { HttpError, isMissingCredential, modelCredentialMissing } from \"../http/errors.js\";\nimport type { GoalsRepo } from \"../db/repos/goals.js\";\nimport type { SessionRow, SessionsRepo } from \"../db/repos/sessions.js\";\nimport { ApprovalRegistry, makeApprove } from \"./approvals.js\";\nimport type { PendingApproval } from \"./approvals.js\";\nimport type { ChannelHub } from \"./channel.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport { LiveTailTracker } from \"./live-tail.js\";\nimport { asSessionSource } from \"./session-sources.js\";\nimport type { SessionSources } from \"./session-sources.js\";\nimport { StreamErrorWatcher } from \"./stream-error-watcher.js\";\nimport type { TitleNotifier } from \"./title-generator.js\";\nimport type { UsageContext } from \"./usage-recorder.js\";\n\n/** 409 for when there's nothing to compact: give the specific reason rather than a one-size-fits-none message. */\nfunction compactUnavailable(why: Exclude<CompactAvailability, \"ok\">): HttpError {\n const messages: Record<typeof why, string> = {\n unsupported: \"This Agent does not have context compaction configured.\",\n empty: \"The current context has nothing to compact (no completed conversation turns yet).\",\n just_compacted:\n \"The context was just compacted and there is no new conversation since; no need to compact again.\",\n };\n return new HttpError(409, \"nothing_to_compact\", messages[why]);\n}\n\n/** Minimal interface for a runtime Session (satisfied by core Session; tests may inject a fake implementation). */\nexport interface RuntimeSession {\n readonly sessionId: string;\n run(\n newMessages: OmniMessage[],\n opts: {\n approve: ApproveFn;\n signal: AbortSignal;\n thinkingLevel?: ThinkingLevelName;\n /** Present = goal mode: core loops rounds inside this one run (see core SessionRunOptions). */\n goal?: { budget?: number };\n },\n ): AsyncGenerator<OmniMessage>;\n compact(opts: { signal: AbortSignal }): AsyncGenerator<OmniMessage>;\n /** Whether compaction is possible and why; when not ok, compact() yields no messages (see core ContextEngine.compactability). */\n compactability(): CompactAvailability;\n /** Queues a mid-run steering message (core `Session.steer`); false when no Task is running. */\n steer(text: string): boolean;\n /** Skips the in-progress reconnect backoff, firing the next retry immediately (core `Session.skipReconnectWait`); false when no wait is in progress. */\n skipReconnectWait(): boolean;\n toolPermission(name: string): \"r\" | \"rw\" | undefined;\n /**\n * Out-of-band one-shot request for title generation (core `Session.generateTitle`,\n * writes no history/Trace). Material defaults to what the Session collects itself\n * (the first Task's text gathered during run); `material` overrides this for\n * subagents.\n */\n generateTitle(args?: {\n material?: { userText: string; assistantText: string };\n signal?: AbortSignal;\n }): Promise<SessionTitleResult>;\n}\n\n/** The underlying loader behind get-or-resume-or-heal. */\nexport interface SessionLoader {\n /**\n * Load a runtime Session from an index row: recover (with a Trace) or self-heal\n * rebuild (no Trace, session_id will change). Throws HttpError(409) for unrecoverable\n * cases such as a missing Workspace.\n */\n load(row: SessionRow): Promise<RuntimeSession>;\n}\n\n/**\n * Production loader: the core SDK's resumeSession / createSession. `sources` (when given)\n * lets the no-Trace self-heal rebuild re-record a known origin into the fresh session_meta;\n * with no registry entry (e.g. the process restarted and no Trace was ever written) the\n * rebuilt Session is unsourced — session_meta is the single source of truth, and none survived.\n */\nexport function createCoreSessionLoader(root: string, sources?: SessionSources): SessionLoader {\n return {\n async load(row: SessionRow): Promise<RuntimeSession> {\n const agent = await createAgent({\n root,\n projectId: row.projectId,\n agentId: row.agentId,\n });\n const located = await findLatestTraceFile(\n tracesDir(root, row.projectId, row.agentId),\n row.sessionId,\n );\n if (located) {\n // With a Trace: rebuild via \"Session Recovery\" (history injected via setHistory,\n // carrying over any residual state).\n // core's recognizable recovery failures (Workspace deleted / Model removed from\n // config / Trace missing session_meta, etc.) are converged to 409, preserving\n // the original message rather than bubbling up as 500.\n try {\n return await agent.resumeSession({ sessionId: row.sessionId });\n } catch (err) {\n // The credential key was deleted after the Session was created: only caught\n // here at recovery time; give the same actionable message.\n if (isMissingCredential(err)) throw modelCredentialMissing(row.modelId);\n throw toUnrecoverableError(err);\n }\n }\n // No Trace (created but never run, and the process has restarted since): self-heal\n // rebuild. A missing Workspace → 409.\n try {\n const stat = await fs.stat(row.workspace);\n if (!stat.isDirectory()) throw new Error(\"not a directory\");\n } catch {\n throw new HttpError(\n 409,\n \"workspace_missing\",\n `This Session's Workspace no longer exists: ${row.workspace}, so it cannot continue. Create a new Session.`,\n );\n }\n const knownSource = sources?.get(row.sessionId);\n try {\n return await agent.createSession({\n workspaceDir: row.workspace,\n modelId: row.modelId,\n provider: row.provider,\n // The rebuilt Session re-records a known origin in its fresh session_meta.\n ...(knownSource != null ? { source: knownSource } : {}),\n });\n } catch (err) {\n if (isMissingCredential(err)) throw modelCredentialMissing(row.modelId);\n throw toUnrecoverableError(err);\n }\n },\n };\n}\n\n/** A plain Error thrown by core recovery/self-heal rebuild → 409 (preserving the original, actionable message). */\nfunction toUnrecoverableError(err: unknown): HttpError {\n if (err instanceof HttpError) return err;\n return new HttpError(\n 409,\n \"session_unrecoverable\",\n err instanceof Error ? err.message : String(err),\n );\n}\n\nexport interface UsageRecorderLike {\n record(ctx: UsageContext, msg: OmniMessage): Promise<void>;\n}\n\nexport interface SessionManagerDeps {\n sessions: SessionsRepo;\n channels: ChannelHub;\n loader: SessionLoader;\n /** Session-origin registry (session_meta is the single source of truth; subagent registration records the forwarded meta's source here). */\n sources: SessionSources;\n recorder: UsageRecorderLike;\n /** Automatic Session title generation (optional: not injected in tests or when disabled). */\n titles?: TitleNotifier;\n /** Error persistence (optional: without it, only logs — same as before this was wired up). */\n errors?: ErrorSink;\n log?: (line: string) => void;\n /** Goal run-state persistence (optional like `titles`: without it, goals run but leave no restorable record). */\n goals?: GoalsRepo;\n}\n\n/** One queued follow-up task (`queueIfBusy`): the task input plus the per-turn thinking level it was posted with. */\ninterface QueuedFollowUp {\n input: OmniMessage[];\n thinkingLevel?: ThinkingLevelName;\n}\n\n/** Active-table entry: a loaded runtime Session plus its running state. */\ninterface RuntimeEntry {\n sessionId: string;\n projectId: string;\n agentId: string;\n /** Vendor grouping for the Session's model (paired with modelId to form a model reference). */\n provider: string;\n modelId: string;\n session: RuntimeSession;\n status: SessionStatus;\n approvals: ApprovalRegistry;\n abort: AbortController | null;\n /** The in-flight drive Promise (awaited during graceful shutdown). */\n running: Promise<void> | null;\n /**\n * Agent config generation this runtime was built under (see\n * invalidateAgentRuntimes): once it falls behind the Agent's current generation,\n * the entry is discarded on its next idle access and re-resumed via the loader.\n */\n generation: number;\n /**\n * Queued follow-up tasks (`queueIfBusy`): task inputs accepted while a Task/compaction was\n * in progress, auto-started one at a time (in order) once the session returns to idle —\n * ordinary tasks with normal semantics, unlike the mid-run steering queue. Each entry\n * keeps the per-turn thinkingLevel it was posted with (applied at auto-start).\n * Deliberately NOT discarded on abort: they are future tasks the user explicitly queued.\n */\n followUps: QueuedFollowUp[];\n /** Timestamp of last activity (refreshed on load / status flip / drive completion), used for idle-eviction checks. */\n lastActivityMs: number;\n}\n\n/** Active-table idle eviction: same convention as the SSE channel (an idle entry with no activity for 30 minutes releases its memory). */\nconst ENTRY_IDLE_MS = 30 * 60 * 1000;\nconst ENTRY_SWEEP_INTERVAL_MS = 60 * 1000;\n\n/** Cap on collected model text for title material (accumulation stops beyond this; the generator side also truncates further). */\nconst TITLE_EXCERPT_LIMIT = 4000;\n/**\n * Early title trigger: once this many characters of main-session body text have streamed,\n * title generation starts right away instead of waiting for the Task to finish — the core\n * Session has captured its (capped) material by then, and a long answer would only overrun\n * it. Short conversations are still covered by the completion trigger in drive's finally.\n */\nconst EARLY_TITLE_BODY_CHARS = 1000;\n\n/** Composite Agent key (used as a Set key, avoiding projectId/agentId concatenation ambiguity). */\nfunction agentKey(projectId: string, agentId: string): string {\n return `${projectId}\\0${agentId}`;\n}\n\n/** If msg is a run_subagent tool call carrying a `prompt`, return its id and prompt (for use as the subagent's title); otherwise null. */\nfunction runSubagentCall(msg: OmniMessage): { toolCallId: string; prompt: string } | null {\n const p = msg.payload as {\n type?: string;\n name?: string;\n arguments?: string;\n tool_call_id?: string;\n };\n if (msg.type !== \"model_msg\" || p.type !== \"tool_call\" || p.name !== \"run_subagent\") return null;\n if (typeof p.arguments !== \"string\" || typeof p.tool_call_id !== \"string\") return null;\n try {\n const args = JSON.parse(p.arguments) as { prompt?: unknown };\n if (typeof args.prompt !== \"string\" || !args.prompt.trim()) return null;\n return { toolCallId: p.tool_call_id, prompt: args.prompt };\n } catch {\n return null; // Arguments were truncated/malformed: this call is doomed, no subagent will result\n }\n}\n\n/** The denied tool_call_id (approval_decision with decision ≠ allow); otherwise null. */\nfunction deniedToolCallId(msg: OmniMessage): string | null {\n const p = msg.payload as { type?: string; decision?: string; tool_call_id?: string };\n if (msg.type !== \"event_msg\" || p.type !== \"approval_decision\") return null;\n if (p.decision === \"allow\" || typeof p.tool_call_id !== \"string\") return null;\n return p.tool_call_id;\n}\n\n/** The tool_call_id of a parent-level tool call that has settled (a complete tool_call_output); otherwise null. */\nfunction settledToolCallId(msg: OmniMessage): string | null {\n const p = msg.payload as { type?: string; tool_call_id?: string };\n if (msg.type !== \"model_msg\" || p.type !== \"tool_call_output\") return null;\n return typeof p.tool_call_id === \"string\" ? p.tool_call_id : null;\n}\n\n/** A subagent registered during this run, plus its title material. */\ninterface ChildSession {\n sessionId: string;\n agentId: string;\n modelId: string;\n /** The prompt of the run_subagent call that spawned it (user material for title generation, and the fallback title). */\n prompt: string;\n /** The model text the subagent itself produced (assistant material for title generation). */\n assistantExcerpt: string;\n}\n\n/** Predicate for a plain-text message on the main session (no origin): title material is drawn only from user/model text. */\nfunction isPlainText(role: \"user\" | \"assistant\") {\n return (msg: OmniMessage): msg is OmniMessage<TextPayload> => {\n const payload = msg.payload as { type?: string; role?: string };\n return (\n msg.type === \"model_msg\" &&\n payload.type === \"text\" &&\n payload.role === role &&\n (!msg.origin || msg.origin.length === 0)\n );\n };\n}\n\n/** For a nested message, the owning Session (end of the origin chain) and text of the model reply; null if it isn't model text. */\nfunction nestedAssistantText(msg: OmniMessage): { sessionId: string; text: string } | null {\n const p = msg.payload as { type?: string; role?: string; text?: string };\n if (msg.type !== \"model_msg\" || p.type !== \"text\" || p.role !== \"assistant\") return null;\n if (!msg.origin || msg.origin.length === 0 || typeof p.text !== \"string\") return null;\n return { sessionId: msg.origin[msg.origin.length - 1]!, text: p.text };\n}\n\nexport class SessionManager {\n private readonly entries = new Map<string, RuntimeEntry>();\n /** Per-Session mutex (serializes get-or-load and status flips); auto-cleaned once the chain drains. */\n private readonly locks = new Map<string, Promise<unknown>>();\n private readonly log: (line: string) => void;\n /** Graceful-shutdown flag: once set, new Tasks/compactions are rejected (503). */\n private closed = false;\n /** Agents currently being deleted (key = agentKey): new Tasks/compactions are always rejected with 409 during this window. */\n private readonly deletingAgents = new Set<string>();\n /** Sessions currently being deleted (guards against the entry/Trace file being rebuilt and reviving it inside the deletion race window). */\n private readonly deletingSessions = new Set<string>();\n /** Per-Agent config generation (key = agentKey), bumped by invalidateAgentRuntimes on vault updates. */\n private readonly agentGenerations = new Map<string, number>();\n /** Open streaming fragments of running sessions (fed by drive, served to GET /messages; see live-tail.ts). */\n private readonly liveTail = new LiveTailTracker();\n private readonly sweepTimer: NodeJS.Timeout;\n\n constructor(private readonly deps: SessionManagerDeps) {\n this.log = deps.log ?? ((line) => console.error(line));\n this.sweepTimer = setInterval(() => this.sweepIdle(), ENTRY_SWEEP_INTERVAL_MS);\n this.sweepTimer.unref?.();\n }\n\n // —— Query surface (used by Session listing / Agent active-count / SSE subscription replay) ——\n\n statusOf(sessionId: string): SessionStatus {\n return this.entries.get(sessionId)?.status ?? \"idle\";\n }\n\n pendingApprovalCount(sessionId: string): number {\n return this.entries.get(sessionId)?.approvals.size ?? 0;\n }\n\n pendingApprovals(sessionId: string): PendingApproval[] {\n return this.entries.get(sessionId)?.approvals.list() ?? [];\n }\n\n /** Number of queued follow-up tasks (`queueIfBusy`) awaiting auto-start. */\n pendingFollowUpCount(sessionId: string): number {\n return this.entries.get(sessionId)?.followUps.length ?? 0;\n }\n\n /**\n * Live tail of a running session: one synthetic `partial_* start` OmniMessage per open\n * streaming fragment, carrying the full accumulated content so far (see live-tail.ts).\n * Empty when idle or when nothing is streaming. GET /messages attaches this (with a\n * channel cursor) so a client joining mid-stream can render the in-progress message.\n */\n liveFragments(sessionId: string): OmniMessage[] {\n return this.liveTail.fragments(sessionId);\n }\n\n /** Number of Sessions for this Agent that are currently running / compacting. */\n activeCountForAgent(projectId: string, agentId: string): number {\n let n = 0;\n for (const e of this.entries.values()) {\n if (e.projectId === projectId && e.agentId === agentId && e.status !== \"idle\") n++;\n }\n return n;\n }\n\n /** Add a newly created Session to the active table (status idle), avoiding a redundant load on the next Task. */\n adopt(row: SessionRow, session: RuntimeSession): void {\n this.entries.set(row.sessionId, {\n sessionId: row.sessionId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n session,\n status: \"idle\",\n approvals: new ApprovalRegistry(),\n abort: null,\n running: null,\n generation: this.generationOf(row.projectId, row.agentId),\n followUps: [],\n lastActivityMs: Date.now(),\n });\n }\n\n /**\n * After an Agent's vault is updated: bump the Agent's config generation so every\n * runtime built before the update is discarded on its next idle access and\n * re-resumed via the loader — resume re-reads agent_state/.vault.toml, so the next\n * Task on any of this Agent's Sessions runs with the new values (history is\n * preserved through the Trace). A Task already in flight is neither aborted nor\n * hot-swapped: it keeps the values it started with, and its entry is rebuilt on\n * the first access after it returns to idle (see ensureEntry).\n */\n invalidateAgentRuntimes(projectId: string, agentId: string): void {\n const key = agentKey(projectId, agentId);\n this.agentGenerations.set(key, this.generationOf(projectId, agentId) + 1);\n }\n\n /**\n * After a Project's models/credentials change: invalidate every cached runtime in this\n * Project, so the next Task re-resumes with the new api_key / base_url. Same\n * effective-value semantics as invalidateAgentRuntimes — no hot swap into a Task already\n * in flight. Iterating the active table is complete here, not a shortcut: the generation\n * map only matters for runtimes that are already cached, and an Agent with no cached\n * entry builds fresh through the loader anyway.\n */\n invalidateProjectRuntimes(projectId: string): void {\n const agentIds = new Set<string>();\n for (const e of this.entries.values()) {\n if (e.projectId === projectId) agentIds.add(e.agentId);\n }\n for (const agentId of agentIds) this.invalidateAgentRuntimes(projectId, agentId);\n }\n\n // —— Task / compaction drive ——\n\n /**\n * Start a Task: get-or-load → 409\n * mutual-exclusion check → publish the input messages first → drive run in the\n * background. Returns the current actual session_id (the new id after self-heal).\n * `opts.thinkingLevel` (optional, validated by the route) rides into this run's\n * `session.run` options — a per-turn parameter, applied to this Task only.\n * With `queueIfBusy`, a busy session (running/compacting) enqueues the input as a\n * follow-up instead of 409: it auto-starts as an ordinary next task once the session\n * returns to idle (`queued: true` in the result; see startQueuedFollowUp), keeping its\n * thinkingLevel for that auto-start.\n */\n async startTask(\n sessionId: string,\n input: OmniMessage[],\n opts?: { thinkingLevel?: ThinkingLevelName; queueIfBusy?: boolean },\n ): Promise<{ sessionId: string; queued: boolean }> {\n return this.withLock(sessionId, async () => {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = await this.ensureEntry(sessionId);\n if (entry.status !== \"idle\" && opts?.queueIfBusy) {\n entry.followUps.push({\n input,\n ...(opts.thinkingLevel !== undefined ? { thinkingLevel: opts.thinkingLevel } : {}),\n });\n entry.lastActivityMs = Date.now();\n // Re-publish the current state so subscribers pick up the new queued count (the\n // input itself is published when the follow-up actually starts).\n this.publishState(entry, entry.status);\n return { sessionId: entry.sessionId, queued: true };\n }\n this.assertIdle(entry);\n this.launchTask(entry, input, opts?.thinkingLevel);\n return { sessionId: entry.sessionId, queued: false };\n });\n }\n\n /**\n * Start a goal run: like startTask, but the run is core goal mode — one\n * `session.run(input, { goal })` call loops rounds until a terminal state, and the\n * Session stays `running` for the whole goal (every round), so the existing abort\n * endpoint interrupts the entire loop and schedules queue behind it as usual. Round\n * inputs are yielded by core and published like any streamed message; progress\n * additionally goes out as goal_* server events and into goal_state (when a repo is\n * wired).\n */\n async startGoal(\n sessionId: string,\n args: {\n /** Round-1 input (text-only, route-validated); its marker-stripped text is the objective. */\n input: OmniMessage[];\n budget: number;\n /** Optional per-goal thinking level: rides every round's Task (route-validated). */\n thinkingLevel?: ThinkingLevelName;\n },\n ): Promise<{ sessionId: string }> {\n return this.withLock(sessionId, async () => {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = await this.ensureEntry(sessionId);\n this.assertIdle(entry);\n // The objective is the user's own text (leading skill-invocation blocks stripped) —\n // the same derivation core records in GOAL.yaml; used for the run-state row, the\n // goal_started event, and as title material.\n const text = args.input\n .filter(isPlainText(\"user\"))\n .map((m) => m.payload.text)\n .join(\"\\n\");\n const objective = stripLeadingMarkerBlocks(text).trim() || text.trim();\n const ac = new AbortController();\n entry.status = \"running\";\n entry.abort = ac;\n entry.lastActivityMs = Date.now();\n this.publishState(entry, \"running\");\n const approve = makeApprove({\n getMode: () => this.deps.sessions.findById(entry.sessionId)?.approvalMode ?? \"always-ask\",\n toolPermission: (name) => entry.session.toolPermission(name),\n registry: entry.approvals,\n publishRequest: (pending) =>\n this.publishEvent(entry, {\n type: \"approval_request\",\n toolCall: pending.toolCall,\n ...(pending.origin !== undefined ? { origin: pending.origin } : {}),\n }),\n });\n const goalId = this.deps.goals?.create({\n sessionId: entry.sessionId,\n projectId: entry.projectId,\n agentId: entry.agentId,\n objective,\n budget: args.budget,\n });\n this.publishEvent(entry, {\n type: \"goal_started\",\n sessionId: entry.sessionId,\n objective,\n budget: args.budget,\n });\n const gen = this.goalStream(entry, {\n input: args.input,\n budget: args.budget,\n ...(args.thinkingLevel !== undefined ? { thinkingLevel: args.thinkingLevel } : {}),\n approve,\n signal: ac.signal,\n ...(goalId !== undefined ? { goalId } : {}),\n });\n // The objective doubles as the title material (same role as a task's input text).\n entry.running = this.drive(entry, gen, { userExcerpt: objective });\n return { sessionId: entry.sessionId };\n });\n }\n\n /**\n * Taps core's goal-mode stream for `drive`: round boundaries (the injected `[goal]`\n * inputs) become goal_round events + goal_state refreshes, and the terminal\n * `goal_finished` event message becomes the goal_finished server event + the run-state\n * row's final status. Token numbers mirror core's own accounting (same\n * `goalTokenDelta`), so the UI shows exactly what the budget check uses.\n */\n private async *goalStream(\n entry: RuntimeEntry,\n args: {\n input: OmniMessage[];\n budget: number;\n thinkingLevel?: ThinkingLevelName;\n approve: ApproveFn;\n signal: AbortSignal;\n goalId?: number;\n },\n ): AsyncGenerator<OmniMessage> {\n const gen = entry.session.run(args.input, {\n approve: args.approve,\n signal: args.signal,\n ...(args.thinkingLevel !== undefined ? { thinkingLevel: args.thinkingLevel } : {}),\n goal: { budget: args.budget },\n });\n let round = 0;\n let used = 0;\n let finished = false;\n try {\n for await (const msg of gen) {\n used += goalTokenDelta(msg);\n if (isGoalRoundInput(msg)) {\n round++;\n if (args.goalId !== undefined) this.deps.goals?.progress(args.goalId, round, used);\n this.publishEvent(entry, {\n type: \"goal_round\",\n sessionId: entry.sessionId,\n round,\n used,\n budget: args.budget,\n });\n }\n const outcome = goalFinishedOf(msg);\n if (outcome) {\n finished = true;\n if (args.goalId !== undefined) {\n this.deps.goals?.finish(\n args.goalId,\n outcome.outcome,\n outcome.rounds,\n outcome.tokensUsed,\n );\n }\n this.publishEvent(entry, {\n type: \"goal_finished\",\n sessionId: entry.sessionId,\n outcome: outcome.outcome,\n rounds: outcome.rounds,\n used: outcome.tokensUsed,\n });\n }\n yield msg;\n }\n if (!finished) {\n // Defensive: core always ends a goal stream with goal_finished; a stream that\n // didn't is a cut-off run — close the row so the UI never shows a forever-active\n // goal.\n this.finishAborted(entry, args.goalId, round, used);\n }\n } catch (err) {\n // Core throws only on infrastructure failures (e.g. GOAL.yaml writes): close the\n // run state as aborted, then let drive's defensive catch record the error. Guarded on\n // `finished`: a throw after the terminal event must not overwrite the row's real\n // outcome (repo.finish is an unconditional UPDATE) or publish a contradicting event.\n if (!finished) this.finishAborted(entry, args.goalId, round, used);\n throw err;\n }\n }\n\n /** Closes a goal's run state as aborted (stream cut off / infrastructure failure). */\n private finishAborted(\n entry: RuntimeEntry,\n goalId: number | undefined,\n round: number,\n used: number,\n ): void {\n if (goalId !== undefined) this.deps.goals?.finish(goalId, \"aborted\", round, used);\n this.publishEvent(entry, {\n type: \"goal_finished\",\n sessionId: entry.sessionId,\n outcome: \"aborted\",\n rounds: round,\n used,\n });\n }\n\n /** Shared task launch (fresh tasks and auto-started follow-ups): flips to running, publishes the input, and drives the run with the per-turn thinking level (if any). Caller holds the session lock and has verified idle. */\n private launchTask(\n entry: RuntimeEntry,\n input: OmniMessage[],\n thinkingLevel?: ThinkingLevelName,\n ): void {\n const channel = this.deps.channels.get(entry.sessionId);\n const ac = new AbortController();\n entry.status = \"running\";\n entry.abort = ac;\n entry.lastActivityMs = Date.now();\n // Publish the input messages first (visible to other subscribers; the Trace is\n // persisted by the SDK), then flip the running status.\n for (const msg of input) channel.publish(msg);\n this.publishState(entry, \"running\");\n\n const approve = makeApprove({\n // Re-reads approval_mode from the DB on every decision (a PATCH takes effect immediately).\n getMode: () => this.deps.sessions.findById(entry.sessionId)?.approvalMode ?? \"always-ask\",\n toolPermission: (name) => entry.session.toolPermission(name),\n registry: entry.approvals,\n publishRequest: (pending) =>\n this.publishEvent(entry, {\n type: \"approval_request\",\n toolCall: pending.toolCall,\n ...(pending.origin !== undefined ? { origin: pending.origin } : {}),\n }),\n });\n const gen = entry.session.run(input, {\n approve,\n signal: ac.signal,\n ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),\n });\n // Title material is collected by the core Session itself during run; here we only\n // keep this call's input user text, used both as the \"material present → attempt\n // generation\" criterion and as the fallback title source if the LLM call fails.\n const userExcerpt = input\n .filter(isPlainText(\"user\"))\n .map((m) => m.payload.text)\n .join(\"\\n\");\n entry.running = this.drive(entry, gen, { userExcerpt });\n }\n\n /**\n * Auto-start of queued follow-ups: called from drive's finally whenever a Task or\n * compaction finishes (abort included — follow-ups are future tasks the user queued, so\n * an abort of the current run does not discard them). Re-validates everything under the\n * session lock (another task may have snuck in, the server may be shutting down, the\n * session may be getting deleted) and launches exactly one queued input — the next\n * finish picks up the one after, keeping order and one-at-a-time semantics.\n */\n private async startQueuedFollowUp(sessionId: string): Promise<void> {\n try {\n await this.withLock(sessionId, async () => {\n if (this.closed || this.deletingSessions.has(sessionId)) return;\n const row = this.deps.sessions.findById(sessionId);\n if (row && this.deletingAgents.has(agentKey(row.projectId, row.agentId))) return;\n const entry = this.entries.get(sessionId);\n if (!entry || entry.status !== \"idle\" || entry.followUps.length === 0) return;\n const next = entry.followUps.shift()!;\n this.launchTask(entry, next.input, next.thinkingLevel);\n });\n } catch (err) {\n this.log(`[followup] auto-start failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n /** Manually compact the context: 409 if already running; compaction output also flows into the SSE channel. */\n async startCompact(sessionId: string): Promise<{ sessionId: string }> {\n return this.withLock(sessionId, async () => {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = await this.ensureEntry(sessionId);\n this.assertIdle(entry);\n // When there's nothing to compact, core's compact() yields no messages at all: we\n // can't just return 202 and walk away, or the frontend would wait forever for a\n // compaction banner that never comes (this is exactly the \"/compact does nothing\n // after an interrupt\" complaint). Reject explicitly, and **say why** clearly —\n // \"just compacted\" and \"haven't talked yet\" share the same internal state\n // (sessionTurns === 0), but are two completely different messages to the user:\n // telling someone who just compacted that there's \"no completed conversation turn\n // yet\" tells them nothing.\n const why = entry.session.compactability();\n if (why !== \"ok\") throw compactUnavailable(why);\n const ac = new AbortController();\n entry.status = \"compacting\";\n entry.abort = ac;\n entry.lastActivityMs = Date.now();\n this.publishState(entry, \"compacting\");\n const gen = entry.session.compact({ signal: ac.signal });\n entry.running = this.drive(entry, gen);\n return { sessionId: entry.sessionId };\n });\n }\n\n /** Submit an approval decision; returns false if the pending approval doesn't exist (already decided/unknown). */\n decideApproval(sessionId: string, toolCallId: string, decision: \"allow\" | \"deny\"): boolean {\n const entry = this.entries.get(sessionId);\n if (!entry) return false;\n return entry.approvals.decide(toolCallId, decision);\n }\n\n /**\n * Mid-run steering: forward the text to the running Session (core delivers it between\n * turns as a standalone `[user_steering]` user message — no SSE event of its own; the\n * message arrives through the stream the drive loop already publishes). 409 when the\n * Session isn't running a Task (idle / compacting / not loaded) or the run finished in the\n * race window — the caller falls back to submitting a normal task.\n */\n steer(sessionId: string, text: string): void {\n const entry = this.entries.get(sessionId);\n if (!entry || entry.status !== \"running\" || !entry.session.steer(text)) {\n throw new HttpError(\n 409,\n \"not_running\",\n \"This Session has no Task in progress; send the message as a new task instead.\",\n );\n }\n entry.lastActivityMs = Date.now();\n }\n\n /**\n * \"Retry now\" on the reconnect countdown: skip the in-progress backoff wait and fire\n * the next retry immediately (the attempt counter is unchanged — the skipped wait does\n * not consume an extra attempt). Returns false as a benign no-op when the session has\n * no active runtime or no reconnect wait is in progress — timing races (the wait\n * elapsed right before the click) must not surface as errors. Mirrors the steer seam:\n * manager → RuntimeSession → core Session → engine.\n */\n retryNow(sessionId: string): boolean {\n const entry = this.entries.get(sessionId);\n // Both running Tasks and compactions can be parked in a reconnect backoff.\n if (!entry || entry.status === \"idle\") return false;\n const skipped = entry.session.skipReconnectWait();\n if (skipped) entry.lastActivityMs = Date.now();\n return skipped;\n }\n\n /**\n * Interrupt the current Task/compaction: pending approvals converge to deny first,\n * then the AbortSignal fires. Returns false if nothing is in progress (the route\n * treats this as a 204 no-op).\n */\n abortTask(sessionId: string): boolean {\n const entry = this.entries.get(sessionId);\n if (!entry || !entry.abort) return false;\n entry.approvals.denyAll();\n entry.abort.abort();\n return true;\n }\n\n /**\n * Before deleting a Project, converge all its active runs and clear them out of the\n * active table. Returns the in-flight drive Promises of the affected entries: the\n * caller (deleteProject) should await them before removing the directory, so that\n * interrupt-cleanup Trace writes don't recreate the directory after deletion.\n */\n abortProject(projectId: string): Promise<void>[] {\n const runnings: Promise<void>[] = [];\n for (const [key, entry] of [...this.entries]) {\n if (entry.projectId !== projectId) continue;\n entry.approvals.denyAll();\n entry.abort?.abort();\n if (entry.running) runnings.push(entry.running);\n this.entries.delete(key);\n }\n return runnings;\n }\n\n /**\n * Before deleting an Agent, converge all its active runs and clear them out of the\n * active table (same semantics as abortProject). Also marks this Agent as \"being\n * deleted\": new Tasks/compactions entering during the deletion process are always\n * rejected with 409 (assertAgentNotDeleting), closing the race window where a new\n * task recreates the directory and revives an already-deleted Agent between the\n * abortAgent snapshot and the directory removal. The caller must call\n * endAgentDeletion once deletion finishes (success or failure).\n */\n beginAgentDeletion(projectId: string, agentId: string): Promise<void>[] {\n this.deletingAgents.add(agentKey(projectId, agentId));\n const runnings: Promise<void>[] = [];\n for (const [key, entry] of [...this.entries]) {\n if (entry.projectId !== projectId || entry.agentId !== agentId) continue;\n entry.approvals.denyAll();\n entry.abort?.abort();\n if (entry.running) runnings.push(entry.running);\n this.entries.delete(key);\n }\n return runnings;\n }\n\n endAgentDeletion(projectId: string, agentId: string): void {\n this.deletingAgents.delete(agentKey(projectId, agentId));\n }\n\n /**\n * Before deleting a single Session, converge its active run and clear it out of the\n * active table (same semantics as beginAgentDeletion). Also marks this Session as\n * \"being deleted\": new Tasks/compactions entering during the deletion process are\n * always rejected with 409 (assertSessionNotDeleting), closing the race window where\n * a new task recreates the entry and Trace file, reviving an already-deleted Session\n * between the abort snapshot and the file removal. The caller must call\n * endSessionDeletion once deletion finishes (success or failure). Returns the\n * in-flight drive Promise: the caller should await it before deleting the Trace file,\n * so cleanup writes don't recreate the file.\n */\n beginSessionDeletion(sessionId: string): Promise<void>[] {\n this.deletingSessions.add(sessionId);\n const entry = this.entries.get(sessionId);\n if (!entry) return [];\n entry.approvals.denyAll();\n entry.abort?.abort();\n this.entries.delete(sessionId);\n return entry.running ? [entry.running] : [];\n }\n\n endSessionDeletion(sessionId: string): void {\n this.deletingSessions.delete(sessionId);\n }\n\n /** Graceful shutdown: reject new tasks (503), interrupt all active runs, and wait for them to finish (default ≤5s). */\n async shutdown(timeoutMs = 5000): Promise<void> {\n this.closed = true;\n clearInterval(this.sweepTimer);\n const pending: Promise<void>[] = [];\n for (const entry of this.entries.values()) {\n if (!entry.abort) continue;\n entry.approvals.denyAll();\n entry.abort.abort();\n if (entry.running) pending.push(entry.running);\n }\n if (pending.length === 0) return;\n await Promise.race([\n Promise.allSettled(pending).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs).unref?.()),\n ]);\n }\n\n /**\n * Active-table idle eviction: removes entries that are idle (idle status, no pending\n * approvals, no in-flight drive) and have been inactive past the timeout, releasing\n * the core Session's full in-memory history. This is purely memory reclamation: the\n * next access re-resumes via the loader, so correctness is unaffected. Lock-table\n * entries are auto-cleaned by withLock once their chain drains (including leftover\n * entries under the old id after self-heal). `now` / `idleMs` are injectable for\n * tests and timers.\n */\n sweepIdle(now: number = Date.now(), idleMs: number = ENTRY_IDLE_MS): void {\n for (const [key, entry] of this.entries) {\n if (entry.status !== \"idle\" || entry.approvals.size !== 0 || entry.running !== null) continue;\n if (entry.followUps.length > 0) continue; // queued follow-ups must not be evicted with the entry\n if (now - entry.lastActivityMs <= idleMs) continue;\n this.entries.delete(key);\n }\n }\n\n // —— Internal ——\n\n private assertOpen(): void {\n if (this.closed) {\n throw new HttpError(\n 503,\n \"shutting_down\",\n \"Server is shutting down; not accepting new Tasks.\",\n );\n }\n }\n\n /** The Agent owning this Session is being deleted → 409 (guards against directory recreation inside the deletion race window). */\n private assertAgentNotDeleting(sessionId: string): void {\n const row = this.deps.sessions.findById(sessionId);\n if (row && this.deletingAgents.has(agentKey(row.projectId, row.agentId))) {\n throw new HttpError(\n 409,\n \"agent_deleting\",\n \"This Agent is being deleted; not accepting new Tasks.\",\n );\n }\n }\n\n /** This Session is being deleted → 409 (guards against the entry/Trace being rebuilt and reviving it inside the deletion race window). */\n private assertSessionNotDeleting(sessionId: string): void {\n if (this.deletingSessions.has(sessionId)) {\n throw new HttpError(\n 409,\n \"session_deleting\",\n \"This Session is being deleted; not accepting new Tasks.\",\n );\n }\n }\n\n private assertIdle(entry: RuntimeEntry): void {\n if (entry.status === \"running\") {\n throw new HttpError(409, \"task_in_progress\", \"This Session already has a Task in progress.\");\n }\n if (entry.status === \"compacting\") {\n throw new HttpError(\n 409,\n \"compacting\",\n \"This Session is compacting its context; not accepting new input.\",\n );\n }\n }\n\n private generationOf(projectId: string, agentId: string): number {\n return this.agentGenerations.get(agentKey(projectId, agentId)) ?? 0;\n }\n\n /** get-or-resume-or-heal: use directly on an active-table hit; otherwise load via the loader, updating the index's primary key on self-heal. */\n private async ensureEntry(sessionId: string): Promise<RuntimeEntry> {\n const existing = this.entries.get(sessionId);\n if (existing) {\n if (existing.generation === this.generationOf(existing.projectId, existing.agentId)) {\n return existing;\n }\n // Built before the last vault update: discard once idle and fall through to a\n // fresh load (resume re-reads the vault). A busy entry is returned as-is — the\n // in-flight run keeps its values and assertIdle rejects the new Task anyway;\n // it is rebuilt on the first access after it finishes.\n if (\n existing.status !== \"idle\" ||\n existing.running !== null ||\n existing.approvals.size !== 0\n ) {\n return existing;\n }\n this.entries.delete(sessionId);\n }\n const row = this.deps.sessions.findById(sessionId);\n if (!row) {\n throw new HttpError(\n 404,\n \"session_not_found\",\n \"Session does not exist or you do not have access.\",\n );\n }\n // Captured before the (awaited) load: a vault update racing with the load leaves\n // this entry stale, so the access after next rebuilds it with the new values.\n const generation = this.generationOf(row.projectId, row.agentId);\n const session = await this.deps.loader.load(row);\n // The Session/Agent was marked for deletion while loading: discard the load result,\n // don't rebuild the entry (avoids reviving an orphaned Trace).\n this.assertSessionNotDeleting(row.sessionId);\n this.assertAgentNotDeleting(row.sessionId);\n let currentId = row.sessionId;\n if (session.sessionId !== row.sessionId) {\n // Self-heal produced a new session_id: update the index's primary key; the SSE\n // channel and pending state are naturally empty for it.\n this.deps.sessions.replaceId(row.sessionId, session.sessionId);\n currentId = session.sessionId;\n }\n const entry: RuntimeEntry = {\n sessionId: currentId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n session,\n status: \"idle\",\n approvals: new ApprovalRegistry(),\n abort: null,\n running: null,\n generation,\n followUps: [],\n lastActivityMs: Date.now(),\n };\n this.entries.set(currentId, entry);\n return entry;\n }\n\n /**\n * Drive the output stream in the background: publish each message + persist usage +\n * persist LLM/tool errors; on completion (including errors) resets to idle and pushes\n * the status. `titleSource` is passed only for Task runs (compaction doesn't generate\n * a title): it collects model text for automatic title generation.\n */\n private async drive(\n entry: RuntimeEntry,\n gen: AsyncGenerator<OmniMessage>,\n titleSource?: { userExcerpt: string },\n ): Promise<void> {\n let earlyTitleFired = false;\n let mainBodyChars = 0;\n const ctx: UsageContext = {\n projectId: entry.projectId,\n agentId: entry.agentId,\n sessionId: entry.sessionId,\n provider: entry.provider,\n modelId: entry.modelId,\n };\n // LLM request failures and tool execution failures aren't expressed via throw (core\n // converges them into the message stream), so the try/catch below can't catch them:\n // the watcher inspects messages one by one and fishes them out for persistence\n // (subagent failures flow through this same stream too; see stream-error-watcher).\n const watcher = this.deps.errors\n ? new StreamErrorWatcher(this.deps.errors, {\n projectId: entry.projectId,\n agentId: entry.agentId,\n sessionId: entry.sessionId,\n })\n : null;\n // Subagent (origin) registration: as soon as session_meta arrives, the child Session\n // is persisted so it appears immediately in the sidebar (the frontend picks it up\n // when it refreshes the list at task completion). The title material is \"the prompt\n // of the run_subagent call that spawned this subagent\" — the subagent's user input\n // is never replayed onto the parent stream (ContextEngine writes the Trace but never\n // yields it), so we can't rely on the subagent's first user message; instead we use\n // the run_subagent tool_call arguments immediately preceding it on the parent stream\n // (depth limited to 1, spawned in order, so taking the most recent one suffices).\n /** Subagents registered during this run (keyed by session id); titles are generated for each on completion. */\n const children = new Map<string, ChildSession>();\n // Unclaimed run_subagent prompts, queued in call order: a single round may spawn\n // multiple subagents in parallel, and a subagent's session_meta only carries the\n // session id (no tool_call_id), so pairing can only be approximated via FIFO (when\n // spawned in parallel and session_meta arrives out of order, two subagents' titles\n // may end up swapped — this only affects the displayed title). A call that will\n // never produce a subagent must be dequeued, or its prompt would be mismatched onto\n // the next subagent: this covers denied calls (approval_decision ≠ allow), and calls\n // that were approved but failed before spawning the subagent (e.g. agent_id doesn't\n // exist) — the latter is cleaned up when the parent-level tool_call_output settles;\n // if the call is still in the queue at that point, it never produced a session_meta.\n const subagentPrompts = new Map<string, string>();\n try {\n for await (const msg of gen) {\n // A parent-level (no origin) run_subagent call: record its prompt for the child\n // session_meta that arrives later to use as its title.\n if (!msg.origin || msg.origin.length === 0) {\n const call = runSubagentCall(msg);\n if (call) subagentPrompts.set(call.toolCallId, call.prompt);\n const denied = deniedToolCallId(msg);\n if (denied) subagentPrompts.delete(denied);\n const settled = settledToolCallId(msg);\n if (settled) subagentPrompts.delete(settled);\n } else if (isSessionMeta(msg)) {\n // Subagent registration is only a \"side effect\" — it must never interrupt the\n // main run flow on error: wrap the whole thing in a defensive try/catch.\n try {\n const child = this.registerChildSession(entry, msg, children);\n // Only a **direct** subagent (origin length 1) claims a queued parent-level\n // run_subagent prompt; deeper sessions are spawned by their own parent and\n // shouldn't consume from this queue.\n if (child && msg.origin!.length === 1) {\n const [pendingId] = subagentPrompts.keys();\n if (pendingId !== undefined) {\n child.prompt = subagentPrompts.get(pendingId) ?? \"\";\n subagentPrompts.delete(pendingId); // Consumed by this session_meta\n }\n }\n } catch (err) {\n this.log(\n `[subagent] Failed to register child session: ${err instanceof Error ? err.message : String(err)}`,\n );\n this.deps.errors?.record({\n source: \"subagent\",\n err,\n ctx,\n code: \"subagent_register_failed\",\n });\n }\n } else {\n // A subagent's model text: its title is generated from the subagent's **own\n // conversation**, so the material is accumulated here.\n const nested = nestedAssistantText(msg);\n const child = nested ? children.get(nested.sessionId) : undefined;\n if (nested && child && child.assistantExcerpt.length < TITLE_EXCERPT_LIMIT) {\n child.assistantExcerpt += (child.assistantExcerpt ? \"\\n\" : \"\") + nested.text;\n }\n }\n // Early title trigger (see EARLY_TITLE_BODY_CHARS): fire as soon as enough main-\n // session body text has streamed; maybeGenerate self-guards (NULL title, single\n // flight), so the completion trigger in finally stays as the short-answer fallback.\n if (!earlyTitleFired && titleSource?.userExcerpt.trim()) {\n const p = msg.payload as { type?: string; role?: string; text?: string };\n if (\n (!msg.origin || msg.origin.length === 0) &&\n msg.type === \"model_msg\" &&\n p.type === \"text\" &&\n p.role === \"assistant\" &&\n p.text\n ) {\n mainBodyChars += p.text.length;\n if (mainBodyChars >= EARLY_TITLE_BODY_CHARS) {\n earlyTitleFired = true;\n this.deps.titles?.maybeGenerate(ctx, entry.session, {\n fallbackText: titleSource.userExcerpt,\n });\n }\n }\n }\n // Live-tail bookkeeping in the same synchronous tick as the publish below: the\n // messages endpoint captures \"channel cursor + open fragments\" between two\n // publishes, so the pair is always a consistent snapshot (see live-tail.ts).\n this.liveTail.observe(entry.sessionId, msg);\n // Re-fetch the channel before every publish (matches publishEvent): the channel\n // may have been recycled and recreated during a long wait on approval, and\n // holding a stale reference would send output to an orphaned, detached channel.\n this.deps.channels.get(entry.sessionId).publish(msg);\n watcher?.observe(msg);\n try {\n await this.deps.recorder.record(ctx, msg);\n } catch (err) {\n this.log(`[usage] Insert failed: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"usage\", err, ctx, code: \"usage_insert_failed\" });\n }\n }\n } catch (err) {\n // The SDK doesn't normally throw (errors are converged into the message stream);\n // this is a defensive record here to avoid crashing the runtime.\n this.log(\n `[session] Run failed: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`,\n );\n this.deps.errors?.record({ source: \"session\", err, ctx, code: \"session_run_failed\" });\n } finally {\n // Wrap-up: persist any still-pending LLM failure and clear the tool-name cache (the watcher's state doesn't carry across runs).\n watcher?.close();\n // The run is over: no fragment will ever continue, so drop the live tail before the\n // idle flip (GET /messages stops attaching `live` the moment status reads idle).\n this.liveTail.clear(entry.sessionId);\n entry.approvals.denyAll();\n entry.status = \"idle\";\n entry.abort = null;\n entry.running = null;\n entry.lastActivityMs = Date.now();\n this.publishState(entry, \"idle\");\n if (titleSource && titleSource.userExcerpt.trim()) {\n // Attempt generation whenever there's user material; whether generation is\n // actually needed (title still NULL, etc.) is decided by the generator itself.\n // Material is collected by the core Session during run; here we only pass the\n // fallback text.\n this.deps.titles?.maybeGenerate(ctx, entry.session, {\n fallbackText: titleSource.userExcerpt,\n });\n }\n // Queued follow-ups: whenever a run finishes (Task or compaction, abort included),\n // the next queued input auto-starts as an ordinary task. Fire-and-forget — it\n // revalidates under the session lock.\n if (entry.followUps.length > 0) void this.startQueuedFollowUp(entry.sessionId);\n // A subagent's title is likewise generated by the model, with material being the\n // subagent's **own conversation**: the prompt that spawned it plus its own reply\n // (material the parent Session collects belongs to the parent, hence the explicit\n // override here). It piggybacks a one-shot request on the parent Session's bare\n // LLM (the child Session object never leaves the SDK); on failure/empty result the\n // generator falls back to the prompt's first line.\n for (const child of children.values()) {\n if (!child.prompt.trim()) continue;\n this.deps.titles?.maybeGenerate(\n // Bookkeeping: Session/Agent record the subagent (the title belongs to it),\n // but the model reference still uses ctx's **parent-Session** pair\n // (provider, modelId) — this one-shot request really does run on the parent\n // Session's bare LLM (a subagent may switch models via run_subagent's\n // model_id).\n { ...ctx, agentId: child.agentId, sessionId: child.sessionId },\n entry.session,\n {\n fallbackText: child.prompt,\n material: { userText: child.prompt, assistantText: child.assistantExcerpt },\n notifyOn: entry.sessionId, // Notify the frontend via the parent Session's SSE channel\n },\n );\n }\n }\n }\n\n /**\n * Register a subagent: persisted only when the origin message is session_meta\n * (agentId is derived from the agent_state path: `<…>/<agentId>/agent_state`).\n * **The title is left blank** — it's generated at the end of this run by the model\n * from the subagent's own conversation (see drive's finally), falling back to the\n * first line of the run_subagent prompt if generation fails. Idempotent (children\n * dedup + insertOrIgnore); a subagent has its own Trace, so it's visible in both the\n * list and the trace view. On successful registration, the entry is put into\n * `children` and returned; a duplicate session_meta returns null.\n */\n private registerChildSession(\n entry: RuntimeEntry,\n msg: OmniMessage,\n children: Map<string, ChildSession>,\n ): ChildSession | null {\n if (!isSessionMeta(msg)) return null;\n const childSid = msg.origin![msg.origin!.length - 1]!;\n if (children.has(childSid)) return null;\n const p = msg.payload as SessionMetaPayload;\n const agentId = path.basename(path.dirname(p.agent_state));\n if (!agentId || agentId === \".\" || agentId === \"..\") return null;\n // The forwarded session_meta records the origin at the source (core's spawn site); fall\n // back to inferring \"subagent\" from the registration path for older metas (narrowed —\n // a junk value also falls back). It goes into the in-process registry only — the index\n // row deliberately stores no source column.\n const source = asSessionSource(p.source) ?? \"subagent\";\n this.deps.sources.set(childSid, source);\n this.deps.sessions.insertOrIgnore({\n sessionId: childSid,\n projectId: entry.projectId,\n agentId,\n provider: p.provider,\n modelId: p.model_id,\n workspace: p.workspace,\n // A subagent's approvals are inherited from the parent Session; the index row is\n // inserted with defaults (matches the convention for Sessions discovered by the CLI).\n approvalMode: \"allow-all\",\n title: null,\n createdAt: new Date().toISOString(),\n });\n // Make the subagent appear immediately in the sidebar: notify via the parent\n // Session's channel (a frontend currently watching the parent run refreshes its list in place).\n this.publishEvent(entry, {\n type: \"session_created\",\n projectId: entry.projectId,\n agentId,\n sessionId: childSid,\n source,\n });\n const child: ChildSession = {\n sessionId: childSid,\n agentId,\n modelId: p.model_id,\n prompt: \"\",\n assistantExcerpt: \"\",\n };\n children.set(childSid, child);\n return child;\n }\n\n private publishState(entry: RuntimeEntry, state: SessionStatus): void {\n // Every state flip also reports the queued follow-up count, so subscribers can render\n // the \"N queued\" hint without a dedicated event type.\n this.publishEvent(entry, { type: \"task_state\", state, queued: entry.followUps.length });\n }\n\n private publishEvent(entry: RuntimeEntry, event: ServerEvent): void {\n this.deps.channels.get(entry.sessionId).publish(event, \"server_event\");\n }\n\n /** Serialize (mutually exclude) execution by sessionId; cleans up the lock-table entry once its chain drains (avoids unbounded growth). */\n private async withLock<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {\n // What's stored in the chain is the already-caught version (used only for\n // sequencing, never propagates errors); the caller gets the original result from `next`.\n const prev = this.locks.get(sessionId) ?? Promise.resolve();\n const next = prev.then(fn);\n const settled: Promise<void> = next\n .then(\n () => undefined,\n () => undefined,\n )\n .then(() => {\n // Only delete if still the tail of the chain (no later waiter): preserves mutual-exclusion semantics.\n if (this.locks.get(sessionId) === settled) this.locks.delete(sessionId);\n });\n this.locks.set(sessionId, settled);\n return next;\n }\n}\n","/**\n * Tool call approval: ApproveFn factory + pending\n * approval registry.\n *\n * Approval mode semantics match the CLI (packages/cli/src/approval.ts):\n * allow-all auto-approves; deny-all auto-denies; read-only allows read-only tools\n * (permission===\"r\") and routes the rest to manual approval; always-ask routes\n * everything to manual approval. Routing to manual approval registers a pending entry\n * and pushes an `approval_request` via SSE, suspending until the frontend decides via\n * `POST /approvals/:toolCallId`; no timeout — pending approvals are resolved to deny\n * when the Task is interrupted (then proceeds through the abort flow).\n *\n * Every approval decision re-reads the current approval_mode (`getMode` reads the DB),\n * so mode changes take effect immediately.\n * Docs: /docs/tools § \"Approval\".\n */\nimport type { ApprovalMode } from \"../api/types.js\";\nimport type {\n ApprovalDecision,\n ApproveFn,\n OmniMessage,\n ToolCallPayload,\n} from \"@prismshadow/penguin-core\";\n\nexport interface PendingApproval {\n toolCall: OmniMessage<ToolCallPayload>;\n origin?: string[];\n}\n\ninterface PendingEntry extends PendingApproval {\n resolve: (decision: ApprovalDecision) => void;\n}\n\n/** Pending approval registry (key = tool_call_id), one per Session runtime. */\nexport class ApprovalRegistry {\n private readonly pending = new Map<string, PendingEntry>();\n\n get size(): number {\n return this.pending.size;\n }\n\n /** All currently pending approvals (for subscription replay). */\n list(): PendingApproval[] {\n return [...this.pending.values()].map(({ toolCall, origin }) => ({\n toolCall,\n ...(origin !== undefined ? { origin } : {}),\n }));\n }\n\n /** Register and wait for a decision. Re-registering the same id (defensive) resolves the old entry as deny. */\n wait(toolCall: OmniMessage<ToolCallPayload>): Promise<ApprovalDecision> {\n const id = toolCall.payload.tool_call_id;\n this.pending.get(id)?.resolve(\"deny\");\n return new Promise<ApprovalDecision>((resolve) => {\n const entry: PendingEntry = {\n toolCall,\n ...(toolCall.origin !== undefined ? { origin: toolCall.origin } : {}),\n resolve: (decision) => {\n this.pending.delete(id);\n resolve(decision);\n },\n };\n this.pending.set(id, entry);\n });\n }\n\n /** Submit a decision; returns false if not found (already decided/unknown). */\n decide(toolCallId: string, decision: ApprovalDecision): boolean {\n const entry = this.pending.get(toolCallId);\n if (!entry) return false;\n entry.resolve(decision);\n return true;\n }\n\n /** Interruption convergence: resolve all pending approvals as deny. */\n denyAll(): void {\n for (const entry of [...this.pending.values()]) entry.resolve(\"deny\");\n }\n}\n\n/**\n * Build the approve callback: re-reads the approval mode on every call; when routed to\n * manual approval, registers a pending entry and suspends after pushing an\n * `approval_request` server event via `publishRequest`.\n */\nexport function makeApprove(args: {\n getMode: () => ApprovalMode;\n toolPermission: (name: string) => \"r\" | \"rw\" | undefined;\n registry: ApprovalRegistry;\n publishRequest: (pending: PendingApproval) => void;\n}): ApproveFn {\n const { getMode, toolPermission, registry, publishRequest } = args;\n const manual = (toolCall: OmniMessage<ToolCallPayload>): Promise<ApprovalDecision> => {\n const promise = registry.wait(toolCall);\n publishRequest({\n toolCall,\n ...(toolCall.origin !== undefined ? { origin: toolCall.origin } : {}),\n });\n return promise;\n };\n return async (toolCall) => {\n switch (getMode()) {\n case \"allow-all\":\n return \"allow\";\n case \"deny-all\":\n return \"deny\";\n case \"read-only\":\n // Auto-approve read-only tools; route read-write/unknown tools to manual approval (matches CLI semantics).\n if (toolPermission(toolCall.payload.name) === \"r\") return \"allow\";\n return manual(toolCall);\n case \"always-ask\":\n default:\n return manual(toolCall);\n }\n };\n}\n","/**\n * Live tail of a running Session's stream: the accumulated state of every OPEN\n * streaming fragment (partial_text / partial_thinking / partial_tool_call /\n * partial_tool_call_output), kept per origin chain.\n *\n * Why: `partial_*` messages never reach the Trace (core's Writer filters them), so a\n * client that joins mid-stream — a refresh during a long tool call — cannot rebuild the\n * in-progress message from `GET /messages` alone, and its fresh EventSource carries no\n * `Last-Event-ID` for the channel buffer to replay. This tracker lets the messages\n * endpoint attach, alongside history, one synthetic `partial_* start` OmniMessage per\n * open fragment whose payload carries the full accumulated content so far (text/thinking\n * prefix, tool-call name + accumulated arguments, tool-output prefix + images); the\n * client seeds these into its stream model and live deltas keep appending on top. See\n * `MessagesResponse.live` in api/types.ts for the client-facing contract.\n *\n * Fed by SessionManager.drive in the same synchronous tick as each channel publish, so a\n * \"channel cursor + fragments\" capture between two publishes is always a consistent\n * snapshot. Mirrors core PartialAggregator's merge semantics (fragment key = payload type\n * + tool_call_id; start reopens, delta accumulates, stop closes) with the origin chain\n * added to the key — the aggregator collapses fragments into complete messages, while\n * this keeps the running prefix instead. A complete model message with the same identity\n * also closes the fragment (covers stop-less closures, e.g. interruption cleanup); the\n * whole session entry is dropped when the run ends (SessionManager.drive's finally).\n */\nimport { isPartialPayload } from \"@prismshadow/penguin-core\";\nimport type {\n CompleteModelPayload,\n OmniMessage,\n PartialModelPayload,\n} from \"@prismshadow/penguin-core\";\n\ntype PartialKind = PartialModelPayload[\"type\"];\n\n/**\n * Per-fragment accumulation cap. Environment already front-truncates tool output online\n * (default 16k chars), so tool fragments stay small by construction; text/thinking have\n * no upstream cap and a fast large-code reply can reach a few hundred KB (see the channel\n * buffer sizing note) — 512KB covers that comfortably while bounding a runaway fragment.\n * When the cap trips, the TAIL is kept (the prefix is dropped): the seeded content then\n * joins seamlessly with the live deltas that follow the capture, and the complete message\n * reconciles the full content at the end anyway. Trimming happens with slack so it costs\n * one copy per `FRAGMENT_CAP_SLACK` chars of growth, not one per delta. Tail-keeping is a\n * deliberate one-rule-for-all: it is seamless for text/thinking/tool output, and for\n * `partial_tool_call` arguments it yields a syntactically broken JSON prefix — accepted,\n * since arguments approaching 512KB are not reachable in practice and the complete\n * message reconciles either way.\n */\nconst FRAGMENT_CAP = 512 * 1024;\nconst FRAGMENT_CAP_SLACK = 64 * 1024;\n\ninterface OpenFragment {\n kind: PartialKind;\n /** Origin chain copied from the opening message (absent = main session). */\n origin?: string[];\n /** Timestamp of the fragment's original start (reused on the synthetic start). */\n timestamp: string;\n /** Accumulated text / thinking / tool-call arguments / tool output (tail-capped). */\n buffer: string;\n name?: string;\n toolCallId?: string;\n /** Tool-output images: not incremental — a single delta carries the whole set; a later one overwrites. */\n images?: string[];\n}\n\n/** The partial kind a complete payload closes out; null when it has no streamed counterpart. */\nfunction partialKindFor(p: CompleteModelPayload): PartialKind | null {\n switch (p.type) {\n case \"text\":\n // Only assistant text streams; a user text mid-run (steering) must not close the model's open fragment.\n return p.role === \"assistant\" ? \"partial_text\" : null;\n case \"thinking\":\n return \"partial_thinking\";\n case \"tool_call\":\n return \"partial_tool_call\";\n case \"tool_call_output\":\n return \"partial_tool_call_output\";\n default:\n return null;\n }\n}\n\n/** Fragment key: origin chain + payload type + tool_call_id (same merge rule as core's PartialAggregator, origin added). */\nfunction fragmentKey(origin: string[] | undefined, kind: PartialKind, toolCallId: string): string {\n // \"\\0\" (the escape, not a raw byte -- a raw NUL makes git classify the file as binary)\n // separates the origin chain from the kind: session ids are [A-Za-z0-9_-], so the\n // separator can never occur inside a chain segment and keys cannot collide.\n return `${(origin ?? []).join(\"/\")}\\0${kind}::${toolCallId}`;\n}\n\nfunction toolCallIdOf(p: object): string {\n const id = (p as { tool_call_id?: unknown }).tool_call_id;\n return typeof id === \"string\" ? id : \"\";\n}\n\nfunction appendDelta(frag: OpenFragment, p: PartialModelPayload): void {\n switch (p.type) {\n case \"partial_text\":\n frag.buffer += p.text;\n break;\n case \"partial_thinking\":\n frag.buffer += p.thinking;\n break;\n case \"partial_tool_call\":\n frag.buffer += p.arguments;\n if (p.name) frag.name = p.name;\n frag.toolCallId = p.tool_call_id;\n break;\n case \"partial_tool_call_output\":\n frag.buffer += p.output;\n if (p.images && p.images.length > 0) frag.images = p.images;\n frag.toolCallId = p.tool_call_id;\n break;\n }\n if (frag.buffer.length > FRAGMENT_CAP + FRAGMENT_CAP_SLACK) {\n frag.buffer = frag.buffer.slice(frag.buffer.length - FRAGMENT_CAP);\n }\n}\n\n/** The synthetic `partial_* start` payload carrying the fragment's full accumulated content. */\nfunction startPayload(frag: OpenFragment): PartialModelPayload {\n switch (frag.kind) {\n case \"partial_text\":\n return { type: \"partial_text\", role: \"assistant\", event_type: \"start\", text: frag.buffer };\n case \"partial_thinking\":\n return {\n type: \"partial_thinking\",\n role: \"assistant\",\n event_type: \"start\",\n thinking: frag.buffer,\n };\n case \"partial_tool_call\":\n return {\n type: \"partial_tool_call\",\n role: \"assistant\",\n event_type: \"start\",\n name: frag.name ?? \"\",\n arguments: frag.buffer,\n tool_call_id: frag.toolCallId ?? \"\",\n };\n case \"partial_tool_call_output\":\n return {\n type: \"partial_tool_call_output\",\n role: \"user\",\n event_type: \"start\",\n output: frag.buffer,\n ...(frag.images !== undefined && frag.images.length > 0 ? { images: frag.images } : {}),\n tool_call_id: frag.toolCallId ?? \"\",\n };\n }\n}\n\nexport class LiveTailTracker {\n /** sessionId → open fragments keyed by fragmentKey, in the order they were opened. */\n private readonly sessions = new Map<string, Map<string, OpenFragment>>();\n\n /** Feed one published message (call in the same synchronous tick as the channel publish). */\n observe(sessionId: string, msg: OmniMessage): void {\n if (msg.type !== \"model_msg\") return;\n const p = msg.payload;\n if (!isPartialPayload(p)) {\n // A complete message closes the matching open fragment (normally the stop already\n // did; this also covers stop-less closures such as interruption cleanup).\n const kind = partialKindFor(p as CompleteModelPayload);\n if (kind === null) return;\n const open = this.sessions.get(sessionId);\n if (!open) return;\n open.delete(fragmentKey(msg.origin, kind, toolCallIdOf(p)));\n if (open.size === 0) this.sessions.delete(sessionId);\n return;\n }\n const key = fragmentKey(msg.origin, p.type, toolCallIdOf(p));\n let open = this.sessions.get(sessionId);\n if (p.event_type === \"start\") {\n if (!open) {\n open = new Map();\n this.sessions.set(sessionId, open);\n }\n // start reopens the fragment: a previous same-key fragment (out-of-order) is replaced.\n const frag: OpenFragment = {\n kind: p.type,\n timestamp: msg.timestamp,\n buffer: \"\",\n ...(msg.origin && msg.origin.length > 0 ? { origin: [...msg.origin] } : {}),\n };\n open.delete(key); // re-insert so the emit order tracks the reopen\n open.set(key, frag);\n appendDelta(frag, p);\n return;\n }\n let frag = open?.get(key);\n if (!frag) {\n // delta/stop without a start: lenient, same as core's PartialAggregator.\n if (p.event_type === \"stop\") return; // nothing was open; nothing to keep or clear\n if (!open) {\n open = new Map();\n this.sessions.set(sessionId, open);\n }\n frag = {\n kind: p.type,\n timestamp: msg.timestamp,\n buffer: \"\",\n ...(msg.origin && msg.origin.length > 0 ? { origin: [...msg.origin] } : {}),\n };\n open.set(key, frag);\n }\n appendDelta(frag, p);\n if (p.event_type === \"stop\") {\n open!.delete(key);\n if (open!.size === 0) this.sessions.delete(sessionId);\n }\n }\n\n /**\n * Synthetic `partial_* start` messages for every open fragment (in open order), each\n * carrying the full accumulated content, the original origin chain, and the original\n * start timestamp. Empty when the session has no open fragments.\n */\n fragments(sessionId: string): OmniMessage[] {\n const open = this.sessions.get(sessionId);\n if (!open) return [];\n const out: OmniMessage[] = [];\n for (const frag of open.values()) {\n out.push({\n timestamp: frag.timestamp,\n type: \"model_msg\",\n payload: startPayload(frag),\n ...(frag.origin !== undefined ? { origin: [...frag.origin] } : {}),\n });\n }\n return out;\n }\n\n /** Drop all fragment state for a session (the run ended; nothing will continue these fragments). */\n clear(sessionId: string): void {\n this.sessions.delete(sessionId);\n }\n}\n","/**\n * In-process registry of Session origins, derived from core `session_meta` — the single\n * source of truth for a Session's origin (the DB stores no `source` column).\n *\n * Populated wherever the server actually has the meta in hand: Session creation\n * (SessionService reads the just-created core Session's meta), subagent registration\n * (SessionManager reads the forwarded child meta), and Trace adoption / lazy list\n * resolution (SessionService reads the Trace head's session_meta). `null` records a\n * **known** user-created Session (meta seen, no source) so the Trace is not re-read on\n * every list; an absent entry means \"unknown\" and the list path resolves it from the\n * Trace once per process lifetime.\n */\nimport type { SessionSource } from \"../api/types.js\";\n\n/**\n * Narrows an untrusted value (on-disk Trace JSON / forwarded meta) to a SessionSource:\n * only the exact known origins pass; anything else — including junk written by third\n * parties — is treated as absent rather than cast through.\n */\nexport function asSessionSource(v: unknown): SessionSource | undefined {\n return v === \"schedule\" || v === \"subagent\" ? v : undefined;\n}\n\nexport class SessionSources {\n private readonly map = new Map<string, SessionSource | null>();\n\n /** Records a Session's origin as read from session_meta (`null` = meta seen, user-created). */\n set(sessionId: string, source: SessionSource | null): void {\n this.map.set(sessionId, source);\n }\n\n /** Known origin, `null` for a known user-created Session, `undefined` when this process has not seen the meta. */\n get(sessionId: string): SessionSource | null | undefined {\n return this.map.get(sessionId);\n }\n\n /** Drops a deleted Session's entry (bulk Agent/Project deletion may leave stale entries; they are never matched again). */\n delete(sessionId: string): void {\n this.map.delete(sessionId);\n }\n}\n","/**\n * Error capture within the message stream: LLM request\n * failures and tool execution failures are **both never expressed via throw** — core\n * converges them into the message stream (LLM and Environment handle errors\n * internally and never throw), so a try/catch can't catch a single one. This watcher\n * hooks onto SessionManager's drive, inspects messages one by one, and fishes them out\n * into error_records (source = `llm` / `environment`), matching usage-recorder's shape:\n * recognizes only a few payload types, no-op on the rest. **One instance per run/compact**\n * (its state wraps up accordingly, see close).\n *\n * LLM (source = `llm`): reads the status of `request_end` —\n * - `failed` / `auth` → unexpected (not retryable: credentials rejected, invalid params,\n * etc., needs a human; both share the llm_failed code — no separate taxonomy);\n * - `timeout` / `malformed` → expected (the engine already reconnects and retries, part\n * of normal operation);\n * - `aborted` / `completed` are not recorded (the former is a user-initiated interrupt,\n * not an error).\n *\n * The message uses the real reason: `request_end` carries the failure detail on\n * non-completed statuses (`message`, from LLMOutcome), and the `abort` event's reason is\n * core's failure-reason prose (e.g. `llm request error: 401 …` / `malformed response\n * failed after N retries`). A `request_end` failure is first held pending (status + its\n * own detail), not persisted immediately, and is resolved at the next request boundary:\n * - Immediately followed by `abort` → use its reason as the message (the real reason);\n * - Immediately followed by `request_begin` (the engine is retrying — no abort will ever\n * arrive) → use the staged request_end's own `message` (the real detail, e.g. a quota\n * code), falling back to the generic status text when it carried none;\n * - Still unresolved when the run ends → close persists it the same way.\n * Exception: when reason is a user-interrupt message (`aborted …`), it's not trusted —\n * \"the user clicked stop during backoff\" isn't the reason for this timeout, so the\n * status text is used instead (that timeout is a genuine failure and is still recorded).\n * Pending state is bucketed by origin: subagent messages interleave with the parent\n * session's (even more so with parallel subagents), and mixing them up would misattribute.\n *\n * Environment (source = `environment`): reads `tool_call_output`'s stop_reason ∈\n * {failed, timeout} → expected (the error is fed back to the model, and the Agent\n * adjusts on its own; `aborted` is denial/interruption, not recorded).\n * `tool_call_output` only has tool_call_id, no tool name, so `tool_call_id → tool name`\n * is cached (tool_call always arrives before its output), and the tool name is written\n * into code (`tool_failed:exec_command`) — so the stats dashboard's \"most common error\n * code\" and the error table can show at a glance which tool failed.\n *\n * **Attribution (ctx) is recorded against the session that actually produced the error,\n * not always the parent Session**: a subagent's LLM failures and tool failures also flow\n * through this same stream (carrying origin); if we simply reused the parent ctx passed\n * in at construction, filtering errors by Agent would always show 0 for the child Agent\n * and an inflated count for the parent — both attribution stats and the troubleshooting\n * target would be wrong. So we recognize `session_meta` carrying origin (a subagent's\n * first message, always arriving before any of its failures), registering\n * `origin → {agentId, sessionId}` (agentId derived from the agent_state path, matching\n * SessionManager.registerChildSession's convention); at persist time we look up the\n * message's origin: a hit records the subagent, a miss (a main-session message, or\n * session_meta hasn't arrived yet) falls back to the parent ctx. projectId is always\n * taken from the parent — a subagent is necessarily in the same Project.\n */\nimport { isEventMessage, isModelMessage, isSessionMeta } from \"@prismshadow/penguin-core\";\nimport path from \"node:path\";\nimport type { OmniMessage, SessionMetaMessage, StopReason } from \"@prismshadow/penguin-core\";\nimport { MESSAGE_MAX } from \"./error-recorder.js\";\nimport type { ErrorContext, ErrorKind, ErrorSink } from \"./error-recorder.js\";\n\n/** Cap on the tool-name cache (bounded, to prevent unbounded growth over a long run; over the limit, evicts the oldest by registration order). */\nexport const TOOL_NAMES_MAX = 1000;\n\n/**\n * Cap on the subagent-identity cache (bounded, same reasoning as TOOL_NAMES_MAX: prevent\n * unbounded growth over a long run). The number of in-flight subagents is naturally\n * bounded by the subagent concurrency limit and falls far short of this value; over the\n * limit, evicts the oldest by registration order (those subagents have long since\n * settled, so even if a failure still arrives, it just falls back to the parent ctx —\n * i.e., the pre-fix behavior).\n */\nexport const ORIGIN_CTX_MAX = 200;\n\n/** Recorded LLM failure states (`aborted` / `completed` are not errors and aren't included here). */\ntype LlmFailure = \"failed\" | \"timeout\" | \"malformed\" | \"auth\";\n\n/** LLM failure state → error code, classification, and fallback message (used when the abort reason isn't available). */\nconst LLM_FAILURES: Record<LlmFailure, { code: string; kind: ErrorKind; text: string }> = {\n failed: { code: \"llm_failed\", kind: \"unexpected\", text: \"LLM request failed (not retryable).\" },\n // Credentials rejection shares the failed bucket (no new taxonomy): same code/kind, the\n // real reason arrives via the abort reason or the event's own message as usual.\n auth: { code: \"llm_failed\", kind: \"unexpected\", text: \"LLM request failed (not retryable).\" },\n timeout: {\n code: \"llm_timeout\",\n kind: \"expected\",\n text: \"LLM request timed out (the engine reconnects and retries).\",\n },\n malformed: {\n code: \"llm_malformed\",\n kind: \"expected\",\n text: \"LLM response could not be parsed (the engine reconnects and retries).\",\n },\n};\n\n/** Recorded tool failure states (`aborted` = denial/interruption, not an error). */\ntype ToolFailure = \"failed\" | \"timeout\";\n\nfunction isLlmFailure(s: unknown): s is LlmFailure {\n return s === \"failed\" || s === \"timeout\" || s === \"malformed\" || s === \"auth\";\n}\n\nfunction isToolFailure(s: unknown): s is ToolFailure {\n return s === \"failed\" || s === \"timeout\";\n}\n\n/** A user-interrupt abort message (core's `aborted by user` / `aborted during …`): not a failure reason. */\nfunction isUserAbortReason(reason: string): boolean {\n return /^aborted\\b/i.test(reason);\n}\n\n/** The session a message belongs to (last origin element; empty string for the main session) — both pending state and the tool-name cache are bucketed by it. */\nfunction originKey(msg: OmniMessage): string {\n const origin = msg.origin;\n return origin && origin.length > 0 ? origin[origin.length - 1]! : \"\";\n}\n\n/**\n * Take the **tail** of the tool output (not the head) as the message: core appends the\n * failure reason (`[tool error] …` / `[tool timeout: …]` / exit code) at the end of the\n * output, so truncating from the head would leave only a chunk of normal stdout and\n * drop the reason.\n */\nfunction toolFailureText(output: string): string {\n if (!output) return \"Tool execution failed (no output).\";\n if (output.length <= MESSAGE_MAX) return output;\n return `…${output.slice(output.length - (MESSAGE_MAX - 1))}`;\n}\n\nexport class StreamErrorWatcher {\n /** LLM failures awaiting a real reason: origin → failure state + the request_end's own detail (see file header; each session has at most one in-flight Request). */\n private readonly pending = new Map<string, { status: LlmFailure; message?: string }>();\n /** Names of in-flight tool calls: `origin \\0 tool_call_id` → tool name (dequeued once output arrives). */\n private readonly toolNames = new Map<string, string>();\n /** Subagent identity: origin → that subagent's `{agentId, sessionId}` (see the file header's attribution section). */\n private readonly originCtx = new Map<string, { agentId: string; sessionId: string }>();\n\n constructor(\n private readonly errors: ErrorSink,\n private readonly ctx: ErrorContext,\n ) {}\n\n /** Consume one outgoing message; messages irrelevant to this watcher are a no-op. */\n observe(msg: OmniMessage): void {\n if (isSessionMeta(msg)) {\n this.registerOrigin(msg);\n return;\n }\n if (isModelMessage(msg)) {\n this.observeTool(msg);\n return;\n }\n if (isEventMessage(msg)) this.observeLlm(msg);\n }\n\n /** run/compact wrap-up: persist any still-pending failure (that never got its abort), and clear caches (prevents leaks). */\n close(): void {\n for (const key of [...this.pending.keys()]) this.flush(key);\n this.pending.clear();\n this.toolNames.clear();\n this.originCtx.clear();\n }\n\n // —— Attribution (see file header) ——\n\n /**\n * Register a subagent's identity: `session_meta` carrying origin is the subagent's\n * first message, always arriving before any of its failures. agentId is derived from\n * the absolute agent_state path (`<…>/<agentId>/agent_state`) — matching\n * SessionManager.registerChildSession's convention; not registered if the path is\n * malformed (that subagent's failures fall back to the parent ctx — better to\n * misattribute than write into a nonexistent agentId). The main session's session_meta\n * (no origin) is already the parent ctx and isn't registered.\n */\n private registerOrigin(msg: SessionMetaMessage): void {\n const key = originKey(msg);\n if (!key) return; // Main session\n const agentId = path.basename(path.dirname(msg.payload.agent_state));\n if (!agentId || agentId === \".\" || agentId === \"..\") return;\n // Bounded (re-registering refreshes registration order; over the limit, evicts the oldest).\n this.originCtx.delete(key);\n this.originCtx.set(key, { agentId, sessionId: key });\n if (this.originCtx.size > ORIGIN_CTX_MAX) {\n const oldest = this.originCtx.keys().next().value;\n if (oldest !== undefined) this.originCtx.delete(oldest);\n }\n }\n\n /**\n * The attribution to persist for this origin: a registered subagent hit → record its\n * own Agent/Session; a miss (a main-session message, or session_meta hasn't arrived\n * yet) → fall back to the parent ctx passed at construction. projectId is always taken\n * from the parent (a subagent is necessarily in the same Project).\n */\n private ctxFor(key: string): ErrorContext {\n const child = this.originCtx.get(key);\n if (!child) return this.ctx;\n return { projectId: this.ctx.projectId, agentId: child.agentId, sessionId: child.sessionId };\n }\n\n // —— LLM ——\n\n private observeLlm(msg: OmniMessage): void {\n const p = msg.payload as {\n type?: string;\n status?: StopReason;\n reason?: string | null;\n message?: string;\n };\n const key = originKey(msg);\n if (p.type === \"request_end\") {\n this.flush(key); // Defensive: if a previous failure is still pending (normally resolved by request_begin), persist it first\n if (isLlmFailure(p.status)) {\n this.pending.set(key, {\n status: p.status,\n // The event's own failure detail (LLMOutcome.message): the message of record when\n // no abort follows (the retry path).\n ...(typeof p.message === \"string\" && p.message.trim() ? { message: p.message } : {}),\n });\n }\n return;\n }\n // A new attempt begins (the engine is retrying): no reason text left to wait for the previous failure, persist using the status text.\n if (p.type === \"request_begin\") {\n this.flush(key);\n return;\n }\n // Interrupted/failed exit: reason is core's only failure-reason text.\n if (p.type === \"abort\") {\n this.flush(key, typeof p.reason === \"string\" ? p.reason : null);\n }\n }\n\n /**\n * Persist a pending LLM failure (no-op if none is pending); `reason` is the abort\n * message that arrived afterward. Pending state is already bucketed by origin, so\n * `key` is exactly \"the session that produced this failure\" — attribution is looked\n * up from it (see file header).\n */\n private flush(key: string, reason?: string | null): void {\n const entry = this.pending.get(key);\n if (entry === undefined) return;\n this.pending.delete(key);\n const spec = LLM_FAILURES[entry.status];\n const trimmed = reason?.trim();\n // Message priority: the abort reason (core's failure prose) → the staged request_end's\n // own detail (the retry path: no abort ever arrives) → the generic status text. A\n // user-interrupt message isn't a failure reason (see file header).\n const message = trimmed && !isUserAbortReason(trimmed) ? trimmed : (entry.message ?? spec.text);\n this.errors.record({\n source: \"llm\",\n err: message,\n ctx: this.ctxFor(key),\n code: spec.code,\n kind: spec.kind,\n });\n }\n\n // —— Environment (tool execution) ——\n\n private observeTool(msg: OmniMessage): void {\n const p = msg.payload as {\n type?: string;\n name?: string;\n output?: string;\n tool_call_id?: string;\n stop_reason?: StopReason;\n };\n if (typeof p.tool_call_id !== \"string\") return;\n const origin = originKey(msg); // The session that made this call (both attribution and the tool-name cache are bucketed by it)\n const key = `${origin}\\0${p.tool_call_id}`;\n\n if (p.type === \"tool_call\" && typeof p.name === \"string\") {\n // tool_call arrives before its output: record the tool name (bounded, re-registering refreshes registration order).\n this.toolNames.delete(key);\n this.toolNames.set(key, p.name);\n if (this.toolNames.size > TOOL_NAMES_MAX) {\n const oldest = this.toolNames.keys().next().value;\n if (oldest !== undefined) this.toolNames.delete(oldest);\n }\n return;\n }\n if (p.type !== \"tool_call_output\") return;\n\n const name = this.toolNames.get(key);\n this.toolNames.delete(key); // This call has settled: dequeue it, the cache only keeps in-flight calls\n if (!isToolFailure(p.stop_reason)) return; // completed / aborted (denial, user interrupt) are not errors\n this.errors.record({\n source: \"environment\",\n err: toolFailureText(p.output ?? \"\"),\n ctx: this.ctxFor(origin),\n // The tool name goes into code: so the stats dashboard's \"most common error code\" and table can show which tool failed.\n code: `tool_${p.stop_reason}:${name ?? \"unknown\"}`,\n kind: \"expected\",\n });\n }\n}\n","/**\n * Schedule runner: a Web server runtime component, active only\n * while the server runs. Loaded at server startup, then periodically reconciles by scanning the\n * `schedule/` directory.\n *\n * Key semantics:\n * - Intent vs state are separate: the file is declarative intent (never written back by the system);\n * run state lives in SQLite.\n * - No backfill for missed fires: any due time earlier than when this scheduler first learned of the\n * task (startup reconcile / first registration / start_at reset) is skipped — periodic tasks advance\n * last_slot straight to now, one-shot tasks are marked missed.\n * - Queue when busy: if the bound Session is running, queue (at most one per task; new due times during\n * the wait only advance, they don't stack), and send once it becomes idle.\n * - Bound Session deleted: record an error and mark invalid; editing the file re-activates it via reconcile.\n * - Deleting the file removes the task; reconcile also cleans up its SQLite run state and queue entry.\n */\nimport { createHash } from \"node:crypto\";\nimport { readdir } from \"node:fs/promises\";\nimport { agentsDir, buildScheduledMessage, userText } from \"@prismshadow/penguin-core\";\nimport type { ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { SchedulesRepo, ScheduleStateRow } from \"../db/repos/schedules.js\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport type { ScheduleDefinition } from \"./schedule-file.js\";\nimport { latestSlotAt, slotInWindow } from \"./schedule-file.js\";\nimport { listScheduleFiles, readScheduleFile, validateScheduleModelRef } from \"./schedule-store.js\";\nimport type { ScheduleServerEvent } from \"../api/types.js\";\n\n/** Reconcile and fire-check interval (min period is 5m, so 30s granularity is plenty). */\nconst TICK_INTERVAL_MS = 30_000;\n\n/** Minimal dependency the scheduler needs from SessionManager (eases test doubles). */\nexport interface ScheduleTaskRunner {\n statusOf(sessionId: string): string;\n startTask(\n sessionId: string,\n input: ReturnType<typeof userText>[],\n ): Promise<{ sessionId: string }>;\n}\n\n/** Minimal dependency the scheduler needs from SessionService: new-Session mode (model ref passed through as a pair). */\nexport interface ScheduleSessionCreator {\n createSession(args: {\n projectId: string;\n agentId: string;\n workspace?: string;\n modelId?: string;\n provider?: string;\n source?: \"schedule\";\n }): Promise<{ sessionId: string }>;\n}\n\n/**\n * Trigger input = a `[scheduled_task]` origin block (task name and fire time) + the prompt\n * body: tells the model this was fired by a schedule; the frontend collapses the origin block\n * into a one-line schedule hint (Trace shows it verbatim). The block itself is built by core's\n * marker module, which also owns the parser the frontend uses.\n */\nexport const scheduledMessage = buildScheduledMessage;\n\nexport interface SchedulerDeps {\n root: string;\n repo: SchedulesRepo;\n projects: ProjectsRepo;\n sessions: SessionsRepo;\n runner: ScheduleTaskRunner;\n sessionCreator: ScheduleSessionCreator;\n errors: ErrorSink;\n /** Fire and send are notified over the user-level event stream (app layer binds userChannelKey). */\n notify: (userId: string, event: ScheduleServerEvent) => void;\n now?: () => number;\n intervalMs?: number;\n}\n\n/** A queued fire (at most one per task). */\ninterface PendingFire {\n projectId: string;\n agentId: string;\n name: string;\n sessionId: string;\n}\n\n/** In-memory view of a registered task (definition + run state + queued flag). */\nexport interface ScheduleEntryView {\n def: ScheduleDefinition;\n state: ScheduleStateRow;\n queued: boolean;\n}\n\nexport class Scheduler {\n private readonly now: () => number;\n private readonly intervalMs: number;\n private timer: ReturnType<typeof setInterval> | null = null;\n /** key = `${projectId}\\0${agentId}\\0${name}` */\n private readonly pending = new Map<string, PendingFire>();\n private ticking = false;\n\n constructor(private readonly deps: SchedulerDeps) {\n this.now = deps.now ?? (() => Date.now());\n this.intervalMs = deps.intervalMs ?? TICK_INTERVAL_MS;\n }\n\n /** Start: run one reconcile immediately (startup semantics: no backfill), then enter the periodic tick. */\n async start(): Promise<void> {\n await this.tickOnce();\n this.timer = setInterval(() => {\n void this.tickOnce();\n }, this.intervalMs);\n this.timer.unref?.();\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n /** One reconcile + fire pass (deterministic entry for tests and routes; concurrent calls run only one). */\n async tickOnce(): Promise<void> {\n if (this.ticking) return;\n this.ticking = true;\n try {\n for (const project of this.deps.projects.listAll()) {\n await this.reconcileProject(project.projectId, project.ownerUserId);\n }\n await this.drainQueue();\n } catch (err) {\n this.deps.errors.record({ source: \"schedule\", err, code: \"schedule_tick_failed\" });\n } finally {\n this.ticking = false;\n }\n }\n\n /** Immediate-effect entry after a route write: reconcile just one Agent and drain its queue. */\n async reconcileAgent(projectId: string, agentId: string): Promise<void> {\n const project = this.deps.projects.findById(projectId);\n if (!project) return;\n await this.reconcileOneAgent(projectId, agentId, project.ownerUserId);\n await this.drainQueue();\n }\n\n /** For route display: the task list after reconcile (including files that failed to parse). */\n async listAgent(\n projectId: string,\n agentId: string,\n ): Promise<{ entries: ScheduleEntryView[]; invalid: Array<{ name: string; error: string }> }> {\n const project = this.deps.projects.findById(projectId);\n const owner = project?.ownerUserId ?? null;\n const files = await listScheduleFiles(this.deps.root, projectId, agentId);\n const entries: ScheduleEntryView[] = [];\n const invalid: Array<{ name: string; error: string }> = [];\n for (const file of files) {\n if (!file.parsed.ok) {\n invalid.push({ name: file.name, error: file.parsed.error });\n continue;\n }\n // A model ref whose (provider, model_id) pair isn't in the config is treated like a parse failure: goes to invalidFiles, not scheduled.\n const refError = await validateScheduleModelRef(this.deps.root, projectId, file.parsed.def);\n if (refError !== null) {\n invalid.push({ name: file.name, error: refError });\n continue;\n }\n const state = this.registerEntry(projectId, agentId, owner, file.parsed.def, file.raw);\n entries.push({\n def: file.parsed.def,\n state,\n queued: this.pending.has(this.keyOf(projectId, agentId, file.name)),\n });\n }\n this.cleanupMissing(\n projectId,\n agentId,\n files.map((f) => f.name),\n );\n return { entries, invalid };\n }\n\n /** For routes: state cleanup after a task is deleted. */\n dropEntry(projectId: string, agentId: string, name: string): void {\n this.pending.delete(this.keyOf(projectId, agentId, name));\n this.deps.repo.delete(projectId, agentId, name);\n }\n\n // -------------------------------------------------------------------------\n\n private keyOf(projectId: string, agentId: string, name: string): string {\n return `${projectId}\\0${agentId}\\0${name}`;\n }\n\n private async reconcileProject(projectId: string, ownerUserId: string): Promise<void> {\n for (const agentId of await this.listAgentIds(projectId)) {\n await this.reconcileOneAgent(projectId, agentId, ownerUserId);\n }\n }\n\n /** Enumerate Agents under a Project: scheduling only cares about Agent dirs that exist on disk (no dir → no tasks). */\n private async listAgentIds(projectId: string): Promise<string[]> {\n try {\n const items = await readdir(agentsDir(this.deps.root, projectId), { withFileTypes: true });\n return items.filter((d) => d.isDirectory()).map((d) => d.name);\n } catch {\n return [];\n }\n }\n\n private async reconcileOneAgent(\n projectId: string,\n agentId: string,\n ownerUserId: string,\n ): Promise<void> {\n const files = await listScheduleFiles(this.deps.root, projectId, agentId);\n for (const file of files) {\n if (!file.parsed.ok) {\n // Skip invalid files and record an error (the recorder dedups within a short window, so storms don't spam).\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(`Invalid schedule file: ${file.name}.toml — ${file.parsed.error}`),\n code: \"schedule_invalid_file\",\n ctx: { projectId, agentId },\n });\n continue;\n }\n const def = file.parsed.def;\n // At reconcile time, check the (provider, model_id) pair names a configured model: a\n // reference that doesn't is treated like an invalid file — skip scheduling and record an\n // error (recorder dedups in a short window); it recovers once the file/config is fixed.\n const refError = await validateScheduleModelRef(this.deps.root, projectId, def);\n if (refError !== null) {\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(`Invalid schedule file: ${file.name}.toml — ${refError}`),\n code: \"schedule_invalid_file\",\n ctx: { projectId, agentId },\n });\n continue;\n }\n const state = this.registerEntry(projectId, agentId, ownerUserId, def, file.raw);\n await this.evaluateEntry(projectId, agentId, def, state);\n }\n this.cleanupMissing(\n projectId,\n agentId,\n files.map((f) => f.name),\n );\n }\n\n /** Register (or sync) run state; set the no-backfill baseline only at first registration / start_at reset. */\n private registerEntry(\n projectId: string,\n agentId: string,\n ownerUserId: string | null,\n def: ScheduleDefinition,\n raw: string,\n ): ScheduleStateRow {\n const defHash = createHash(\"sha1\").update(raw).digest(\"hex\");\n const { row, fresh } = this.deps.repo.registerOrSync({\n projectId,\n agentId,\n name: def.name,\n startAtMs: def.startAtMs,\n defHash,\n creatorUserId: ownerUserId,\n });\n if (!fresh) return row;\n // Baseline: if the due time is already in the past at registration → no backfill (mark one-shot tasks\n // missed, let periodic tasks consume all past slots); if start_at is still in the future, do nothing and fire normally when due.\n const slot = latestSlotAt(def, this.now());\n if (slot === null) return row;\n if (def.periodMs === undefined) {\n this.deps.repo.markMissed(projectId, agentId, def.name);\n } else {\n this.deps.repo.markSlot(projectId, agentId, def.name, slot);\n }\n return this.deps.repo.find(projectId, agentId, def.name) ?? row;\n }\n\n /** Clean up run state and queue entries for deleted files (deleting a file removes the task). */\n private cleanupMissing(projectId: string, agentId: string, presentNames: string[]): void {\n const removed = this.deps.repo.deleteMissing(projectId, agentId, presentNames);\n for (const name of removed) this.pending.delete(this.keyOf(projectId, agentId, name));\n }\n\n /** Fire decision: if enabled and within the window, consume new due times step by step. */\n private async evaluateEntry(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n ): Promise<void> {\n if (!def.enabled || state.invalidReason !== null) return;\n if (def.periodMs === undefined && (state.firedOnce || state.missed)) return;\n const nowMs = this.now();\n const slot = latestSlotAt(def, nowMs);\n if (slot === null || !slotInWindow(def, slot)) return;\n if (state.lastSlotMs !== null && slot <= state.lastSlotMs) return;\n // Consume this slot: never retry the same slot whether the send then succeeds, queues, or fails (the twin rule of no-backfill).\n this.deps.repo.markSlot(projectId, agentId, def.name, slot);\n await this.dispatch(projectId, agentId, def, state);\n }\n\n /** Send one fire: queue if the bound Session is busy; in new-Session mode, create and send immediately. */\n private async dispatch(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n ): Promise<void> {\n const key = this.keyOf(projectId, agentId, def.name);\n if (def.sessionId !== undefined) {\n const row = this.deps.sessions.findById(def.sessionId);\n if (!row || row.projectId !== projectId || row.agentId !== agentId) {\n this.deps.errors.record({\n source: \"schedule\",\n err: new Error(\n `Schedule ${def.name} is bound to a Session that does not exist: ${def.sessionId}`,\n ),\n code: \"schedule_session_missing\",\n ctx: { projectId, agentId, sessionId: def.sessionId },\n });\n this.deps.repo.markInvalid(projectId, agentId, def.name, \"session_missing\");\n return;\n }\n if (this.deps.runner.statusOf(def.sessionId) !== \"idle\") {\n // Queue when busy: at most one per task; new slots during the wait are consumed but don't stack.\n if (!this.pending.has(key)) {\n this.pending.set(key, { projectId, agentId, name: def.name, sessionId: def.sessionId });\n this.notifyFor(state, {\n type: \"schedule_queued\",\n projectId,\n agentId,\n name: def.name,\n sessionId: def.sessionId,\n });\n }\n return;\n }\n await this.send(projectId, agentId, def, state, def.sessionId);\n return;\n }\n // New-Session mode: each fire opens a new session (optional workspace and paired model ref; same semantics as opening a session manually).\n try {\n const info = await this.deps.sessionCreator.createSession({\n projectId,\n agentId,\n ...(def.workspace !== undefined ? { workspace: def.workspace } : {}),\n ...(def.modelId !== undefined ? { modelId: def.modelId } : {}),\n ...(def.provider !== undefined ? { provider: def.provider } : {}),\n source: \"schedule\",\n });\n await this.send(projectId, agentId, def, state, info.sessionId);\n } catch (err) {\n this.deps.errors.record({\n source: \"schedule\",\n err,\n code: \"schedule_create_session_failed\",\n ctx: { projectId, agentId },\n });\n }\n }\n\n private async send(\n projectId: string,\n agentId: string,\n def: ScheduleDefinition,\n state: ScheduleStateRow,\n sessionId: string,\n ): Promise<void> {\n const firedAt = new Date(this.now()).toISOString();\n try {\n await this.deps.runner.startTask(sessionId, [\n userText(scheduledMessage(def.name, firedAt, def.prompt)),\n ]);\n } catch (err) {\n this.deps.errors.record({\n source: \"schedule\",\n err,\n code: \"schedule_send_failed\",\n ctx: { projectId, agentId, sessionId },\n });\n return;\n }\n this.deps.repo.markFired(projectId, agentId, def.name, firedAt, def.periodMs === undefined);\n this.notifyFor(state, {\n type: \"schedule_fired\",\n projectId,\n agentId,\n name: def.name,\n sessionId,\n });\n }\n\n /** Drain the queue: send once the target Session is idle; drop if the task is disabled/deleted/invalid. */\n private async drainQueue(): Promise<void> {\n for (const [key, fire] of [...this.pending]) {\n const state = this.deps.repo.find(fire.projectId, fire.agentId, fire.name);\n if (!state || state.invalidReason !== null) {\n this.pending.delete(key);\n continue;\n }\n const file = await readScheduleFile(this.deps.root, fire.projectId, fire.agentId, fire.name);\n if (!file || !file.parsed.ok || !file.parsed.def.enabled) {\n this.pending.delete(key);\n continue;\n }\n const row = this.deps.sessions.findById(fire.sessionId);\n if (!row) {\n this.pending.delete(key);\n this.deps.repo.markInvalid(fire.projectId, fire.agentId, fire.name, \"session_missing\");\n continue;\n }\n if (this.deps.runner.statusOf(fire.sessionId) !== \"idle\") continue;\n this.pending.delete(key);\n await this.send(fire.projectId, fire.agentId, file.parsed.def, state, fire.sessionId);\n }\n }\n\n /** Notify the creator (falls back to the Project owner at registration; silent if still absent). */\n private notifyFor(state: ScheduleStateRow, event: ScheduleServerEvent): void {\n const userId = state.creatorUserId;\n if (userId) this.deps.notify(userId, event);\n }\n}\n","/**\n * The **policy layer** for automatic Session title generation (conversation-page\n * extension).\n *\n * \"How to generate\" lives in the core SDK (`session.generateTitle`: an out-of-band\n * one-shot request on the session's own Model, no tools, thinking disabled, writes no\n * history/Trace); this module is only responsible for host-side policy:\n * - When to generate: after a Task completes and the DB row's title is still NULL\n * (i.e. after the first successful conversation);\n * - Persistence and notification: writes sessions.title and pushes a `session_title`\n * server event to the Session channel;\n * - Bookkeeping: the one-shot request's token consumption is converted to token_usage\n * and handed to usage-recorder for persistence;\n * - Silent failure (logged): the title stays NULL and naturally retries after the next\n * Task completes.\n */\nimport {\n emptyTokenCounts,\n sanitizeTitle,\n stripConversationMarkers,\n tokenUsage,\n} from \"@prismshadow/penguin-core\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { ChannelHub } from \"./channel.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport type { RuntimeSession } from \"./session-manager.js\";\nimport type { UsageContext, UsageRecorder } from \"./usage-recorder.js\";\n\nexport interface TitleGeneratorDeps {\n sessions: SessionsRepo;\n channels: ChannelHub;\n recorder: Pick<UsageRecorder, \"record\">;\n /** Error persistence (optional: without it, only logs — same as before this was wired up). */\n errors?: ErrorSink;\n log?: (line: string) => void;\n}\n\n/** Host-side parameters for one title-generation request. */\nexport interface TitleRequest {\n /** Fallback material for when the LLM fails or returns an empty result (cleaned and truncated from the first non-empty line). */\n fallbackText: string;\n /** Material override (for subagents — the material is the subagent's own conversation); defaults to the first Task's material self-collected by the core Session. */\n material?: { userText: string; assistantText: string };\n /** The channel to push the `session_title` event to; defaults to `ctx.sessionId`. A\n * subagent has no SSE channel of its own, so its title must reach the frontend via\n * the **parent Session's** channel (the list updates in place by sessionId). */\n notifyOn?: string;\n}\n\n/** session-manager's minimal dependency on the title generator (tests inject a fake implementation). */\nexport interface TitleNotifier {\n maybeGenerate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): void;\n}\n\nexport class TitleGenerator implements TitleNotifier {\n private readonly inflight = new Set<string>();\n private readonly log: (line: string) => void;\n\n constructor(private readonly deps: TitleGeneratorDeps) {\n this.log = deps.log ?? ((line) => console.error(line));\n }\n\n /** Generate a title in the background (fire-and-forget) when conditions are met: the row exists, title is still NULL, and no generation is already in flight. */\n maybeGenerate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): void {\n const row = this.deps.sessions.findById(ctx.sessionId);\n if (!row || row.title !== null) return;\n if (this.inflight.has(ctx.sessionId)) return;\n this.inflight.add(ctx.sessionId);\n void this.generate(ctx, session, req)\n .catch((err: unknown) => {\n this.log(`[title] Generation failed: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"title\", err, ctx, code: \"title_failed\" });\n })\n .finally(() => {\n this.inflight.delete(ctx.sessionId);\n });\n }\n\n private async generate(\n ctx: UsageContext,\n session: Pick<RuntimeSession, \"generateTitle\">,\n req: TitleRequest,\n ): Promise<void> {\n let title: string | null = null;\n try {\n // Material defaults to what the core Session self-collects during run; it's only\n // overridden in scenarios like subagents where the material isn't on that Session.\n const res = await session.generateTitle(\n req.material ? { material: req.material } : undefined,\n );\n title = res.title;\n // The one-shot request's real consumption is metered as usual (converted to token_usage and handed to recorder, attributed to this Session).\n if (res.usage) {\n try {\n await this.deps.recorder.record(ctx, tokenUsage(emptyTokenCounts(), res.usage));\n } catch (err) {\n this.log(\n `[title] Usage insert failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n this.deps.errors?.record({\n source: \"title\",\n err,\n ctx,\n code: \"title_usage_insert_failed\",\n });\n }\n }\n } catch (err) {\n // A model request error (rate limit / timeout / network, etc.) shouldn't leave the\n // title permanently missing: log it and fall through to the fallback.\n this.log(`[title] Model request failed: ${err instanceof Error ? err.message : String(err)}`);\n this.deps.errors?.record({ source: \"title\", err, ctx, code: \"title_llm_failed\" });\n }\n // When the LLM produces no usable title (failure / empty result), truncate the fallback material's first line — this guarantees a title is always generated.\n const finalTitle = title ?? fallbackTitle(req.fallbackText);\n if (finalTitle === null) return;\n // There may already be a concurrent write during generation (e.g. a future manual rename): only persist if still NULL.\n const latest = this.deps.sessions.findById(ctx.sessionId);\n if (!latest || latest.title !== null) return;\n this.deps.sessions.updateTitle(ctx.sessionId, finalTitle);\n this.deps.channels\n .get(req.notifyOn ?? ctx.sessionId)\n .publish(\n { type: \"session_title\", sessionId: ctx.sessionId, title: finalTitle },\n \"server_event\",\n );\n }\n}\n\n/** Fallback title: take the material's first non-empty line, sanitize and truncate; if sanitizing empties it out (pure punctuation, etc.) fall back to the truncated original text; returns null if all-whitespace. */\nfunction fallbackTitle(text: string): string | null {\n // Strip machine markers first: a skill invocation prepends a `[use_skills]` block, so the\n // raw first non-empty line would otherwise be that marker rather than the user's request.\n const firstLine = stripConversationMarkers(text)\n .split(\"\\n\")\n .find((l) => l.trim().length > 0);\n if (!firstLine) return null;\n // sanitizeTitle strips a pure-punctuation line down to empty — in that case keep the truncated original text, guaranteeing \"a title is always obtained\".\n return sanitizeTitle(firstLine) ?? firstLine.trim().slice(0, 30);\n}\n","/**\n * Usage persistence.\n *\n * Consumes the Session output stream:\n * - A subagent's `session_meta` (carrying origin) → registers the mapping \"origin's\n * last session_id → (provider, model_id)\" (a subagent may use a different Model, so\n * cost is priced against the actual Model used);\n * - `token_usage` → inserts one usage_records row: the four token fields come from\n * `payload.request` (per-Request increment; subagents are recorded one row at a\n * time, with `session_id` attributed to their owning main Session). Only tokens are\n * persisted, not cost — pricing may be added later, and cost is computed on the fly\n * against current pricing when usage-service queries.\n * The attribution key is always the paired reference `(provider, model_id)` (the same\n * model_id name across different vendors is attributed separately).\n */\nimport { isEventMessage, isSessionMeta } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport { formatLocalDate } from \"../internal/dates.js\";\nimport type { UsageRepo } from \"../db/repos/usage.js\";\n\n/** Attribution context for one record (top-level Session scope). */\nexport interface UsageContext {\n projectId: string;\n agentId: string;\n /** Top-level Session id (the current actual id after self-heal). */\n sessionId: string;\n /** Vendor grouping for the top-level Session's model (paired with modelId; the fallback attribution when the origin mapping has no hit). */\n provider: string;\n /** Upstream model_id of the top-level Session (paired with provider). */\n modelId: string;\n}\n\n/** Cap on the subagent attribution mapping: over the limit, evicts the oldest by insertion order (an evicted entry falls back to the main Session's Model attribution). */\nexport const ORIGIN_MODELS_MAX = 1000;\n\nexport class UsageRecorder {\n /** Subagent model attribution mapping: origin's last session_id → paired reference (session_id is globally unique). */\n private readonly originModels = new Map<string, { provider: string; modelId: string }>();\n\n constructor(\n private readonly usage: UsageRepo,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n /** Consume one outgoing message; messages other than session_meta / token_usage are a no-op. */\n async record(ctx: UsageContext, msg: OmniMessage): Promise<void> {\n if (isSessionMeta(msg) && msg.origin && msg.origin.length > 0) {\n const originSessionId = msg.origin[msg.origin.length - 1]!;\n // Bounded mapping (avoids unbounded growth over a long-running process): re-inserting refreshes insertion order, over the limit evicts the oldest.\n this.originModels.delete(originSessionId);\n this.originModels.set(originSessionId, {\n provider: msg.payload.provider,\n modelId: msg.payload.model_id,\n });\n if (this.originModels.size > ORIGIN_MODELS_MAX) {\n const oldest = this.originModels.keys().next().value;\n if (oldest !== undefined) this.originModels.delete(oldest);\n }\n return;\n }\n if (!isEventMessage(msg)) return;\n const payload = msg.payload as {\n type?: string;\n request?: { cache_read: number; cache_write: number; output: number; total: number };\n status?: string;\n };\n\n const originSessionId =\n msg.origin && msg.origin.length > 0 ? msg.origin[msg.origin.length - 1]! : null;\n // Empty origin → main session's Model; otherwise look up the mapping, falling back to the main Session's Model (paired) on a miss.\n const ref =\n originSessionId === null\n ? { provider: ctx.provider, modelId: ctx.modelId }\n : (this.originModels.get(originSessionId) ?? {\n provider: ctx.provider,\n modelId: ctx.modelId,\n });\n const now = this.now();\n const base = {\n ts: now.toISOString(),\n date: formatLocalDate(now),\n projectId: ctx.projectId,\n agentId: ctx.agentId,\n sessionId: ctx.sessionId,\n originSessionId,\n provider: ref.provider,\n modelId: ref.modelId,\n };\n\n if (payload.type === \"token_usage\" && payload.request) {\n // A successful request: persist along with tokens (status defaults to completed).\n const r = payload.request;\n this.usage.insert({\n ...base,\n cacheRead: r.cache_read,\n cacheWrite: r.cache_write,\n output: r.output,\n total: r.total,\n });\n return;\n }\n // A failed request (request_end and not completed, usually with no token_usage):\n // persist 0 tokens + status, feeding the \"model success rate\" stat; a successful\n // request is already counted once via the token_usage branch above, not repeated here.\n if (payload.type === \"request_end\" && payload.status && payload.status !== \"completed\") {\n this.usage.insert({\n ...base,\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n total: 0,\n status: payload.status,\n });\n }\n }\n}\n","/**\n * id rules: user_id / project_id / agent_id are semantic ids\n * chosen by their creator at creation time — starting with a lowercase letter,\n * containing only lowercase letters, digits, and underscores. The id doubles as the\n * directory name, so keeping it all-lowercase avoids directory name collisions on\n * case-insensitive filesystems (e.g. macOS) at the source.\n * The hyphen is a **reserved separator**: it only appears at the join point of a\n * non-admin project_id's \"<username>-<suffix>\" concatenation. Usernames never\n * contain a hyphen, so the first hyphen is the ownership boundary — no username can\n * ever be crafted to collide with another user's prefix, keeping namespaces\n * non-overlapping. session_id and temporary workspace ids are still generated by\n * the server (randomHex8).\n */\nimport { randomBytes } from \"node:crypto\";\n\n/** 8-character lowercase hex random string (used for server-generated ids like session_id / temporary workspace). */\nexport function randomHex8(): string {\n return randomBytes(4).toString(\"hex\");\n}\n\n/** General semantic id rule: starts with a lowercase letter, followed by lowercase letters, digits, or underscores only, 2-64 chars (no hyphen). */\nexport const SEMANTIC_ID_PATTERN = /^[a-z][a-z0-9_]{1,63}$/;\n\n/** Username tightens the general rule to 2-32 chars: leaves headroom for the default Project id `<username>-default_project`. */\nexport const USERNAME_PATTERN = /^[a-z][a-z0-9_]{1,31}$/;\n\n/** Suffix segment of a non-admin project_id (after `<username>-`): lowercase letters, digits, and underscores only. */\nexport const PROJECT_SUFFIX_PATTERN = /^[a-z0-9_]+$/;\n\n/** Upper bound on total project_id length (username <=32 + separator + suffix). */\nexport const PROJECT_ID_MAX_LENGTH = 64;\n\n/** Human-readable description of the rule (reused in error messages). */\nexport const SEMANTIC_ID_RULE =\n \"starts with a lowercase letter; lowercase letters, digits and underscores only\";\n","/**\n * Admin user backend: user list / create / reset password / delete.\n *\n * - Create: username is the user_id (^[a-z][a-z0-9_-]{1,31}$); admin sets the initial\n * password, flagged with password_is_initial; a default Project `proj-<username>` is\n * auto-created, rolling back the user row on failure.\n * - Reset password: also flags the password as initial and clears all of the user's\n * login sessions (forcing re-login).\n * - Delete: the built-in admin cannot be deleted; Projects owned by the user are\n * deleted along with it (including data directories), with sessions/memberships/UI\n * preferences cascade-deleted via foreign keys.\n */\nimport type { UserInfo } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { MIN_PASSWORD_LENGTH, toUserInfo } from \"../auth/service.js\";\nimport { hashPassword } from \"../auth/password.js\";\nimport type { AuthSessionsRepo } from \"../db/repos/auth-sessions.js\";\nimport type { ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport { SEMANTIC_ID_RULE, USERNAME_PATTERN } from \"./ids.js\";\nimport type { ProjectService } from \"./project-service.js\";\n\nexport interface AdminServiceDeps {\n users: UsersRepo;\n authSessions: AuthSessionsRepo;\n projects: ProjectsRepo;\n projectService: ProjectService;\n now?: () => Date;\n}\n\nexport class AdminService {\n private readonly now: () => Date;\n\n constructor(private readonly deps: AdminServiceDeps) {\n this.now = deps.now ?? (() => new Date());\n }\n\n listUsers(): UserInfo[] {\n return this.deps.users.list().map(toUserInfo);\n }\n\n async createUser(userId: string, password: string): Promise<UserInfo> {\n if (!USERNAME_PATTERN.test(userId)) {\n throw new HttpError(\n 400,\n \"invalid_user_id\",\n `Username must be 2–32 characters: ${SEMANTIC_ID_RULE}.`,\n );\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"Password must be at least 8 characters.\");\n }\n if (this.deps.users.findById(userId)) {\n throw new HttpError(409, \"user_exists\", `User already exists: ${userId}.`);\n }\n const user: UserRow = {\n userId,\n passwordHash: await hashPassword(password),\n isAdmin: false,\n passwordIsInitial: true,\n createdAt: this.now().toISOString(),\n };\n this.deps.users.insert(user);\n try {\n await this.deps.projectService.provisionInitialProject(user, false);\n } catch (err) {\n // Compensation: roll back the user row if default Project creation fails (e.g. proj-<username> already taken).\n this.deps.users.delete(user.userId);\n throw err;\n }\n return toUserInfo(user);\n }\n\n /** Reset another user's password: flags it as initial and clears all their sessions (prompts a password change on next login). */\n async resetPassword(userId: string, password: string): Promise<void> {\n if (!this.deps.users.findById(userId)) {\n throw new HttpError(404, \"user_not_found\", `User does not exist: ${userId}.`);\n }\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new HttpError(400, \"invalid_password\", \"Password must be at least 8 characters.\");\n }\n this.deps.users.updatePassword(userId, await hashPassword(password), true);\n this.deps.authSessions.deleteByUser(userId);\n }\n\n /** Delete user: the built-in admin cannot be deleted; owned Projects (including data directories) are deleted along with it. */\n async deleteUser(userId: string): Promise<void> {\n const target = this.deps.users.findById(userId);\n if (!target) {\n throw new HttpError(404, \"user_not_found\", `User does not exist: ${userId}.`);\n }\n if (target.isAdmin) {\n throw new HttpError(409, \"cannot_delete_admin\", \"The built-in admin cannot be deleted.\");\n }\n for (const project of this.deps.projects.listByOwner(userId)) {\n await this.deps.projectService.destroyProject(project.projectId);\n }\n this.deps.users.delete(userId); // auth_sessions / project_members / ui_prefs cascade-deleted\n }\n}\n","/**\n * Agent config read/write (config is an editable file).\n *\n * system_config.yaml is edited via yaml's `parseDocument`: only the keys provided in\n * the request are updated, the rest of the file (including comments) is preserved\n * as-is; AGENTS.md is overwritten in full.\n * The vault (agent_state/.vault.toml) is read/written via core's loadAgentVault/saveAgentVault;\n * plaintext values only ever hit disk, and are always masked in responses.\n */\nimport fs from \"node:fs/promises\";\nimport { parseDocument, parse as parseYaml } from \"yaml\";\nimport {\n agentsMdPath,\n agentStateDir,\n agentStateVersion,\n VAULT_VALUE_MAX_LENGTH,\n isValidVaultKey,\n loadAgentVault,\n resetSystemConfigToDefaults,\n saveAgentVault,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport type {\n MCPServerConfig,\n ThinkingLevelName,\n ToolDefinitionConfig,\n} from \"@prismshadow/penguin-core\";\nimport type {\n AgentConfigDto,\n AgentConfigUpdateRequest,\n AgentModelConfigDto,\n AgentCompactionConfigDto,\n VaultEntryInfo,\n VaultResponse,\n VaultUpdateRequest,\n} from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport {\n badRequest,\n optionalBoolean,\n optionalEnum,\n optionalNumber,\n optionalString,\n} from \"../http/validate.js\";\nimport { maskApiKey } from \"./project-config-service.js\";\n\nconst THINKING_LEVELS: readonly ThinkingLevelName[] = [\"none\", \"low\", \"medium\", \"high\", \"xhigh\"];\nconst COMPACTION_MODES = [\"summarize\", \"discard\"] as const;\n\nfunction asRecord(v: unknown): Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : {};\n}\n\nexport interface AgentConfigView {\n agentsMd: string;\n systemConfigYaml: string;\n config: AgentConfigDto;\n stateDir: string;\n}\n\nexport class AgentConfigService {\n constructor(private readonly root: string) {}\n\n /** Whether the Agent exists (determined by the presence of system_config.yaml, matching the CLI's convention). */\n async exists(projectId: string, agentId: string): Promise<boolean> {\n try {\n await fs.access(systemConfigPath(this.root, projectId, agentId));\n return true;\n } catch {\n return false;\n }\n }\n\n async requireExists(projectId: string, agentId: string): Promise<void> {\n if (!(await this.exists(projectId, agentId))) {\n throw new HttpError(404, \"agent_not_found\", \"Agent does not exist.\");\n }\n }\n\n /**\n * Read list-card metadata: name / description + tool count (sum of tools.builtin\n * and tools.mcpServers entries; MCP counted per server). Silently falls back to\n * empty / 0 if the file is corrupt.\n */\n async readCardMeta(\n projectId: string,\n agentId: string,\n ): Promise<{ name?: string; description?: string; toolCount: number; version: number }> {\n try {\n const raw = await fs.readFile(systemConfigPath(this.root, projectId, agentId), \"utf8\");\n const parsed = asRecord(parseYaml(raw));\n const tools = asRecord(parsed.tools);\n const countOf = (v: unknown): number => (Array.isArray(v) ? v.length : 0);\n return {\n ...(typeof parsed.name === \"string\" ? { name: parsed.name } : {}),\n ...(typeof parsed.description === \"string\" ? { description: parsed.description } : {}),\n toolCount: countOf(tools.builtin) + countOf(tools.mcpServers),\n version: agentStateVersion({ version: parsed.version as number | undefined }),\n };\n } catch {\n return { toolCount: 0, version: 1 };\n }\n }\n\n /** Structured config view (matching the edit form's shape) + raw text + AGENTS.md + State path. */\n async getConfig(projectId: string, agentId: string): Promise<AgentConfigView> {\n await this.requireExists(projectId, agentId);\n const yamlPath = systemConfigPath(this.root, projectId, agentId);\n const systemConfigYaml = await fs.readFile(yamlPath, \"utf8\");\n const parsed = asRecord(parseYaml(systemConfigYaml));\n const model = asRecord(parsed.model);\n const compaction = asRecord(parsed.compaction);\n const tools = asRecord(parsed.tools);\n\n let agentsMd = \"\";\n try {\n agentsMd = await fs.readFile(agentsMdPath(this.root, projectId, agentId), \"utf8\");\n } catch {\n // Treat a missing AGENTS.md as an empty file (it normally exists after initialization).\n }\n\n const modelDto: AgentModelConfigDto = {\n ...(typeof model.max_tokens === \"number\" ? { maxTokens: model.max_tokens } : {}),\n ...(typeof model.thinking_level === \"string\"\n ? { thinkingLevel: model.thinking_level as ThinkingLevelName }\n : {}),\n ...(typeof model.timeoutMs === \"number\" ? { timeoutMs: model.timeoutMs } : {}),\n };\n const compactionDto: AgentCompactionConfigDto = {\n ...(typeof compaction.max_context_length === \"number\"\n ? { maxContextLength: compaction.max_context_length }\n : {}),\n ...(typeof compaction.max_session_turns === \"number\"\n ? { maxSessionTurns: compaction.max_session_turns }\n : {}),\n ...(compaction.mode === \"summarize\" || compaction.mode === \"discard\"\n ? { mode: compaction.mode }\n : {}),\n ...(typeof compaction.prompt === \"string\" ? { prompt: compaction.prompt } : {}),\n };\n const config: AgentConfigDto = {\n ...(typeof parsed.name === \"string\" ? { name: parsed.name } : {}),\n ...(typeof parsed.description === \"string\" ? { description: parsed.description } : {}),\n version: agentStateVersion({ version: parsed.version as number | undefined }),\n systemPrompt: typeof parsed.system_prompt === \"string\" ? parsed.system_prompt : \"\",\n ...(typeof parsed.max_turns === \"number\" ? { maxTurns: parsed.max_turns } : {}),\n ...(Object.keys(modelDto).length > 0 ? { model: modelDto } : {}),\n ...(Object.keys(compactionDto).length > 0 ? { compaction: compactionDto } : {}),\n toolsBuiltin: Array.isArray(tools.builtin) ? (tools.builtin as ToolDefinitionConfig[]) : [],\n mcpServers: Array.isArray(tools.mcpServers) ? (tools.mcpServers as MCPServerConfig[]) : [],\n };\n return {\n agentsMd,\n systemConfigYaml,\n config,\n stateDir: agentStateDir(this.root, projectId, agentId),\n };\n }\n\n /**\n * PUT accepts any subset: only the provided keys are updated (parseDocument\n * preserves comments and untouched content); agentsMd is overwritten in full.\n * Numeric validation: >0 or -1; thinkingLevel / mode are validated as enums.\n */\n async updateConfig(\n projectId: string,\n agentId: string,\n req: AgentConfigUpdateRequest,\n ): Promise<void> {\n await this.requireExists(projectId, agentId);\n // Finish all config validation and document changes before writing to disk\n // (if validation fails, AGENTS.md is not written either, avoiding a partial update).\n if (req.config !== undefined) {\n await this.applyConfigUpdate(projectId, agentId, req.config);\n }\n if (req.agentsMd !== undefined) {\n await fs.writeFile(agentsMdPath(this.root, projectId, agentId), req.agentsMd, \"utf8\");\n }\n }\n\n /**\n * Overwrites system_config.yaml with the current code defaults (core's\n * resetSystemConfigToDefaults) — same semantics as updating an installed skill:\n * only the identity fields (name / description / version) survive; the system\n * prompt, max_turns, model/compaction settings and the tool list (incl. MCP\n * servers) become the current defaults. AGENTS.md, skills and the vault are\n * untouched.\n */\n async resetConfig(projectId: string, agentId: string): Promise<void> {\n await this.requireExists(projectId, agentId);\n await resetSystemConfigToDefaults(this.root, projectId, agentId);\n }\n\n private async applyConfigUpdate(\n projectId: string,\n agentId: string,\n config: NonNullable<AgentConfigUpdateRequest[\"config\"]>,\n ): Promise<void> {\n const cfg = config as unknown as Record<string, unknown>;\n const yamlPath = systemConfigPath(this.root, projectId, agentId);\n const doc = parseDocument(await fs.readFile(yamlPath, \"utf8\"));\n\n const setIfProvided = (path: string[], value: unknown): void => {\n if (value !== undefined) doc.setIn(path, value);\n };\n\n setIfProvided([\"name\"], optionalString(cfg, \"name\", { maxLen: 100, label: \"name\" }));\n setIfProvided(\n [\"description\"],\n optionalString(cfg, \"description\", { maxLen: 2000, label: \"description\" }),\n );\n setIfProvided(\n [\"system_prompt\"],\n optionalString(cfg, \"systemPrompt\", { label: \"systemPrompt\" }),\n );\n setIfProvided(\n [\"max_turns\"],\n optionalNumber(cfg, \"maxTurns\", { integer: true, positiveOrMinusOne: true }),\n );\n\n if (cfg.model !== undefined) {\n const model = asRecord(cfg.model);\n setIfProvided(\n [\"model\", \"max_tokens\"],\n optionalNumber(model, \"maxTokens\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided(\n [\"model\", \"thinking_level\"],\n optionalEnum(model, \"thinkingLevel\", THINKING_LEVELS),\n );\n setIfProvided(\n [\"model\", \"timeoutMs\"],\n optionalNumber(model, \"timeoutMs\", { integer: true, positiveOrMinusOne: true }),\n );\n }\n if (cfg.compaction !== undefined) {\n const compaction = asRecord(cfg.compaction);\n setIfProvided(\n [\"compaction\", \"max_context_length\"],\n optionalNumber(compaction, \"maxContextLength\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided(\n [\"compaction\", \"max_session_turns\"],\n optionalNumber(compaction, \"maxSessionTurns\", { integer: true, positiveOrMinusOne: true }),\n );\n setIfProvided([\"compaction\", \"mode\"], optionalEnum(compaction, \"mode\", COMPACTION_MODES));\n setIfProvided([\"compaction\", \"prompt\"], optionalString(compaction, \"prompt\"));\n }\n if (cfg.toolsBuiltin !== undefined) {\n doc.setIn([\"tools\", \"builtin\"], validateToolsBuiltin(cfg.toolsBuiltin));\n }\n if (cfg.mcpServers !== undefined) {\n doc.setIn([\"tools\", \"mcpServers\"], validateMcpServers(cfg.mcpServers));\n }\n\n await fs.writeFile(yamlPath, doc.toString(), \"utf8\");\n }\n\n /** Read the Agent vault (agent_state/.vault.toml): values are always masked, plaintext is never sent to the client. */\n async getVault(projectId: string, agentId: string): Promise<VaultResponse> {\n await this.requireExists(projectId, agentId);\n const vault = await loadAgentVault(this.root, projectId, agentId);\n const entries: VaultEntryInfo[] = Object.entries(vault).map(([key, value]) => ({\n key,\n valueMasked: maskApiKey(value),\n }));\n return { entries };\n }\n\n /**\n * PUT replaces the whole vault table (same semantics as models): keys absent from\n * the body are deleted; omitting value keeps the existing value (a new key must\n * provide a value). Key names are validated against shell environment variable\n * naming rules (same rule as core); deleting everything removes the whole\n * .vault.toml file.\n */\n async updateVault(\n projectId: string,\n agentId: string,\n req: VaultUpdateRequest,\n ): Promise<VaultResponse> {\n await this.requireExists(projectId, agentId);\n const prev = await loadAgentVault(this.root, projectId, agentId);\n\n const seen = new Set<string>();\n const nextVault: Record<string, string> = {};\n for (const entry of req.entries) {\n if (!isValidVaultKey(entry.key)) {\n throw badRequest(\n `Invalid vault key name: ${entry.key} (letters, digits and underscores only, and must not start with a digit).`,\n );\n }\n if (seen.has(entry.key)) {\n throw badRequest(`entries contains a duplicate key: ${entry.key}.`);\n }\n seen.add(entry.key);\n const prevValue = prev[entry.key];\n if (entry.value !== undefined) {\n // Values are injected into the child process environment: an oversized value would\n // make exec spawn fail (E2BIG), so we reject it on write (same limit as core).\n if (entry.value.length > VAULT_VALUE_MAX_LENGTH) {\n throw badRequest(\n `Vault value too long: ${entry.key} (limit ${VAULT_VALUE_MAX_LENGTH} characters).`,\n );\n }\n nextVault[entry.key] = entry.value;\n } else if (prevValue !== undefined) {\n nextVault[entry.key] = prevValue;\n } else {\n throw badRequest(`New key ${entry.key} must provide a value.`);\n }\n }\n\n await saveAgentVault(this.root, projectId, agentId, nextVault);\n return this.getVault(projectId, agentId);\n }\n}\n\nfunction validateToolsBuiltin(value: unknown): ToolDefinitionConfig[] {\n if (!Array.isArray(value)) throw badRequest(\"toolsBuiltin must be an array.\");\n return value.map((item, i) => {\n const t = asRecord(item);\n if (typeof t.name !== \"string\" || t.name.length === 0) {\n throw badRequest(`toolsBuiltin[${i}].name must be a non-empty string.`);\n }\n if (typeof t.description !== \"string\") {\n throw badRequest(`toolsBuiltin[${i}].description must be a string.`);\n }\n if (t.permission !== undefined && t.permission !== \"r\" && t.permission !== \"rw\") {\n throw badRequest(`toolsBuiltin[${i}].permission must be one of r / rw.`);\n }\n if (t.forModel !== undefined && t.forModel !== \"vision\" && t.forModel !== \"text-only\") {\n throw badRequest(`toolsBuiltin[${i}].forModel must be one of vision / text-only.`);\n }\n optionalBoolean(t, \"call_description\", `toolsBuiltin[${i}].call_description`);\n optionalNumber(t, \"timeoutMs\", {\n integer: true,\n positiveOrMinusOne: true,\n label: `toolsBuiltin[${i}].timeoutMs`,\n });\n optionalNumber(t, \"maxOutputLength\", {\n integer: true,\n positiveOrMinusOne: true,\n label: `toolsBuiltin[${i}].maxOutputLength`,\n });\n return t as unknown as ToolDefinitionConfig;\n });\n}\n\nfunction validateMcpServers(value: unknown): MCPServerConfig[] {\n if (!Array.isArray(value)) throw badRequest(\"mcpServers must be an array.\");\n return value.map((item, i) => {\n const s = asRecord(item);\n if (typeof s.name !== \"string\" || s.name.length === 0) {\n throw badRequest(`mcpServers[${i}].name must be a non-empty string.`);\n }\n if (s.config === null || typeof s.config !== \"object\" || Array.isArray(s.config)) {\n throw badRequest(`mcpServers[${i}].config must be an object.`);\n }\n return s as unknown as MCPServerConfig;\n });\n}\n","/**\n * `.project_config.toml` read/write (single hidden config file).\n *\n * Doesn't reuse core's loadProjectConfig/saveProjectConfig (they only keep known\n * fields): reads and writes the complete object directly via smol-toml, preserving\n * extension fields like `name`. credential (api_key / base_url / created_at) is\n * **inlined on the model entry** — there's no longer a supplementary section or\n * secrets file; since the file contains secrets, it's always written with mode\n * 0600. Plaintext only ever hits disk, and is always masked in responses.\n *\n * Model references are **fully split into separate fields**: an entry is\n * stored as two independent fields, `provider` and `model_id`; the `(provider,\n * model_id)` pair is the entry's unique key. `model_id` is the upstream request id,\n * sent to AgentHub verbatim — string concatenation like `<provider>/<id>` is\n * forbidden everywhere in the pipeline. `default_model` / `vision_model` are `{\n * provider, model_id }` paired references (TOML tables).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport {\n GenerativeModel,\n catalogEntryFor,\n defaultProjectConfig,\n projectConfigPath,\n renderProjectConfigToml,\n resolveModelEnv,\n userText,\n} from \"@prismshadow/penguin-core\";\nimport type { LLMOutcome, ModelRef, OmniMessage } from \"@prismshadow/penguin-core\";\nimport type {\n ModelInfo,\n ModelPricingDto,\n ModelRefDto,\n ModelsResponse,\n ModelsUpdateRequest,\n ModelTestRequest,\n ModelTestResponse,\n} from \"../api/types.js\";\nimport { badRequest } from \"../http/validate.js\";\nimport type { PricingRates } from \"./usage-service.js\";\n\ntype RawTable = Record<string, unknown>;\n\n/**\n * API key masking: length <=12 -> `***`, otherwise `first4…last4`; plaintext is\n * never sent to the client. The 12-char threshold: `first4…last4` exposes 8\n * characters, which for a 9-12 character short secret would leak more than half of\n * it, so those are masked in full instead.\n */\nexport function maskApiKey(key: string): string {\n if (key.length <= 12) return \"***\";\n return `${key.slice(0, 4)}…${key.slice(-4)}`;\n}\n\nfunction asTable(v: unknown): RawTable {\n return v !== null && typeof v === \"object\" && !Array.isArray(v) ? (v as RawTable) : {};\n}\n\nfunction asArray(v: unknown): RawTable[] {\n return Array.isArray(v) ? v.map(asTable) : [];\n}\n\nfunction optNum(v: unknown): number | undefined {\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\nfunction optStr(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** Leniently reads a paired reference table (default_model / vision_model); returns undefined on a shape mismatch (including the old string format). */\nfunction optRef(v: unknown): ModelRef | undefined {\n const t = asTable(v);\n const provider = optStr(t.provider);\n const modelId = optStr(t.model_id);\n return provider !== undefined && modelId !== undefined\n ? { provider, model_id: modelId }\n : undefined;\n}\n\n/** Whether an entry matches a paired reference (the entry's provider / model_id fields must be strings). */\nfunction entryMatches(m: RawTable, provider: string, modelId: string): boolean {\n return m.provider === provider && m.model_id === modelId;\n}\n\n/** In-process Map/Set key for a paired reference (\\0-separated to avoid concatenation ambiguity; never persisted, not an id format). */\nfunction refKey(provider: string, modelId: string): string {\n return `${provider}\\0${modelId}`;\n}\n\n/** Display form of a paired reference (for error messages; display only, not a storage format). */\nfunction showRef(provider: string, modelId: string): string {\n return `(provider=${provider}, model_id=${modelId})`;\n}\n\n/**\n * Connectivity probe prompt: asks for one word, so the whole exchange fits in a\n * single-digit output budget. The wording discourages reasoning and the trailing empty\n * <think></think> makes many reasoning models treat their thinking phase as already\n * closed - keeping the probe's tiny budget on actual output instead of burning it on\n * thinking.\n */\nconst PROBE_PROMPT =\n 'ping - reply with the single word \"pong\" and nothing else. Do not think or explain.\\n<think></think>';\n\n/**\n * Speed probe prompt: a one-word answer can't be timed (a compliant model emits 1-3\n * tokens, and the window is then dominated by the final usage chunk's round trip), so\n * speed mode asks for something long enough to run into its raised cap. Counting to 50 is\n * deterministic and needs no knowledge, so every model produces the same token stream at\n * its own decoding rate. Carries the same anti-thinking hint as the connectivity prompt.\n */\nconst SPEED_PROBE_PROMPT =\n \"Count from 1 to 50 as a comma-separated list, and nothing else. Do not think or explain.\\n<think></think>\";\n\nexport class ProjectConfigService {\n constructor(private readonly root: string) {}\n\n private filePath(projectId: string): string {\n return projectConfigPath(this.root, projectId);\n }\n\n /**\n * When the Project config (models/credentials) last changed: the config file's mtime as\n * ISO — an honest persistent source that survives server restarts (every models update\n * rewrites the file). undefined when the file doesn't exist yet.\n */\n private async configUpdatedAt(projectId: string): Promise<string | undefined> {\n try {\n const st = await fs.stat(this.filePath(projectId));\n return st.mtime.toISOString();\n } catch {\n return undefined;\n }\n }\n\n /** Reads the raw TOML object; returns an empty object if the file doesn't exist (does not write to disk). */\n async readRaw(projectId: string): Promise<RawTable> {\n let raw: string;\n try {\n raw = await fs.readFile(this.filePath(projectId), \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw err;\n }\n return asTable(parseToml(raw));\n }\n\n /**\n * Writes the whole object to disk: the file inlines secrets like api_key, always\n * written with mode 0600 (the `mode` option only applies at creation time, so\n * chmod is used to enforce it on existing files too — matching core's\n * saveProjectConfig behavior).\n */\n async writeRaw(projectId: string, data: RawTable): Promise<void> {\n const file = this.filePath(projectId);\n await fs.mkdir(path.dirname(file), { recursive: true });\n // Rendering goes through core's single writer: paired references become inline\n // tables, models is placed last — matching the CLI's output format exactly\n // (the same file should never have two formats).\n await fs.writeFile(file, renderProjectConfigToml(data), { encoding: \"utf8\", mode: 0o600 });\n await fs.chmod(file, 0o600);\n }\n\n /**\n * Initial config for a newly created Project: display name + preset built-in\n * model catalog (the default model and all preset entries, sourced from the same\n * core defaultProjectConfig; a gateway model's base_url is already inlined on the\n * entry, with no key); users only need to fill in an API key as needed (leave it\n * blank to fall back to the provider's environment variable).\n */\n async writeInitialConfig(projectId: string, name: string): Promise<void> {\n const preset = defaultProjectConfig();\n await this.writeRaw(projectId, {\n name,\n ...(preset.default_model !== undefined ? { default_model: preset.default_model } : {}),\n models: preset.models,\n });\n }\n\n /**\n * Backfills preset models (for onboarding an existing Project, e.g. the\n * `default_project` shared with the CLI when the first user is onboarded — its\n * directory already existed and never went through `writeInitialConfig`, so it\n * previously had no models and no default model).\n *\n * **Only backfills when there are no models at all**: a Project that already has\n * models configured (via the CLI or edited by the user) is left as-is, and its\n * other fields (name, etc.) are preserved too — existing config is never\n * overwritten.\n */\n async ensurePresetModels(projectId: string): Promise<void> {\n const raw = await this.readRaw(projectId);\n if (asArray(raw.models).length > 0) return;\n const preset = defaultProjectConfig();\n await this.writeRaw(projectId, {\n ...raw,\n // Also reset to the preset default_model if the existing one points at a now-deleted model, to keep the default model valid.\n ...(preset.default_model !== undefined ? { default_model: preset.default_model } : {}),\n models: preset.models,\n });\n }\n\n /** Project display name (the toml's name; returns undefined if unset, the frontend falls back to displaying the id). */\n async getName(projectId: string): Promise<string | undefined> {\n const raw = await this.readRaw(projectId);\n return typeof raw.name === \"string\" ? raw.name : undefined;\n }\n\n /** Paired reference of the default Model; returns undefined if unconfigured (or in the old string format). */\n async getDefaultModelRef(projectId: string): Promise<ModelRef | undefined> {\n const raw = await this.readRaw(projectId);\n return optRef(raw.default_model);\n }\n\n /** Pricing lookup for usage-recorder: the current pricing for this paired reference (undefined if none -> cost is NULL). */\n async getPricing(\n projectId: string,\n provider: string,\n modelId: string,\n ): Promise<PricingRates | undefined> {\n const raw = await this.readRaw(projectId);\n const entry = asArray(raw.models).find((m) => entryMatches(m, provider, modelId));\n const pricing = entry ? asTable(entry.pricing) : {};\n const cacheRead = optNum(pricing.cache_read);\n const cacheWrite = optNum(pricing.cache_write);\n const output = optNum(pricing.output);\n if (cacheRead === undefined && cacheWrite === undefined && output === undefined) {\n return undefined;\n }\n return { cacheRead: cacheRead ?? 0, cacheWrite: cacheWrite ?? 0, output: output ?? 0 };\n }\n\n /**\n * Model connectivity test: the model reference `(provider, modelId)` is submitted\n * as a pair in the request body; sends one minimal request using that model's\n * config (optionally overridden with an unsaved apiKey / baseUrl) — no tools, no\n * system prompt, thinking disabled, a tiny output cap, 20s timeout — just to see\n * whether the endpoint answers. The model id sent to AgentHub is `modelId`\n * itself (the upstream id verbatim; client_type inference follows it).\n *\n * A reasoning-heavy model may ignore the disabled thinking level and burn the\n * whole tiny output cap on thinking (finish_reason=length with no text — AgentHub\n * raises EmptyResponseError, collapsed to a malformed outcome): the endpoint\n * demonstrably streamed model output, which is everything a connectivity test\n * proves, so that case counts as ok too (see probeVerdict).\n *\n * Never throws: the LLM layer collapses auth/parameter/network errors into an\n * `LLMOutcome`, which is translated here into ok / message. Consumes very few\n * Tokens (single-digit output; speed mode spends up to its 64-token cap to have a\n * window worth timing), and writes no Trace and records no usage.\n */\n async testModel(projectId: string, req: ModelTestRequest): Promise<ModelTestResponse> {\n const raw = await this.readRaw(projectId);\n // Testable even if the model isn't in the config yet (validate before saving when adding a custom model): in that case all parameters come from the request body.\n const entry = asArray(raw.models).find((m) => entryMatches(m, req.provider, req.modelId)) ?? {};\n // Always tests against the **current form draft**: checking \"clear\" means the saved key is not fallen back to; an explicit null base URL is treated as cleared.\n const savedKey = optStr(entry.api_key);\n const apiKey = req.clearApiKey ? undefined : (req.apiKey ?? savedKey);\n const savedBaseUrl = optStr(entry.base_url);\n const baseUrl = req.baseUrl === null ? undefined : (req.baseUrl ?? savedBaseUrl);\n const clientType = req.clientType ?? optStr(entry.client_type);\n\n const startedAt = Date.now();\n try {\n // Construction must be inside the try block: the underlying provider SDK can\n // throw during **client construction** itself when a credential is missing\n // (models on the OpenAI protocol need apiKey/OPENAI_API_KEY) — the whole point\n // of a connectivity test is to collapse that kind of failure into\n // `{ ok:false }`; if construction were outside the try, a missing-key test\n // would bubble up as a 500.\n const llm = new GenerativeModel({\n modelId: req.modelId,\n ...(apiKey ? { apiKey } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n ...(clientType ? { clientType } : {}),\n tools: [],\n thinkingLevel: \"none\",\n // Speed mode pairs a raised cap with a prompt that keeps generating (see\n // SPEED_PROBE_PROMPT), so the stream lasts long enough for TTFT/TPS to describe\n // decoding rather than one round trip; the plain connectivity test keeps the\n // single-digit-token budget.\n maxTokens: req.speed ? 64 : 16,\n requestTimeoutMs: 20_000,\n });\n const gen = llm.streamGenerate({\n newMessages: [userText(req.speed ? SPEED_PROBE_PROMPT : PROBE_PROMPT)],\n });\n let sawContent = false;\n let firstContentAt: number | null = null;\n let outputTokens = 0;\n for (;;) {\n const step = await gen.next();\n if (step.done) {\n const verdict = probeVerdict(step.value, sawContent);\n if (!verdict.ok) return verdict;\n const res: ModelTestResponse = { ok: true, latencyMs: Date.now() - startedAt };\n if (firstContentAt !== null) {\n res.ttftMs = firstContentAt - startedAt;\n // Output rate over the streaming window (first content -> stream end), dropped\n // when the sample is too small to mean anything (see probeTps): usage is only\n // reported on completed streams, so thinking-only malformed endings carry TTFT\n // but no rate, and so does a model that answers in a couple of tokens.\n const tps = probeTps(outputTokens, Date.now() - firstContentAt);\n if (tps !== undefined) res.tps = tps;\n }\n return res;\n }\n if (isProbeContent(step.value)) {\n sawContent = true;\n if (firstContentAt === null) firstContentAt = Date.now();\n }\n const p = step.value.payload as { type?: string; request?: { output?: number } };\n if (p.type === \"token_usage\" && typeof p.request?.output === \"number\") {\n outputTokens = p.request.output;\n }\n }\n } catch (err) {\n // Defensive: an unexpected exception during construction/iteration (the LLM layer promises not to throw; this is a fallback).\n return {\n ok: false,\n message: (err instanceof Error ? err.message : String(err)).slice(0, 300),\n };\n }\n }\n\n /**\n * GET models view: masks credential (inline fields), flags the default Model;\n * the group is the entry's `provider` field, looked up in the built-in catalog by\n * the `(provider, model_id)` pair to fill in displayName / envKey (entries outside\n * the catalog are treated as custom models: envKey only has a fallback for the\n * openai protocol). vision follows the TOML annotation when present, otherwise\n * falls back to the catalog annotation (if neither exists, the field is omitted =\n * supported by default).\n */\n async getModels(projectId: string): Promise<ModelsResponse> {\n const raw = await this.readRaw(projectId);\n const defaultRef = optRef(raw.default_model);\n const visionRef = optRef(raw.vision_model);\n const models: ModelInfo[] = asArray(raw.models)\n // An entry is valid only if both provider and model_id are strings (an entry in the old concatenated format lacks provider and is ignored).\n .filter((m) => typeof m.provider === \"string\" && typeof m.model_id === \"string\")\n .map((m) => {\n const provider = m.provider as string;\n const modelId = m.model_id as string;\n const pricing = asTable(m.pricing);\n const pricingDto: ModelPricingDto | undefined =\n optNum(pricing.cache_read) !== undefined ||\n optNum(pricing.cache_write) !== undefined ||\n optNum(pricing.output) !== undefined\n ? {\n cacheRead: optNum(pricing.cache_read) ?? 0,\n cacheWrite: optNum(pricing.cache_write) ?? 0,\n output: optNum(pricing.output) ?? 0,\n }\n : undefined;\n const clientType = optStr(m.client_type);\n const cat = catalogEntryFor(provider, modelId);\n // The env fallback is reported as-is: follows the same rule as\n // AgentHub routing — an explicit client_type takes priority (the openai\n // protocol reads OPENAI_*, independent of the group), otherwise it's\n // auto-routed to a provider client based on model_id; an id that can't be\n // routed has no fallback (no envKey, and AgentHub will reject that id).\n const envKey = resolveModelEnv(modelId, clientType)?.envKey;\n const vision = typeof m.vision === \"boolean\" ? m.vision : cat?.supportsVision;\n // Output cap: TOML annotation only (user-owned; the built-in catalog never presets it).\n const maxTokens = optNum(m.max_tokens);\n // Display name: the explicit TOML field (user-edited) takes priority, then the built-in catalog.\n const displayName = optStr(m.display_name) ?? cat?.displayName;\n // credential is inlined on the entry: a credential block is emitted if either api_key or base_url is present.\n const apiKey = optStr(m.api_key);\n const credBaseUrl = optStr(m.base_url);\n const createdAt = optStr(m.created_at);\n const info: ModelInfo = {\n provider,\n modelId,\n ...(displayName !== undefined ? { displayName } : {}),\n isDefault:\n defaultRef !== undefined &&\n defaultRef.provider === provider &&\n defaultRef.model_id === modelId,\n ...(optNum(m.context_window) !== undefined\n ? { contextWindow: optNum(m.context_window)! }\n : {}),\n ...(clientType ? { clientType } : {}),\n ...(vision !== undefined ? { vision } : {}),\n ...(maxTokens !== undefined ? { maxTokens } : {}),\n ...(envKey ? { envKey } : {}),\n ...(pricingDto ? { pricing: pricingDto } : {}),\n ...(apiKey !== undefined || credBaseUrl !== undefined\n ? {\n credential: {\n ...(apiKey !== undefined ? { apiKeyMasked: maskApiKey(apiKey) } : {}),\n ...(credBaseUrl !== undefined ? { baseUrl: credBaseUrl } : {}),\n ...(createdAt !== undefined ? { createdAt } : {}),\n },\n }\n : {}),\n };\n return info;\n });\n const toDto = (ref: ModelRef): ModelRefDto => ({\n provider: ref.provider,\n modelId: ref.model_id,\n });\n const updatedAt = await this.configUpdatedAt(projectId);\n return {\n ...(defaultRef !== undefined ? { defaultModel: toDto(defaultRef) } : {}),\n ...(visionRef !== undefined ? { visionModel: toDto(visionRef) } : {}),\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n models,\n };\n }\n\n /**\n * PUT replaces the whole models table: key =\n * `(provider, modelId)`; model entries that no longer appear are deleted along\n * with their inline credential; omitting apiKey keeps the existing value,\n * providing one overwrites it and records created_at, clearApiKey clears it;\n * baseUrl null clears it / omitted keeps it. A key change (either the group or\n * the upstream id changes) is migrated as a pair via `renamedFrom`: credential and\n * unknown fields migrate along with the base entry, and default/vision pointers\n * follow. Other extension fields in the toml (name, etc.) are preserved.\n */\n async updateModels(projectId: string, req: ModelsUpdateRequest): Promise<ModelsResponse> {\n const raw = await this.readRaw(projectId);\n const prevModels = asArray(raw.models);\n\n const seen = new Set<string>();\n const nextModels: RawTable[] = [];\n // Rename mapping (old reference key -> new reference): default model / vision model pointers follow a key change instead of being lost on a full table replacement.\n const renamed = new Map<string, ModelRefDto>();\n for (const entry of req.models) {\n const key = refKey(entry.provider, entry.modelId);\n if (seen.has(key)) {\n throw badRequest(\n `models contains a duplicate model reference: ${showRef(entry.provider, entry.modelId)}.`,\n );\n }\n seen.add(key);\n if (\n entry.renamedFrom !== undefined &&\n !(\n entry.renamedFrom.provider === entry.provider &&\n entry.renamedFrom.modelId === entry.modelId\n )\n ) {\n renamed.set(refKey(entry.renamedFrom.provider, entry.renamedFrom.modelId), {\n provider: entry.provider,\n modelId: entry.modelId,\n });\n }\n\n // Model entry: uses the old entry (the entry for the original reference when\n // the key changed) as the base, preserving unknown fields and inline\n // credential; known fields are replaced wholesale per the request (omitted\n // means removed).\n const prevRef = entry.renamedFrom ?? { provider: entry.provider, modelId: entry.modelId };\n const prev = prevModels.find((m) => entryMatches(m, prevRef.provider, prevRef.modelId)) ?? {};\n const next: RawTable = { ...prev, provider: entry.provider, model_id: entry.modelId };\n delete next.context_window;\n delete next.client_type;\n delete next.vision;\n delete next.max_tokens;\n delete next.pricing;\n delete next.display_name;\n // Leftover key from the old concatenated format (request_model_id): defensively stripped, never written to disk again.\n delete next.request_model_id;\n\n // Display name: **only written to disk when it differs from the built-in\n // catalog (looked up by the paired reference)** — preset models keep the\n // config clean, only user-edited ones (including those not found in the\n // catalog) get written into the TOML.\n const catNew = catalogEntryFor(entry.provider, entry.modelId);\n if (entry.displayName && entry.displayName !== catNew?.displayName) {\n next.display_name = entry.displayName;\n }\n if (entry.contextWindow !== undefined) next.context_window = entry.contextWindow;\n if (entry.clientType) next.client_type = entry.clientType;\n // Treated as supported by default: only written to disk when explicitly annotated (both true/false are kept; false drives a frontend blocking hint).\n if (entry.vision !== undefined) next.vision = entry.vision;\n // Inherit-the-Agent-value by default: only written to disk when explicitly annotated (omitted on a full-table PUT = the annotation is cleared).\n if (entry.maxTokens !== undefined) next.max_tokens = entry.maxTokens;\n if (entry.pricing !== undefined) {\n next.pricing = {\n unit: \"usd_per_mtok\",\n cache_read: entry.pricing.cacheRead,\n cache_write: entry.pricing.cacheWrite,\n output: entry.pricing.output,\n };\n }\n\n // credential is inlined on the entry; added/removed on top of the old value per the request (migrates automatically with the base entry when the key changes).\n if (entry.clearApiKey) {\n delete next.api_key;\n delete next.created_at;\n }\n if (entry.apiKey !== undefined) {\n next.api_key = entry.apiKey;\n next.created_at = new Date().toISOString();\n }\n if (entry.baseUrl === null) delete next.base_url;\n else if (entry.baseUrl !== undefined) next.base_url = entry.baseUrl;\n nextModels.push(next);\n }\n\n // default_model: when provided it must be present in models; when omitted the previous value is kept (the pointer follows a key rename; if it was deleted, it's removed).\n let defaultModel: ModelRefDto | undefined;\n if (req.defaultModel !== undefined) {\n if (!seen.has(refKey(req.defaultModel.provider, req.defaultModel.modelId))) {\n throw badRequest(\n `defaultModel must be included in models: ${showRef(req.defaultModel.provider, req.defaultModel.modelId)}.`,\n );\n }\n defaultModel = req.defaultModel;\n } else {\n const prevRef = optRef(raw.default_model);\n if (prevRef !== undefined) {\n const prevKey = refKey(prevRef.provider, prevRef.model_id);\n const followed = renamed.get(prevKey) ?? {\n provider: prevRef.provider,\n modelId: prevRef.model_id,\n };\n if (seen.has(refKey(followed.provider, followed.modelId))) defaultModel = followed;\n }\n }\n\n // vision_model: same semantics as default_model; additionally must not be annotated vision=false (can't proxy-read images if unsupported).\n const targetOf = (ref: ModelRefDto) =>\n req.models.find((m) => m.provider === ref.provider && m.modelId === ref.modelId);\n let visionModel: ModelRefDto | undefined;\n if (req.visionModel !== undefined) {\n if (!seen.has(refKey(req.visionModel.provider, req.visionModel.modelId))) {\n throw badRequest(\n `visionModel must be included in models: ${showRef(req.visionModel.provider, req.visionModel.modelId)}.`,\n );\n }\n if (targetOf(req.visionModel)?.vision === false) {\n throw badRequest(\n `visionModel must not point to a model annotated as not supporting images: ${showRef(req.visionModel.provider, req.visionModel.modelId)}.`,\n );\n }\n visionModel = req.visionModel;\n } else {\n const prevRef = optRef(raw.vision_model);\n if (prevRef !== undefined) {\n const prevKey = refKey(prevRef.provider, prevRef.model_id);\n const followed = renamed.get(prevKey) ?? {\n provider: prevRef.provider,\n modelId: prevRef.model_id,\n };\n if (seen.has(refKey(followed.provider, followed.modelId))) {\n // The former vision model is now annotated as not supporting images: the annotation takes priority, and the pointer is dropped as invalid.\n if (targetOf(followed)?.vision !== false) visionModel = followed;\n }\n }\n }\n\n const toRaw = (ref: ModelRefDto): RawTable => ({\n provider: ref.provider,\n model_id: ref.modelId,\n });\n const next: RawTable = { ...raw, models: nextModels };\n if (defaultModel !== undefined) next.default_model = toRaw(defaultModel);\n else delete next.default_model;\n if (visionModel !== undefined) next.vision_model = toRaw(visionModel);\n else delete next.vision_model;\n await this.writeRaw(projectId, next);\n return this.getModels(projectId);\n }\n}\n\n/**\n * Whether a streamed message carries genuine model content (thinking or text, partial delta\n * or complete backfill) — the probe's \"the endpoint really answered\" signal. Tool calls and\n * event messages don't count (the probe declares no tools).\n */\nexport function isProbeContent(msg: OmniMessage): boolean {\n const p = msg.payload as { type?: string; thinking?: string; text?: string };\n if (p.type === \"partial_thinking\" || p.type === \"thinking\") return Boolean(p.thinking);\n if (p.type === \"partial_text\" || p.type === \"text\") return Boolean(p.text);\n return false;\n}\n\n/**\n * Probe verdict from the terminal LLM outcome. `completed` always passes. A `malformed`\n * ending after genuine streamed content also passes: the typical case is a reasoning-heavy\n * model that ignores the disabled thinking level and burns the probe's tiny max_tokens\n * entirely on thinking (finish_reason=length -> AgentHub's EmptyResponseError) — the\n * endpoint, credential, and model id all demonstrably work, which is what a connectivity\n * test measures. Everything else (auth/parameter failures, timeouts, malformed with nothing\n * received) fails with the outcome's message.\n */\nexport function probeVerdict(\n outcome: LLMOutcome,\n sawContent: boolean,\n): { ok: true } | { ok: false; message: string } {\n if (outcome.status === \"completed\") return { ok: true };\n if (outcome.status === \"malformed\" && sawContent) return { ok: true };\n const detail = \"message\" in outcome && outcome.message ? outcome.message : outcome.status;\n return { ok: false, message: String(detail).slice(0, 300) };\n}\n\n/**\n * Sample floors below which the streaming window says nothing about decoding rate. The\n * speed probe's 64-token cap clears both by a wide margin, so hitting a floor means the\n * model didn't really stream (a one-word answer, or usage that never arrived).\n */\nconst PROBE_TPS_MIN_TOKENS = 16;\nconst PROBE_TPS_MIN_WINDOW_MS = 100;\n\n/**\n * Output rate (tokens/s) over the probe's streaming window (first content -> stream end),\n * rounded to 1dp; undefined when the sample is too small to be meaningful. A stream's\n * closing usage chunk costs a round trip on its own, so a two-token answer measures network\n * jitter and nothing else — 2 tokens in 30ms reads as 66.7 tok/s and the same model 30ms\n * later reads as 33.3, which the card badges would paint green vs yellow. Callers report\n * TTFT alone rather than a fabricated rate; a malformed ending carries no usage at all and\n * lands here as 0 tokens.\n */\nexport function probeTps(outputTokens: number, windowMs: number): number | undefined {\n if (outputTokens < PROBE_TPS_MIN_TOKENS || windowMs <= PROBE_TPS_MIN_WINDOW_MS) return undefined;\n return Math.round((outputTokens / (windowMs / 1000)) * 10) / 10;\n}\n","/**\n * Agent service.\n *\n * The list is the union of \"DB index ∪ directory scan\": a subdirectory under\n * `<project>/` containing `agent_state/system_config.yaml` is treated as an Agent;\n * unmanaged ones found are backfilled into the DB — this handles Agents created\n * directly via the CLI.\n * Create: generate agent-<8hex>, initialize Agent State via core's `createAgent`,\n * then write name/description into system_config.yaml (parseDocument preserves the\n * template's comments).\n */\nimport fs from \"node:fs/promises\";\nimport type { Dirent } from \"node:fs\";\nimport path from \"node:path\";\nimport { HttpError } from \"../http/errors.js\";\nimport {\n agentDir,\n agentsDir,\n agentsMdPath,\n BUILTIN_AGENT_IDS,\n createAgent as coreCreateAgent,\n isValidId,\n loadAgentVault,\n scheduleDir,\n skillsDir,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport type { AgentsRepo } from \"../db/repos/agents.js\";\nimport { SEMANTIC_ID_PATTERN, SEMANTIC_ID_RULE } from \"./ids.js\";\nimport type { AgentConfigService } from \"./agent-config-service.js\";\n\nexport interface AgentListItem {\n agentId: string;\n name?: string;\n description?: string;\n createdAt?: string;\n /** Last config modification time: the later of system_config.yaml / AGENTS.md mtime. */\n updatedAt?: string;\n /** Tool count: number of tools.builtin + tools.mcpServers entries (MCP counted per server). */\n toolCount: number;\n /** Agent State version number (missing field treated as 1). */\n version: number;\n /** Number of vault keys. */\n vaultKeyCount: number;\n /** Number of scheduled tasks (count of .toml files under schedule/, including invalid ones). */\n scheduleCount: number;\n /** Number of installed Skills (count of skills/<name>/ directories that contain a SKILL.md). */\n skillCount: number;\n}\n\nexport class AgentService {\n constructor(\n private readonly root: string,\n private readonly agents: AgentsRepo,\n private readonly agentConfig: AgentConfigService,\n ) {}\n\n /** Union of DB index ∪ directory scan; unmanaged directory Agents are backfilled into the DB. */\n async listAgents(projectId: string): Promise<AgentListItem[]> {\n const known = new Map(this.agents.list(projectId).map((r) => [r.agentId, r]));\n\n let entries: string[] = [];\n try {\n const dirents = await fs.readdir(agentsDir(this.root, projectId), { withFileTypes: true });\n entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);\n } catch {\n // The Project's agents/ directory doesn't exist yet (no Agent directories): return from the DB index only.\n }\n for (const agentId of entries) {\n if (known.has(agentId) || !isValidId(agentId)) continue;\n const configPath = systemConfigPath(this.root, projectId, agentId);\n let createdAt: string;\n try {\n const stat = await fs.stat(configPath);\n createdAt = (stat.birthtime.getTime() > 0 ? stat.birthtime : stat.mtime).toISOString();\n } catch {\n continue; // A directory without system_config.yaml is not an Agent (e.g. a temp folder)\n }\n const row = { projectId, agentId, createdAt };\n this.agents.insertOrIgnore(row);\n known.set(agentId, row);\n }\n\n // Meta reads and mtime stats for each Agent run in parallel (Promise.all preserves the sorted order).\n const sorted = [...known.values()].sort((a, b) =>\n a.createdAt === b.createdAt\n ? a.agentId.localeCompare(b.agentId)\n : a.createdAt < b.createdAt\n ? -1\n : 1,\n );\n return Promise.all(\n sorted.map(async (row) => {\n const [meta, updatedAt, vaultKeyCount, scheduleCount, skillCount] = await Promise.all([\n this.agentConfig.readCardMeta(projectId, row.agentId),\n this.configUpdatedAt(projectId, row.agentId),\n this.vaultKeyCount(projectId, row.agentId),\n this.scheduleCount(projectId, row.agentId),\n this.skillCount(projectId, row.agentId),\n ]);\n return {\n agentId: row.agentId,\n ...meta,\n createdAt: row.createdAt,\n ...(updatedAt !== undefined ? { updatedAt } : {}),\n vaultKeyCount,\n scheduleCount,\n skillCount,\n };\n }),\n );\n }\n\n /** Number of vault keys (falls back to 0 on read failure). */\n private async vaultKeyCount(projectId: string, agentId: string): Promise<number> {\n try {\n return Object.keys(await loadAgentVault(this.root, projectId, agentId)).length;\n } catch {\n return 0;\n }\n }\n\n /** Number of scheduled tasks: count of .toml files under schedule/ (0 if the directory doesn't exist). */\n private async scheduleCount(projectId: string, agentId: string): Promise<number> {\n try {\n const names = await fs.readdir(scheduleDir(this.root, projectId, agentId));\n return names.filter((n) => n.endsWith(\".toml\")).length;\n } catch {\n return 0;\n }\n }\n\n /** Number of installed Skills: count of skills/<name>/ directories containing a SKILL.md (0 if the directory doesn't exist). */\n private async skillCount(projectId: string, agentId: string): Promise<number> {\n const base = skillsDir(this.root, projectId, agentId);\n let dirents: Dirent[];\n try {\n dirents = await fs.readdir(base, { withFileTypes: true });\n } catch {\n return 0;\n }\n const present = await Promise.all(\n dirents\n .filter((d) => d.isDirectory())\n .map(async (d) => {\n try {\n await fs.access(path.join(base, d.name, \"SKILL.md\"));\n return true;\n } catch {\n return false;\n }\n }),\n );\n return present.filter(Boolean).length;\n }\n\n /** Last config modification time: the later of system_config.yaml and AGENTS.md mtime; omitted if neither is readable. */\n private async configUpdatedAt(projectId: string, agentId: string): Promise<string | undefined> {\n const paths = [\n systemConfigPath(this.root, projectId, agentId),\n agentsMdPath(this.root, projectId, agentId),\n ];\n const times = await Promise.all(\n paths.map(async (p) => {\n try {\n return (await fs.stat(p)).mtime.getTime();\n } catch {\n return 0;\n }\n }),\n );\n const max = Math.max(...times);\n return max > 0 ? new Date(max).toISOString() : undefined;\n }\n\n /**\n * Delete an Agent: the sole built-in Agent\n * default_agent (shared with the CLI, the default conversation Agent) cannot be\n * deleted; callers must first drain any active run via manager.abortAgent.\n * The directory is deleted recursively (including Trace), and the DB's\n * agents/sessions index rows are removed along with it; usage records are kept\n * (historical stats are unaffected).\n */\n async deleteAgent(projectId: string, agentId: string): Promise<void> {\n if (BUILTIN_AGENT_IDS.includes(agentId)) {\n throw new HttpError(\n 409,\n \"cannot_delete_builtin_agent\",\n \"Built-in Agents (default_agent) are provisioned with the Project and cannot be deleted from the web.\",\n );\n }\n await fs.rm(agentDir(this.root, projectId, agentId), { recursive: true, force: true });\n this.agents.delete(projectId, agentId);\n }\n\n /**\n * Create an Agent: the id is chosen by the creator (a semantic id, checked for\n * duplicates against both the DB and the directory within the Project — a 409\n * if taken, which naturally also blocks built-in Agent ids) → initialize State →\n * write name/description (name defaults to the id).\n */\n async createAgent(\n projectId: string,\n agentId: string,\n name?: string,\n description?: string,\n ): Promise<AgentListItem> {\n if (!SEMANTIC_ID_PATTERN.test(agentId)) {\n throw new HttpError(\n 400,\n \"invalid_agent_id\",\n `Agent id must be 2–64 characters: ${SEMANTIC_ID_RULE}.`,\n );\n }\n const taken =\n this.agents.exists(projectId, agentId) ||\n (await fs.stat(agentDir(this.root, projectId, agentId)).then(\n () => true,\n () => false,\n ));\n if (taken) {\n throw new HttpError(409, \"agent_exists\", `Agent id is already taken: ${agentId}.`);\n }\n const displayName = name ?? agentId;\n await coreCreateAgent({ root: this.root, projectId, agentId });\n try {\n await this.agentConfig.updateConfig(projectId, agentId, {\n config: { name: displayName, ...(description !== undefined ? { description } : {}) },\n });\n } catch (err) {\n // If initialization fails partway through, clean up the directory: an orphaned\n // directory would make retries with this agent id 409 forever.\n await fs\n .rm(agentDir(this.root, projectId, agentId), { recursive: true, force: true })\n .catch(() => {});\n throw err;\n }\n const createdAt = new Date().toISOString();\n this.agents.insertOrIgnore({ projectId, agentId, createdAt });\n // The init template ships with a default toolset and version number; read back the actual values.\n const meta = await this.agentConfig.readCardMeta(projectId, agentId);\n return {\n agentId,\n name: displayName,\n ...(description !== undefined ? { description } : {}),\n createdAt,\n updatedAt: createdAt,\n toolCount: meta.toolCount,\n version: meta.version,\n vaultKeyCount: 0,\n scheduleCount: 0,\n // Read the real count: coreCreateAgent seeds the default skill set for default_agent.\n skillCount: await this.skillCount(projectId, agentId),\n };\n }\n}\n","/**\n * Benchmark score reading (read-only display): walks `benchmarks/<id>/`, reads\n * `benchmark_config.toml` (title,\n * description, evaluation Model, per-case run count `runs`) and `scoreboard.yaml`\n * (evaluations[], scoreboard v2: each case carries a runs array and a summary).\n * Content is created and refined by benchmark_builder; the server only reads it.\n * Missing or corrupt files always degrade gracefully (title falls back to the\n * directory name, scores come back empty) rather than throwing.\n *\n * The three per-case metrics trust the file's own values; when missing they're\n * computed as the average over the runs array. The old format (no runs at the\n * case level, a single session_id) is parsed as a single run — the server backfills\n * one run entry.\n * Docs: /docs/self-improvement § \"Benchmark storage\".\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport { parse as parseYaml } from \"yaml\";\nimport { benchmarksDir } from \"@prismshadow/penguin-core\";\nimport type {\n BenchmarkCaseScore,\n BenchmarkEvaluation,\n BenchmarkRunScore,\n BenchmarkSummary,\n BenchmarksResponse,\n} from \"../api/types.js\";\n\nfunction asRecord(v: unknown): Record<string, unknown> {\n return v !== null && typeof v === \"object\" && !Array.isArray(v)\n ? (v as Record<string, unknown>)\n : {};\n}\n\nfunction numberOr(v: unknown): number | undefined {\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\nfunction stringOr(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\n/** Shapes a single run entry: score is the minimum requirement, other fields tolerate being absent; a bad entry returns null and is dropped. */\nfunction toRun(v: unknown): BenchmarkRunScore | null {\n const r = asRecord(v);\n const score = numberOr(r.score);\n if (score === undefined) return null;\n const cost = numberOr(r.cost);\n const durationMs = numberOr(r.duration_ms);\n const sessionId = stringOr(r.session_id);\n return {\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n };\n}\n\n/** Average of a metric across runs; undefined when there's no value at all (never forced to 0). */\nfunction averageOf(runs: BenchmarkRunScore[], pick: (r: BenchmarkRunScore) => number | undefined) {\n const values = runs.map(pick).filter((v): v is number => v !== undefined);\n if (values.length === 0) return undefined;\n return values.reduce((a, b) => a + b, 0) / values.length;\n}\n\n/**\n * Shapes a case-level entry (scoreboard v2): the three metrics trust the file's own\n * values, falling back to an average over runs when missing; the old format (no\n * runs, a single case-level session_id) is backfilled into a single run. case and a\n * score (from the file or derivable from runs) are the minimum requirement,\n * otherwise the entry is dropped.\n */\nfunction toCase(v: unknown): BenchmarkCaseScore | null {\n const cr = asRecord(v);\n const caseId = stringOr(cr.case);\n if (caseId === undefined) return null;\n const parsedRuns = Array.isArray(cr.runs)\n ? cr.runs.map(toRun).filter((r): r is BenchmarkRunScore => r !== null)\n : [];\n const score = numberOr(cr.score) ?? averageOf(parsedRuns, (r) => r.score);\n if (score === undefined) return null;\n const cost = numberOr(cr.cost) ?? averageOf(parsedRuns, (r) => r.cost);\n const durationMs = numberOr(cr.duration_ms) ?? averageOf(parsedRuns, (r) => r.durationMs);\n const sessionId = stringOr(cr.session_id);\n const runs: BenchmarkRunScore[] =\n parsedRuns.length > 0\n ? parsedRuns\n : [\n // The old format is parsed as a single run: the case-level values are that run's raw result.\n {\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n },\n ];\n return {\n case: caseId,\n score,\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n ...(sessionId !== undefined ? { sessionId } : {}),\n runs,\n };\n}\n\n/** Shapes a single evaluation record: time and score are the minimum requirement, other fields (summary, etc.) tolerate being absent. */\nfunction toEvaluation(v: unknown): BenchmarkEvaluation | null {\n const r = asRecord(v);\n const time = r.time instanceof Date ? r.time.toISOString() : r.time;\n const score = numberOr(r.score);\n if (typeof time !== \"string\" || time === \"\" || score === undefined) return null;\n const cases: BenchmarkCaseScore[] = Array.isArray(r.cases)\n ? r.cases.map(toCase).filter((c): c is BenchmarkCaseScore => c !== null)\n : [];\n const summary = stringOr(r.summary);\n // Title and body are separate: summary_title is a one-line\n // conclusion, summary is the body text.\n const summaryTitle = stringOr(r.summary_title);\n // The Model actually used for this evaluation run (paired with provider):\n // charted curves are split into series by model, each with a distinct color.\n const modelId = stringOr(r.model_id);\n const provider = stringOr(r.provider);\n const version = numberOr(r.version);\n const cost = numberOr(r.cost);\n const durationMs = numberOr(r.duration_ms);\n return {\n time,\n ...(summaryTitle !== undefined ? { summaryTitle } : {}),\n ...(summary !== undefined ? { summary } : {}),\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n score,\n ...(version !== undefined ? { version } : {}),\n ...(cost !== undefined ? { cost } : {}),\n ...(durationMs !== undefined ? { durationMs } : {}),\n cases,\n };\n}\n\nexport class BenchmarkService {\n constructor(private readonly root: string) {}\n\n async list(projectId: string, agentId: string): Promise<BenchmarksResponse> {\n const dir = benchmarksDir(this.root, projectId, agentId);\n let items: Array<{ name: string; isDir: boolean }>;\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n items = entries.map((e) => ({ name: e.name, isDir: e.isDirectory() }));\n } catch {\n return { benchmarks: [] }; // Doesn't exist when unconfigured.\n }\n const benchmarks: BenchmarkSummary[] = [];\n for (const item of items.filter((i) => i.isDir).sort((a, b) => a.name.localeCompare(b.name))) {\n benchmarks.push(await this.readBenchmark(path.join(dir, item.name), item.name));\n }\n return { benchmarks };\n }\n\n private async readBenchmark(benchDir: string, id: string): Promise<BenchmarkSummary> {\n // benchmark_config.toml: title, description, and per-case run count (falls back\n // to defaults if corrupt). The model isn't part of the config — each evaluation\n // carries the Model actually used for that run.\n let title = id;\n let description: string | undefined;\n let runs: number | undefined;\n try {\n const config = asRecord(\n parseToml(await fs.readFile(path.join(benchDir, \"benchmark_config.toml\"), \"utf8\")),\n );\n if (typeof config.title === \"string\" && config.title !== \"\") title = config.title;\n if (typeof config.description === \"string\" && config.description !== \"\") {\n description = config.description;\n }\n const configRuns = numberOr(config.runs);\n if (configRuns !== undefined && Number.isInteger(configRuns) && configRuns >= 1) {\n runs = configRuns;\n }\n } catch {\n // Missing or corrupt: title falls back to the directory name.\n }\n\n // scoreboard.yaml: evaluations[] is appended over time; bad entries are dropped one by one.\n let evaluations: BenchmarkEvaluation[] = [];\n try {\n const scoreboard = asRecord(\n parseYaml(await fs.readFile(path.join(benchDir, \"scoreboard.yaml\"), \"utf8\")),\n );\n if (Array.isArray(scoreboard.evaluations)) {\n evaluations = scoreboard.evaluations\n .map(toEvaluation)\n .filter((e): e is BenchmarkEvaluation => e !== null);\n }\n } catch {\n // No scores yet.\n }\n\n // Case count: number of case subfolders (the statement/rubric structure isn't validated here).\n let caseCount = 0;\n try {\n const entries = await fs.readdir(benchDir, { withFileTypes: true });\n caseCount = entries.filter((e) => e.isDirectory()).length;\n } catch {\n // Stays at 0.\n }\n\n return {\n id,\n title,\n ...(description !== undefined ? { description } : {}),\n ...(runs !== undefined ? { runs } : {}),\n caseCount,\n evaluations,\n };\n }\n}\n","/**\n * Agent State version snapshots and export/import.\n *\n * A snapshot = `snapshots/v<version>.tar.gz`, packaging `agent_state/` (archive\n * entries are rooted at `agent_state/`), **excluding `.vault.toml`** (secrets never\n * go into a snapshot); if a snapshot for the same version already exists, it isn't\n * repacked. Import goes by the `version` inside the package and keeps the current\n * vault; a snapshot of the current version is automatically taken before import;\n * importing a package version equal to or lower than the current version requires\n * explicit confirmation (otherwise 409).\n * Docs: /docs/self-improvement § \"Snapshots and versions\".\n */\nimport { randomBytes } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport * as tar from \"tar\";\nimport { parse as parseYaml } from \"yaml\";\nimport {\n agentDir,\n agentStateDir,\n agentStateVersion,\n agentVaultPath,\n snapshotsDir,\n systemConfigPath,\n} from \"@prismshadow/penguin-core\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\n\n/** Vault file name inside a snapshot/import package (used for archive filtering). */\nconst VAULT_BASENAME = \".vault.toml\";\n\nfunction isVaultEntry(entryPath: string): boolean {\n return path.posix.basename(entryPath.replaceAll(\"\\\\\", \"/\")) === VAULT_BASENAME;\n}\n\nexport class SnapshotService {\n constructor(private readonly root: string) {}\n\n /** Current Agent State version number (missing field treated as 1); throws 404 if the Agent doesn't exist. */\n async currentVersion(projectId: string, agentId: string): Promise<number> {\n let raw: string;\n try {\n raw = await fs.readFile(systemConfigPath(this.root, projectId, agentId), \"utf8\");\n } catch {\n throw new HttpError(404, \"agent_not_found\", \"Agent does not exist.\");\n }\n const parsed = parseYaml(raw) as { version?: unknown } | null;\n return agentStateVersion({\n version: typeof parsed?.version === \"number\" ? parsed.version : undefined,\n });\n }\n\n /** Ensures a snapshot exists for the current version (not repacked for the same version), returns the snapshot file path and version number. */\n async ensureSnapshot(\n projectId: string,\n agentId: string,\n ): Promise<{ version: number; file: string }> {\n const version = await this.currentVersion(projectId, agentId);\n const dir = snapshotsDir(this.root, projectId, agentId);\n const file = path.join(dir, `v${version}.tar.gz`);\n try {\n await fs.access(file);\n return { version, file };\n } catch {\n // No snapshot for this version yet: pack it.\n }\n await fs.mkdir(dir, { recursive: true });\n const tmp = `${file}.tmp-${randomBytes(4).toString(\"hex\")}`;\n await tar.create(\n {\n gzip: true,\n cwd: agentDir(this.root, projectId, agentId),\n file: tmp,\n portable: true,\n filter: (p) => !isVaultEntry(p),\n },\n [\"agent_state\"],\n );\n await fs.rename(tmp, file);\n return { version, file };\n }\n\n /** Export: automatically packs a snapshot first if none exists, returns the info needed for download. */\n async exportArchive(\n projectId: string,\n agentId: string,\n ): Promise<{ version: number; file: string; fileName: string }> {\n const { version, file } = await this.ensureSnapshot(projectId, agentId);\n return { version, file, fileName: `${agentId}-v${version}.tar.gz` };\n }\n\n /**\n * Import: validates the package structure and `version`, compares versions\n * (higher than current imports directly, same version or older requires\n * `confirm`), automatically snapshots the current version before import, then\n * replaces `agent_state/` while keeping the current vault.\n */\n async importArchive(\n projectId: string,\n agentId: string,\n archive: Buffer,\n confirm: boolean,\n ): Promise<{ version: number }> {\n const current = await this.currentVersion(projectId, agentId);\n const base = agentDir(this.root, projectId, agentId);\n const staging = path.join(base, `.import-${randomBytes(6).toString(\"hex\")}`);\n await fs.mkdir(staging, { recursive: true });\n try {\n const archiveFile = path.join(staging, \"archive.tar.gz\");\n await fs.writeFile(archiveFile, archive);\n const extractDir = path.join(staging, \"extracted\");\n await fs.mkdir(extractDir, { recursive: true });\n try {\n await tar.extract({\n file: archiveFile,\n cwd: extractDir,\n filter: (p) => !isVaultEntry(p),\n });\n } catch {\n throw badRequest(\"Import failed: not a valid tar.gz snapshot package.\");\n }\n\n // Validation: the package must contain agent_state/system_config.yaml, and version must be valid.\n const configPath = path.join(extractDir, \"agent_state\", \"system_config.yaml\");\n let parsed: unknown;\n try {\n parsed = parseYaml(await fs.readFile(configPath, \"utf8\"));\n } catch {\n throw badRequest(\"Import failed: the package is missing agent_state/system_config.yaml.\");\n }\n if (\n parsed === null ||\n typeof parsed !== \"object\" ||\n typeof (parsed as { system_prompt?: unknown }).system_prompt !== \"string\"\n ) {\n throw badRequest(\"Import failed: the package's system_config.yaml is invalid.\");\n }\n const incomingRaw = (parsed as { version?: unknown }).version;\n if (\n incomingRaw !== undefined &&\n (!Number.isInteger(incomingRaw) || (incomingRaw as number) < 1)\n ) {\n throw badRequest(\"Import failed: the package's version is invalid.\");\n }\n const incoming = agentStateVersion({\n version: typeof incomingRaw === \"number\" ? incomingRaw : undefined,\n });\n if (incoming <= current && !confirm) {\n throw new HttpError(\n 409,\n \"version_conflict\",\n `Package version v${incoming} is not higher than the current v${current}; confirmation is required to import.`,\n );\n }\n\n // Automatically snapshots the current version before import (reused if one already exists for that version), so a mistaken import can be rolled back.\n await this.ensureSnapshot(projectId, agentId);\n\n // Replace agent_state: first merge the current vault into the staging\n // directory to be swapped in (extraction already filtered out any vault\n // inside the package, so no conflict), making the replacement a pure rename\n // swap — once the swap lands, there's no further write that can fail. The\n // vault never goes into a snapshot, so if recovery fails after the swap, the\n // `finally` cleanup of staging would delete the only vault copy with no way\n // to roll back.\n const stateDir = agentStateDir(this.root, projectId, agentId);\n const incomingState = path.join(extractDir, \"agent_state\");\n let vault: Buffer | null = null;\n try {\n vault = await fs.readFile(agentVaultPath(this.root, projectId, agentId));\n } catch {\n // No vault means nothing to preserve.\n }\n if (vault !== null) {\n await fs.writeFile(path.join(incomingState, VAULT_BASENAME), vault, { mode: 0o600 });\n }\n const trash = path.join(staging, \"replaced-agent_state\");\n await fs.rename(stateDir, trash);\n try {\n await fs.rename(incomingState, stateDir);\n } catch (err) {\n await fs.rename(trash, stateDir); // Rollback: restore the old directory if swapping in the new one fails.\n throw err;\n }\n return { version: incoming };\n } finally {\n await fs.rm(staging, { recursive: true, force: true });\n }\n }\n}\n","/**\n * Project service.\n *\n * The single implementation point for authorization rules: `requireProjectAccess`\n * (owner or member, otherwise 404 without leaking existence) and\n * `requireProjectOwner` (owner only; 403 when known to be accessible, 404 when not)\n * are reused by every route; the non-throwing `canAccess` (for error attribution)\n * is likewise just a sibling wrapper around them — all three share the single\n * `resolveAccess` decision, with no second rule set maintained separately.\n * Also handles Project create / list / delete, member authorization, and initial\n * Project provisioning at signup.\n */\nimport fs from \"node:fs/promises\";\nimport { DEFAULT_PROJECT_ID, projectDir, provisionProjectAgents } from \"@prismshadow/penguin-core\";\nimport type { MemberInfo, ProjectRole, ProjectSummary } from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport type { AgentsRepo } from \"../db/repos/agents.js\";\nimport type { ErrorsRepo } from \"../db/repos/errors.js\";\nimport type { MembersRepo } from \"../db/repos/members.js\";\nimport type { GoalsRepo } from \"../db/repos/goals.js\";\nimport type { ProjectRow, ProjectsRepo } from \"../db/repos/projects.js\";\nimport type { SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { SchedulesRepo } from \"../db/repos/schedules.js\";\nimport type { UsageRepo } from \"../db/repos/usage.js\";\nimport type { UserRow, UsersRepo } from \"../db/repos/users.js\";\nimport type { SessionManager } from \"../runtime/session-manager.js\";\nimport {\n PROJECT_ID_MAX_LENGTH,\n PROJECT_SUFFIX_PATTERN,\n SEMANTIC_ID_PATTERN,\n SEMANTIC_ID_RULE,\n} from \"./ids.js\";\nimport type { ProjectConfigService } from \"./project-config-service.js\";\n\n/** Fallback timeout for waiting on runs to settle before deleting a Project. */\nconst ABORT_SETTLE_TIMEOUT_MS = 5000;\n\nasync function dirExists(path: string): Promise<boolean> {\n try {\n const stat = await fs.stat(path);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport interface ProjectServiceDeps {\n root: string;\n users: UsersRepo;\n projects: ProjectsRepo;\n members: MembersRepo;\n agents: AgentsRepo;\n sessions: SessionsRepo;\n usage: UsageRepo;\n errors: ErrorsRepo;\n schedules: SchedulesRepo;\n goals: GoalsRepo;\n projectConfig: ProjectConfigService;\n manager: SessionManager;\n}\n\nexport class ProjectService {\n constructor(private readonly deps: ProjectServiceDeps) {}\n\n // —— Authorization rules (single implementation point) ——\n\n /**\n * The **sole** implementation of the owner / member check: returns the row with\n * a role if accessible, otherwise null. `requireProjectAccess` below (throws 404)\n * and `canAccess` (returns boolean) are both just wrappers around it — there's\n * only one copy of the decision rule, since writing it twice would eventually\n * drift out of sync.\n */\n private resolveAccess(\n userId: string,\n projectId: string,\n ): (ProjectRow & { role: ProjectRole }) | null {\n const row = this.deps.projects.findById(projectId);\n if (!row) return null;\n if (row.ownerUserId === userId) return { ...row, role: \"owner\" };\n if (this.deps.members.isMember(projectId, userId)) return { ...row, role: \"member\" };\n return null;\n }\n\n /** Accessible by owner or member; otherwise 404 (does not leak Project existence). */\n requireProjectAccess(userId: string, projectId: string): ProjectRow & { role: ProjectRole } {\n const row = this.resolveAccess(userId, projectId);\n if (!row) {\n throw new HttpError(\n 404,\n \"project_not_found\",\n \"Project does not exist or you do not have access.\",\n );\n }\n return row;\n }\n\n /**\n * The non-throwing version of the same check: used for **error attribution**\n * (app.onError) — that runs on the error-handling path, where throwing another\n * 404 would only break error handling; whether access is granted shouldn't be\n * expressed as an exception there anyway.\n */\n canAccess(userId: string, projectId: string): boolean {\n return this.resolveAccess(userId, projectId) !== null;\n }\n\n /** Owner only: 403 when known accessible as a member; 404 when not accessible. */\n requireProjectOwner(userId: string, projectId: string): ProjectRow {\n const row = this.requireProjectAccess(userId, projectId);\n if (row.role !== \"owner\") {\n throw new HttpError(\n 403,\n \"owner_required\",\n \"Only the Project owner can perform this operation.\",\n );\n }\n return row;\n }\n\n /** List of project_ids accessible to the current user (owned + granted access) (used by workspace-guard). */\n accessibleProjectIds(userId: string): string[] {\n return this.deps.projects.listAccessible(userId).map((p) => p.projectId);\n }\n\n // —— Project lifecycle ——\n\n /** List of owned + granted-access Projects; display names are read from each project_config.toml. */\n async listProjects(userId: string): Promise<ProjectSummary[]> {\n const rows = this.deps.projects.listAccessible(userId);\n return Promise.all(\n rows.map(async (row) => {\n const name = await this.deps.projectConfig.getName(row.projectId);\n return {\n projectId: row.projectId,\n ...(name !== undefined ? { name } : {}),\n role: row.role,\n ownerUserId: row.ownerUserId,\n createdAt: row.createdAt,\n };\n }),\n );\n }\n\n /**\n * Create a Project: the id is chosen by the creator (a semantic id, checked for\n * duplicates against both the DB and the directory — 409 if taken), the initial\n * config is written (display name defaults to the id), and the built-in Agent is\n * initialized.\n * A non-admin's id is forced to be \"<username>-<suffix>\", where the suffix is\n * lowercase letters, digits, and underscores only — the hyphen is a reserved\n * separator, usernames never contain a hyphen, so the first hyphen is the\n * ownership boundary and the prefix can never be crafted from another username;\n * an admin's id contains no hyphen (occupying no user's namespace).\n */\n async createProject(owner: UserRow, projectId: string, name?: string): Promise<ProjectSummary> {\n if (owner.isAdmin) {\n if (!SEMANTIC_ID_PATTERN.test(projectId)) {\n throw new HttpError(\n 400,\n \"invalid_project_id\",\n `Project id must be 2–64 characters: ${SEMANTIC_ID_RULE} (the hyphen is reserved as the user namespace separator).`,\n );\n }\n } else {\n const prefix = `${owner.userId}-`;\n const suffix = projectId.startsWith(prefix) ? projectId.slice(prefix.length) : \"\";\n if (!PROJECT_SUFFIX_PATTERN.test(suffix) || projectId.length > PROJECT_ID_MAX_LENGTH) {\n throw new HttpError(\n 400,\n \"project_id_prefix_required\",\n `Project id must start with ${prefix}, followed by lowercase letters, digits or underscores (at most ${PROJECT_ID_MAX_LENGTH} in total).`,\n );\n }\n }\n if (\n this.deps.projects.findById(projectId) !== null ||\n (await dirExists(projectDir(this.deps.root, projectId)))\n ) {\n throw new HttpError(409, \"project_exists\", `Project id is already taken: ${projectId}.`);\n }\n const displayName = name ?? projectId;\n const createdAt = new Date().toISOString();\n // Insert the DB row first: the primary key is the final arbiter for concurrent\n // creation with the same id (the duplicate check above has an await gap), and a\n // conflict is mapped to 409 with **no cleanup** — the directory belongs to the\n // winner, and cleaning up here would wrongly delete the other side's data.\n try {\n this.deps.projects.insert({ projectId, ownerUserId: owner.userId, createdAt });\n } catch (err) {\n if (err instanceof Error && err.message.includes(\"UNIQUE\")) {\n throw new HttpError(409, \"project_exists\", `Project id is already taken: ${projectId}.`);\n }\n throw err;\n }\n // If file initialization fails, roll back the DB row and clean up the\n // directory: an orphaned directory would make retries with this id 409 forever\n // (a typical scenario: signup failure rolled back the user row, but the\n // <username>-default_project directory was left behind).\n try {\n await fs.mkdir(projectDir(this.deps.root, projectId), { recursive: true });\n await this.deps.projectConfig.writeInitialConfig(projectId, displayName);\n await this.provisionBuiltinAgents(projectId);\n } catch (err) {\n this.deps.projects.delete(projectId);\n await fs\n .rm(projectDir(this.deps.root, projectId), { recursive: true, force: true })\n .catch(() => {});\n throw err;\n }\n return {\n projectId,\n name: displayName,\n role: \"owner\",\n ownerUserId: owner.userId,\n createdAt,\n };\n }\n\n /**\n * Initial Project provisioned at signup:\n * the built-in admin adopts `default_project` (if the directory already exists,\n * it's adopted directly without overwriting existing config — shared with the\n * CLI); other users get `<username>-default_project` created, with display name\n * defaulting to the username.\n */\n async provisionInitialProject(user: UserRow, isAdmin: boolean): Promise<void> {\n if (!isAdmin) {\n await this.createProject(user, `${user.userId}-${DEFAULT_PROJECT_ID}`, user.userId);\n return;\n }\n const projectId = DEFAULT_PROJECT_ID;\n // Initialize the built-in Agent (loaded without overwriting if it already exists); this also ensures the directory exists.\n await this.provisionBuiltinAgents(projectId);\n // Adopting an existing directory doesn't go through writeInitialConfig: preset\n // models and the default model are backfilled instead (only when there are no\n // models at all; a default_project already configured via the CLI is left\n // as-is).\n await this.deps.projectConfig.ensurePresetModels(projectId);\n this.deps.projects.insert({\n projectId,\n ownerUserId: user.userId,\n createdAt: new Date().toISOString(),\n });\n }\n\n /**\n * Delete a Project (owner): default_project is refused; deleting the user's\n * **last accessible Project** is refused too (deleting it would leave the list\n * empty, with no Project to select in the Web client and the page stuck on a\n * skeleton screen — a typical case being a non-first user deleting the initial\n * Project provisioned at signup); active runs are drained first, then the DB and\n * directory are cleared.\n */\n async deleteProject(userId: string, projectId: string): Promise<void> {\n this.requireProjectOwner(userId, projectId);\n if (projectId === DEFAULT_PROJECT_ID) {\n throw new HttpError(\n 409,\n \"cannot_delete_default_project\",\n \"default_project is shared with the CLI and cannot be deleted from the web.\",\n );\n }\n if (this.deps.projects.listAccessible(userId).length <= 1) {\n throw new HttpError(\n 409,\n \"cannot_delete_last_project\",\n \"This is the account's last Project; deleting it would leave no Project available. Create a new Project first.\",\n );\n }\n await this.destroyProject(projectId);\n }\n\n /**\n * The actual deletion (no authorization or protection checks): shared by\n * deleteProject and the cascade cleanup when an admin deletes a user.\n * Abort follow-up (writing the abort event to Trace, etc.) happens\n * asynchronously: waits for runs to settle (capped at 5s) before deleting the\n * directory, to avoid the Trace writer recreating the directory after deletion.\n */\n async destroyProject(projectId: string): Promise<void> {\n const runnings = this.deps.manager.abortProject(projectId);\n if (runnings.length > 0) {\n await Promise.race([\n Promise.allSettled(runnings).then(() => undefined),\n new Promise<void>((resolve) => setTimeout(resolve, ABORT_SETTLE_TIMEOUT_MS).unref?.()),\n ]);\n }\n this.deps.projects.delete(projectId); // project_members cascade-deleted\n this.deps.agents.deleteByProject(projectId);\n this.deps.sessions.deleteByProject(projectId);\n this.deps.usage.deleteByProject(projectId);\n this.deps.errors.deleteByProject(projectId);\n this.deps.schedules.deleteByProject(projectId);\n this.deps.goals.deleteByProject(projectId);\n await fs.rm(projectDir(this.deps.root, projectId), { recursive: true, force: true });\n }\n\n // —— Member authorization ——\n\n /** Member list: owner (role=owner) + members. */\n listMembers(userId: string, projectId: string): MemberInfo[] {\n const project = this.requireProjectAccess(userId, projectId);\n const members = this.deps.members.list(projectId);\n return [\n { userId: project.ownerUserId, role: \"owner\", createdAt: project.createdAt },\n ...members.map((m) => ({\n userId: m.userId,\n role: \"member\" as const,\n createdAt: m.createdAt,\n })),\n ];\n }\n\n /** Grant member access (owner): invites by username; 404 if the user doesn't exist, 409 for the owner themself or an existing member. */\n addMember(userId: string, projectId: string, targetUserId: string): MemberInfo {\n const project = this.requireProjectOwner(userId, projectId);\n const target = this.deps.users.findById(targetUserId);\n if (!target) {\n throw new HttpError(404, \"user_not_found\", `User does not exist: ${targetUserId}.`);\n }\n if (target.userId === project.ownerUserId) {\n throw new HttpError(\n 409,\n \"already_owner\",\n \"The owner does not need to grant access to themselves.\",\n );\n }\n if (this.deps.members.isMember(projectId, target.userId)) {\n throw new HttpError(\n 409,\n \"already_member\",\n `${targetUserId} is already a member of this Project.`,\n );\n }\n const createdAt = new Date().toISOString();\n this.deps.members.insert({ projectId, userId: target.userId, createdAt });\n return { userId: target.userId, role: \"member\", createdAt };\n }\n\n /** Revoke member access (owner). */\n removeMember(userId: string, projectId: string, targetUserId: string): void {\n this.requireProjectOwner(userId, projectId);\n if (!this.deps.members.isMember(projectId, targetUserId)) {\n throw new HttpError(404, \"member_not_found\", `This Project has no member: ${targetUserId}.`);\n }\n this.deps.members.delete(projectId, targetUserId);\n }\n\n /**\n * Ensures the Project's built-in Agent exists (the sole built-in Agent\n * default_agent; initialized if the directory is empty, otherwise loaded without\n * overwriting) and indexes it. createdAt increments by 1ms in preset order, so\n * built-in Agents stably sort first; other Agents backfilled by directory\n * scanning are sorted by their own createdAt and are outside the scope of this\n * guarantee.\n */\n private async provisionBuiltinAgents(projectId: string): Promise<void> {\n const agentIds = await provisionProjectAgents({ root: this.deps.root, projectId });\n const base = Date.now();\n agentIds.forEach((agentId, i) => {\n this.deps.agents.insertOrIgnore({\n projectId,\n agentId,\n createdAt: new Date(base + i).toISOString(),\n });\n });\n }\n}\n","/**\n * Session index service.\n *\n * The list is DB index ∪ Trace directory discovery: scans\n * `<agent>/traces/<date>/<session_id>_<index3>.jsonl`; an unmanaged Session (e.g.\n * one started via the CLI) has its first line's session_meta read for\n * (provider, model_id) / workspace, which is backfilled into a DB row\n * (approval_mode defaults, createdAt is taken from the timestamp embedded in\n * session_id).\n * Create: via core's `agent.createSession` (the model reference is always a complete\n * (provider, modelId) pair — both or neither; omitting both falls back to the\n * Project's default reference, 400 if there is none); the new Session is\n * added to session-manager's active table (state idle).\n */\nimport path from \"node:path\";\nimport { open, readdir } from \"node:fs/promises\";\nimport {\n createAgent,\n findLatestTraceFile,\n isSessionMeta,\n parseTraceLines,\n readTraceTolerant,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type { SessionMetaMessage } from \"@prismshadow/penguin-core\";\nimport type {\n ApprovalMode,\n SessionCategory,\n SessionCategoryCounts,\n SessionInfo,\n SessionSource,\n} from \"../api/types.js\";\nimport { HttpError, isMissingCredential, modelCredentialMissing } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\nimport type { SessionRow, SessionsRepo } from \"../db/repos/sessions.js\";\nimport type { SessionManager } from \"../runtime/session-manager.js\";\nimport { asSessionSource } from \"../runtime/session-sources.js\";\nimport type { SessionSources } from \"../runtime/session-sources.js\";\nimport type { ProjectConfigService } from \"./project-config-service.js\";\n\nconst TRACE_FILE_RE = /^(.+)_(\\d{3})\\.jsonl$/;\nconst SESSION_ID_TS_RE = /^session-(\\d{4})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-[0-9a-f]{8}$/;\n\n/** Head window for session_meta reads: generous for a long system prompt, far below a whole multi-MB shard. */\nconst TRACE_HEAD_BYTES = 256 * 1024;\n\n/**\n * Parse a Trace file's head window only. session_meta is the first line core writes to\n * every shard, so a bounded read finds it without pulling the whole file into memory —\n * category filtering / counts may need every Session's source in a single request.\n * The window is cut at its last newline (the tail fragment is incomplete); a first line\n * larger than the whole window falls back to the full tolerant read.\n */\nasync function readTraceHead(filePath: string) {\n const fh = await open(filePath, \"r\");\n let text: string;\n let truncated: boolean;\n try {\n // allocUnsafe: only subarray(0, bytesRead) is ever read, so the uninitialized tail never leaks.\n const { buffer, bytesRead } = await fh.read(\n Buffer.allocUnsafe(TRACE_HEAD_BYTES),\n 0,\n TRACE_HEAD_BYTES,\n 0,\n );\n text = buffer.subarray(0, bytesRead).toString(\"utf8\");\n truncated = bytesRead === TRACE_HEAD_BYTES;\n } finally {\n await fh.close();\n }\n if (!truncated) return parseTraceLines(text);\n const nl = text.lastIndexOf(\"\\n\");\n if (nl === -1) return readTraceTolerant(filePath);\n return parseTraceLines(text.slice(0, nl + 1));\n}\n\n/** Derives creation time from the local timestamp embedded in session_id; returns null if it doesn't match. */\nexport function sessionIdCreatedAt(sessionId: string): string | null {\n const m = SESSION_ID_TS_RE.exec(sessionId);\n if (!m) return null;\n const [, y, mo, d, h, mi, s] = m;\n const date = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nexport interface SessionServiceDeps {\n root: string;\n sessions: SessionsRepo;\n manager: SessionManager;\n projectConfig: ProjectConfigService;\n /** In-process origin registry derived from session_meta (the DB stores no source column). */\n sources: SessionSources;\n}\n\nexport class SessionService {\n constructor(private readonly deps: SessionServiceDeps) {}\n\n /**\n * DB row -> SessionInfo (run status and pending approval count come from session-manager).\n * Async because `source` is derived from session_meta: a registry miss (Session predating\n * this process) falls back to reading the Trace head once (see sourceOf). `traces` is the\n * list flow's one-walk discovery result; without it a miss locates the shard itself.\n */\n async toInfo(\n row: SessionRow,\n hasTrace: boolean,\n traces?: ReadonlyMap<string, TraceLocation>,\n ): Promise<SessionInfo> {\n const source = await this.sourceOf(row, hasTrace, traces);\n return {\n sessionId: row.sessionId,\n projectId: row.projectId,\n agentId: row.agentId,\n provider: row.provider,\n modelId: row.modelId,\n workspace: row.workspace,\n approvalMode: row.approvalMode,\n ...(row.title !== null ? { title: row.title } : {}),\n ...(source !== undefined ? { source } : {}),\n createdAt: row.createdAt,\n status: this.deps.manager.statusOf(row.sessionId),\n pendingApprovalCount: this.deps.manager.pendingApprovalCount(row.sessionId),\n pendingFollowUpCount: this.deps.manager.pendingFollowUpCount(row.sessionId),\n hasTrace,\n archived: (row.archivedAt ?? null) !== null,\n };\n }\n\n /**\n * A Session's origin, with session_meta as the single source of truth: the in-process\n * registry answers first (populated at creation / subagent registration / adoption);\n * on a miss (a Session created before this process started) the earliest Trace shard's\n * session_meta is read once and cached. A Session with no Trace yet stays unknown and\n * is NOT cached negatively — its meta may appear with the first run.\n */\n private async sourceOf(\n row: SessionRow,\n hasTrace: boolean,\n traces?: ReadonlyMap<string, TraceLocation>,\n ): Promise<SessionSource | undefined> {\n const known = this.deps.sources.get(row.sessionId);\n if (known !== undefined) return known ?? undefined;\n if (!hasTrace) return undefined;\n // Single-session paths carry no discovery map: locate this Session's earliest shard on demand.\n const location =\n traces?.get(row.sessionId) ??\n (await this.discoverTraces(row.projectId, row.agentId)).get(row.sessionId);\n if (!location) return undefined;\n const meta = await this.readTraceMeta(location);\n if (!meta) return undefined; // Unreadable/corrupt Trace: stay unknown, retry on the next list.\n // On-disk values are untrusted: only the exact known origins pass, junk = user-created.\n const source = asSessionSource(meta.payload.source) ?? null;\n this.deps.sources.set(row.sessionId, source);\n return source ?? undefined;\n }\n\n /** Whether this Session already has a Trace record (a Task has been run). */\n async hasTrace(row: SessionRow): Promise<boolean> {\n return (await this.discoverTraces(row.projectId, row.agentId)).has(row.sessionId);\n }\n\n /**\n * The list category of a row: archived wins (an explicit user action), then the\n * origin's bucket, and no/unknown source is `active` — the same precedence the\n * sidebar's partition applies to loaded rows, so server filtering and client\n * rendering can never disagree.\n */\n private async categoryOf(\n row: SessionRow,\n hasTrace: boolean,\n traces?: ReadonlyMap<string, TraceLocation>,\n ): Promise<SessionCategory> {\n if ((row.archivedAt ?? null) !== null) return \"archived\";\n return (await this.sourceOf(row, hasTrace, traces)) ?? \"active\";\n }\n\n /**\n * List: DB ∪ Trace directory discovery, sorted by createdAt descending. Optional\n * `paging` returns just that slice (the sidebar pages with limit+1 to detect \"has\n * more\"); slicing happens before toInfo, so per-request source derivation (lazy\n * Trace-head reads) stays bounded by the page size. Discovery/adoption still scans\n * the whole directory — the union and global ordering need every id.\n *\n * `category` filters to one sidebar bucket **before** paging, so offset/limit page\n * within the category. Filtering needs each walked row's category (a possible\n * Trace-head read per row, cached in the sources registry); without `withCounts`\n * the walk stops as soon as the requested page is complete. `withCounts` classifies\n * every row and returns per-category totals over the whole list — plus the same\n * totals broken down by Workspace path — so the sidebar can label the collapsed\n * folders (and a workspace group can know its own share) without loading them.\n */\n async listSessions(\n projectId: string,\n agentId: string,\n opts: {\n paging?: { offset: number; limit: number };\n category?: SessionCategory;\n withCounts?: boolean;\n } = {},\n ): Promise<{\n sessions: SessionInfo[];\n counts?: SessionCategoryCounts;\n workspaceCounts?: Record<string, SessionCategoryCounts>;\n }> {\n const { paging, category, withCounts } = opts;\n const traces = await this.discoverTraces(projectId, agentId);\n const rows = new Map(\n this.deps.sessions.listByAgent(projectId, agentId).map((r) => [r.sessionId, r]),\n );\n\n // Unmanaged Trace Sessions: backfill an index row by reading the first line's session_meta.\n for (const [sessionId, location] of traces) {\n if (rows.has(sessionId)) continue;\n const discovered = await this.adoptTraceSession(projectId, agentId, sessionId, location);\n if (discovered) rows.set(sessionId, discovered);\n }\n\n const sorted = [...rows.values()].sort(\n (a, b) => b.createdAt.localeCompare(a.createdAt) || b.sessionId.localeCompare(a.sessionId),\n );\n const toPage = (page: SessionRow[]) =>\n Promise.all(page.map((row) => this.toInfo(row, traces.has(row.sessionId), traces)));\n\n // No classification asked for: slice straight away (the pre-category behavior).\n if (category === undefined && !withCounts) {\n return {\n sessions: await toPage(\n paging ? sorted.slice(paging.offset, paging.offset + paging.limit) : sorted,\n ),\n };\n }\n\n const want = paging ? paging.offset + paging.limit : Infinity;\n const counts: SessionCategoryCounts = { active: 0, subagent: 0, schedule: 0, archived: 0 };\n const workspaceCounts: Record<string, SessionCategoryCounts> = {};\n const matched: SessionRow[] = [];\n for (const row of sorted) {\n if (!withCounts && matched.length >= want) break;\n const cat = await this.categoryOf(row, traces.has(row.sessionId), traces);\n counts[cat] += 1;\n if (withCounts) {\n const ws = (workspaceCounts[row.workspace] ??= {\n active: 0,\n subagent: 0,\n schedule: 0,\n archived: 0,\n });\n ws[cat] += 1;\n }\n if ((category === undefined || cat === category) && matched.length < want) matched.push(row);\n }\n const sessions = await toPage(paging ? matched.slice(paging.offset, want) : matched);\n return withCounts ? { sessions, counts, workspaceCounts } : { sessions };\n }\n\n /**\n * Session stats (Agents list card): total count = size of the union of DB index\n * ∪ Trace directory discovery; activity = number of active Sessions per day over\n * the last `days` days (deduplicated count of Sessions created that day or with a\n * Trace record that day; index 0 = earliest, last index = today). Counts only —\n * does not backfill index rows.\n */\n async sessionStats(\n projectId: string,\n agentId: string,\n days: number,\n ): Promise<{ sessionCount: number; activity: number[] }> {\n const all = new Set<string>();\n const byDate = new Map<string, Set<string>>();\n const mark = (date: string, sessionId: string): void => {\n all.add(sessionId);\n const set = byDate.get(date) ?? new Set<string>();\n set.add(sessionId);\n byDate.set(date, set);\n };\n\n // Trace directory: the date directory name is the local date (yyyy-mm-dd) that core uses when writing to disk.\n const dir = tracesDir(this.deps.root, projectId, agentId);\n for (const dateDir of await listDirsSafe(dir)) {\n for (const file of await listFilesSafe(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (match) mark(dateDir, match[1]!);\n }\n }\n // DB index: the creation day also counts as active (a Session that hasn't run a Task yet produces no Trace).\n for (const row of this.deps.sessions.listByAgent(projectId, agentId)) {\n const created = new Date(row.createdAt);\n if (Number.isNaN(created.getTime())) all.add(row.sessionId);\n else mark(localDate(created), row.sessionId);\n }\n\n const activity: number[] = [];\n const now = new Date();\n for (let i = days - 1; i >= 0; i--) {\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i);\n activity.push(byDate.get(localDate(d))?.size ?? 0);\n }\n return { sessionCount: all.size, activity };\n }\n\n /**\n * Create a Session: the model reference is a complete `(provider, modelId)` pair.\n * Half a reference is a client error, never something to resolve — the missing half\n * is never guessed, since a guessed provider would send an entry's credential to a\n * vendor nobody named. Omitting both falls back to the Project's default reference\n * (400 prompting to configure a model first if there is none). `workspace` is already\n * validated by the route guard. The new Session is added to the active table\n * (idle).\n */\n async createSession(args: {\n projectId: string;\n agentId: string;\n /** Upstream id of the session's model; always paired with provider. Omit both for the Project's default reference. */\n modelId?: string;\n /** The provider group for `modelId`; always paired with modelId, never inferred. */\n provider?: string;\n workspace?: string;\n approvalMode?: ApprovalMode;\n /** Session source marker (schedule when triggered by a scheduled task; defaults to user-created). */\n source?: \"schedule\";\n }): Promise<SessionInfo> {\n if ((args.modelId === undefined) !== (args.provider === undefined)) {\n throw badRequest(\n \"modelId and provider must be given together as a (provider, modelId) pair: specify both, or neither to use the Project's default model.\",\n );\n }\n let modelId: string;\n let provider: string;\n if (args.modelId !== undefined && args.provider !== undefined) {\n modelId = args.modelId;\n provider = args.provider;\n } else {\n // The guard above leaves only \"both omitted\" here: fall back to the Project default.\n const def = await this.deps.projectConfig.getDefaultModelRef(args.projectId);\n if (def === undefined) {\n throw new HttpError(\n 400,\n \"no_default_model\",\n \"This Project has no default model yet. Add a model on the Models page and set it as the default first.\",\n );\n }\n modelId = def.model_id;\n provider = def.provider;\n }\n const agent = await createAgent({\n root: this.deps.root,\n projectId: args.projectId,\n agentId: args.agentId,\n });\n let session;\n try {\n session = await agent.createSession({\n modelId,\n provider,\n ...(args.workspace !== undefined ? { workspaceDir: args.workspace } : {}),\n // The origin is also recorded in core session_meta (Trace), not just the index row.\n ...(args.source !== undefined ? { source: args.source } : {}),\n });\n } catch (err) {\n // A missing credential is its own category (the frontend shows localized text\n // by code); other core errors (the pair naming no configured entry, Workspace\n // not existing, etc.) are collapsed to 400 — the guard already blocks most cases.\n if (isMissingCredential(err)) throw modelCredentialMissing(modelId);\n throw new HttpError(\n 400,\n \"session_create_failed\",\n err instanceof Error ? err.message : String(err),\n );\n }\n // The origin is derived from the just-created core Session's session_meta (the single\n // source of truth) rather than echoing args.source back: what the registry serves is\n // exactly what the Trace will record.\n const metaMsg = session.metaMessage;\n this.deps.sources.set(\n session.sessionId,\n isSessionMeta(metaMsg) ? (asSessionSource(metaMsg.payload.source) ?? null) : null,\n );\n const row: SessionRow = {\n sessionId: session.sessionId,\n projectId: args.projectId,\n agentId: args.agentId,\n provider: session.provider,\n modelId: session.modelId,\n workspace: session.workspaceDir,\n approvalMode: args.approvalMode ?? \"allow-all\",\n title: null,\n createdAt: new Date().toISOString(),\n };\n this.deps.sessions.insert(row);\n this.deps.manager.adopt(row, session);\n return this.toInfo(row, false);\n }\n\n /**\n * Absolute path of a Session's **latest** Trace file (the current context shard);\n * undefined when no Trace exists. Costs a directory walk, so only the single-session\n * GET includes it in the DTO (see SessionInfo.tracePath) — the web's `/model` switch\n * hands it to the new session's `[model_switch_from]` block so the model can read the\n * source history itself when it needs it.\n */\n async latestTracePath(row: SessionRow): Promise<string | undefined> {\n const located = await findLatestTraceFile(\n tracesDir(this.deps.root, row.projectId, row.agentId),\n row.sessionId,\n );\n return located?.path;\n }\n\n /**\n * One walk over the Trace directory: session_id → its **earliest** shard (the shard\n * whose head carries the original session_meta). Discovery (which Sessions have\n * records) and the meta-read location come out of a single pass, so classifying every\n * row (`counts=1`) costs one directory walk total instead of one per Session.\n */\n private async discoverTraces(\n projectId: string,\n agentId: string,\n ): Promise<Map<string, TraceLocation>> {\n const dir = tracesDir(this.deps.root, projectId, agentId);\n const out = new Map<string, TraceLocation>();\n for (const dateDir of await listDirsSafe(dir)) {\n for (const file of await listFilesSafe(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match) continue;\n const sessionId = match[1]!;\n const index = Number(match[2]);\n const cur = out.get(sessionId);\n if (!cur || index < cur.index) {\n out.set(sessionId, { path: path.join(dir, dateDir, file), index });\n }\n }\n }\n return out;\n }\n\n /**\n * session_meta from a located Trace shard head; null when unreadable or it has no\n * meta. Shared by adoption backfill and lazy `source` resolution.\n */\n private async readTraceMeta(location: TraceLocation): Promise<SessionMetaMessage | null> {\n let messages;\n try {\n messages = await readTraceHead(location.path);\n } catch {\n return null; // Corrupt file: skip (does not block the list)\n }\n return messages.find(isSessionMeta) ?? null;\n }\n\n /** Adopts a Session that exists only in the Trace directory: reads session_meta from the first line of the earliest index file. */\n private async adoptTraceSession(\n projectId: string,\n agentId: string,\n sessionId: string,\n location: TraceLocation,\n ): Promise<SessionRow | null> {\n const meta = await this.readTraceMeta(location);\n if (!meta) return null;\n // An older Trace version's session_meta lacks provider (the model reference\n // wasn't split into separate fields yet): no backward compat, skip adoption\n // (core will give a clear error on resume; the product hasn't launched yet, so\n // old data can simply be deleted and recreated).\n if (typeof meta.payload.provider !== \"string\") return null;\n // The adoption read already has the meta in hand: record the origin (single source of\n // truth); on-disk values are narrowed — junk counts as user-created.\n this.deps.sources.set(sessionId, asSessionSource(meta.payload.source) ?? null);\n const row: SessionRow = {\n sessionId,\n projectId,\n agentId,\n provider: meta.payload.provider,\n modelId: meta.payload.model_id,\n workspace: meta.payload.workspace,\n // The approval mode for an unmanaged Session (started via the CLI) isn't in the Trace, so it's backfilled with the default value.\n approvalMode: \"allow-all\",\n title: null,\n createdAt: sessionIdCreatedAt(sessionId) ?? meta.timestamp,\n };\n // Idempotent backfill: concurrent list calls may discover the same Session for the first time simultaneously (consistent with AgentsRepo's convention).\n this.deps.sessions.insertOrIgnore(row);\n return row;\n }\n}\n\n/** A located Trace shard of one Session: absolute path plus its shard index. */\ninterface TraceLocation {\n path: string;\n index: number;\n}\n\n/** Local date as yyyy-mm-dd (matches the Trace date directory convention: core's internal formatLocalDate, not publicly exported). */\nfunction localDate(d: Date): string {\n const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`);\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\nasync function listDirsSafe(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nasync function listFilesSafe(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n","/**\n * Trace service.\n *\n * History messages: all of the Session's index files concatenated in order\n * (readTraceTolerant, tolerating a truncated last line), containing only the\n * complete messages and events that were actually written to Trace (naturally\n * excluding partial_*); in-flight increments are continued by SSE.\n * Performance analysis is derived from a single Trace file: nearest-neighbor\n * pairing of request_begin/end, tool call duration pairing, reconnect / compaction\n * counts, and Token trend.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n agentsDir,\n isSessionMeta,\n parseTraceLines,\n parseUserSteeringText,\n readTraceTolerant,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport type {\n AgentTracesResponse,\n RequestSpan,\n ToolCallSpan,\n TraceAnalysisResponse,\n TraceEventsResponse,\n TraceFileInfo,\n TraceImportResponse,\n TraceModelSegment,\n TraceTaskStats,\n TraceToolSpan,\n UsageTrendPointInTrace,\n} from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { formatLocalDate } from \"../internal/dates.js\";\n\nconst TRACE_FILE_RE = /^(.+)_(\\d{3})\\.jsonl$/;\n\n/**\n * session_id an imported Trace file may declare: same alphabet as resource ids plus a length\n * cap. The value becomes part of the target **filename**, so this is a path-traversal defense,\n * checked right next to the path construction — never trust the caller to have validated it.\n */\nconst IMPORT_SESSION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;\n\n/** Recursion depth cap for sub-session expansion (run_subagent depth is already constrained by the SDK; this is just a defensive backstop against cycles). */\nconst MAX_SUBAGENT_DEPTH = 4;\n\ninterface LocatedFile {\n path: string;\n date: string;\n index: number;\n}\n\n/**\n * A **direct sub-session pointer** (the `subagent` event in the parent Trace) ->\n * the sub-session's Session id. The pointer only\n * records the Session id; the sub-session's Agent is located within the Project by\n * its Trace file.\n */\nfunction subagentPointer(msg: OmniMessage): string | null {\n if (msg.type !== \"event_msg\") return null;\n const p = msg.payload as { type?: string; session_id?: unknown };\n if (p.type !== \"subagent\" || typeof p.session_id !== \"string\" || p.session_id === \"\") {\n return null;\n }\n return p.session_id;\n}\n\nasync function listDirs(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nasync function listFiles(dir: string): Promise<string[]> {\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\nexport class TraceService {\n constructor(private readonly root: string) {}\n\n /** All of this Session's Trace files (sorted by index ascending). */\n private async locateAll(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<LocatedFile[]> {\n const dir = tracesDir(this.root, projectId, agentId);\n const out: LocatedFile[] = [];\n for (const dateDir of await listDirs(dir)) {\n for (const file of await listFiles(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match || match[1] !== sessionId) continue;\n out.push({ path: path.join(dir, dateDir, file), date: dateDir, index: Number(match[2]) });\n }\n }\n return out.sort((a, b) => a.index - b.index);\n }\n\n /** Deletes all of this Session's Trace files (called when the Session is deleted). */\n async deleteSessionTraces(projectId: string, agentId: string, sessionId: string): Promise<void> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n for (const file of files) {\n await fs.rm(file.path, { force: true });\n }\n }\n\n /**\n * History messages: all index files concatenated in order, with sub-sessions\n * **expanded in place**.\n *\n * The parent Trace only records a `subagent` pointer event at the spawn point\n * (recording just the child Session id; the content lives in the child\n * Session's own Trace). Here the pointer is used to locate the child Trace\n * within the Project, read it recursively, and splice the child messages —\n * tagged with an origin chain — back in at the pointer's position, so that when\n * the session is reopened, the frontend can re-attach the sub-session to the\n * run_subagent tool card via origin (the child Trace's first `session_meta`,\n * once given an origin, takes the same shape as what's forwarded over the live\n * stream). When expansion succeeds, the pointer event itself is no longer\n * emitted; when the child Trace is missing (deleted), the pointer event is kept\n * so API consumers can still know it existed.\n */\n async readMessages(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<OmniMessage[]> {\n return this.readMessagesExpanded(projectId, agentId, sessionId, {\n index: null,\n ancestry: new Set([sessionId]),\n depth: 0,\n });\n }\n\n /**\n * A Project-wide session location index (sessionId -> agentId): built by\n * scanning every Agent's traces directory. Built lazily the first time a\n * subagent pointer is encountered, then reused across the whole readMessages\n * call — rescanning per pointer would blow up into tens of thousands of readdir\n * calls under multiple sub-sessions plus recursive expansion.\n */\n private async buildSessionIndex(projectId: string): Promise<Map<string, string>> {\n const index = new Map<string, string>();\n for (const agentId of await listDirs(agentsDir(this.root, projectId))) {\n const dir = tracesDir(this.root, projectId, agentId);\n for (const dateDir of await listDirs(dir)) {\n for (const file of await listFiles(path.join(dir, dateDir))) {\n const match = TRACE_FILE_RE.exec(file);\n if (match && !index.has(match[1]!)) index.set(match[1]!, agentId);\n }\n }\n }\n return index;\n }\n\n private async readMessagesExpanded(\n projectId: string,\n agentId: string,\n sessionId: string,\n ctx: { index: Map<string, string> | null; ancestry: Set<string>; depth: number },\n ): Promise<OmniMessage[]> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const out: OmniMessage[] = [];\n for (const file of files) {\n for (const msg of await readTraceTolerant(file.path)) {\n // The depth cap guards against runaway recursion; ancestry guards against a\n // cyclic pointer (a tampered Trace pointing to itself/an ancestor is not expanded).\n const childSid = ctx.depth < MAX_SUBAGENT_DEPTH ? subagentPointer(msg) : null;\n if (!childSid || ctx.ancestry.has(childSid)) {\n out.push(msg);\n continue;\n }\n ctx.index ??= await this.buildSessionIndex(projectId);\n const childAgent = ctx.index.get(childSid);\n let nested: OmniMessage[] = [];\n if (childAgent) {\n ctx.ancestry.add(childSid);\n nested = await this.readMessagesExpanded(projectId, childAgent, childSid, {\n ...ctx,\n depth: ctx.depth + 1,\n });\n ctx.ancestry.delete(childSid);\n }\n // Child Trace missing (deleted): keep the pointer event, since the sub-session's content can't be recovered.\n if (nested.length === 0) {\n out.push(msg);\n continue;\n }\n for (const m of nested) out.push({ ...m, origin: [childSid, ...(m.origin ?? [])] });\n }\n }\n return out;\n }\n\n /** List of Trace files (index / date / size / mtime). */\n async listTraceFiles(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<TraceFileInfo[]> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const out: TraceFileInfo[] = [];\n for (const file of files) {\n const stat = await fs.stat(file.path);\n out.push({\n index: file.index,\n date: file.date,\n sizeBytes: stat.size,\n mtime: stat.mtime.toISOString(),\n });\n }\n return out;\n }\n\n /** Reads events from the Trace file at the given index, paginated by line (for loading large files in pages). */\n async readEvents(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n offset: number,\n limit: number,\n ): Promise<TraceEventsResponse> {\n const messages = await this.readFileByIndex(projectId, agentId, sessionId, index);\n return {\n events: messages.slice(offset, offset + limit),\n offset,\n limit,\n total: messages.length,\n };\n }\n\n /** Performance analysis: derived from a single Trace file. */\n async analyze(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n ): Promise<TraceAnalysisResponse> {\n const messages = await this.readFileByIndex(projectId, agentId, sessionId, index);\n\n const requests: RequestSpan[] = [];\n let openRequest: RequestSpan | null = null;\n const toolCalls: ToolCallSpan[] = [];\n const openToolCalls = new Map<string, ToolCallSpan>();\n let reconnectCount = 0;\n let compactionCount = 0;\n const usageTrend: UsageTrendPointInTrace[] = [];\n // Timeline (serial-duration estimation): Trace records completion times; model\n // messages are produced\n // serially (autoregressive decoding), so each segment's start = the previous\n // event's time (the request's first segment = request_begin); a tool's\n // approval/execution runs in parallel with model decoding, on its own lane;\n // prevSerialTs is cleared after request_end, and the next request_begin\n // restarts the count (which presumes all of the previous round's\n // tool_call_output have already come back).\n const modelSegments: TraceModelSegment[] = [];\n const toolSpans: TraceToolSpan[] = [];\n const openSpansById = new Map<string, TraceToolSpan>();\n let prevSerialTs: string | null = null;\n // Task grouping: one user turn contains multiple Request rounds (the Agent\n // loop sends another round each time it calls a tool); the turn ends once the\n // model produces only text with no further tool call. Consecutive Requests are\n // merged into one Task on this basis, and each Task gets its own independent\n // timeline — different Tasks can be far apart in time (the user is thinking or\n // has stepped away), and sharing one timeline would leave large gaps.\n // Compaction forms its own turn: both compaction_begin/compaction_end break a\n // continuation, so the compaction request becomes its own Task, and the\n // request that resumes after compaction starts yet another Task.\n let taskIndex = -1;\n let continuation = false; // The previous round's Request called a tool -> the next request_begin continues the same Task\n let sawToolCallThisRequest = false;\n // Compaction interval (compaction_begin..compaction_end): the compaction\n // request's request_begin/request_end and token_usage all fall inside it (see\n // core context-engine's summarize flow), which is used to exclude the\n // compaction request entirely from TPS — matching the same convention as\n // compactionActive in the Chat page's task-stats.\n let compactionActive = false;\n // Token / duration totals per Task (computed server-side over the whole file:\n // frontend events are fetched in pages, so summing them there would be\n // mismatched).\n const taskStats = new Map<number, TraceTaskStats>();\n const ensureTask = (ti: number): TraceTaskStats => {\n let t = taskStats.get(ti);\n if (t === undefined) {\n t = {\n taskIndex: ti,\n messageFrom: -1,\n messageTo: -1,\n startTs: \"\",\n endTs: \"\",\n tokens: { cacheRead: 0, cacheWrite: 0, output: 0 },\n llmMs: 0,\n };\n taskStats.set(ti, t);\n }\n return t;\n };\n\n /**\n * Which turn each message belongs to: **decided definitively in one\n * sequential pass**, not left for the frontend to guess by timestamp.\n *\n * Timestamp boundaries can't be pulled apart — the same millisecond can\n * contain \"the previous turn's last reply, compaction_begin, the compaction\n * prompt, and the next turn's request_begin\" all at once, so assigning by\n * time would inevitably misfile this turn's reply into the next turn.\n *\n * Rule (a turn = one user turn; `request_end` is the end of some Request\n * within a turn):\n * - The **starting marker** of a new turn: the main session's user Prompt\n * (outside compaction), or compaction_begin (compaction forms its own\n * turn). Messages after the marker and before that turn's first\n * `request_begin` (subsequent images from a multi-image send, the\n * compaction prompt) are always held pending, waiting for\n * `request_begin` to settle the new taskIndex before the whole span is\n * assigned at once — they belong to the **new** turn, not the tail of\n * the previous one.\n * - Other messages belong to the current taskIndex: tool output and\n * approval decisions arriving after request_end still belong to this\n * turn (they're the results of tools this turn's Request initiated).\n */\n const msgTask: number[] = new Array<number>(messages.length).fill(-1);\n /** The pending new turn's starting point (message index); settled once request_begin determines the taskIndex. */\n let pendingFrom: number | null = null;\n\n for (let mi = 0; mi < messages.length; mi++) {\n const msg = messages[mi]!;\n const p = msg.payload as Record<string, unknown> & { type?: string };\n // The timeline only looks at the main session (a Trace itself never contains origin messages; this is a defensive skip).\n const hasOrigin = msg.origin !== undefined && msg.origin.length > 0;\n\n // Starting marker of a new turn: the main session's user Prompt (outside\n // compaction) -> a new user turn; compaction_begin -> a compaction turn\n // (compaction forms its own turn). A single send can be \"text + multiple\n // images\" = multiple messages; only the **first** one counts (once\n // pendingFrom is set, it's not changed again), otherwise the turn's start\n // would shift to the last image.\n // Mid-run steering (`[user_steering]`-wrapped user text, delivered between turns of a\n // running Task): never a turn starter — same exclusion idea as the compaction summary —\n // and it forces the next request to be a continuation (a steering-only continuation\n // turn has no preceding tool call, but it is still the same Task).\n const isSteeringText =\n !hasOrigin &&\n msg.type === \"model_msg\" &&\n p.type === \"text\" &&\n p.role === \"user\" &&\n typeof p.text === \"string\" &&\n parseUserSteeringText(p.text) !== null;\n if (isSteeringText) continuation = true;\n const startsUserTurn =\n !hasOrigin &&\n !compactionActive &&\n !isSteeringText &&\n msg.type === \"model_msg\" &&\n ((p.type === \"text\" && p.role === \"user\") || p.type === \"image_url\");\n const startsCompactionTurn =\n !hasOrigin && msg.type === \"event_msg\" && p.type === \"compaction_begin\";\n if (startsUserTurn || startsCompactionTurn) {\n if (pendingFrom === null) pendingFrom = mi;\n // A user Prompt **always starts a new turn**: judging continuation solely\n // by \"did the previous turn call a tool\" isn't enough — if the previous\n // turn ended in timeout/malformed (given up after exhausting retries),\n // retryable would leave continuation at true, and this new message would\n // get merged into that failed turn, smearing the two turns' messages /\n // Tokens / TPS / duration together.\n if (startsUserTurn) continuation = false;\n }\n // A main-session message that isn't pending belongs to the current turn immediately (taskIndex < 0 = before the first request_begin, e.g. session_meta).\n if (!hasOrigin && pendingFrom === null) msgTask[mi] = taskIndex;\n\n if (msg.type === \"event_msg\") {\n if (p.type === \"request_begin\") {\n if (!hasOrigin) {\n prevSerialTs = msg.timestamp;\n if (!continuation) taskIndex++; // Not a continuation -> a new Task\n sawToolCallThisRequest = false;\n }\n // Settle taskIndex before opening the span: the span belongs directly to\n // the current Task. Nearest-neighbor pairing: if the previous begin was\n // never closed (process exited mid-run), the span is left open.\n openRequest = { beginTs: msg.timestamp, taskIndex };\n if (compactionActive) openRequest.compaction = true;\n requests.push(openRequest);\n if (!hasOrigin) {\n const t = ensureTask(taskIndex);\n if (compactionActive) t.compaction = true; // This turn is a compaction turn\n // This turn's duration starts at the first request_begin. It doesn't\n // use the timestamp of the user Prompt / compaction summary or other\n // user text: `[context_summary]` is created during compaction but only\n // written to disk on the next run, so resuming the next day would\n // stretch the first turn out by a whole day for no reason; the Prompt\n // to request-dispatch gap is only ever milliseconds anyway.\n if (t.startTs === \"\") t.startTs = msg.timestamp;\n // The new turn's taskIndex is only settled here: the pending span\n // (user Prompt / multiple images / compaction prompt) is assigned in\n // full to **this** turn — they're the start of the new turn, not the\n // tail of the previous one.\n if (pendingFrom !== null) {\n for (let k = pendingFrom; k < mi; k++) {\n if (messages[k]!.origin === undefined) msgTask[k] = taskIndex;\n }\n pendingFrom = null;\n }\n msgTask[mi] = taskIndex;\n }\n } else if (p.type === \"approval_decision\") {\n if (!hasOrigin && typeof p.tool_call_id === \"string\") {\n const span = openSpansById.get(p.tool_call_id);\n if (span && span.approvalTs === undefined) {\n span.approvalTs = msg.timestamp;\n if (typeof p.decision === \"string\") span.decision = p.decision;\n // Approval wait time is subtracted out of the LLM generation\n // duration: core does `await approve(tc)` inside the streaming loop,\n // so the entire manual wait sits between request_begin and\n // request_end (see RequestSpan.approvalWaitMs). Without subtracting\n // it, \"5s generation + 55s approval wait\" would show 100 tok/s as 8 tok/s.\n if (openRequest) {\n const wait = Date.parse(msg.timestamp) - Date.parse(span.callTs);\n if (Number.isFinite(wait) && wait > 0) {\n openRequest.approvalWaitMs = (openRequest.approvalWaitMs ?? 0) + wait;\n }\n }\n }\n }\n } else if (p.type === \"request_end\") {\n const status = typeof p.status === \"string\" ? p.status : undefined;\n // timeout/malformed is automatically reconnected by core within the same\n // run (context-engine's retry loop); the resent Request still belongs to\n // **the same user turn**: it must continue the turn, otherwise a single\n // timeout would split that turn's Tokens/duration/TPS across two Tasks.\n const retryable = status === \"timeout\" || status === \"malformed\";\n if (!hasOrigin) {\n prevSerialTs = null;\n continuation = sawToolCallThisRequest || retryable;\n }\n if (retryable) reconnectCount++;\n if (openRequest) {\n openRequest.endTs = msg.timestamp;\n const dur = Date.parse(msg.timestamp) - Date.parse(openRequest.beginTs);\n if (Number.isFinite(dur)) {\n openRequest.durationMs = dur;\n openRequest.activeMs = Math.max(0, dur - (openRequest.approvalWaitMs ?? 0));\n }\n if (status !== undefined) openRequest.status = status;\n // TPS denominator: accumulated per the turn a Request belongs to. A\n // compaction request counts too — it belongs to **its own compaction\n // turn** (compaction forms its own turn), so it neither pollutes a\n // user turn's TPS, nor does the compaction turn fail to report its own\n // generation speed accurately. A failed retry's duration is counted as\n // well — it belongs to the same turn as the retry that eventually\n // succeeded, and \"how long this turn took to produce these tokens\"\n // should include the retries by definition.\n if (!hasOrigin && openRequest.activeMs !== undefined) {\n ensureTask(openRequest.taskIndex).llmMs += openRequest.activeMs;\n }\n openRequest = null;\n }\n } else if (p.type === \"compaction_begin\") {\n compactionCount++;\n // Compaction forms its own turn: otherwise, if the previous turn called\n // a tool, continuation would still be true and the compaction request\n // would get merged into the previous Task.\n if (!hasOrigin) {\n continuation = false;\n compactionActive = true;\n }\n } else if (p.type === \"compaction_end\") {\n // Both ends of compaction break a continuation. This closing one can't\n // be skipped: if the compaction request itself exhausts its retries and\n // ends in timeout, the retryable check above would mark it as \"continued\",\n // and without clearing it here, the next user turn after compaction\n // would get merged into this compaction Task.\n if (!hasOrigin) {\n continuation = false;\n compactionActive = false;\n }\n } else if (p.type === \"token_usage\") {\n const request = p.request as\n | { total?: number; cache_read?: number; cache_write?: number; output?: number }\n | undefined;\n const session = p.session as { total?: number } | undefined;\n usageTrend.push({\n ts: msg.timestamp,\n requestTotal: request?.total ?? 0,\n sessionTotal: session?.total ?? 0,\n });\n if (!hasOrigin) {\n const t = ensureTask(taskIndex);\n // Cumulative usage for this turn (a running total): those tokens were\n // actually paid for, so the cost can't be dropped. `tokens.output` also\n // doubles as the numerator for output TPS — compaction's output\n // belongs to **its own compaction turn** (compaction forms its own\n // turn), so a user turn's TPS isn't polluted by it, while the\n // compaction turn can still accurately report \"how fast the summary\n // was generated\".\n t.tokens.cacheRead += request?.cache_read ?? 0;\n t.tokens.cacheWrite += request?.cache_write ?? 0;\n t.tokens.output += request?.output ?? 0;\n if (!compactionActive) {\n // The context snapshot only takes non-compaction Requests: tokens\n // consumed by compaction aren't the post-compaction context\n // footprint. A later write overwrites an earlier one -> this\n // naturally leaves behind the snapshot of the Task's **last**\n // non-compaction Request = the context footprint at the end of this\n // turn. Accumulating would be wrong: each Request's input carries\n // the entire history afresh (see TraceTaskStats).\n t.context = {\n cacheRead: request?.cache_read ?? 0,\n cacheWrite: request?.cache_write ?? 0,\n output: request?.output ?? 0,\n };\n }\n }\n }\n continue;\n }\n if (msg.type !== \"model_msg\") continue;\n // Model serial segments: assistant-side thinking/text/tool_call (a user input sent instantaneously occupies no segment).\n if (\n !hasOrigin &&\n prevSerialTs !== null &&\n (p.type === \"thinking\" ||\n p.type === \"tool_call\" ||\n (p.type === \"text\" && p.role === \"assistant\"))\n ) {\n const segment: TraceModelSegment = {\n kind: p.type === \"thinking\" ? \"thinking\" : p.type === \"tool_call\" ? \"tool_call\" : \"text\",\n startTs: prevSerialTs,\n endTs: msg.timestamp,\n taskIndex,\n };\n if (p.type === \"tool_call\" && typeof p.tool_call_id === \"string\") {\n segment.toolCallId = p.tool_call_id;\n if (typeof p.name === \"string\") segment.name = p.name;\n }\n modelSegments.push(segment);\n prevSerialTs = msg.timestamp;\n }\n if (p.type === \"tool_call\" && typeof p.tool_call_id === \"string\") {\n if (!hasOrigin) sawToolCallThisRequest = true; // This turn called a tool -> the next turn continues the same Task\n const callStop = typeof p.stop_reason === \"string\" ? p.stop_reason : undefined;\n const span: ToolCallSpan = {\n toolCallId: p.tool_call_id,\n name: typeof p.name === \"string\" ? p.name : \"\",\n startTs: msg.timestamp,\n };\n if (callStop !== undefined) span.stopReason = callStop;\n openToolCalls.set(p.tool_call_id, span);\n toolCalls.push(span);\n // The timeline lane only accepts calls that \"will actually be executed\":\n // an interrupt-compensation tool_call (stop_reason other than completed)\n // never gets an approval/output, and putting it on a lane would render as\n // a phantom \"executing\" state spanning the whole timeline — so it's\n // skipped outright.\n if (!hasOrigin && (callStop === undefined || callStop === \"completed\")) {\n const timeline: TraceToolSpan = {\n toolCallId: p.tool_call_id,\n name: typeof p.name === \"string\" ? p.name : \"\",\n callTs: msg.timestamp,\n taskIndex,\n };\n openSpansById.set(p.tool_call_id, timeline);\n toolSpans.push(timeline);\n }\n } else if (p.type === \"tool_call_output\" && typeof p.tool_call_id === \"string\") {\n const span = openToolCalls.get(p.tool_call_id);\n if (span && span.endTs === undefined) {\n span.endTs = msg.timestamp;\n const dur = Date.parse(msg.timestamp) - Date.parse(span.startTs);\n if (Number.isFinite(dur)) span.durationMs = dur;\n if (typeof p.stop_reason === \"string\") span.stopReason = p.stop_reason;\n }\n const timeline = openSpansById.get(p.tool_call_id);\n if (timeline && timeline.outputTs === undefined) {\n timeline.outputTs = msg.timestamp;\n if (typeof p.stop_reason === \"string\") timeline.stopReason = p.stop_reason;\n }\n }\n }\n\n // A pending span that never got a request_begin (interrupted right after the\n // user sent it / the process exited): it's a turn that never got to run,\n // and forms its own turn — reattaching it to the previous turn would smear\n // two separate user sends together.\n if (pendingFrom !== null) {\n taskIndex++;\n for (let k = pendingFrom; k < messages.length; k++) {\n if (messages[k]!.origin === undefined) msgTask[k] = taskIndex;\n }\n ensureTask(taskIndex);\n }\n\n // Each turn's message index range and end-of-turn time are always derived from\n // the per-message assignment done above (same source, so they never disagree\n // with each other). Messages before the first request_begin (session_meta)\n // have taskIndex -1 and are assigned to the first turn, otherwise they'd have\n // nowhere to sit on the page. The turn duration's **starting point** isn't\n // decided here — it was already settled at request_begin (duration only looks\n // at LLM requests; timestamps of the user Prompt / compaction summary or other\n // user text don't participate, see TraceTaskStats.startTs).\n const firstTask = [...taskStats.keys()].sort((a, b) => a - b)[0];\n for (let k = 0; k < messages.length; k++) {\n let ti = msgTask[k]!;\n if (ti < 0) {\n if (firstTask === undefined) continue;\n ti = firstTask;\n msgTask[k] = ti;\n }\n const t = ensureTask(ti);\n if (t.messageFrom < 0 || k < t.messageFrom) t.messageFrom = k;\n if (k > t.messageTo) t.messageTo = k;\n // session_meta is only **listed** in the first turn, and doesn't count\n // toward the end-of-turn time: it's metadata written when the session was\n // created, and its timestamp has nothing to do with this turn (it also gets\n // rewritten verbatim at the start of a new file after compaction splits the file).\n if (messages[k]!.type === \"session_meta\") continue;\n const ts = messages[k]!.timestamp;\n if (t.endTs === \"\" || ts > t.endTs) t.endTs = ts;\n }\n\n const tasks = [...taskStats.values()].sort((a, b) => a.taskIndex - b.taskIndex);\n // Total elapsed time = **the sum of each turn's duration**, matching exactly\n // the scope shown per-turn below (**including compaction turns** — their wall\n // clock time genuinely elapsed, each turn's card has its own duration, and the\n // overall total is their sum, so the numbers must add up). It is not \"last\n // message timestamp minus first message timestamp\": that would be the whole\n // file's wall-clock span, counting in the gaps **between** turns (the user\n // thinking, stepping out for coffee, coming back the next day) — none of which\n // is time the Agent spent working. A degenerate turn with no Request has an\n // empty startTs and counts as 0.\n // Note this uses a different convention from the Session's cumulative elapsed\n // time on the Chat page: that one only accumulates user turns (compaction\n // after a turn ends doesn't count toward the turn).\n const elapsedMs = tasks.reduce((sum, t) => {\n const span = Date.parse(t.endTs) - Date.parse(t.startTs);\n return sum + (Number.isFinite(span) ? Math.max(0, span) : 0);\n }, 0);\n return {\n elapsedMs,\n requests,\n tasks,\n toolCalls,\n modelSegments,\n toolSpans,\n reconnectCount,\n compactionCount,\n usageTrend,\n };\n }\n\n /** Level-by-level browsing (newest first): Agent -> date -> Session -> Trace files. */\n async agentTraces(projectId: string, agentId: string): Promise<AgentTracesResponse> {\n const dir = tracesDir(this.root, projectId, agentId);\n const dates = (await listDirs(dir)).sort().reverse();\n const out: AgentTracesResponse = { dates: [] };\n for (const date of dates) {\n const bySession = new Map<string, { index: number; sizeBytes: number }[]>();\n for (const file of await listFiles(path.join(dir, date))) {\n const match = TRACE_FILE_RE.exec(file);\n if (!match) continue;\n const sessionId = match[1]!;\n const stat = await fs.stat(path.join(dir, date, file));\n const files = bySession.get(sessionId) ?? [];\n files.push({ index: Number(match[2]), sizeBytes: stat.size });\n bySession.set(sessionId, files);\n }\n if (bySession.size === 0) continue;\n out.dates.push({\n date,\n // session_id embeds a timestamp, so reverse lexicographic order is reverse chronological order.\n sessions: [...bySession.entries()]\n .sort((a, b) => b[0].localeCompare(a[0]))\n .map(([sessionId, files]) => ({\n sessionId,\n files: files.sort((a, b) => a.index - b.index),\n })),\n });\n }\n return out;\n }\n\n private async readFileByIndex(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n ): Promise<OmniMessage[]> {\n const file = await this.locateByIndex(projectId, agentId, sessionId, index);\n return readTraceTolerant(file.path);\n }\n\n private async locateByIndex(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n ): Promise<LocatedFile> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const file = files.find((f) => f.index === index);\n if (!file) {\n throw new HttpError(\n 404,\n \"trace_not_found\",\n `This Session has no Trace file with index ${index}.`,\n );\n }\n return file;\n }\n\n /** Raw bytes of the Trace file at the given index (export/download: the file is served verbatim). */\n async readFileRaw(\n projectId: string,\n agentId: string,\n sessionId: string,\n index: number,\n ): Promise<Buffer> {\n const file = await this.locateByIndex(projectId, agentId, sessionId, index);\n return fs.readFile(file.path);\n }\n\n /**\n * Imports an uploaded Trace file (raw JSONL content). Validates the content itself —\n * parseable JSONL whose first record is a `session_meta` carrying a filename-safe\n * `session_id` (400 invalid_trace otherwise) — then writes it under the Agent's traces\n * directory as a **new Session**: a session id the Agent already has is rejected with\n * 409 `trace_session_exists` (splicing a further index into an existing Session would\n * corrupt its concatenated transcript, silently become its resume point, and could\n * collide with a live Writer's rotation), so the imported file is always index 1. The\n * date dir comes from the first record's timestamp (falling back to now), formatted as\n * a **local** date — the same convention as core's Trace Writer — so an export →\n * import round-trip lands in the same date dir on non-UTC servers.\n */\n async importTraceFile(\n projectId: string,\n agentId: string,\n content: string,\n ): Promise<TraceImportResponse> {\n const invalid = (message: string) => new HttpError(400, \"invalid_trace\", message);\n let records: OmniMessage[];\n try {\n records = parseTraceLines(content);\n } catch {\n throw invalid(\"The file is not valid Trace JSONL.\");\n }\n if (records.length === 0) throw invalid(\"The file contains no Trace records.\");\n const first = records[0]!;\n if (!isSessionMeta(first))\n throw invalid(\"The first record of a Trace file must be session_meta.\");\n // The payload type declares session_id: string, but the value came from user-supplied JSON —\n // re-check the runtime shape before it becomes part of a filename.\n const sessionId: unknown = (first.payload as { session_id?: unknown }).session_id;\n if (typeof sessionId !== \"string\" || !IMPORT_SESSION_ID_RE.test(sessionId)) {\n throw invalid(\"session_meta carries a missing or invalid session_id.\");\n }\n const duplicate = () =>\n new HttpError(\n 409,\n \"trace_session_exists\",\n `This Agent already has a Session with id ${sessionId}; a duplicate Trace cannot be imported.`,\n );\n if ((await this.locateAll(projectId, agentId, sessionId)).length > 0) throw duplicate();\n const ts = Date.parse(first.timestamp);\n const date = formatLocalDate(Number.isNaN(ts) ? new Date() : new Date(ts));\n const index = 1;\n const dir = path.join(tracesDir(this.root, projectId, agentId), date);\n await fs.mkdir(dir, { recursive: true });\n const file = path.join(dir, `${sessionId}_${String(index).padStart(3, \"0\")}.jsonl`);\n try {\n // Normalize to exactly one trailing newline (the JSONL convention the writer\n // follows). `wx` closes the check-then-write race: two concurrent imports of the\n // same new session id both pass the locateAll check, but only one can create the\n // file — the loser's EEXIST maps to the same 409 as the pre-check.\n await fs.writeFile(file, content.replace(/\\n+$/, \"\") + \"\\n\", {\n encoding: \"utf8\",\n flag: \"wx\",\n });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") throw duplicate();\n throw err;\n }\n return { sessionId, index, date };\n }\n}\n","/**\n * Update-check service: resolves the newest published release from the GitHub Releases\n * API and compares it with the running version (core's VERSION), for the web UI's\n * update reminder (GET /api/version/update-check).\n *\n * This is the server's only outbound internet call, so it is strictly fail-soft: check()\n * never throws and never surfaces an upstream failure as a 5xx — a failed lookup returns\n * a normal response with `error` set and `latestVersion` null. Outcomes are cached\n * in-memory (success for 1 hour, failure for 10 minutes) so a sidebar that opens on every\n * page load cannot hammer GitHub's unauthenticated rate limit; concurrent requests share\n * one in-flight lookup. `PENGUIN_UPDATE_CHECK=off` disables the lookup entirely (no\n * network call), for air-gapped or privacy-sensitive deployments.\n *\n * The request mirrors the CLI's fetchLatestVersion (packages/cli/src/commands/update.ts):\n * same endpoint, same accept / user-agent header shape, same 15s timeout — the server\n * cannot import the CLI package (the dependency runs the other way), hence the small\n * duplication. fetch and the clock are injectable so tests never touch the network.\n */\nimport { BUILD_DATE, VERSION, compareVersions, normalizeVersion } from \"@prismshadow/penguin-core\";\nimport type { UpdateCheckResponse } from \"../api/types.js\";\n\n/** Repository the released artifacts come from (same slug as cli/update.ts's REPO_SLUG). */\nconst REPO_SLUG = \"Prism-Shadow/penguin-harness\";\n/** Releases API endpoint for the newest published release. */\nexport const LATEST_RELEASE_API = `https://api.github.com/repos/${REPO_SLUG}/releases/latest`;\n\n/** How long a successful lookup is served from cache. */\nexport const SUCCESS_TTL_MS = 60 * 60 * 1000;\n/** How long a failed lookup is served from cache (short: transient failures should heal). */\nexport const FAILURE_TTL_MS = 10 * 60 * 1000;\n\nexport interface UpdateCheckServiceOptions {\n /** Test double for the network call (defaults to global fetch). */\n fetchImpl?: typeof fetch;\n /** Injectable clock for TTL determinism in tests. */\n now?: () => Date;\n /** Environment to read PENGUIN_UPDATE_CHECK from (defaults to process.env). */\n env?: Record<string, string | undefined>;\n}\n\nexport class UpdateCheckService {\n private readonly fetchImpl: typeof fetch;\n private readonly now: () => Date;\n private readonly env: Record<string, string | undefined>;\n private cached: { response: UpdateCheckResponse; expiresAt: number } | null = null;\n private inflight: Promise<UpdateCheckResponse> | null = null;\n\n constructor(options: UpdateCheckServiceOptions = {}) {\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.now = options.now ?? (() => new Date());\n this.env = options.env ?? process.env;\n }\n\n /**\n * Never throws; never makes a network call when disabled or (without `force`) while\n * the cache is fresh. `force` — the web's manual \"check for updates\" action — skips\n * the freshness check and performs a real lookup even over a warm cache: the cache\n * exists to shield GitHub's unauthenticated rate limit from *passive* checks fired\n * by every sidebar open, and an explicit user click is rare enough to press through\n * it. Everything else is unchanged: the opt-out stays authoritative (force never\n * dials out under PENGUIN_UPDATE_CHECK=off), the outcome lands in the same cache\n * (subsequent passive checks reuse it), and concurrent callers — forced or not —\n * still share one in-flight lookup.\n */\n async check(force = false): Promise<UpdateCheckResponse> {\n if (this.env[\"PENGUIN_UPDATE_CHECK\"] === \"off\") {\n return {\n currentVersion: VERSION,\n buildDate: BUILD_DATE,\n latestVersion: null,\n updateAvailable: false,\n releaseUrl: null,\n publishedAt: null,\n checkedAt: this.now().toISOString(),\n disabled: true,\n };\n }\n if (!force && this.cached && this.now().getTime() < this.cached.expiresAt) {\n return this.cached.response;\n }\n this.inflight ??= this.lookup().finally(() => {\n this.inflight = null;\n });\n return this.inflight;\n }\n\n /** One real lookup; stores the outcome (success or failure) in the cache with its TTL. */\n private async lookup(): Promise<UpdateCheckResponse> {\n const response = await this.resolveLatest();\n const ttl = response.error === undefined ? SUCCESS_TTL_MS : FAILURE_TTL_MS;\n this.cached = { response, expiresAt: this.now().getTime() + ttl };\n return response;\n }\n\n /**\n * Failure taxonomy mirrors the CLI's fetchLatestVersion: an unreachable endpoint or\n * timeout is \"network\"; a 403/429 is \"rate_limited\" (unauthenticated clients hit\n * GitHub's per-IP limit); any other non-2xx status, an unparsable body, or a body\n * without a usable tag_name is \"bad_response\".\n */\n private async resolveLatest(): Promise<UpdateCheckResponse> {\n const checkedAt = this.now().toISOString();\n const base = { currentVersion: VERSION, buildDate: BUILD_DATE, checkedAt };\n const failure = (error: \"network\" | \"rate_limited\" | \"bad_response\"): UpdateCheckResponse => ({\n ...base,\n latestVersion: null,\n updateAvailable: false,\n releaseUrl: null,\n publishedAt: null,\n error,\n });\n\n let res: Response;\n try {\n res = await this.fetchImpl(LATEST_RELEASE_API, {\n headers: { accept: \"application/vnd.github+json\", \"user-agent\": \"penguin-server\" },\n signal: AbortSignal.timeout(15_000),\n });\n } catch {\n return failure(\"network\");\n }\n if (res.status === 403 || res.status === 429) return failure(\"rate_limited\");\n if (!res.ok) return failure(\"bad_response\");\n\n let body: { tag_name?: unknown; html_url?: unknown; published_at?: unknown };\n try {\n body = (await res.json()) as typeof body;\n } catch {\n return failure(\"bad_response\");\n }\n const tag = typeof body.tag_name === \"string\" ? normalizeVersion(body.tag_name) : \"\";\n if (tag === \"\") return failure(\"bad_response\");\n return {\n ...base,\n latestVersion: tag,\n updateAvailable: compareVersions(tag, VERSION) > 0,\n releaseUrl: typeof body.html_url === \"string\" ? body.html_url : null,\n publishedAt: typeof body.published_at === \"string\" ? body.published_at : null,\n };\n }\n}\n","/**\n * Usage statistics query.\n *\n * Cost is **computed in real time**: usage_records only stores Tokens (pricing may\n * be added later), so at query time each Model's cost is converted using the\n * current Project's configured pricing — the repo returns raw Token totals broken\n * down by `(provider, model_id)` paired reference, and this service looks up each\n * reference's price once and folds it into cost / hasUncosted (if a Model has no\n * pricing, its consumption is excluded from cost and hasUncosted is flagged).\n * Summary cards (today / last 7 days / cumulative), grouped aggregation (date /\n * agent / model / session, with the session dimension supporting agentId drill-down\n * filtering), and a 30-day trend.\n * Server-side error statistics (error_records) ride along on the same response:\n * the statistics center fetches everything in one request, and filters are\n * naturally shared; unattributed errors (login failures, process crashes, and other\n * errors with no Project context) are visible only to admins, see the ErrorsRepo\n * file header.\n */\nimport type {\n UsageBucket,\n UsageErrors,\n UsageGroupBy,\n UsageGroupRow,\n UsageResponse,\n} from \"../api/types.js\";\nimport type { ErrorFilter, ErrorsRepo } from \"../db/repos/errors.js\";\nimport type {\n UsageRepo,\n UsageModelSums,\n UsageGroupModelSums,\n UsageFilter,\n} from \"../db/repos/usage.js\";\nimport { formatLocalDate, localDateMinusDays } from \"../internal/dates.js\";\n\n/** Number of most-recent entries kept in the error detail table. */\nconst ERROR_RECENT_N = 20;\n\n/** The three pricing buckets (usd_per_mtok convention), returned by the pricing lookup callback. */\nexport interface PricingRates {\n cacheRead: number;\n cacheWrite: number;\n output: number;\n}\n\nexport type PricingLookup = (\n projectId: string,\n provider: string,\n modelId: string,\n) => Promise<PricingRates | undefined>;\n\nexport interface UsageQuery {\n from?: string;\n to?: string;\n groupBy: UsageGroupBy;\n /** Top-level filter: view by Agent (also used for groupBy=session drill-down). */\n agentId?: string;\n /** Top-level filter: view by Model (paired with modelId; the dropdown always sends them as a pair). */\n provider?: string;\n modelId?: string;\n /** Whether to include unattributed errors: admin only (the route passes user.isAdmin), defaults to false. */\n includeGlobalErrors?: boolean;\n}\n\n/** Cost formula: sum of the three buckets, in USD per million Tokens. */\nfunction costOf(sums: UsageModelSums, rates: PricingRates): number {\n return (\n (sums.cacheRead * rates.cacheRead +\n sums.cacheWrite * rates.cacheWrite +\n sums.output * rates.output) /\n 1e6\n );\n}\n\n/** In-process Map key for a paired reference (\\0-separated, the same style as session-manager's agentKey; never persisted). */\nfunction refKey(provider: string, modelId: string): string {\n return `${provider}\\0${modelId}`;\n}\n\nexport class UsageService {\n constructor(\n private readonly usage: UsageRepo,\n private readonly errors: ErrorsRepo,\n private readonly lookupPricing: PricingLookup,\n private readonly now: () => Date = () => new Date(),\n ) {}\n\n async query(projectId: string, q: UsageQuery): Promise<UsageResponse> {\n const today = formatLocalDate(this.now());\n // Top-level filter: agent + model (the cost center switches views by agent/model; the model filter is always sent as a pair).\n const base: UsageFilter = {};\n if (q.agentId !== undefined) base.agentId = q.agentId;\n if (q.provider !== undefined) base.provider = q.provider;\n if (q.modelId !== undefined) base.modelId = q.modelId;\n\n const win = (from?: string, to?: string): UsageFilter => ({\n ...base,\n ...(from !== undefined ? { from } : {}),\n ...(to !== undefined ? { to } : {}),\n });\n\n const todayRows = this.usage.bucketByModel(projectId, win(today, today));\n const last7dRows = this.usage.bucketByModel(projectId, win(localDateMinusDays(this.now(), 6)));\n const totalRows = this.usage.bucketByModel(projectId, win(q.from, q.to));\n const groupRows = this.usage.groupsByModel(projectId, q.groupBy, win(q.from, q.to));\n // Fixed 30-day window; affected by the agent/model filter.\n const trendFrom = localDateMinusDays(this.now(), 29);\n const trendRows = this.usage.groupsByModel(projectId, \"date\", win(trendFrom));\n // Agent call-count chart: not affected by the agent filter (shows all agents), but still affected by the date + model filter.\n const agentRows = this.usage.groupsByModel(projectId, \"agent\", {\n ...(q.provider !== undefined ? { provider: q.provider } : {}),\n ...(q.modelId !== undefined ? { modelId: q.modelId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n });\n // Model success-rate chart: not affected by the model filter (shows all models), but still affected by the date + agent filter.\n const statusRows = this.usage.statusByModel(projectId, {\n ...(q.agentId !== undefined ? { agentId: q.agentId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n });\n // Error statistics: likewise not affected by the model filter (HTTP / process errors have no Model dimension), but still affected by the date + agent filter.\n const errorFilter: ErrorFilter = {\n ...(q.agentId !== undefined ? { agentId: q.agentId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n // Unattributed errors are visible only to admins (regular members only see errors within their own Project, see the ErrorsRepo file header).\n ...(q.includeGlobalErrors === true ? { includeGlobal: true } : {}),\n };\n\n // Each paired reference that occurs is looked up for its current price only once.\n const rates = new Map<string, PricingRates | undefined>();\n const allRefs = new Map<string, { provider: string; modelId: string }>();\n for (const r of [...todayRows, ...last7dRows, ...totalRows, ...groupRows, ...trendRows]) {\n allRefs.set(refKey(r.provider, r.modelId), { provider: r.provider, modelId: r.modelId });\n }\n for (const [key, ref] of allRefs) {\n rates.set(key, await this.lookupPricing(projectId, ref.provider, ref.modelId));\n }\n\n const byAgentMap = new Map<string, { requests: number; total: number }>();\n for (const r of agentRows) {\n const acc = byAgentMap.get(r.key) ?? { requests: 0, total: 0 };\n acc.requests += r.requests;\n acc.total += r.total;\n byAgentMap.set(r.key, acc);\n }\n\n return {\n summary: {\n today: this.foldBucket(todayRows, rates),\n last7d: this.foldBucket(last7dRows, rates),\n total: this.foldBucket(totalRows, rates),\n },\n groupBy: q.groupBy,\n groups: this.foldGroups(groupRows, rates, q.groupBy),\n trend: this.foldTrend(trendRows, rates),\n byAgent: [...byAgentMap.entries()]\n .map(([agentId, v]) => ({ agentId, requests: v.requests, total: v.total }))\n .sort((a, b) => b.requests - a.requests),\n success: statusRows.sort((a, b) => b.total - a.total),\n errors: this.foldErrors(projectId, errorFilter),\n agentIds: this.usage.distinctAgentIds(projectId),\n models: this.usage.distinctModels(projectId),\n };\n }\n\n /** Error statistics: summary info (total / unexpected / most common error code) + the last N entries, all filtered by the selected range. */\n private foldErrors(projectId: string, f: ErrorFilter): UsageErrors {\n const { total, unexpected } = this.errors.summary(projectId, f);\n return {\n total,\n unexpected,\n topCode: this.errors.topCode(projectId, f),\n recent: this.errors.recent(projectId, f, ERROR_RECENT_N),\n };\n }\n\n private foldBucket(\n rows: UsageModelSums[],\n rates: Map<string, PricingRates | undefined>,\n ): UsageBucket {\n let total = 0;\n let requests = 0;\n let cost: number | null = null;\n let hasUncosted = false;\n for (const r of rows) {\n total += r.total;\n requests += r.requests;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) cost = (cost ?? 0) + costOf(r, rate);\n else hasUncosted = true;\n }\n return { total, requests, cost, hasUncosted };\n }\n\n private foldGroups(\n rows: UsageGroupModelSums[],\n rates: Map<string, PricingRates | undefined>,\n groupBy: UsageGroupBy,\n ): UsageGroupRow[] {\n // The model dimension folds by paired reference (a shared model_id name across providers is split into separate rows); other dimensions fold by their group key.\n const keyOf = (r: UsageGroupModelSums): string =>\n groupBy === \"model\" ? refKey(r.provider, r.key) : r.key;\n const byKey = new Map<string, UsageGroupRow>();\n for (const r of rows) {\n const acc = byKey.get(keyOf(r)) ?? {\n key: r.key,\n ...(groupBy === \"model\" ? { provider: r.provider } : {}),\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n total: 0,\n requests: 0,\n cost: null as number | null,\n hasUncosted: false,\n };\n acc.cacheRead += r.cacheRead;\n acc.cacheWrite += r.cacheWrite;\n acc.output += r.output;\n acc.total += r.total;\n acc.requests += r.requests;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) acc.cost = (acc.cost ?? 0) + costOf(r, rate);\n else acc.hasUncosted = true;\n byKey.set(keyOf(r), acc);\n }\n const out = [...byKey.values()];\n // The date dimension sorts by key descending (most recent first); other dimensions sort by total Token count descending.\n if (groupBy === \"date\") out.sort((a, b) => b.key.localeCompare(a.key));\n else out.sort((a, b) => b.total - a.total);\n return out;\n }\n\n private foldTrend(\n rows: UsageGroupModelSums[],\n rates: Map<string, PricingRates | undefined>,\n ): UsageResponse[\"trend\"] {\n const byDate = new Map<\n string,\n { total: number; cacheRead: number; cacheWrite: number; output: number; cost: number | null }\n >();\n for (const r of rows) {\n const acc = byDate.get(r.key) ?? {\n total: 0,\n cacheRead: 0,\n cacheWrite: 0,\n output: 0,\n cost: null,\n };\n acc.total += r.total;\n acc.cacheRead += r.cacheRead;\n acc.cacheWrite += r.cacheWrite;\n acc.output += r.output;\n const rate = rates.get(refKey(r.provider, r.modelId));\n if (rate) acc.cost = (acc.cost ?? 0) + costOf(r, rate);\n byDate.set(r.key, acc);\n }\n return [...byDate.entries()]\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([date, v]) => ({\n date,\n total: v.total,\n cacheRead: v.cacheRead,\n cacheWrite: v.cacheWrite,\n output: v.output,\n cost: v.cost,\n }));\n }\n}\n","/**\n * Workspace HTML preview served from a separate origin.\n *\n * `GET /preview/:token/*` sits outside `/api` and outside the auth middleware: the\n * preview origin never receives the session cookie, so the signed token in the path is\n * the only credential. Path-based (rather than the query-parameter `files/content`\n * endpoint) so a page's relative subresources — `app.js`, `style.css`, images — resolve\n * against the document and actually load.\n *\n * Because the boundary is now the origin itself, the response does NOT carry the CSP\n * sandbox: the page gets a normal origin with working storage and cookies, which is what\n * lets third-party embeds run. That only stays safe while the host check below holds —\n * this same process also answers on the App origin, and serving Agent-written HTML there\n * would be a same-origin XSS with full API access.\n *\n * Design: design/specs/05-ARCHITECTURE.md § \"Workspace 文件预览\".\n */\nimport { Hono } from \"hono\";\nimport type { SessionsRepo } from \"../../db/repos/sessions.js\";\nimport type { WorkspaceFilesService } from \"../../services/workspace-files-service.js\";\nimport type { PreviewTokenSigner } from \"../../services/preview-token.js\";\nimport { hostOnly, requestAuthority } from \"../../services/preview-token.js\";\n\nexport interface PreviewDeps {\n sessionsRepo: SessionsRepo;\n workspaceFiles: WorkspaceFilesService;\n previewTokens: PreviewTokenSigner;\n}\n\n/** Directory-style requests resolve to index.html, matching ordinary static hosting. */\nconst DIRECTORY_INDEX = \"index.html\";\n\nexport function previewRoutes(deps: PreviewDeps) {\n const app = new Hono();\n\n app.get(\"/:token/*\", async (c) => {\n const token = c.req.param(\"token\");\n const payload = token ? deps.previewTokens.verify(token) : null;\n // A bad or expired token and a wrong host both answer 404 with no detail: this\n // endpoint is unauthenticated, so it should not confirm what exists.\n if (!payload) return c.text(\"Not found\", 404);\n\n // The host binding is the load-bearing check — see the file header.\n const authority = requestAuthority(c.req.url, c.req.header(\"host\"));\n if (hostOnly(authority).toLowerCase() !== payload.host.toLowerCase()) {\n return c.text(\"Not found\", 404);\n }\n\n const row = deps.sessionsRepo.findById(payload.sessionId);\n if (!row) return c.text(\"Not found\", 404);\n\n // Everything after `/preview/<token>/` is the Workspace-relative path; the service\n // re-resolves it against the Workspace and rejects `..` and symlink escapes.\n const prefix = `/preview/${token}/`;\n const raw = c.req.path.startsWith(prefix) ? c.req.path.slice(prefix.length) : \"\";\n let rel: string;\n try {\n rel = decodeURIComponent(raw);\n } catch {\n return c.text(\"Not found\", 404);\n }\n if (rel === \"\" || rel.endsWith(\"/\")) rel += DIRECTORY_INDEX;\n\n let file;\n try {\n file = await deps.workspaceFiles.read(row.workspace, rel);\n } catch {\n return c.text(\"Not found\", 404);\n }\n\n return new Response(new Uint8Array(file.data), {\n status: 200,\n headers: {\n \"Content-Type\": file.contentType,\n \"X-Content-Type-Options\": \"nosniff\",\n // Without this, third-party requests made by the page leak the token-bearing\n // URL through Referer — a risk that only exists because this origin exists to\n // let third-party embeds run.\n \"Referrer-Policy\": \"no-referrer\",\n // Previews are per-token and short-lived; never let a shared cache keep them.\n \"Cache-Control\": \"no-store\",\n },\n });\n });\n\n return app;\n}\n","/**\n * Server runtime config (ServerConfig) — parsed from environment variables.\n *\n * The data root directory is shared with the SDK / CLI (`resolveRoot()`:\n * PENGUIN_HOME or ~/.penguin/data); the SQLite index database defaults to\n * `<root>/web.db` (overridable via PENGUIN_WEB_DB, tests use \":memory:\").\n * In production, the SPA is served statically once the frontend build output\n * directory (PENGUIN_WEB_DIST, the bundled web-dist/, or ../web/dist) is\n * detected to exist.\n * Docs: /docs/configuration § \"Environment variables\".\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { DEFAULT_SERVER_PORT, resolveRoot } from \"@prismshadow/penguin-core\";\n\nexport interface ServerConfig {\n /** Local data root directory (shared with the SDK/CLI). */\n root: string;\n /** HTTP listen address and port (defaults to 127.0.0.1:7364, deliberately avoiding common ports like 3000/8080). */\n host: string;\n port: number;\n /** SQLite database path; \":memory:\" for test injection. */\n dbPath: string;\n /** Frontend static assets directory; whether it's enabled is decided by checking existence when the app is assembled. */\n webDist: string;\n /**\n * Origin that serves Workspace HTML previews (PENGUIN_PREVIEW_ORIGIN), e.g.\n * `https://preview.example.com`. It must differ from the App origin by **hostname** —\n * cookies ignore ports, so a second port would still share the session cookie. Unset\n * is the norm locally: the loopback counterpart (`127.0.0.1` <-> `localhost`) is\n * derived per request instead. See design § \"Workspace 文件预览\".\n */\n previewOrigin: string | null;\n /** Login session validity period (7 days). */\n authSessionTtlMs: number;\n /** Sliding renewal threshold: if the remaining validity is below this value when validation succeeds, it's renewed to the full TTL (renews under 6 days). */\n authSessionRenewMs: number;\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Default frontend build output directory, first match wins:\n * - `<this package>/web-dist`: npm package layout — the release workflow copies the built\n * web assets into the published package, so an `npm install` gets the Web UI too;\n * - `<this package>/../web/dist`: monorepo layout (resolves the same whether running\n * from src or dist), also the fallback when neither exists.\n */\nfunction defaultWebDist(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const bundled = path.resolve(here, \"..\", \"web-dist\");\n if (fs.existsSync(bundled)) return bundled;\n return path.resolve(here, \"..\", \"..\", \"web\", \"dist\");\n}\n\n/**\n * Validates PENGUIN_PREVIEW_ORIGIN into a bare origin, or throws. An unparseable value\n * is a hard failure rather than a silent fallback: falling back would quietly serve\n * previews same-origin, which is the configuration this variable exists to avoid.\n */\nfunction normalizePreviewOrigin(raw: string | undefined): string | null {\n if (!raw || raw.trim() === \"\") return null;\n let url: URL;\n try {\n url = new URL(raw.trim());\n } catch {\n throw new Error(`Invalid PENGUIN_PREVIEW_ORIGIN=${raw} (expected an absolute origin)`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new Error(`Invalid PENGUIN_PREVIEW_ORIGIN=${raw} (only http/https are supported)`);\n }\n return url.origin;\n}\n\n/** Parses server config from environment variables (PORT / HOST / PENGUIN_HOME / PENGUIN_WEB_DIST / PENGUIN_WEB_DB / PENGUIN_PREVIEW_ORIGIN). */\nexport function resolveServerConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const root = env.PENGUIN_HOME ?? resolveRoot();\n // An empty PORT string is treated as unset (the common `.env` case of an empty\n // `PORT=`): Number(\"\") === 0 would pass the range check and bind to a random\n // port; this matches the CLI's resolvePort convention.\n const port = Number(env.PORT || DEFAULT_SERVER_PORT);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new Error(`Invalid port configuration PORT=${env.PORT}`);\n }\n return {\n root,\n host: env.HOST ?? \"127.0.0.1\",\n port,\n dbPath: env.PENGUIN_WEB_DB ?? path.join(root, \"web.db\"),\n webDist: env.PENGUIN_WEB_DIST ?? defaultWebDist(),\n previewOrigin: normalizePreviewOrigin(env.PENGUIN_PREVIEW_ORIGIN),\n authSessionTtlMs: 7 * DAY_MS,\n authSessionRenewMs: 6 * DAY_MS,\n };\n}\n"],"mappings":"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","names":["fs","path","Hono","mapRow","mapRow","mapRow","mapRow","randomBytes","deps","randomBytes","deps","app","Hono","getCookie","deps","app","Hono","getCookie","Hono","randomBytes","timingSafeEqual","app","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","createHash","Hono","isValidId","ms","path","path","isValidId","deps","app","Hono","createHash","Hono","deps","app","Hono","fs","path","Hono","app","Hono","deps","path","fs","fs","Hono","deps","app","Hono","fs","Hono","deps","app","Hono","fs","path","Hono","deps","app","Hono","path","fs","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","fs","path","Hono","fs","fs","fs","path","fs","path","deps","app","Hono","fs","path","input","sessionId","Hono","deps","app","Hono","fs","path","isSessionMeta","open","frag","path","path","fs","deps","isSessionMeta","path","createHash","userText","deps","createHash","userText","deps","isEventMessage","isSessionMeta","isSessionMeta","originSessionId","isEventMessage","randomBytes","deps","fs","fs","path","parseToml","userText","fs","parseToml","path","userText","next","THINKING_LEVELS","fs","config","path","fs","path","agentsDir","agentsMdPath","isValidId","loadAgentVault","scheduleDir","skillsDir","systemConfigPath","fs","agentsDir","isValidId","systemConfigPath","loadAgentVault","scheduleDir","skillsDir","path","agentsMdPath","fs","path","parseToml","parseYaml","asRecord","config","randomBytes","fs","path","parseYaml","agentDir","agentStateDir","agentStateVersion","systemConfigPath","path","fs","systemConfigPath","parseYaml","agentStateVersion","randomBytes","agentDir","agentStateDir","fs","path","fs","deps","path","readdir","createAgent","findLatestTraceFile","isSessionMeta","tracesDir","deps","tracesDir","path","createAgent","isSessionMeta","findLatestTraceFile","readdir","fs","path","agentsDir","isSessionMeta","parseTraceLines","readTraceTolerant","tracesDir","TRACE_FILE_RE","fs","tracesDir","path","agentsDir","readTraceTolerant","parseTraceLines","isSessionMeta","BUILD_DATE","VERSION","refKey","Hono","deps","app","Hono","config","deps","app","Hono","fs","CONTENT_TYPES","path","fs","path"]}
|