@prismshadow/penguin-server 0.2.0 → 0.2.2

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/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/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/services/task-attachments.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 { bodyLimit } from \"hono/body-limit\";\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, workspaceFiles);\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 //\n // The cap has to be measured, not read: a chunked request carries no `content-length` at all,\n // so a header check alone passes a body of any size — the sinks behind it (task input images,\n // file attachments, Trace import) then decode whatever arrives. hono's bodyLimit keeps the\n // header fast path when the length is declared and otherwise counts bytes off the stream,\n // aborting the moment the total crosses the cap.\n app.use(\n \"/api/*\",\n bodyLimit({\n maxSize: MAX_BODY_BYTES,\n // Its default is a bare text/plain 413; throw the App's own error instead so the response\n // stays the documented `payload_too_large` body that every client already handles.\n onError: () => {\n throw new HttpError(413, \"payload_too_large\", \"Request body exceeds the 20MB limit.\");\n },\n }),\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 // Columns added to the schema after a web.db was formed: CREATE TABLE IF NOT EXISTS never\n // touches an existing table, so they are ALTERed in here. Keep the list in sync with\n // schema.ts; drop entries only in a release allowed to break existing web.db files.\n ensureColumn(db, \"sessions\", \"client\", \"TEXT\");\n ensureColumn(db, \"sessions\", \"has_trace\", \"INTEGER NOT NULL DEFAULT 0\");\n return db;\n}\n\n/** Idempotent per-column upgrade for databases formed before the column existed. */\nfunction ensureColumn(db: DatabaseSync, table: string, column: string, ddl: string): void {\n const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];\n if (cols.some((c) => c.name === column)) return;\n db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${ddl}`);\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\n * once. The one exception: columns added to an existing table after release of a web.db are\n * ALTERed in by the idempotent per-column guard in database.ts (ensureColumn), since CREATE\n * TABLE IF NOT EXISTS never touches an existing table.\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 client TEXT, -- creating client: 'web' (created via the Web App) | 'cli' (adopted from a CLI Trace); NULL = legacy row, treated as web\n has_trace INTEGER NOT NULL DEFAULT 0, -- cache: a Trace record exists (set at task start / adoption / subagent registration; lazily backfilled by list hydration)\n created_at TEXT NOT NULL\n); -- the schedule/subagent SOURCE is NOT stored: session_meta in the Trace is the single source of truth (see runtime/session-sources.ts); \"client\" is a different, DB-only axis (who created the row)\nCREATE INDEX IF NOT EXISTS idx_sessions_agent_created ON sessions(project_id, agent_id, created_at DESC);\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, offset = 0): 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 OFFSET :offset`,\n )\n .all({ ...params, limit, offset });\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 /**\n * Creating client: \"web\" (created via the Web App/server) or \"cli\" (adopted from a Trace\n * the CLI left behind); NULL = legacy row from before the column existed, treated as web.\n * The schedule/subagent SOURCE is deliberately NOT a row field — core session_meta in the\n * Trace stays the single source of truth for it (runtime/session-sources.ts); `client` is\n * a separate, DB-only axis that meta never records.\n */\n client?: \"web\" | \"cli\" | null;\n /** Cache: a Trace record exists (set at task start / adoption / subagent registration; backfilled by list hydration). */\n hasTrace?: boolean;\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 client: (r.client as \"web\" | \"cli\" | null) ?? null,\n hasTrace: (r.has_trace as number) === 1,\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, client, has_trace, 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.client ?? null,\n row.hasTrace ? 1 : 0,\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, client, has_trace, 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.client ?? null,\n row.hasTrace ? 1 : 0,\n row.createdAt,\n );\n }\n\n /** Flip the has_trace cache once a Trace record exists (task start / discovery hydration); idempotent. */\n markHasTrace(sessionId: string): void {\n this.db.prepare(\"UPDATE sessions SET has_trace = 1 WHERE session_id = ?\").run(sessionId);\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 /**\n * An Agent's rows, newest first (the list order the sidebar shows; served by\n * idx_sessions_agent_created). `webOnly` keeps only web-created rows — NULL counts as\n * web (legacy rows predate the column and the user chose to grandfather them as visible).\n */\n listByAgent(projectId: string, agentId: string, opts: { webOnly?: boolean } = {}): SessionRow[] {\n const filter = opts.webOnly ? \" AND (client IS NULL OR client = 'web')\" : \"\";\n const rows = this.db\n .prepare(\n `SELECT * FROM sessions WHERE project_id = ? AND agent_id = ?${filter}\n ORDER BY created_at DESC, session_id DESC`,\n )\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, PATCH|DELETE /api/projects/:p.\n */\nimport { Hono } from \"hono\";\nimport type {\n ProjectCreateResponse,\n ProjectUpdateResponse,\n ProjectsResponse,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport {\n badRequest,\n optionalString,\n readJson,\n requireString,\n requireValidId,\n} 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 /** Rename (owner): the display name only — the id names the directory and is immutable. */\n app.patch(\"/:projectId\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const body = await readJson(c);\n // Trimmed before the length check, so \" \" is rejected rather than stored as a blank\n // display name (minLen alone counts the spaces). The web client trims too; this makes a\n // direct API call behave the same.\n const name = requireString(body, \"name\", { maxLen: 100, label: \"name\" }).trim();\n if (name === \"\") throw badRequest(\"name must be at least 1 characters.\");\n const project = await deps.projectService.renameProject(c.var.user.userId, projectId, name);\n return c.json({ project } satisfies ProjectUpdateResponse);\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 * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/files\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/files/content\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/rubric/files\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/rubric/files/content\n * Returns the Agent's Benchmark list (title/description from benchmark_config.toml)\n * along with the evaluations[] from scoreboard.yaml.\n */\nimport { Hono, type Context } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport type { CaseMaterial } from \"../../api/types.js\";\nimport { requireValidId } from \"../validate.js\";\n\nconst TEXT_PREVIEW_BYTES = 256 * 1024;\n\nfunction listCaseFiles(deps: AppDeps, material: CaseMaterial) {\n return async (c: Context<AppEnv>) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const benchmarkId = requireValidId(c, \"benchmarkId\");\n const caseId = requireValidId(c, \"caseId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(\n await deps.benchmarks.listCaseFiles(\n projectId,\n agentId,\n benchmarkId,\n caseId,\n c.req.query(\"path\") ?? \"\",\n material,\n ),\n );\n };\n}\n\nfunction readCaseFile(deps: AppDeps, material: CaseMaterial) {\n return async (c: Context<AppEnv>) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const benchmarkId = requireValidId(c, \"benchmarkId\");\n const caseId = requireValidId(c, \"caseId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const download = c.req.query(\"download\") === \"1\";\n const boundedPreview = !download && c.req.query(\"preview\") === \"1\";\n const { data, fileName, contentType, scriptable, truncated } =\n await deps.benchmarks.readCaseFile(\n projectId,\n agentId,\n benchmarkId,\n caseId,\n c.req.query(\"path\") ?? \"\",\n material,\n boundedPreview ? { maxBytes: TEXT_PREVIEW_BYTES } : undefined,\n );\n return new Response(new Uint8Array(data), {\n status: 200,\n headers: {\n \"Content-Type\": !download && scriptable ? \"text/plain; charset=utf-8\" : contentType,\n \"Content-Disposition\": `${download ? \"attachment\" : \"inline\"}; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n \"X-Content-Type-Options\": \"nosniff\",\n ...(truncated ? { \"X-Content-Truncated\": \"1\" } : {}),\n },\n });\n };\n}\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 app.get(\"/:benchmarkId/cases\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const benchmarkId = requireValidId(c, \"benchmarkId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(await deps.benchmarks.listCases(projectId, agentId, benchmarkId));\n });\n\n app.get(\"/:benchmarkId/cases/:caseId/files\", listCaseFiles(deps, \"statement\"));\n app.get(\"/:benchmarkId/cases/:caseId/files/content\", readCaseFile(deps, \"statement\"));\n app.get(\"/:benchmarkId/cases/:caseId/rubric/files\", listCaseFiles(deps, \"rubric\"));\n app.get(\"/:benchmarkId/cases/:caseId/rubric/files/content\", readCaseFile(deps, \"rubric\"));\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 * GET /api/projects/:p/usage/errors?offset&limit&from&to&agentId — one page of the error\n * detail table, for paging back past the first page the dashboard already returns.\n */\nimport { Hono } from \"hono\";\nimport type { UsageGroupBy } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, optionalDateParam, paginationQuery, 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 // One page of the error detail table, newest first. The dashboard response above already\n // carries the first page; this serves \"show me earlier ones\" without refetching the whole\n // aggregate. Takes the date/agent filter only — the model filter never applied to errors\n // (HTTP and process errors have no Model dimension), so accepting it here would imply a\n // narrowing the summary above does not do.\n app.get(\"/errors\", (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const { offset, limit } = paginationQuery(c);\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 return c.json(\n deps.usageService.queryErrors(projectId, {\n offset,\n limit,\n // Same admin-only rule as the dashboard: a regular member seeing another tenant's\n // unattributed errors would be a cross-tenant leak.\n includeGlobalErrors: c.var.user.isAdmin,\n ...(from !== undefined ? { from } : {}),\n ...(to !== undefined ? { to } : {}),\n ...(agentId !== undefined && agentId !== \"\" ? { agentId } : {}),\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\";\nimport {\n assertAttachmentBudget,\n attachFilesToInput,\n parseAttachmentPart,\n removeAttachments,\n} from \"../../services/task-attachments.js\";\nimport type { TaskAttachment } from \"../../services/task-attachments.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/**\n * A base64 `data:` URL of an image, in the exact shape core parses it back out of\n * (`imagesToScratchpadPaths`): one mime type, the `;base64,` marker, a non-empty base64 body.\n * The mime is deliberately unconstrained — core maps the ones it knows to a file extension and\n * falls back to `.bin`, and the image tools sniff the magic bytes rather than trusting either.\n *\n * Checking the body, not just the `data:` prefix, is what keeps the failure here instead of\n * three layers down: core turns a data URL it cannot parse into an \"[an attached image could\n * not be saved and was dropped]\" line, which for an HTTP caller means a 202 followed by a\n * message quietly missing its picture. The file-attachment field has always validated its own\n * payload this way (parseAttachmentPart); this is the same rule for images.\n */\nconst IMAGE_DATA_URL = /^data:[^;,]+;base64,[A-Za-z0-9+/=\\s]+$/;\n\n/**\n * The image-URL rule every image-carrying request field obeys: a `data:` URL the session\n * keeps (inline, or written to the scratchpad without vision) or an http(s) URL it references.\n * `field` names the offending value in the error, so each caller reads as if it validated\n * inline.\n */\nfunction requireImageUrl(url: unknown, field: string): string {\n if (typeof url === \"string\") {\n if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) return url;\n if (IMAGE_DATA_URL.test(url)) return url;\n }\n throw badRequest(\n `${field} must be an http(s) URL or a base64 data: URL (data:<mime>;base64,<bytes>).`,\n );\n}\n\n/**\n * Resolve a scratchpad file name to an absolute path inside `dir`, or null when it could point\n * anywhere else (the caller turns that into the same 404 a missing file gets, so a probe learns\n * nothing either way).\n *\n * A character whitelist is deliberately NOT the guard: an attachment keeps the name the user\n * gave it, `报告.pdf` included, so the check is structural instead — no separators, no control\n * characters, not a relative marker — and then *confirmed* by resolving the path and requiring\n * its parent to be this session's directory exactly. That last step is what actually contains\n * the read: it also rejects the shapes a character class misses, such as a Windows\n * drive-relative `C:evil.png`.\n */\nfunction resolveScratchpadFile(dir: string, fileName: string): string | null {\n if (!fileName || fileName === \".\" || fileName === \"..\") return null;\n for (const ch of fileName) {\n const code = ch.codePointAt(0)!;\n if (code < 0x20 || code === 0x7f || ch === \"/\" || ch === \"\\\\\") return null;\n }\n const resolved = path.resolve(dir, fileName);\n return path.dirname(resolved) === path.resolve(dir) ? resolved : null;\n}\n\n/**\n * A validated Prompt: the message parts that go straight into the run, plus the file\n * attachments, which still have to be written to disk (see attachFilesToInput). Kept apart\n * because validation stays synchronous and side-effect free — nothing touches the filesystem\n * until the request is known to be good, and goal mode can reject files before any bytes land.\n */\ninterface ParsedTaskInput {\n messages: OmniMessage[];\n attachments: TaskAttachment[];\n}\n\n/** Validate Prompt input parts: text, image (data: / http(s) URL), or an uploaded file. */\nfunction parseTaskInput(body: Record<string, unknown>): ParsedTaskInput {\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 const messages: OmniMessage[] = [];\n const attachments: TaskAttachment[] = [];\n input.forEach((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 messages.push(userText(part.text));\n return;\n }\n if (part.type === \"image_url\") {\n messages.push(imageUrlMessage(requireImageUrl(part.imageUrl, `input[${i}].imageUrl`)));\n return;\n }\n if (part.type === \"file\") {\n // Not an OmniMessage of its own: the file becomes an `[attached file: …]` line on the\n // text message once written to the scratchpad, so it carries no payload into the run.\n attachments.push(parseAttachmentPart(part, i));\n // Per-request count / total-bytes caps, re-checked on every part so a hostile `input`\n // is cut off at the item that crosses the line (see assertAttachmentBudget).\n assertAttachmentBudget(attachments);\n return;\n }\n throw badRequest(`input[${i}].type must be one of text / image_url / file.`);\n });\n return { messages, attachments };\n}\n\n/**\n * Validate the optional `images` field of a steer request: a list of `data:` / http(s) URLs\n * (same rule as a task input's `imageUrl`), absent or empty = a text-only steering message.\n */\nfunction parseSteerImages(body: Record<string, unknown>): string[] {\n const images = body.images;\n if (images === undefined) return [];\n if (!Array.isArray(images)) throw badRequest(\"images must be an array.\");\n return images.map((url, i) => requireImageUrl(url, `images[${i}]`));\n}\n\n/**\n * Validate the optional `files` field of a steer request: the same shape and caps as a task\n * input's `{type:\"file\"}` parts (parseAttachmentPart, budget re-checked per item), absent or\n * empty = no attachments.\n */\nfunction parseSteerFiles(body: Record<string, unknown>): TaskAttachment[] {\n const files = body.files;\n if (files === undefined) return [];\n if (!Array.isArray(files)) throw badRequest(\"files must be an array.\");\n const attachments: TaskAttachment[] = [];\n files.forEach((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`files[${i}] must be an object.`);\n }\n attachments.push(parseAttachmentPart(item as Record<string, unknown>, i, \"files\"));\n assertAttachmentBudget(attachments);\n });\n return attachments;\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 // `cli=1` widens the list to CLI-created Sessions (Trace-directory discovery + adoption);\n // the default serves web rows straight from the DB (see SessionService.listSessions).\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 rawCli = c.req.query(\"cli\");\n if (rawCli !== undefined && rawCli !== \"1\") throw badRequest(\"cli 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 ...(rawCli !== undefined ? { includeCli: 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 (input images saved to disk for image-unsupported models, the\n // composer's file attachments, model-generated temp files): read by filename, so the\n // conversation UI can render a message's \"[attached image: <path>]\" attachment line back\n // into an image. Restricted to this session's own scratchpad directory (see\n // resolveScratchpadFile); a name is never reused for different bytes — uploads take a random\n // suffix on collision — 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 const filePath = resolveScratchpadFile(\n path.join(scratchpadDir(deps.config.root, row.projectId, row.agentId), row.sessionId),\n fileName,\n );\n if (!filePath) throw new HttpError(404, \"file_not_found\", \"File does not exist.\");\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 // SECURITY BOUNDARY — do not extend casually. This map is an allowlist of types that are\n // safe to hand a browser inline from the App's own origin, and it is the only reason the\n // bytes below (arbitrary user uploads and Agent-written temp files) cannot become stored\n // XSS. Every image type here is inert when rendered. Adding `.svg`, `.html`, `.pdf` or\n // anything else that a browser parses as a document would look like a one-line convenience\n // and would immediately be same-origin script execution — such a type needs the treatment\n // the Workspace read gives it (plain-text downgrade or a sandbox CSP), not a map entry.\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()];\n return c.body(new Uint8Array(bytes), 200, {\n \"content-type\": mime ?? \"application/octet-stream\",\n // nosniff: the composer's file attachments land in this same directory, so the bytes\n // here are arbitrary user content served from the App's own origin — without it a\n // browser could sniff an `application/octet-stream` upload back into HTML and run it\n // same-origin (the same defense workspace file reads apply).\n \"x-content-type-options\": \"nosniff\",\n // Second, independent layer for everything that fell off the allowlist: the only reason\n // this endpoint is fetched inline is the conversation's <img> tags, so anything that is\n // not one of those images is served as a download and never renders as a document —\n // nosniff alone would be the whole defense otherwise.\n ...(mime === undefined\n ? {\n \"content-disposition\": `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n }\n : {}),\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 pendingSteering = deps.manager.pendingSteeringOf(row.sessionId);\n const initialEvents: ServerEvent[] = [\n {\n type: \"task_state\",\n state: deps.manager.statusOf(row.sessionId),\n queued: deps.manager.pendingFollowUpCount(row.sessionId),\n // Undelivered steering rides the snapshot too, so the composer's \"steering queued\"\n // hint (and what it says) survives a reload.\n ...(pendingSteering.length > 0 ? { pendingSteering } : {}),\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 needs non-empty text, since its marker-stripped text becomes the\n // objective that every round re-injects and an image on its own doesn't say what the\n // goal is. Images can come along — core folds them into `[attached image: <path>]` lines\n // inside the objective (whatever the model's vision) so they survive the rounds. File\n // attachments cannot: nothing folds them into the objective, so they are turned away\n // here, before any upload is written to disk.\n const { messages, attachments } = parseTaskInput(body);\n const text = messages\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) {\n throw badRequest(\"goal mode requires a non-empty text objective.\");\n }\n if (attachments.length > 0) {\n throw badRequest(\"goal mode accepts text and images only (no file attachments).\");\n }\n const { sessionId } = await deps.manager.startGoal(row.sessionId, {\n input: messages,\n budget: goal.budget,\n ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),\n });\n return c.json({ sessionId } satisfies TaskCreateResponse, 202);\n }\n const parsed = 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 // Advisory pre-check, so the overwhelmingly common rejection — sending while a Task is\n // running, without queueIfBusy — never writes bytes it would then have to take back. The\n // authoritative check still runs under the Session lock inside startTask; this one is\n // lock-free and may pass on a race, which the cleanup below covers.\n deps.manager.assertCanAcceptTask(row.sessionId, { queueIfBusy });\n // File attachments land in this Session's scratchpad (deleted along with the Session) and\n // are handed to the model as `[attached file: <path>]` lines on the message text. Written\n // even when the task ends up queued as a follow-up: the queued input must be complete, and\n // the queue is drained by this same Session. A Trace-less Session that self-heals into a\n // new id below keeps its files under the id they were written with — the paths in the\n // message stay valid; only the delete-with-the-Session cleanup misses them in that case.\n const { input, written } = await attachFilesToInput(\n parsed.messages,\n parsed.attachments,\n scratchpadDir(deps.config.root, row.projectId, row.agentId),\n row.sessionId,\n );\n try {\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 } catch (err) {\n // The Task never started, so nothing references these files and nothing will ever clean\n // them up — and the Web keeps the chips on failure, so the user's retry would otherwise\n // land a second copy of every one of them.\n await removeAttachments(written);\n throw err;\n }\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, with any images following it as\n // user image messages (the model sees the whole thing without the loop being interrupted).\n // 409 not_running when no Task is in progress — the frontend then falls back to a normal\n // 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 const images = parseSteerImages(body);\n const files = parseSteerFiles(body);\n // Any part can carry the message on its own: an image or a file with no caption is a\n // complete steering message, and so is plain text.\n if (!text && images.length === 0 && files.length === 0) {\n throw badRequest(\"text, images or files must carry the steering message.\");\n }\n // The wire shape becomes core's: a user text message (omitted when the images are the\n // whole message, so the fold's path lines aren't preceded by a blank one) plus one image\n // message each — the same input a normal task would carry. File attachments land in the\n // Session scratchpad exactly as a task's do and ride as `[attached file: <path>]` lines\n // on the steering text (a files-only input becomes a line-only text message).\n const { input, written } = await attachFilesToInput(\n [...(text ? [userText(text)] : []), ...images.map((url) => imageUrlMessage(url))],\n files,\n scratchpadDir(deps.config.root, row.projectId, row.agentId),\n row.sessionId,\n );\n try {\n deps.manager.steer(row.sessionId, input, {\n text,\n images: images.length,\n files: files.length,\n });\n } catch (err) {\n // 409 (not running) or any other refusal: the files must not stay behind — the\n // frontend falls back to a normal task POST, which writes its own copies.\n await removeAttachments(written);\n throw err;\n }\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 { WorkspaceFileEntry, 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 /** True when a bounded preview returned only the beginning of the file. */\n truncated?: boolean;\n}\n\nexport interface WorkspaceFileReadOptions {\n /** Return at most this many bytes. Used for bounded text previews. */\n maxBytes?: number;\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 /**\n * List a directory: dirs come first, each group sorted by name. Entries whose\n * canonical target leaves the Workspace are omitted, so listing cannot expose\n * metadata for an out-of-bounds symlink.\n */\n async list(workspace: string, rel: string): Promise<WorkspaceFilesResponse> {\n const dir = await this.resolveRead(workspace, rel);\n const realBase = await this.realBase(workspace);\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 listed = await Promise.all(\n dirents.map(async (d): Promise<WorkspaceFileEntry | null> => {\n try {\n const canonical = await fs.realpath(path.join(dir, d.name));\n this.assertInside(canonical, realBase);\n const stat = await fs.stat(canonical);\n return {\n name: d.name,\n kind: stat.isDirectory() ? \"dir\" : \"file\",\n sizeBytes: stat.size,\n mtime: stat.mtime.toISOString(),\n };\n } catch {\n return null;\n }\n }),\n );\n const entries = listed.filter((entry): entry is WorkspaceFileEntry => entry !== null);\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(\n workspace: string,\n rel: string,\n options?: WorkspaceFileReadOptions,\n ): 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 const maxBytes = options?.maxBytes;\n if (\n maxBytes !== undefined &&\n (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > MAX_READ_BYTES)\n ) {\n throw badRequest(\"maxBytes must be a positive integer within the read limit.\");\n }\n if (maxBytes === undefined && stat.size > MAX_READ_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"File exceeds the 50MB read limit.\");\n }\n let data: Buffer;\n let truncated = false;\n if (maxBytes !== undefined && stat.size > maxBytes) {\n const handle = await fs.open(file, \"r\");\n try {\n const buffer = Buffer.alloc(maxBytes);\n const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);\n data = buffer.subarray(0, bytesRead);\n truncated = true;\n } finally {\n await handle.close();\n }\n } else {\n data = await fs.readFile(file);\n }\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 ...(truncated ? { truncated: true } : {}),\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 * Composer file attachments — the `{type:\"file\"}` variant of TaskInputPart.\n *\n * The browser has no Session while a chat is still a draft, so there is no upload endpoint to\n * call before sending: the file rides the task request itself as a base64 `data:` URL, exactly\n * like a pasted image does. On the way in it is written to the **Session scratchpad**\n * (`<agent>/scratchpad/<sessionId>/`, deleted with the Session, so cleanup is free) and the\n * message text gains one `[attached file: <absolute path>]` line per file — the bytes never\n * enter the conversation, the model opens the file by path with its ordinary file tools.\n *\n * The line format and its placement are not defined here: they are shared with core's\n * `[attached image: …]` producer and the Web renderer that parses both\n * (`@prismshadow/penguin-core/markers` → attachment-lines.ts, plus `appendAttachmentLines`),\n * so the two conventions cannot drift apart.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n appendAttachmentLines,\n attachedFileLine,\n modelVisiblePath,\n} from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\n\n/**\n * Per-file cap. Deliberately below the workspace upload's 14MB: several attachments can ride\n * one task request, and the whole body still has to fit the global 20MB limit (base64 inflates\n * by 4/3), so a smaller per-file ceiling keeps \"one big file\" working while leaving room for\n * \"a handful of ordinary ones\". Oversize is a 413, matching the workspace upload route.\n */\nexport const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;\n\n/**\n * Per-request caps, checked while the parts are validated — before a single byte reaches the\n * disk. They are deliberately NOT delegated to the global body cap: this module has to hold on\n * its own, so that a change to that middleware (or a caller that never goes through it) cannot\n * silently unbound it. Without them a legal 20MB body fits ~350k minimal `file` parts, which is\n * 350k sequential writes into one directory and 350k marker lines on one message.\n *\n * 20 files is far past any plausible composer use — the chip row stops being usable long before\n * that — while still allowing \"drop a folder of small files in\". 12MB of decoded bytes keeps one\n * full-size 10MB attachment usable next to a couple of ordinary ones; base64 inflates by 4/3, so\n * 12MB decoded is ~16MB of body and this cap, not the 20MB body cap, is the one a caller\n * actually reaches.\n */\nexport const MAX_ATTACHMENT_COUNT = 20;\nexport const MAX_TOTAL_ATTACHMENT_BYTES = 12 * 1024 * 1024;\n\n/**\n * Longest stem kept on disk, measured in **UTF-8 bytes**: filesystems cap a name near 255\n * bytes, and a CJK character costs three of them — a character count would let a Chinese name\n * blow the real limit while an English one stayed far under it.\n */\nconst MAX_STEM_BYTES = 80;\n\n/** Windows reserves these device names with or without an extension (`con`, `con.txt`), case-insensitively. */\nconst WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;\n\n/** Format and control characters (Unicode category C) — invisible, and the vector behind right-to-left file-name spoofing. */\nconst INVISIBLE_CHAR = /\\p{C}/u;\n\n/**\n * True when a character must not reach a file name. ASCII keeps the long-standing whitelist:\n * a space or a shell metacharacter inside a path the model is about to paste into a command is\n * a footgun, so anything outside `[A-Za-z0-9._-]` still becomes `-` down there. Above ASCII the\n * rule inverts — the character is kept as typed, so `报告.pdf` reaches the model as `报告.pdf`\n * instead of collapsing to an anonymous `file.pdf` (CJK, accents and emoji are all harmless to\n * a shell). The exception is Unicode category C: invisible controls, and the bidi overrides\n * that let a name render as something it is not.\n */\nfunction unsafeNameChar(ch: string): boolean {\n if (/[A-Za-z0-9._-]/.test(ch)) return false;\n return ch.codePointAt(0)! < 0x80 || INVISIBLE_CHAR.test(ch);\n}\n\n/** Replace every unsafe character with `-`, iterating code points so a surrogate pair survives intact. */\nfunction sanitizeSegment(value: string): string {\n return Array.from(value, (ch) => (unsafeNameChar(ch) ? \"-\" : ch)).join(\"\");\n}\n\n/** Truncate to a UTF-8 byte budget on character boundaries (iterating a string yields whole code points, so a surrogate pair is never split). */\nfunction truncateBytes(value: string, maxBytes: number): string {\n if (Buffer.byteLength(value) <= maxBytes) return value;\n let out = \"\";\n for (const ch of value) {\n if (Buffer.byteLength(out) + Buffer.byteLength(ch) > maxBytes) break;\n out += ch;\n }\n return out;\n}\n\n/** The 6-hex space makes a second collision negligible; the cap only guards against a filesystem stuck on EEXIST. */\nconst MAX_NAME_ATTEMPTS = 16;\n\n/** One validated attachment, bytes already decoded (they are held in memory only until the write below). */\nexport interface TaskAttachment {\n /** Original file name as submitted (validated: non-empty, no path separators, no `..`). */\n fileName: string;\n bytes: Buffer;\n}\n\n/**\n * Validate one `{type:\"file\"}` input part. Shape problems are 400s in the same style as the\n * neighbouring text/image checks; only the size cap answers 413 (`file_too_large`, the code\n * the Web App already has copy for). `index` is the part's position in the request array\n * named by `field` (default `input`; the steer route passes `files`), so the message points\n * at the offending item like the other input errors do.\n */\nexport function parseAttachmentPart(\n part: Record<string, unknown>,\n index: number,\n field = \"input\",\n): TaskAttachment {\n const fileName = part.fileName;\n // Path separators and `..` are rejected rather than sanitized away: the name is the user's,\n // and a name that looks like a path means the caller is confused about the contract (the\n // write below composes the path itself, and sanitization happens there).\n if (\n typeof fileName !== \"string\" ||\n fileName.length === 0 ||\n fileName.includes(\"/\") ||\n fileName.includes(\"\\\\\") ||\n fileName.includes(\"..\") ||\n fileName.includes(\"\\0\")\n ) {\n throw badRequest(\n `${field}[${index}].fileName must be a non-empty file name without path separators or \"..\".`,\n );\n }\n const dataUrl = part.dataUrl;\n // `[^,]*` for the media type, not `[^;,]*`: a browser may hand out parameters\n // (`data:text/plain;charset=utf-8;base64,…`), and only the `;base64,` marker separates the\n // type from the payload. The payload's character class is the actual check that it IS\n // base64 (whitespace tolerated — line-wrapped encoders decode fine).\n const match =\n typeof dataUrl === \"string\" ? /^data:[^,]*;base64,([A-Za-z0-9+/=\\s]+)$/.exec(dataUrl) : null;\n if (!match) {\n throw badRequest(`${field}[${index}].dataUrl must be a base64 data: URL of the file's bytes.`);\n }\n const bytes = Buffer.from(match[1]!, \"base64\");\n if (bytes.length === 0) {\n throw badRequest(`${field}[${index}].dataUrl decodes to an empty file.`);\n }\n if (bytes.length > MAX_ATTACHMENT_BYTES) {\n throw new HttpError(\n 413,\n \"file_too_large\",\n `Attached file exceeds the ${MAX_ATTACHMENT_BYTES / (1024 * 1024)}MB limit.`,\n );\n }\n return { fileName, bytes };\n}\n\n/**\n * Enforce the per-request caps against everything accepted so far. Called after **each** `file`\n * part rather than once at the end, so an oversized `input` stops at the part that crosses the\n * line instead of base64-decoding the whole array first. Both answer 413: the count reuses a\n * dedicated `too_many_files` code, the aggregate the `payload_too_large` the body cap already\n * uses — from the caller's side it is the same \"this request is too big\" outcome.\n */\nexport function assertAttachmentBudget(attachments: TaskAttachment[]): void {\n if (attachments.length > MAX_ATTACHMENT_COUNT) {\n throw new HttpError(\n 413,\n \"too_many_files\",\n `A message may carry at most ${MAX_ATTACHMENT_COUNT} attached files.`,\n );\n }\n let total = 0;\n for (const a of attachments) total += a.bytes.length;\n if (total > MAX_TOTAL_ATTACHMENT_BYTES) {\n throw new HttpError(\n 413,\n \"payload_too_large\",\n `Attached files exceed the ${MAX_TOTAL_ATTACHMENT_BYTES / (1024 * 1024)}MB total limit for one message.`,\n );\n }\n}\n\n/**\n * Map a submitted name onto a name that is safe on disk **and** still recognizably the user's\n * own: `报告 2026.pdf` becomes `报告-2026.pdf` (see unsafeNameChar — the words survive, only the\n * shell-hostile ASCII is replaced), so the model reads a meaningful path and a person looking\n * at the message recognizes what they attached.\n *\n * The rest is Windows-shaped hygiene: trailing dots and spaces are dropped (Windows silently\n * strips them, so `a.` and `a` would be the same file), a reserved device name is prefixed\n * (`con.txt` → `_con.txt`), the stem is capped by UTF-8 bytes, and a stem that sanitizes away\n * entirely falls back to `file` rather than producing a bare extension.\n */\nfunction scratchpadName(fileName: string): string {\n const ext = sanitizeSegment(path.extname(fileName));\n const rawStem = fileName.slice(0, fileName.length - path.extname(fileName).length);\n // Trim after truncating: a cut can expose a trailing dot or space that was mid-name before.\n const stem = truncateBytes(sanitizeSegment(rawStem), MAX_STEM_BYTES).replace(/[. ]+$/, \"\");\n // Nothing but replacement dashes carries no more information than an empty stem did.\n if (!stem || /^-+$/.test(stem)) return `file${ext}`;\n return WINDOWS_RESERVED.test(stem) ? `_${stem}${ext}` : `${stem}${ext}`;\n}\n\n/**\n * Write one attachment into `dir` and return its absolute path. The plain sanitized name is\n * tried first (the model — and the user reading the message — sees `report.pdf`, not an opaque\n * id); \"wx\" makes the create exclusive, so a second upload of the same name lands next to the\n * first as `report-3f9a1c.pdf` instead of overwriting it (same convention as core's image\n * uploads).\n *\n * \"wx\" is O_CREAT|O_EXCL, which also refuses to follow a symlink at the final component: a link\n * planted at `report.pdf` fails with EEXIST and the retry allocates a suffixed name instead of\n * writing through it. The containment check for the *directory* is separate — see openScratchpadDir.\n */\nasync function writeAttachment(dir: string, attachment: TaskAttachment): Promise<string> {\n const base = scratchpadName(attachment.fileName);\n const ext = path.extname(base);\n const stem = base.slice(0, base.length - ext.length);\n for (let attempt = 0; attempt < MAX_NAME_ATTEMPTS; attempt++) {\n const name = attempt === 0 ? base : `${stem}-${randomBytes(3).toString(\"hex\")}${ext}`;\n const file = path.join(dir, name);\n try {\n await fs.writeFile(file, attachment.bytes, { flag: \"wx\" });\n return file;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n }\n }\n throw new Error(\n `failed to allocate a unique attachment file name under ${dir} after ${MAX_NAME_ATTEMPTS} attempts`,\n );\n}\n\n/**\n * Create (or reuse) this Session's scratchpad directory and hand back the path to write into.\n *\n * `fs.mkdir(…, {recursive:true})` succeeds silently when the directory is already a **symlink**\n * to somewhere else, and nothing downstream would notice — so the result is realpath'd and\n * required to still sit inside the Agent's scratchpad root, the same containment rule the\n * Workspace upload path applies (workspace-files-service.resolveWriteParent). Only the Agent\n * process can plant such a link and it runs as this server's uid, so this is consistency rather\n * than a privilege boundary; it costs one resolution per message that carries attachments.\n *\n * The check is on the canonical path but the write stays on the logical one: the path travels\n * into the message text, and the read/delete endpoints address a Session by its logical\n * directory, so canonicalizing here would only make those disagree on hosts where the data root\n * itself sits behind a link (macOS `/var`, a Windows 8.3 temp path).\n */\nasync function openScratchpadDir(root: string, sessionId: string): Promise<string> {\n const dir = path.join(root, sessionId);\n await fs.mkdir(dir, { recursive: true });\n const canonicalRoot = await fs.realpath(root);\n const rel = path.relative(canonicalRoot, await fs.realpath(dir));\n if (rel !== sessionId) {\n throw new Error(\n `session scratchpad ${dir} resolves outside the agent scratchpad root; refusing to write attachments`,\n );\n }\n return dir;\n}\n\n/** Best-effort undo of a batch of writes; errors are swallowed because every caller is already on an error path (a failed cleanup must not replace the original failure). */\nexport async function removeAttachments(files: string[]): Promise<void> {\n await Promise.all(files.map((f) => fs.rm(f, { force: true }).catch(() => {})));\n}\n\n/** Result of a write batch: the Prompt to run, plus the paths written so the caller can undo them if the Task never starts. */\nexport interface AttachedFiles {\n input: OmniMessage[];\n written: string[];\n}\n\n/**\n * Land every attachment in the Session scratchpad under `root` and return the Prompt with one\n * `[attached file: <path>]` line appended per file. Placement follows core's shared rule\n * (after the last user text message; attachments-only input becomes a line-only text\n * message), so a Prompt carrying both images and files still ends in a single trailing block.\n * Returns `messages` untouched when there is nothing to attach — no directory is created.\n *\n * All-or-nothing: a failure part-way through the batch removes what it already wrote, so a 500\n * never leaves files on disk that no message refers to. The caller owns the other half of that\n * guarantee — if starting the Task fails afterwards it must call removeAttachments(written),\n * otherwise the user's retry would land a second copy of every file.\n */\nexport async function attachFilesToInput(\n messages: OmniMessage[],\n attachments: TaskAttachment[],\n root: string,\n sessionId: string,\n): Promise<AttachedFiles> {\n if (attachments.length === 0) return { input: messages, written: [] };\n const dir = await openScratchpadDir(root, sessionId);\n const written: string[] = [];\n try {\n // Sequential on purpose: the exclusive-create retry above resolves collisions against files\n // that already exist, and writing the batch one at a time keeps two same-named uploads in\n // the same message from racing each other for the plain name.\n for (const attachment of attachments) {\n written.push(await writeAttachment(dir, attachment));\n }\n } catch (err) {\n await removeAttachments(written);\n throw err;\n }\n return {\n input: appendAttachmentLines(\n messages,\n written.map((filePath) => attachedFileLine(modelVisiblePath(filePath))),\n ),\n written,\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`, and an LLM `failed` the engine went on to retry (the engine\n * reconnects on all three, so a request the ladder carried cost the user nothing); tool\n * execution `failed` / `timeout` (the error is fed back to the model, and the Agent\n * adjusts on 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 `auth` (the credential was rejected and only\n * a human can replace it) and an LLM `failed` the retries did not recover (the run ended\n * on it and the user lost the turn).\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 parseUserSteeringText,\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 { PendingSteeringInfo, 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 input (core `Session.steer`); false when no Task is running. */\n steer(input: OmniMessage[]): 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 /**\n * Steering messages queued on core but not yet delivered to the model — a display mirror\n * of core's steering queue (same FIFO order), so the composer's \"steering queued\" hint and\n * its content survive reloads: pushed on `steer`, shifted as each `[user_steering]`\n * message appears on the drive stream, dropped wholesale when the run exits (core\n * discards undelivered steering on abort/completion). Broadcast on every `task_state`.\n */\n pendingSteering: PendingSteeringInfo[];\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. */\n/** Whether this main-stream message is a delivered `[user_steering]` user text (one per queued steering entry, see core's steeringMessages). */\nfunction isDeliveredSteering(msg: OmniMessage): boolean {\n const p = msg.payload as { type?: string; role?: string; text?: string };\n if (msg.type !== \"model_msg\" || p.type !== \"text\" || p.role !== \"user\") return false;\n return typeof p.text === \"string\" && parseUserSteeringText(p.text) !== null;\n}\n\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 /** Steering messages queued but not yet delivered to the model (display mirror; see RuntimeEntry.pendingSteering). */\n pendingSteeringOf(sessionId: string): PendingSteeringInfo[] {\n return this.entries.get(sessionId)?.pendingSteering ?? [];\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 pendingSteering: [],\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 * Cheap, lock-free rehearsal of the 409/503 conditions startTask checks, throwing exactly the\n * same HttpErrors. **Advisory only**: it neither takes the Session lock nor loads an entry, so\n * a session that isn't in the active table reads as acceptable and a status change racing this\n * call is not caught — the authoritative check is still the one inside startTask.\n *\n * It exists so a caller that has irreversible work to do first (POST /tasks writes the\n * message's file attachments to disk) can find out about the ordinary \"a Task is already\n * running\" rejection before doing it, instead of undoing it afterwards.\n */\n assertCanAcceptTask(sessionId: string, opts?: { queueIfBusy?: boolean }): void {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = this.entries.get(sessionId);\n if (entry && !opts?.queueIfBusy) this.assertIdle(entry);\n }\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 (route-validated to carry text; images may ride along); 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 // `isPlainText` leaves attached images out, so this copy carries no\n // `[attached image: <path>]` lines — which suits its readers, since a status card and a\n // generated title read better without absolute scratchpad paths. Core keeps its own\n // folded copy (Session.runGoal) for what the rounds actually re-inject.\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 message to the running Session (core delivers it between\n * turns as a standalone `[user_steering]` user message followed by its images — no SSE\n * event of its own; the messages arrive through the stream the drive loop already\n * publishes). 409 when the Session isn't running a Task (idle / compacting / not loaded)\n * or the run finished in the race window — the caller falls back to submitting a normal\n * task, which carries the same text and images.\n *\n * `info` is the queued message's display summary: mirrored on the entry and broadcast via\n * `task_state` (and the SSE subscribe snapshot) until the delivered `[user_steering]`\n * message is observed on the stream — that is what keeps the composer's \"steering queued\"\n * hint, content included, alive across reloads.\n */\n steer(sessionId: string, input: OmniMessage[], info: PendingSteeringInfo): void {\n const entry = this.entries.get(sessionId);\n if (!entry || entry.status !== \"running\" || !entry.session.steer(input)) {\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.pendingSteering.push(info);\n entry.lastActivityMs = Date.now();\n this.publishState(entry, entry.status);\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 pendingSteering: [],\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 // Every driven run (task, goal round, compaction) writes Trace lines: flip the row's\n // has_trace cache here, the single choke point, so listing can serve it from the DB\n // without a directory walk (see SessionService.listSessions).\n this.deps.sessions.markHasTrace(entry.sessionId);\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 // Steering delivery: core emits exactly one `[user_steering]` user text per queued\n // entry, in queue order — shift the display mirror and re-broadcast so the\n // composer's \"steering queued\" hint retires the moment the message is on stream.\n if (entry.pendingSteering.length > 0 && isDeliveredSteering(msg)) {\n entry.pendingSteering.shift();\n this.publishState(entry, entry.status);\n }\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 // The run is over, so core has discarded any undelivered steering (see ContextEngine's\n // steeringQueue) — drop the mirror with it; the idle publish below broadcasts the\n // now-empty state.\n entry.pendingSteering = [];\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 // Spawned by this server's run (client NULL = web); its Trace exists by construction.\n hasTrace: true,\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 and the undelivered steering\n // mirror, so subscribers can render both hints without a dedicated event type.\n this.publishEvent(entry, {\n type: \"task_state\",\n state,\n queued: entry.followUps.length,\n ...(entry.pendingSteering.length > 0 ? { pendingSteering: entry.pendingSteering } : {}),\n });\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 * - `auth` → unexpected (`llm_auth`): the credential was rejected, the engine never retries\n * it, and only a human holding the key can fix it. Its own code rather than the failed\n * bucket, because dedup is `(source, code, Project)` over a short window: sharing a code\n * with a class that now fires on every recovered blip would let a real credential failure\n * be swallowed as a duplicate of something already handled.\n * - `failed` → depends on whether the retry ladder carried it. The engine reconnects on\n * `failed` too, so the same status covers both \"a gateway hiccup nobody needed to know\n * about\" and \"the run died on it\":\n * - another attempt followed (a `request_begin` resolved the pending record) → expected\n * (`llm_failed_retried`), same footing as timeout/malformed: recorded for the record,\n * not raised at an operator;\n * - nothing followed but the end of the run (an `abort`, or close) → unexpected\n * (`llm_failed`): the retries did not recover it, the user lost the turn, 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` 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. This boundary is\n * also what tells a survived `failed` from an exhausted one (see the classification above);\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). Exactly one shape is\n * dropped: a command tool's ordinary non-zero exit, which is how a shell command reports\n * information rather than a fault — see isOrdinaryCommandExit; the same tools' environment\n * faults (killed by a signal, spawn failure, tool timeout) still record.\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/** Error code, classification, and fallback message (the last used when no abort reason is available). */\ninterface FailureSpec {\n code: string;\n kind: ErrorKind;\n text: string;\n}\n\n/** LLM failure state → its spec, for a failure the retry ladder did NOT carry (see the file header). */\nconst LLM_FAILURES: Record<LlmFailure, FailureSpec> = {\n // Reached here only when no further attempt followed: the ladder ran out (or the run ended\n // on it), so the user lost the turn and someone should look. A `failed` that was retried\n // takes LLM_FAILED_RETRIED instead.\n failed: {\n code: \"llm_failed\",\n kind: \"unexpected\",\n text: \"LLM request failed and the retries did not recover it.\",\n },\n // Its own code, not the failed bucket: dedup is `(source, code, Project)` over a short\n // window, and `llm_failed_retried` can now fire on any recovered blip — sharing a bucket\n // would let a genuine credential failure be dropped as a duplicate of one.\n auth: {\n code: \"llm_auth\",\n kind: \"unexpected\",\n text: \"LLM request rejected: the provider did not accept the credentials.\",\n },\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/**\n * A `failed` the engine went on to retry: expected, exactly like timeout/malformed — the\n * engine's defined handling path absorbed it and the user never lost anything, so it belongs\n * in the record but not in an operator's queue. Its own code so the exhausted case keeps\n * `llm_failed` to itself and neither can dedup the other away.\n */\nconst LLM_FAILED_RETRIED: FailureSpec = {\n code: \"llm_failed_retried\",\n kind: \"expected\",\n text: \"LLM request failed (the engine reconnects and retries).\",\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/**\n * The command tools. Both funnel their exit status through core's `resultForExit`, which maps\n * *any* non-zero exit to `failed` — and a non-zero exit is how shell commands return\n * information, not a fault: `grep` exits 1 when nothing matches, `test -f` when the file is\n * absent, `diff` when files differ. Recording those made the cost center's error table mostly a\n * log of ordinary Agent work, burying the real errors and eating the row cap that exists to\n * protect them. (Only the row cap: the recorder's dedup key carries the error code, so this\n * noise could ever have crowded out only *itself*.) The Agent sees the failure in the tool\n * output and adjusts — nothing about it needs a human.\n *\n * Both belong here: `input_command` is how a backgrounded `exec_command` is polled for its\n * eventual exit, so covering only one would record the *same* command's non-zero exit when it\n * finishes in the background and drop it when it finishes in the foreground.\n */\nconst COMMAND_TOOLS: ReadonlySet<string> = new Set([\"exec_command\", \"input_command\"]);\n\n/**\n * `resultForExit`'s terminal marker for an ordinary non-zero exit. Anchored to the end because\n * the marker is a `note`, which Environment appends after the (possibly truncated) output — a\n * command's own stdout may contain anything, including this text.\n */\nconst ORDINARY_EXIT_NOTE = /\\[exit code: [^\\]\\n]*\\]\\s*$/;\n\n/**\n * Whether a command-tool failure is just an ordinary non-zero exit — the one failure shape the\n * error table is better off without (see COMMAND_TOOLS).\n *\n * The discrimination is by note rather than by tool name, because `failed` from these tools is\n * not only \"the command exited non-zero\": it also covers termination by signal (an OOM kill, a\n * segfault), a spawn failure (nonexistent workdir, EMFILE, an unresolvable shell), a missing\n * command session manager, and a tool timeout. Those are config/environment faults — the\n * recorder's own \"needs a human\" category — and no amount of Agent self-correction resolves\n * them, so they must keep landing in the table. `resultForExit` writes the exit code as the\n * output's last note, which tells the two apart precisely.\n *\n * An unknown name (tool_call evicted from the cache, or never seen) still qualifies: only these\n * two tools ever emit that marker, so dropping it beats recording a nameless\n * `tool_failed:unknown` for exactly the noise this exists to remove.\n */\nfunction isOrdinaryCommandExit(name: string | undefined, output: string): boolean {\n if (name !== undefined && !COMMAND_TOOLS.has(name)) return false;\n return ORDINARY_EXIT_NOTE.test(output);\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\n // previous failure, persist using the status text — and this is the proof the ladder\n // carried it, which is what downgrades a `failed` from unexpected to expected.\n if (p.type === \"request_begin\") {\n this.flush(key, null, true);\n return;\n }\n // Interrupted/failed exit: reason is core's only failure-reason text. No attempt follows\n // an abort, so a `failed` resolved here is one the retries did not recover.\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, and `retried` says whether another attempt followed\n * (only a `request_begin` proves that). 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 * `retried` defaults to false so every other resolution — an abort, the defensive flush at\n * the next request_end, or close() at the end of the run — is read as \"nothing came after\n * this failure\". That is the conservative direction: at worst a recovered blip is escalated,\n * never a lost turn silently downgraded.\n */\n private flush(key: string, reason?: string | null, retried = false): void {\n const entry = this.pending.get(key);\n if (entry === undefined) return;\n this.pending.delete(key);\n const spec =\n retried && entry.status === \"failed\" ? LLM_FAILED_RETRIED : 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 const output = p.output ?? \"\";\n if (isOrdinaryCommandExit(name, output)) return; // a shell's exit status is information, not a fault\n this.errors.record({\n source: \"environment\",\n err: toolFailureText(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 /**\n * Rewrites the display name, preserving every other field (models, credentials, default\n * model): read-modify-write of the same toml, like ensurePresetModels. The id itself is\n * immutable — only this label changes.\n */\n async setName(projectId: string, name: string): Promise<void> {\n const raw = await this.readRaw(projectId);\n await this.writeRaw(projectId, { ...raw, name });\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, description, per-case run count `runs`) and\n * `scoreboard.yaml` (evaluations[], each case carries its model-written averages\n * and a runs array).\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 * Case and Evaluation averages are authoritative file values. The server validates\n * the current shape but never recomputes aggregates and does not migrate or backfill\n * old Scoreboard formats.\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 BenchmarkCaseSummary,\n BenchmarkCasesResponse,\n BenchmarkEvaluation,\n BenchmarkRunScore,\n BenchmarkSummary,\n BenchmarksResponse,\n CaseMaterial,\n WorkspaceFilesResponse,\n} from \"../api/types.js\";\nimport type {\n WorkspaceFileContent,\n WorkspaceFileReadOptions,\n WorkspaceFilesService,\n} from \"./workspace-files-service.js\";\nimport { HttpError } from \"../http/errors.js\";\n\nconst STATEMENT_TITLE_READ_BYTES = 64 * 1024;\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 scoreOr(v: unknown): number | undefined {\n const value = numberOr(v);\n return value !== undefined && value >= 0 && value <= 100 ? value : undefined;\n}\n\nfunction nonNegativeOr(v: unknown): number | undefined {\n const value = numberOr(v);\n return value !== undefined && value >= 0 ? value : undefined;\n}\n\nfunction nonNegativeIntegerOr(v: unknown): number | undefined {\n const value = nonNegativeOr(v);\n return value !== undefined && Number.isInteger(value) ? value : undefined;\n}\n\n/** `null` is the one valid unknown-cost representation; undefined means invalid input. */\nfunction nullableCostOr(v: unknown): number | null | undefined {\n if (v === null) return null;\n return nonNegativeOr(v);\n}\n\nfunction stringOr(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\nfunction isWithin(parent: string, child: string): boolean {\n const relative = path.relative(parent, child);\n return relative !== \"\" && !relative.startsWith(\"..\") && !path.isAbsolute(relative);\n}\n\nfunction statementTitle(statement: string, fallback: string): string {\n const heading = /^#\\s+(.+)$/m.exec(statement)?.[1]?.trim();\n return heading?.replace(/^Case\\s+\\d+\\s*:\\s*/i, \"\") || fallback;\n}\n\nasync function readStatementTitle(readme: string, fallback: string): Promise<string> {\n const handle = await fs.open(readme, \"r\");\n try {\n const buffer = Buffer.alloc(STATEMENT_TITLE_READ_BYTES);\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);\n return statementTitle(buffer.subarray(0, bytesRead).toString(\"utf8\"), fallback);\n } finally {\n await handle.close();\n }\n}\n\n/** Shapes one current-format Run; a malformed entry invalidates its containing Case. */\nfunction toRun(v: unknown): BenchmarkRunScore | null {\n const r = asRecord(v);\n const score = scoreOr(r.score);\n const cost = nullableCostOr(r.cost);\n const durationMs = nonNegativeIntegerOr(r.duration_ms);\n const sessionId = stringOr(r.session_id);\n if (score === undefined || cost === undefined || durationMs === undefined || !sessionId)\n return null;\n return { score, cost, durationMs, sessionId };\n}\n\n/**\n * Shapes one current-format Case. Its stored aggregates are trusted as written:\n * this parser intentionally performs no average or consistency calculation.\n */\nfunction toCase(v: unknown): BenchmarkCaseScore | null {\n const cr = asRecord(v);\n const caseId = stringOr(cr.case);\n const score = scoreOr(cr.score);\n const cost = nullableCostOr(cr.cost);\n const durationMs = nonNegativeIntegerOr(cr.duration_ms);\n if (\n !caseId ||\n score === undefined ||\n cost === undefined ||\n durationMs === undefined ||\n \"max_score\" in cr ||\n !Array.isArray(cr.runs) ||\n cr.runs.length === 0\n ) {\n return null;\n }\n const parsedRuns = cr.runs.map(toRun);\n if (parsedRuns.some((run) => run === null)) return null;\n const runs = parsedRuns as BenchmarkRunScore[];\n return {\n case: caseId,\n score,\n cost,\n durationMs,\n runs,\n };\n}\n\n/** Shapes one current-format Evaluation and trusts its stored aggregate metrics. */\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 = scoreOr(r.score);\n const cost = nullableCostOr(r.cost);\n const durationMs = nonNegativeIntegerOr(r.duration_ms);\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 const modelId = stringOr(r.model_id);\n const provider = stringOr(r.provider);\n const thinkingLevel = stringOr(r.thinking_level);\n const version = nonNegativeIntegerOr(r.version);\n if (\n typeof time !== \"string\" ||\n time === \"\" ||\n score === undefined ||\n cost === undefined ||\n durationMs === undefined ||\n !modelId ||\n !provider ||\n !thinkingLevel ||\n version === undefined ||\n version < 1 ||\n !Array.isArray(r.cases) ||\n r.cases.length === 0\n ) {\n return null;\n }\n const parsedCases = r.cases.map(toCase);\n if (parsedCases.some((item) => item === null)) return null;\n const cases = parsedCases as BenchmarkCaseScore[];\n return {\n time,\n ...(summaryTitle !== undefined ? { summaryTitle } : {}),\n ...(summary !== undefined ? { summary } : {}),\n modelId,\n provider,\n thinkingLevel,\n score,\n version,\n cost,\n durationMs,\n cases,\n };\n}\n\nexport class BenchmarkService {\n constructor(\n private readonly root: string,\n private readonly workspaceFiles: WorkspaceFilesService,\n ) {}\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 async listCases(\n projectId: string,\n agentId: string,\n benchmarkId: string,\n ): Promise<BenchmarkCasesResponse> {\n const baseDir = benchmarksDir(this.root, projectId, agentId);\n const benchDir = path.join(baseDir, benchmarkId);\n let entries: Array<{ name: string; isDirectory(): boolean }>;\n let realBaseDir: string;\n let realBenchDir: string;\n try {\n [entries, realBaseDir, realBenchDir] = await Promise.all([\n fs.readdir(benchDir, { withFileTypes: true }),\n fs.realpath(baseDir),\n fs.realpath(benchDir),\n ]);\n } catch {\n return { cases: [] };\n }\n if (!isWithin(realBaseDir, realBenchDir)) return { cases: [] };\n\n const cases: BenchmarkCaseSummary[] = [];\n for (const entry of entries\n .filter((item) => item.isDirectory() && item.name.startsWith(\"CASE-\"))\n .sort((a, b) => a.name.localeCompare(b.name))) {\n const fallback: BenchmarkCaseSummary = { id: entry.name, title: entry.name };\n try {\n const statementDir = await this.caseMaterialRoot(\n projectId,\n agentId,\n benchmarkId,\n entry.name,\n \"statement\",\n );\n const realReadme = await fs.realpath(path.join(statementDir, \"README.md\"));\n if (!isWithin(statementDir, realReadme)) throw new Error(\"README escapes Statement\");\n cases.push({\n id: entry.name,\n title: await readStatementTitle(realReadme, entry.name),\n });\n } catch {\n cases.push(fallback);\n }\n }\n return { cases };\n }\n\n async listCaseFiles(\n projectId: string,\n agentId: string,\n benchmarkId: string,\n caseId: string,\n rel: string,\n material: CaseMaterial,\n ): Promise<WorkspaceFilesResponse> {\n const materialRoot = await this.caseMaterialRoot(\n projectId,\n agentId,\n benchmarkId,\n caseId,\n material,\n );\n return this.workspaceFiles.list(materialRoot, rel);\n }\n\n async readCaseFile(\n projectId: string,\n agentId: string,\n benchmarkId: string,\n caseId: string,\n rel: string,\n material: CaseMaterial,\n options?: WorkspaceFileReadOptions,\n ): Promise<WorkspaceFileContent> {\n const materialRoot = await this.caseMaterialRoot(\n projectId,\n agentId,\n benchmarkId,\n caseId,\n material,\n );\n return this.workspaceFiles.read(materialRoot, rel, options);\n }\n\n private async caseMaterialRoot(\n projectId: string,\n agentId: string,\n benchmarkId: string,\n caseId: string,\n material: CaseMaterial,\n ): Promise<string> {\n const benchDir = path.join(benchmarksDir(this.root, projectId, agentId), benchmarkId);\n const caseDir = path.join(benchDir, caseId);\n const materialRoot = path.join(caseDir, material);\n try {\n const [realBenchDir, realCaseDir, realMaterialRoot] = await Promise.all([\n fs.realpath(benchDir),\n fs.realpath(caseDir),\n fs.realpath(materialRoot),\n ]);\n if (\n !isWithin(realBenchDir, realCaseDir) ||\n path.dirname(realMaterialRoot) !== realCaseDir ||\n path.basename(realMaterialRoot) !== material\n ) {\n throw new Error(\"Case material is not canonical\");\n }\n return realMaterialRoot;\n } catch {\n throw new HttpError(404, \"not_found\", `Case ${material} does not exist.`);\n }\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 semantic Case subfolders.\n let caseCount = 0;\n try {\n const entries = await fs.readdir(benchDir, { withFileTypes: true });\n caseCount = entries.filter((e) => e.isDirectory() && e.name.startsWith(\"CASE-\")).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 * Rename a Project's display name (owner): the id stays immutable — it names the directory,\n * the Workspace paths and every stored reference — so only the label in project_config.toml\n * changes. Returns the refreshed summary so the caller can swap it into its list without a\n * reload.\n */\n async renameProject(userId: string, projectId: string, name: string): Promise<ProjectSummary> {\n const row = this.requireProjectOwner(userId, projectId);\n await this.deps.projectConfig.setName(projectId, name);\n // requireProjectOwner already established the role; it returns the plain row.\n return {\n projectId,\n name,\n role: \"owner\",\n ownerUserId: row.ownerUserId,\n createdAt: row.createdAt,\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 default list is served from the DB index alone (web rows: `client` web/NULL) — no\n * Trace directory scanning in steady state (#139). `includeCli` widens it to DB ∪ Trace\n * directory discovery: scans `<agent>/traces/<date>/<session_id>_<index3>.jsonl`; an\n * unmanaged Session (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 marked\n * `client: \"cli\"` (approval_mode defaults, createdAt is taken from the timestamp embedded\n * in session_id) — the default list keeps excluding it, but it becomes individually\n * reachable (deep links).\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, sorted by createdAt descending. The default serves **web sessions straight from\n * the DB** (`client` web/NULL rows) with no Trace directory scanning — the answer to\n * many-session sidebar reloads re-walking the filesystem on every request (#139). One\n * lazy discovery walk still runs for a list call that contains rows this process has not\n * classified yet (no in-process source entry): it supplies the Trace locations for the\n * one-time head reads and backfills the `has_trace` cache; once every row is classified,\n * list calls touch only the DB. `includeCli` widens the list to DB ∪ Trace directory\n * discovery (adopting unmanaged CLI Traces as `client: \"cli\"` rows), which inherently\n * scans — that path is opt-in via the \"show CLI sessions\" preference.\n *\n * Optional `paging` returns just that slice (the sidebar pages with limit+1 to detect\n * \"has more\"); slicing happens before toInfo, so per-request source derivation (lazy\n * Trace-head reads) stays bounded by the page size.\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 includeCli?: boolean;\n } = {},\n ): Promise<{\n sessions: SessionInfo[];\n counts?: SessionCategoryCounts;\n workspaceCounts?: Record<string, SessionCategoryCounts>;\n }> {\n const { paging, category, withCounts, includeCli } = opts;\n const rows = new Map(\n this.deps.sessions\n .listByAgent(projectId, agentId, { webOnly: !includeCli })\n .map((r) => [r.sessionId, r]),\n );\n\n let traces: ReadonlyMap<string, TraceLocation> | undefined;\n if (includeCli) {\n traces = await this.discoverTraces(projectId, agentId);\n // Unmanaged Trace Sessions (the CLI's): backfill an index row by reading the first\n // line's session_meta, marked client \"cli\" so the default list can exclude them.\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 } else if ([...rows.values()].some((r) => this.deps.sources.get(r.sessionId) === undefined)) {\n // Hydration walk: some rows predate this process and are unclassified — locate their\n // Traces once so sourceOf's head reads (cached afterwards) and the has_trace cache\n // don't have to walk per row. Steady state (everything classified) skips this.\n traces = await this.discoverTraces(projectId, agentId);\n for (const row of rows.values()) {\n if (!row.hasTrace && traces.has(row.sessionId)) {\n row.hasTrace = true;\n this.deps.sessions.markHasTrace(row.sessionId);\n }\n }\n }\n\n const sorted = [...rows.values()].sort(\n (a, b) => b.createdAt.localeCompare(a.createdAt) || b.sessionId.localeCompare(a.sessionId),\n );\n const rowHasTrace = (row: SessionRow): boolean =>\n traces ? traces.has(row.sessionId) : row.hasTrace === true;\n const toPage = (page: SessionRow[]) =>\n Promise.all(page.map((row) => this.toInfo(row, rowHasTrace(row), 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, rowHasTrace(row), 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 // Everything created through this service is the Web App's (schedule runs included);\n // \"cli\" is stamped only by Trace adoption. NULL means a legacy row, treated as web.\n client: \"web\",\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 // Adopted = a Trace this server never created, i.e. the CLI's: the default list\n // (web-only) excludes these rows; the \"show CLI sessions\" preference includes them.\n client: \"cli\",\n hasTrace: true,\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 /** Inside the image run that follows a `[user_steering]` text (see the turn-start rule below). */\n let steeringImages = false;\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 // Images sent with a steering message ride immediately behind its text, exactly as a\n // Prompt's images ride behind theirs — and they inherit its exclusion: still the same\n // Task, so `steeringImages` keeps the window open across the whole run of them and\n // anything else on the main session closes it (an images-only Prompt after a steering\n // message is a genuine new turn). A subagent's messages pass through without closing it:\n // they belong to another session's stream and say nothing about this one's grouping.\n // The Web answers the same \"what is one Task\" question over the live stream — see\n // `openSteering` in web/src/lib/omni/stream-model.ts; the two need to stay in step.\n const isImage = !hasOrigin && msg.type === \"model_msg\" && p.type === \"image_url\";\n if (!hasOrigin && !isSteeringText && !(isImage && steeringImages)) steeringImages = false;\n if (isSteeringText) steeringImages = true;\n const startsUserTurn =\n !hasOrigin &&\n !compactionActive &&\n !isSteeringText &&\n !steeringImages &&\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 a retryable status (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 // A status core reconnects on within the same run (context-engine's retry\n // loop) leaves the resent Request in **the same user turn**: it must\n // continue the turn, otherwise a single blip would split that turn's\n // Tokens/duration/TPS across two Tasks and inflate the Task count.\n //\n // This list must track the engine's, and the engine's differs by loop:\n // the turn loop retries `failed` too, while compaction deliberately fails\n // fast and stops on it (core's TURN_RETRY_STATUSES vs\n // COMPACTION_RETRY_STATUSES). Hence the compactionActive guard — a failed\n // compaction request really is the end of that request, and counting it\n // as a reconnect would invent an attempt that never happened.\n const retryable =\n status === \"timeout\" ||\n status === \"malformed\" ||\n (status === \"failed\" && !compactionActive);\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 UsageErrorsPage,\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/**\n * Number of most-recent entries kept in the error detail table. Also the page size the whole\n * feature runs on, but nothing needs to hard-code it: `errors.recent` is exactly this many rows\n * whenever a second page exists, so the client derives its page size from the response instead\n * of holding a constant that could drift out of step with this one.\n */\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/** One page of the error detail table (see {@link UsageService.queryErrors}). */\nexport interface UsageErrorsQuery {\n offset: number;\n limit: number;\n from?: string;\n to?: string;\n agentId?: string;\n /** Admin only: include errors with no Project attribution (see the ErrorsRepo file header). */\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 /**\n * One page of the error detail table, newest first. The dashboard's own response already\n * carries the first page (`errors.recent`); this serves the \"show me earlier ones\" paging,\n * where refetching the whole aggregate to move one page would be wasteful. `total` is the\n * filtered row count, so the caller knows when it has reached the end.\n *\n * Takes the same filter the dashboard applies — date + agent, and admin-only visibility of\n * unattributed errors — so a page never widens what the summary above it counted.\n *\n * Paging is by offset into a newest-first table that grows at its head, so errors recorded\n * between two page requests shift every row down and a page can re-show rows already seen.\n * Accepted deliberately: this is a diagnostic table read at a moment in time, not a feed to\n * be walked exhaustively, and keying off the newest row's id would cost a cursor the caller\n * has no other use for.\n */\n queryErrors(projectId: string, q: UsageErrorsQuery): UsageErrorsPage {\n const f: ErrorFilter = {\n ...(q.agentId !== undefined ? { agentId: q.agentId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n ...(q.includeGlobalErrors === true ? { includeGlobal: true } : {}),\n };\n return {\n items: this.errors.recent(projectId, f, q.limit, q.offset),\n total: this.errors.summary(projectId, f).total,\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","fs","path","randomBytes","path","randomBytes","fs","path","deps","app","Hono","fs","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","path","fs","config","parseToml","parseYaml","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","parseUserSteeringText","readTraceTolerant","tracesDir","TRACE_FILE_RE","fs","tracesDir","path","agentsDir","readTraceTolerant","parseUserSteeringText","parseTraceLines","isSessionMeta","BUILD_DATE","VERSION","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/net/proxy.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/server-settings.ts","../src/db/repos/sessions.ts","../src/db/repos/trace-index.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/routes/desktop.ts","../src/http/validate.ts","../src/http/routes/admin-settings.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/chat-defaults.ts","../src/http/routes/sessions.ts","../src/services/message-window.ts","../src/services/workspace-guard.ts","../src/services/workspace-files-service.ts","../src/services/task-attachments.ts","../src/http/routes/vault.ts","../src/http/routes/memory.ts","../src/http/routes/schedules.ts","../src/runtime/schedule-file.ts","../src/runtime/schedule-store.ts","../src/internal/mtime-gate.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/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/desktop-service.ts","../src/services/agent-config-service.ts","../src/services/project-config-service.ts","../src/services/memory-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-index.ts","../src/internal/trace-head.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 fs from \"node:fs\";\nimport path from \"node:path\";\nimport { config as loadDotenv } from \"dotenv\";\nimport { serve } from \"@hono/node-server\";\nimport { buildAppDeps, createApp } from \"./app.js\";\nimport { ADMIN_USER_ID } from \"./auth/service.js\";\nimport { resolveServerConfig } from \"./config.js\";\nimport {\n clearInitialAdminPassword,\n readInitialAdminPassword,\n renderInitialPasswordNotice,\n storeInitialAdminPassword,\n} from \"./initial-password.js\";\nimport { applyProxySettings, installGlobalProxyDispatcher } from \"./net/proxy.js\";\nimport { loopbackHostRoles } from \"./services/preview-token.js\";\nimport { acquireServerLock, liveServerLock, releaseServerLock } from \"./lock.js\";\n\nloadDotenv({ quiet: true });\n\n// Outbound proxy support, installed at the earliest point (right after dotenv, which may\n// itself define HTTP_PROXY): replaces globalThis.fetch with undici's and sets the global\n// dispatcher, starting from the defaults (app switch on, no explicit address). The\n// persisted values can only be read once the database is open, so they are applied via\n// applyProxySettings right after buildAppDeps below — nothing in between makes an\n// outbound request. See net/proxy.ts.\ninstallGlobalProxyDispatcher();\n\n/** Exit code for \"another server already owns this data root\" (see lock.ts). */\nconst EXIT_ALREADY_RUNNING = 3;\n\nconst config = resolveServerConfig();\n\n// Single instance per data root: web.db is single-writer and the scheduler must not run\n// twice, so refuse to start when a live server already owns this root — BEFORE opening\n// the database. The CLI and the desktop shell pre-check the same lock for a friendlier\n// path (open / attach to the existing instance); this is the in-process backstop.\nconst existingLock = await liveServerLock(config.root);\nif (existingLock) {\n console.error(\n `Another PenguinHarness server is already running on this data root (pid ${existingLock.pid}).`,\n );\n console.error(`Existing instance: http://localhost:${existingLock.port}/`);\n process.exit(EXIT_ALREADY_RUNNING);\n}\n\nconst deps = buildAppDeps(config);\n// The database is open now: bring the dispatcher in line with the persisted proxy\n// settings (absent rows read as the defaults: app switch on, no explicit address)\n// before the first possible outbound request (update check, LLM calls — all behind\n// HTTP handlers).\napplyProxySettings({\n proxyForApp: deps.serverSettingsRepo.getProxyForApp(),\n proxyUrl: deps.serverSettingsRepo.getProxyUrl(),\n});\nconst app = createApp(deps);\n\n// Built-in admin seed (idempotent): creates admin and adopts default_project when the\n// users table is empty. The returned initial password (random unless pinned via\n// PENGUIN_SEED_ADMIN_PASSWORD) is persisted in the data root while it remains the\n// initial one, and EVERY startup re-prints the framed reminder until the password is\n// changed (any password update for admin removes the file — see initial-password.ts).\n// Never printed (or persisted) in desktop mode: the seed there is fully random by design\n// (config.ts) and sign-in goes through the shell's one-shot token, so showing it would\n// only leak a credential into a log nobody needs.\nconst seededAdminPassword = await deps.authService.seedAdmin();\nif (config.desktopToken === null) {\n if (seededAdminPassword !== null) {\n storeInitialAdminPassword(config.root, seededAdminPassword);\n }\n if (deps.authService.adminPasswordIsInitial()) {\n const initialPassword = seededAdminPassword ?? readInitialAdminPassword(config.root);\n // Roots seeded before the password was persisted have no file: stay silent — the\n // plaintext is unrecoverable, and a reminder with nothing to type is pure nagging.\n if (initialPassword !== null) {\n console.log(renderInitialPasswordNotice(ADMIN_USER_ID, initialPassword));\n }\n } else {\n // Heal: the flag was cleared while the file survived (e.g. the password was changed\n // under a build that predates the file) — a stale plaintext must not outlive it.\n clearInitialAdminPassword(config.root);\n }\n}\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;\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`). 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 *\n * Created inside the main listener's callback so it reuses the ACTUAL bound port: with\n * PORT=0 both listeners resolving 0 independently would land on two different ports and\n * every preview URL (same port, counterpart host) would refuse connections.\n */\nlet ipv6Loopback: ReturnType<typeof serve> | null = null;\n\n/** Port announcement (PENGUIN_PORT_FILE): tmp + rename, so a polling reader never sees a partial write. */\nfunction writePortFile(file: string, port: number): void {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const tmp = `${file}.${process.pid}.tmp`;\n fs.writeFileSync(tmp, `${port}\\n`);\n fs.renameSync(tmp, file);\n}\n\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 if (config.desktopToken !== null) console.log(\"Desktop mode: enabled\");\n // PORT=0 asked for an ephemeral port: record the real one so everything derived from\n // the server's own port is correct — Workspace preview URLs above all, which are built\n // from the bind port on purpose (see resolvePreviewTarget) and would otherwise point at\n // port 0 and fail to load. deps.config is this same object, so both route call sites\n // (me.ts, sessions.ts) observe the update.\n config.port = info.port;\n // The root exists by now (openDatabase created it), and the pre-start check found no\n // live owner — record ourselves as this root's server.\n acquireServerLock(config.root, {\n pid: process.pid,\n port: info.port,\n startedAt: new Date().toISOString(),\n });\n if (config.portFile !== null) writePortFile(config.portFile, info.port);\n if (config.host === \"127.0.0.1\" || config.host === \"localhost\") {\n ipv6Loopback = serve({ fetch: app.fetch, hostname: \"::1\", port: info.port });\n ipv6Loopback.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 }\n});\n\n/** Removes the instance lock and port file (best-effort; runs on both exit paths). */\nfunction cleanupInstanceFiles(): void {\n releaseServerLock(config.root);\n if (config.portFile !== null) {\n try {\n fs.rmSync(config.portFile, { force: true });\n } catch {\n // Best-effort: a stale port file is rewritten by the next server.\n }\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 cleanupInstanceFiles();\n process.exit(exitCode);\n });\n // Fallback: a long-lived SSE connection may block the close callback, so force exit after 1s.\n setTimeout(() => {\n cleanupInstanceFiles();\n process.exit(exitCode);\n }, 1000).unref();\n}\n\nprocess.on(\"SIGINT\", () => void shutdown(\"SIGINT\"));\nprocess.on(\"SIGTERM\", () => void shutdown(\"SIGTERM\"));\n\n// Desktop shell quit path: POST /api/desktop/shutdown lands here — the same graceful\n// shutdown as the signals, reachable over HTTP because a Windows child kill is a hard\n// TerminateProcess with no signal delivery.\ndeps.desktop?.onShutdownRequest(() => void shutdown(\"desktop-shutdown\"));\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 { bodyLimit } from \"hono/body-limit\";\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { ProxyEnvPolicy } from \"@prismshadow/penguin-core\";\nimport type { ServerConfig } from \"./config.js\";\nimport { mergedNoProxy } from \"./net/proxy.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 { ServerSettingsRepo } from \"./db/repos/server-settings.js\";\nimport { SessionsRepo } from \"./db/repos/sessions.js\";\nimport { TraceIndexRepo } from \"./db/repos/trace-index.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 { ADMIN_USER_ID, AuthService } from \"./auth/service.js\";\nimport { clearInitialAdminPassword } from \"./initial-password.js\";\nimport { handleError, HttpError, errorBody } from \"./http/errors.js\";\nimport { adminUsersRoutes } from \"./http/routes/admin.js\";\nimport { adminSettingsRoutes } from \"./http/routes/admin-settings.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 { chatDefaultsRoutes } from \"./http/routes/chat-defaults.js\";\nimport { vaultRoutes } from \"./http/routes/vault.js\";\nimport { memoryRoutes } from \"./http/routes/memory.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 { DesktopService } from \"./services/desktop-service.js\";\nimport { desktopRoutes } from \"./http/routes/desktop.js\";\nimport { AgentConfigService } from \"./services/agent-config-service.js\";\nimport { MemoryService } from \"./services/memory-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 { TraceIndexService } from \"./services/trace-index.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 /** Admin-level server-global settings (currently the proxy switches and address). */\n serverSettingsRepo: ServerSettingsRepo;\n authService: AuthService;\n adminService: AdminService;\n projectService: ProjectService;\n projectConfigService: ProjectConfigService;\n agentService: AgentService;\n agentConfigService: AgentConfigService;\n memoryService: MemoryService;\n sessionService: SessionService;\n traceService: TraceService;\n /** Trace-file index (derived cache + reconciler); routes use it for delete-time coherence. */\n traceIndex: TraceIndexService;\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 /** Desktop mode (PENGUIN_DESKTOP_TOKEN): one-shot login + shutdown token holder; null outside desktop mode. */\n desktop: DesktopService | null;\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 serverSettingsRepo = new ServerSettingsRepo(db);\n // Command-subprocess proxy policy for core, keyed on the\n // \"agent environment uses the proxy\" switch (the app switch only drives the server's\n // own dispatcher, see net/proxy.ts): switch off → strip HTTP(S)_PROXY/ALL_PROXY; on\n // with an explicit address → inject that address (with the merged loopback NO_PROXY)\n // over whatever the environment carries; on without an address → pass the environment\n // through. A getter, not a snapshot: it is re-read at every command spawn, so a\n // settings change reaches already-loaded Sessions. Threaded through BOTH core entry\n // paths — the loader (resume/self-heal) and SessionService (creation, whose runtime\n // the manager adopts for the first Task).\n const proxyEnv = (): ProxyEnvPolicy | null => {\n if (!serverSettingsRepo.getProxyForAgent()) return { mode: \"strip\" };\n const url = serverSettingsRepo.getProxyUrl();\n return url === null ? null : { mode: \"inject\", url, noProxy: mergedNoProxy() };\n };\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 memoryService = new MemoryService(config.root, agentConfigService);\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),\n // SessionService (creation / adoption / lazy list resolution), and the Trace index /\n // listing classification.\n const sessionSources = new SessionSources();\n // Trace-file index: the derived cache every trace listing/locating path serves from\n // (mtime-gated reconciler keeps it in step with the on-disk tree; see trace-index.ts).\n const traceIndexRepo = new TraceIndexRepo(db);\n const traceIndex = new TraceIndexService(config.root, traceIndexRepo, sessionSources);\n const traceService = new TraceService(config.root, {\n index: traceIndex,\n sessions: sessionsRepo,\n sources: sessionSources,\n });\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, workspaceFiles);\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 const manager = new SessionManager({\n sessions: sessionsRepo,\n channels,\n loader: overrides.loader ?? createCoreSessionLoader(config.root, sessionSources, { proxyEnv }),\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 traceIndex,\n });\n // Any password update for the built-in admin makes the persisted initial-password\n // plaintext stale (either the password is no longer initial, or a reset replaced it\n // with an admin-chosen value that is never persisted): drop the file so later startups\n // stop re-printing a credential that no longer signs in.\n const onPasswordChanged = (userId: string): void => {\n if (userId === ADMIN_USER_ID) clearInitialAdminPassword(config.root);\n };\n const authService = new AuthService({\n users: usersRepo,\n authSessions: authSessionsRepo,\n provisionInitialProject: (user, isAdmin) =>\n projectService.provisionInitialProject(user, isAdmin),\n seedAdminPassword: config.seedAdminPassword,\n onPasswordChanged,\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 onPasswordChanged,\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 traceIndex,\n proxyEnv,\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 projectConfig: projectConfigService,\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 serverSettingsRepo,\n authService,\n adminService,\n projectService,\n projectConfigService,\n agentService,\n agentConfigService,\n memoryService,\n sessionService,\n traceService,\n traceIndex,\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 desktop: config.desktopToken !== null ? new DesktopService(config.desktopToken) : null,\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 // 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 //\n // The cap has to be measured, not read: a chunked request carries no `content-length` at all,\n // so a header check alone passes a body of any size — the sinks behind it (task input images,\n // file attachments, Trace import) then decode whatever arrives. hono's bodyLimit keeps the\n // header fast path when the length is declared and otherwise counts bytes off the stream,\n // aborting the moment the total crosses the cap.\n app.use(\n \"/api/*\",\n bodyLimit({\n maxSize: MAX_BODY_BYTES,\n // Its default is a bare text/plain 413; throw the App's own error instead so the response\n // stays the documented `payload_too_large` body that every client already handles.\n onError: () => {\n throw new HttpError(413, \"payload_too_large\", \"Request body exceeds the 20MB limit.\");\n },\n }),\n );\n app.use(\"/api/*\", jsonOnlyWrites);\n\n // Public routes (no login required).\n app.route(\"/api/auth\", authRoutes(deps));\n // Desktop shutdown authenticates with the shell's Bearer token, not the cookie\n // session, so it mounts outside authMiddleware (and only in desktop mode).\n if (deps.desktop) {\n app.route(\"/api/desktop\", desktopRoutes(deps));\n }\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/admin/settings\", adminSettingsRoutes(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/chat-defaults\", chatDefaultsRoutes(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/memory\", memoryRoutes(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.\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 * Outbound networking and the admin proxy settings.\n *\n * Node 24's built-in fetch does not read the proxy environment variables\n * (NODE_USE_ENV_PROXY is ineffective on 24.18), so the server routes ALL of its own\n * outbound traffic through undici: the startup entry replaces `globalThis.fetch` with\n * undici's fetch once, and this module keeps undici's global dispatcher in line with the\n * admin-level settings — undici's fetch resolves the global dispatcher per call, so a\n * settings change takes effect for newly initiated connections without a restart.\n *\n * Dispatcher per settings state (keyed on the \"application uses the proxy\" switch; the\n * separate agent-environment switch never touches the dispatcher — it only drives the\n * command-subprocess policy in app.ts):\n * - app switch off: a plain `Agent` — direct connections, proxy variables ignored;\n * - on, no explicit address (default): `EnvHttpProxyAgent` — honors HTTP_PROXY /\n * HTTPS_PROXY (both spellings; undici reads the lowercase name first) with a\n * NO_PROXY list merged from the environment plus the loopback names;\n * - on with an explicit address (`proxyUrl`): `EnvHttpProxyAgent` again, but with its\n * `httpProxy` / `httpsProxy` options pinned to that URL — undici consults those\n * BEFORE the environment (verified against undici 7.29's constructor:\n * `httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY`), so the explicit\n * address governs both http and https traffic regardless of ambient env, while the\n * merged NO_PROXY keeps working through the same `noProxy` option.\n *\n * The loopback merge is load-bearing in every state: the CLI's readiness probe\n * (`penguin web` imports the server in-process and polls its own root path), SSE, and\n * Workspace previews all ride the loopback names, so a proxy that intercepts loopback\n * would take the whole App down. `localhost,127.0.0.1,::1` is therefore ALWAYS appended\n * to the effective NO_PROXY, regardless of what the environment declares.\n *\n * The values persisted in server_settings stay authoritative (the routes read/write the\n * repo); this module only mirrors them into the process-global dispatcher. The command\n * subprocess side — keyed on the agent-environment switch: strip the proxy variables\n * when it is off, inject the explicit address when set, pass through otherwise — lives\n * in core (CommandSessionManager, threaded through the session loader as a\n * ProxyEnvPolicy getter), not here.\n */\nimport { Agent, EnvHttpProxyAgent, fetch as undiciFetch, setGlobalDispatcher } from \"undici\";\nimport type { Dispatcher } from \"undici\";\n\n/** Loopback names every effective NO_PROXY must contain (see module doc). */\nexport const LOOPBACK_NO_PROXY = [\"localhost\", \"127.0.0.1\", \"::1\"] as const;\n\n/** The slice of the admin proxy settings the dispatcher mirrors (persisted in server_settings). */\nexport interface ProxySettings {\n /** The \"application uses the proxy\" switch (default on). */\n proxyForApp: boolean;\n /** Explicit proxy URL (normalized, see {@link normalizeProxyUrl}); null = follow the proxy environment variables. */\n proxyUrl: string | null;\n}\n\n/**\n * The effective NO_PROXY: the environment's entries (lowercase spelling first, matching\n * undici's own precedence) with the loopback names appended when missing. Exported as a\n * pure helper for tests and for app.ts's command-subprocess policy getter; `env`\n * defaults to the real process environment.\n */\nexport function mergedNoProxy(env: NodeJS.ProcessEnv = process.env): string {\n const raw = env.no_proxy ?? env.NO_PROXY ?? \"\";\n // Same separators undici's parser accepts (comma or whitespace).\n const entries = raw.split(/[,\\s]/).filter((e) => e !== \"\");\n const present = new Set(entries.map((e) => e.toLowerCase()));\n for (const name of LOOPBACK_NO_PROXY) {\n if (!present.has(name)) entries.push(name);\n }\n return entries.join(\",\");\n}\n\n/**\n * Normalizes a proxy address to its canonical URL, or null when it is not acceptable.\n * Accepted forms: `http://host[:port]`, `https://host[:port]`, and the bare\n * `host[:port]` / `host` shorthand (normalized to `http://…`). Anything else — other\n * schemes (the dispatcher speaks only HTTP(S) proxies; a socks:// URL would break every\n * server fetch, see os-proxy.ts for the same rule), credentials, paths, queries — is\n * rejected rather than stored: server_settings only ever holds normalized values.\n */\nexport function normalizeProxyUrl(raw: string): string | null {\n const trimmed = raw.trim();\n if (trimmed === \"\") return null;\n // Bare host[:port] shorthand: no scheme -> http. The scheme test requires \"://\", so\n // \"host:8080\" (a port, not a scheme) takes the shorthand path.\n const candidate = /^[a-zA-Z][a-zA-Z0-9+.-]*:\\/\\//.test(trimmed) ? trimmed : `http://${trimmed}`;\n let url: URL;\n try {\n url = new URL(candidate);\n } catch {\n return null;\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return null;\n if (url.hostname === \"\" || url.username !== \"\" || url.password !== \"\") return null;\n // A proxy address is host-only; the parser's bare \"/\" (its normal form for an absent\n // path) is tolerated, an actual path/query/fragment means this was not a proxy address.\n if ((url.pathname !== \"/\" && url.pathname !== \"\") || url.search !== \"\" || url.hash !== \"\") {\n return null;\n }\n // origin is the canonical form: lowercased scheme/host, default ports dropped.\n return url.origin;\n}\n\n/** The EnvHttpProxyAgent option subset this module assembles (undici's Options type is not re-exported cleanly). */\nexport interface EnvProxyAgentOptions {\n httpProxy?: string;\n httpsProxy?: string;\n noProxy: string;\n}\n\n/**\n * The EnvHttpProxyAgent options for the switch-on states (pure choice, exported for\n * tests): always the merged NO_PROXY — passed via opts.noProxy, which REPLACES the\n * environment lookup inside EnvHttpProxyAgent, so merging is this module's job (undici\n * would otherwise use env NO_PROXY verbatim, without the loopback exemption) — plus,\n * with an explicit address, the httpProxy/httpsProxy overrides that pin both traffic\n * kinds to it.\n */\nexport function envProxyAgentOptions(\n proxyUrl: string | null,\n env?: NodeJS.ProcessEnv,\n): EnvProxyAgentOptions {\n const noProxy = mergedNoProxy(env);\n return proxyUrl === null ? { noProxy } : { httpProxy: proxyUrl, httpsProxy: proxyUrl, noProxy };\n}\n\n/**\n * Builds the global dispatcher for a settings state (pure choice, exported for tests):\n * app switch off → direct-connect Agent, on → EnvHttpProxyAgent (env-driven, or pinned\n * to the explicit address; see {@link envProxyAgentOptions}).\n */\nexport function buildProxyDispatcher(settings: ProxySettings, env?: NodeJS.ProcessEnv): Dispatcher {\n if (!settings.proxyForApp) return new Agent();\n return new EnvHttpProxyAgent(envProxyAgentOptions(settings.proxyUrl, env));\n}\n\n/** Current settings mirrored for the dispatcher (the DB rows are the persisted truth). */\nlet current: ProxySettings = { proxyForApp: true, proxyUrl: null };\n/** True once the startup entry has installed undici globally (never in tests). */\nlet installed = false;\n\n/**\n * One-time install at the startup entry (index.ts), before anything can fetch: replaces\n * `globalThis.fetch` with undici's and sets the initial dispatcher. Starts from the\n * defaults (on, no explicit address) — the entry applies the persisted values via\n * {@link applyProxySettings} as soon as the database is open, before any outbound\n * request exists. Tests never call this, so they keep the runtime's own fetch (and\n * their fetch stubs).\n */\nexport function installGlobalProxyDispatcher(): void {\n installed = true;\n setGlobalDispatcher(buildProxyDispatcher(current));\n // Cast: undici's fetch is typed against its own RequestInit/Response declarations,\n // which are structurally compatible with the runtime globals for every caller here.\n globalThis.fetch = undiciFetch as unknown as typeof globalThis.fetch;\n}\n\n/**\n * Applies a settings state to the process: rebuilds the global dispatcher so new\n * connections follow it immediately (no restart). Called by the startup entry with the\n * persisted values and by PUT /api/admin/settings on every change. Outside the\n * installed runtime (tests) it only records the state.\n */\nexport function applyProxySettings(settings: ProxySettings): void {\n if (settings.proxyForApp === current.proxyForApp && settings.proxyUrl === current.proxyUrl) {\n return;\n }\n current = { proxyForApp: settings.proxyForApp, proxyUrl: settings.proxyUrl };\n if (installed) setGlobalDispatcher(buildProxyDispatcher(current));\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 // Columns added to the schema after a web.db was formed: CREATE TABLE IF NOT EXISTS never\n // touches an existing table, so they are ALTERed in here. Keep the list in sync with\n // schema.ts; drop entries only in a release allowed to break existing web.db files.\n ensureColumn(db, \"sessions\", \"client\", \"TEXT\");\n ensureColumn(db, \"sessions\", \"has_trace\", \"INTEGER NOT NULL DEFAULT 0\");\n ensureColumn(db, \"auth_sessions\", \"via\", \"TEXT\");\n ensureColumn(db, \"trace_files\", \"page_stats\", \"TEXT\");\n return db;\n}\n\n/** Idempotent per-column upgrade for databases formed before the column existed. */\nfunction ensureColumn(db: DatabaseSync, table: string, column: string, ddl: string): void {\n const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];\n if (cols.some((c) => c.name === column)) return;\n db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${ddl}`);\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\n * once. The one exception: columns added to an existing table after release of a web.db are\n * ALTERed in by the idempotent per-column guard in database.ts (ensureColumn), since CREATE\n * TABLE IF NOT EXISTS never touches an existing table.\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 via TEXT -- 'password' | 'desktop'; NULL = legacy row (password)\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 client TEXT, -- creating client: 'web' (created via the Web App) | 'cli' (adopted from a CLI Trace); NULL = legacy row, treated as web\n has_trace INTEGER NOT NULL DEFAULT 0, -- cache: a Trace record exists (set at task start / adoption / subagent registration; lazily backfilled by list hydration)\n created_at TEXT NOT NULL\n); -- the schedule/subagent SOURCE is NOT stored: session_meta in the Trace is the single source of truth (see runtime/session-sources.ts); \"client\" is a different, DB-only axis (who created the row)\nCREATE INDEX IF NOT EXISTS idx_sessions_agent_created ON sessions(project_id, agent_id, created_at DESC);\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 | compaction | 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);\nCREATE TABLE IF NOT EXISTS server_settings ( -- admin-level server-global settings (GET/PUT /api/admin/settings)\n key TEXT PRIMARY KEY, -- setting name, e.g. 'proxy_for_app'\n value TEXT NOT NULL -- JSON-encoded value; an absent row means the setting's built-in default\n);\nCREATE TABLE IF NOT EXISTS trace_files ( -- DERIVED CACHE of the on-disk Trace tree (services/trace-index.ts): the directories stay the single source of truth, every row is rebuildable from disk, and a row is never authority for absence — consumers reconcile + retry on a miss, so a stale index costs one extra scan, never a false 404\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n session_id TEXT NOT NULL,\n file_index INTEGER NOT NULL, -- shard index NNN of <session_id>_NNN.jsonl (name/path are reconstructed from the row, never stored: the data root may move)\n date TEXT NOT NULL, -- date directory name (local yyyy-mm-dd)\n size_bytes INTEGER NOT NULL, -- last observed size (listings stat-refresh the returned page and write back; an actively-appended shard may lag in between)\n page_stats TEXT, -- cached message-window scan state at this shard's END (services/message-window.ts ShardPrefixRecord JSON); immutable shards only, NULL = not yet computed, nulled whenever size_bytes changes\n PRIMARY KEY (project_id, agent_id, session_id, file_index)\n);\nCREATE INDEX IF NOT EXISTS idx_trace_files_agent_date ON trace_files(project_id, agent_id, date);\nCREATE INDEX IF NOT EXISTS idx_trace_files_session ON trace_files(session_id);\nCREATE TABLE IF NOT EXISTS trace_sessions ( -- per-session facts read ONCE at registration from the earliest shard's head (same derived-cache rules as trace_files; listing needs zero head-reads)\n session_id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n source TEXT, -- session_meta origin: 'subagent' | 'schedule' | NULL = user-created (or head not yet readable)\n workspace TEXT NOT NULL DEFAULT '',\n title TEXT, -- first-prompt fallback title (sessions.title always wins when present)\n provider TEXT, -- model reference from session_meta (CLI adoption reads it from here; NULL = meta unreadable / legacy without provider)\n model_id TEXT,\n first_ts TEXT, -- first record's timestamp (adoption's createdAt fallback when the id embeds no time)\n meta_read INTEGER NOT NULL DEFAULT 0 -- 1 once the head parsed; 0 = facts unknown, retried by the next reconcile that touches the session\n);\nCREATE INDEX IF NOT EXISTS idx_trace_sessions_agent ON trace_sessions(project_id, agent_id);\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 /** How the session was established (\"password\" | \"desktop\"); null on rows formed before the column existed (treated as \"password\"). */\n via: string | null;\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, via) VALUES (?, ?, ?, ?, ?)\",\n )\n .run(row.tokenHash, row.userId, row.createdAt, row.expiresAt, row.via);\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 via: (r.via as string | null) ?? null,\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, offset = 0): 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 OFFSET :offset`,\n )\n .all({ ...params, limit, offset });\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 * server_settings table repo (admin-level server-global settings): key-value storage\n * with JSON-encoded values. An absent row means the setting's built-in default, so\n * settings added after a web.db was formed need no migration.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/** Key of the \"application uses the proxy\" switch (the server's own outbound dispatcher); default on. */\nconst PROXY_FOR_APP_KEY = \"proxy_for_app\";\n\n/** Key of the \"agent environment uses the proxy\" switch (command subprocess env policy); default on. */\nconst PROXY_FOR_AGENT_KEY = \"proxy_for_agent\";\n\n/**\n * Legacy single-switch key from the unreleased #225 iteration (never in a release): read\n * as the fallback default for BOTH new switches while their own keys are absent, so a\n * main-branch deployment that had toggled it keeps its choice. Read-only adoption — the\n * legacy key is never written back, and either new key, once set, wins for its switch.\n */\nconst LEGACY_USE_SYSTEM_PROXY_KEY = \"use_system_proxy\";\n\n/** Key of the explicit proxy address; absent/null = follow the proxy environment variables. */\nconst PROXY_URL_KEY = \"proxy_url\";\n\nexport class ServerSettingsRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n /** Returns the raw JSON-encoded value; null if never set. */\n get(key: string): string | null {\n const r = this.db.prepare(\"SELECT value FROM server_settings WHERE key = ?\").get(key);\n return r ? (r.value as string) : null;\n }\n\n set(key: string, value: string): void {\n this.db\n .prepare(\n `INSERT INTO server_settings (key, value) VALUES (?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value`,\n )\n .run(key, value);\n }\n\n /** Shared switch read: this key if present, else the legacy single switch, else the default: on. */\n private getProxySwitch(key: string): boolean {\n const raw = this.get(key);\n if (raw !== null) return raw !== \"false\";\n return this.get(LEGACY_USE_SYSTEM_PROXY_KEY) !== \"false\";\n }\n\n /** The \"application uses the proxy\" switch (the server's own outbound dispatcher). */\n getProxyForApp(): boolean {\n return this.getProxySwitch(PROXY_FOR_APP_KEY);\n }\n\n setProxyForApp(value: boolean): void {\n this.set(PROXY_FOR_APP_KEY, JSON.stringify(value));\n }\n\n /** The \"agent environment uses the proxy\" switch (command subprocess env policy). */\n getProxyForAgent(): boolean {\n return this.getProxySwitch(PROXY_FOR_AGENT_KEY);\n }\n\n setProxyForAgent(value: boolean): void {\n this.set(PROXY_FOR_AGENT_KEY, JSON.stringify(value));\n }\n\n /**\n * The explicit proxy address (normalized at write time by the settings route); null =\n * follow the proxy environment variables. An absent or unreadable row reads as null\n * (the safe default: behave as before the setting existed).\n */\n getProxyUrl(): string | null {\n const raw = this.get(PROXY_URL_KEY);\n if (raw === null) return null;\n try {\n const value: unknown = JSON.parse(raw);\n return typeof value === \"string\" && value !== \"\" ? value : null;\n } catch {\n return null;\n }\n }\n\n setProxyUrl(value: string | null): void {\n this.set(PROXY_URL_KEY, JSON.stringify(value));\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 /**\n * Creating client: \"web\" (created via the Web App/server) or \"cli\" (adopted from a Trace\n * the CLI left behind); NULL = legacy row from before the column existed, treated as web.\n * The schedule/subagent SOURCE is deliberately NOT a row field — core session_meta in the\n * Trace stays the single source of truth for it (runtime/session-sources.ts); `client` is\n * a separate, DB-only axis that meta never records.\n */\n client?: \"web\" | \"cli\" | null;\n /** Cache: a Trace record exists (set at task start / adoption / subagent registration; backfilled by list hydration). */\n hasTrace?: boolean;\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 client: (r.client as \"web\" | \"cli\" | null) ?? null,\n hasTrace: (r.has_trace as number) === 1,\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, client, has_trace, 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.client ?? null,\n row.hasTrace ? 1 : 0,\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, client, has_trace, 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.client ?? null,\n row.hasTrace ? 1 : 0,\n row.createdAt,\n );\n }\n\n /** Flip the has_trace cache once a Trace record exists (task start / discovery hydration); idempotent. */\n markHasTrace(sessionId: string): void {\n this.db.prepare(\"UPDATE sessions SET has_trace = 1 WHERE session_id = ?\").run(sessionId);\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 /**\n * An Agent's rows, newest first (the list order the sidebar shows; served by\n * idx_sessions_agent_created). `webOnly` keeps only web-created rows — NULL counts as\n * web (legacy rows predate the column and the user chose to grandfather them as visible).\n */\n listByAgent(projectId: string, agentId: string, opts: { webOnly?: boolean } = {}): SessionRow[] {\n const filter = opts.webOnly ? \" AND (client IS NULL OR client = 'web')\" : \"\";\n const rows = this.db\n .prepare(\n `SELECT * FROM sessions WHERE project_id = ? AND agent_id = ?${filter}\n ORDER BY created_at DESC, session_id DESC`,\n )\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 * trace_files / trace_sessions repo: the Trace-tree derived cache (see schema.ts and\n * services/trace-index.ts for the cache rules). Pure row access — reconciliation\n * policy (mtime gates, head-reads) lives in the service.\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport type { SessionSource } from \"../../api/types.js\";\n\n/** One indexed Trace shard. */\nexport interface TraceFileRow {\n projectId: string;\n agentId: string;\n sessionId: string;\n fileIndex: number;\n date: string;\n sizeBytes: number;\n}\n\n/** One Session's registration-time facts (earliest shard's head). */\nexport interface TraceSessionRow {\n sessionId: string;\n projectId: string;\n agentId: string;\n source: SessionSource | null;\n workspace: string;\n title: string | null;\n provider: string | null;\n modelId: string | null;\n firstTs: string | null;\n metaRead: boolean;\n}\n\nfunction mapFile(r: Record<string, unknown>): TraceFileRow {\n return {\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n sessionId: r.session_id as string,\n fileIndex: r.file_index as number,\n date: r.date as string,\n sizeBytes: r.size_bytes as number,\n };\n}\n\nfunction mapSession(r: Record<string, unknown>): TraceSessionRow {\n const source = r.source as string | null;\n return {\n sessionId: r.session_id as string,\n projectId: r.project_id as string,\n agentId: r.agent_id as string,\n // Narrowed on read as well as write: junk in a hand-edited DB must not leak out as a source.\n source: source === \"subagent\" || source === \"schedule\" ? source : null,\n workspace: (r.workspace as string | null) ?? \"\",\n title: (r.title as string | null) ?? null,\n provider: (r.provider as string | null) ?? null,\n modelId: (r.model_id as string | null) ?? null,\n firstTs: (r.first_ts as string | null) ?? null,\n metaRead: (r.meta_read as number) === 1,\n };\n}\n\nexport class TraceIndexRepo {\n constructor(private readonly db: DatabaseSync) {}\n\n upsertFile(row: TraceFileRow): void {\n // page_stats is a derivation of the shard's CONTENT: any observed size change voids\n // it (an unchanged size keeps the cached scan — reconcile passes re-upsert rows).\n this.db\n .prepare(\n `INSERT INTO trace_files (project_id, agent_id, session_id, file_index, date, size_bytes)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, agent_id, session_id, file_index)\n DO UPDATE SET date = excluded.date,\n page_stats = CASE WHEN excluded.size_bytes = trace_files.size_bytes\n THEN trace_files.page_stats ELSE NULL END,\n size_bytes = excluded.size_bytes`,\n )\n .run(row.projectId, row.agentId, row.sessionId, row.fileIndex, row.date, row.sizeBytes);\n }\n\n /** Refreshes one shard's last-observed size (listings write back their page stats); a changed size voids the cached window scan. */\n updateFileSize(\n projectId: string,\n agentId: string,\n sessionId: string,\n fileIndex: number,\n sizeBytes: number,\n ): void {\n this.db\n .prepare(\n `UPDATE trace_files SET\n page_stats = CASE WHEN size_bytes = ? THEN page_stats ELSE NULL END,\n size_bytes = ?\n WHERE project_id = ? AND agent_id = ? AND session_id = ? AND file_index = ?`,\n )\n .run(sizeBytes, sizeBytes, projectId, agentId, sessionId, fileIndex);\n }\n\n /** Cached message-window prefix record of one shard (see services/message-window.ts); null = none. */\n getPageStats(\n projectId: string,\n agentId: string,\n sessionId: string,\n fileIndex: number,\n ): string | null {\n const r = this.db\n .prepare(\n `SELECT page_stats FROM trace_files\n WHERE project_id = ? AND agent_id = ? AND session_id = ? AND file_index = ?`,\n )\n .get(projectId, agentId, sessionId, fileIndex);\n return r ? ((r.page_stats as string | null) ?? null) : null;\n }\n\n /** Stores a shard's message-window prefix record, guarded on the size it was computed for (a concurrent append voids the write). */\n setPageStats(\n projectId: string,\n agentId: string,\n sessionId: string,\n fileIndex: number,\n sizeBytes: number,\n pageStats: string,\n ): void {\n this.db\n .prepare(\n `UPDATE trace_files SET page_stats = ?\n WHERE project_id = ? AND agent_id = ? AND session_id = ? AND file_index = ? AND size_bytes = ?`,\n )\n .run(pageStats, projectId, agentId, sessionId, fileIndex, sizeBytes);\n }\n\n deleteFile(projectId: string, agentId: string, sessionId: string, fileIndex: number): void {\n this.db\n .prepare(\n `DELETE FROM trace_files\n WHERE project_id = ? AND agent_id = ? AND session_id = ? AND file_index = ?`,\n )\n .run(projectId, agentId, sessionId, fileIndex);\n }\n\n /** All indexed shards of one Agent (date desc, session desc, index asc — the listing's canonical order). */\n listFilesByAgent(projectId: string, agentId: string): TraceFileRow[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM trace_files WHERE project_id = ? AND agent_id = ?\n ORDER BY session_id DESC, file_index ASC`,\n )\n .all(projectId, agentId);\n return rows.map(mapFile);\n }\n\n /** One Session's shards, index ascending. */\n listFilesBySession(projectId: string, agentId: string, sessionId: string): TraceFileRow[] {\n const rows = this.db\n .prepare(\n `SELECT * FROM trace_files\n WHERE project_id = ? AND agent_id = ? AND session_id = ?\n ORDER BY file_index ASC`,\n )\n .all(projectId, agentId, sessionId);\n return rows.map(mapFile);\n }\n\n /** Owning Agent of a Session's Trace within a Project (the subagent-pointer resolver); null when unindexed. */\n findAgentBySession(projectId: string, sessionId: string): string | null {\n const r = this.db\n .prepare(`SELECT agent_id FROM trace_files WHERE project_id = ? AND session_id = ? LIMIT 1`)\n .get(projectId, sessionId);\n return r ? (r.agent_id as string) : null;\n }\n\n upsertSession(row: TraceSessionRow): void {\n this.db\n .prepare(\n `INSERT INTO trace_sessions (session_id, project_id, agent_id, source, workspace, title, provider, model_id, first_ts, meta_read)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(session_id) DO UPDATE SET\n project_id = excluded.project_id, agent_id = excluded.agent_id,\n source = excluded.source, workspace = excluded.workspace, title = excluded.title,\n provider = excluded.provider, model_id = excluded.model_id,\n first_ts = excluded.first_ts, meta_read = excluded.meta_read`,\n )\n .run(\n row.sessionId,\n row.projectId,\n row.agentId,\n row.source,\n row.workspace,\n row.title,\n row.provider,\n row.modelId,\n row.firstTs,\n row.metaRead ? 1 : 0,\n );\n }\n\n getSession(sessionId: string): TraceSessionRow | null {\n const r = this.db.prepare(\"SELECT * FROM trace_sessions WHERE session_id = ?\").get(sessionId);\n return r ? mapSession(r) : null;\n }\n\n listSessionsByAgent(projectId: string, agentId: string): TraceSessionRow[] {\n const rows = this.db\n .prepare(\"SELECT * FROM trace_sessions WHERE project_id = ? AND agent_id = ?\")\n .all(projectId, agentId);\n return rows.map(mapSession);\n }\n\n /** Removes one Session's rows from both tables (Session delete keeps the cache coherent). */\n deleteBySession(sessionId: string): void {\n this.db.prepare(\"DELETE FROM trace_files WHERE session_id = ?\").run(sessionId);\n this.db.prepare(\"DELETE FROM trace_sessions WHERE session_id = ?\").run(sessionId);\n }\n\n deleteByAgent(projectId: string, agentId: string): void {\n this.db\n .prepare(\"DELETE FROM trace_files WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n this.db\n .prepare(\"DELETE FROM trace_sessions WHERE project_id = ? AND agent_id = ?\")\n .run(projectId, agentId);\n }\n\n deleteByProject(projectId: string): void {\n this.db.prepare(\"DELETE FROM trace_files WHERE project_id = ?\").run(projectId);\n this.db.prepare(\"DELETE FROM trace_sessions WHERE project_id = ?\").run(projectId);\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, SessionVia } 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 /** How the current session was established (\"password\" | \"desktop\"); legacy rows read as \"password\". */\n sessionVia: SessionVia;\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 authed = token ? auth.authenticateWithMeta(token) : null;\n if (!authed) {\n throw new HttpError(401, \"unauthorized\", \"Not signed in or the sign-in has expired.\");\n }\n c.set(\"user\", authed.user);\n c.set(\"sessionVia\", authed.via);\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 with a random `penguin-<4 digits>` initial password\n * (printed once by the startup entrypoint; PENGUIN_SEED_ADMIN_PASSWORD injects\n * a fixed one for tests/e2e), and it adopts `default_project`; all other users\n * are created by an admin via the user 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, randomInt } 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. */\nexport const ADMIN_USER_ID = \"admin\";\n\n/**\n * Login throttling (per userId): the seeded initial password is `penguin-<4 digits>` —\n * 10,000 combinations — so unthrottled guessing would enumerate it in minutes. After\n * LOGIN_FREE_ATTEMPTS consecutive failures, the next attempt is admitted only after an\n * exponentially growing delay from the last failure (1s, 2s, … capped at 60s; attempts\n * inside the window are 429 `too_many_attempts` and do not extend it). Beyond ~40\n * failures that is one guess per minute, so the 10k space stops being enumerable, while\n * a legitimate user who mistyped a few times never waits more than the cap. A successful\n * login clears the counter. Counters are process memory (a restart clears them —\n * restarting is slower than waiting out the cap) and are kept for nonexistent userIds\n * too, so throttling is not an account-existence oracle. Known limit: a concurrent burst\n * can slip in before its first failure is recorded; the steady-state backoff still\n * dominates the search space.\n */\nconst LOGIN_FREE_ATTEMPTS = 5;\nconst LOGIN_BACKOFF_START_MS = 1000;\nconst LOGIN_BACKOFF_CAP_MS = 60_000;\n/** Failure entries idle longer than this are swept (bounds the map; far above the cap). */\nconst LOGIN_FAILURE_IDLE_MS = 15 * 60_000;\n\n/**\n * Random initial password for the seeded admin: `penguin-<4 digits>` — brand-related and\n * easy to type, shown once in the server startup output (the README, docs and login-page\n * hint all describe this form).\n */\nexport function generateInitialAdminPassword(): string {\n return \"penguin-\" + String(randomInt(0, 10000)).padStart(4, \"0\");\n}\n\nfunction sha256Hex(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\n/**\n * How a session was established: \"password\" via the login form, \"desktop\" via the\n * desktop shell's one-shot token. Persisted per session so desktop-specific allowances\n * (password change without the old password) apply only to sessions the shell itself\n * opened. Legacy rows (NULL) read as \"password\".\n */\nexport type SessionVia = \"password\" | \"desktop\";\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 /** Fixed initial password for the seeded admin (config.seedAdminPassword); null generates a random one at seed time. */\n seedAdminPassword: string | null;\n /**\n * Fired after any successful password update (self change / desktop set). The server\n * wires it to drop the stored initial-password plaintext once it goes stale (see\n * initial-password.ts); standalone/test constructions may omit it.\n */\n onPasswordChanged?: (userId: string) => 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 * Returns the initial password when it actually seeded — the caller prints it,\n * the only place a generated password is ever shown — and null when users\n * already exist.\n */\n async seedAdmin(): Promise<string | null> {\n if (this.deps.users.count() > 0) return null;\n const password = this.deps.seedAdminPassword ?? generateInitialAdminPassword();\n // The override (PENGUIN_SEED_ADMIN_PASSWORD) must meet the same policy as every\n // other initial/reset password; rejecting it here, before any insert, keeps a\n // configuration typo from creating a trivially weak privileged account. Generated\n // passwords are always 12 characters and never trip this.\n if (password.length < MIN_PASSWORD_LENGTH) {\n throw new Error(\n `PENGUIN_SEED_ADMIN_PASSWORD must be at least ${MIN_PASSWORD_LENGTH} characters.`,\n );\n }\n const user: UserRow = {\n userId: ADMIN_USER_ID,\n passwordHash: await hashPassword(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 return password;\n }\n\n /** Whether the built-in admin still runs on its initial password (drives the startup reminder notice). */\n adminPasswordIsInitial(): boolean {\n return this.deps.users.findById(ADMIN_USER_ID)?.passwordIsInitial === true;\n }\n\n /** Consecutive login failures per userId (see the throttling comment on the constants). */\n private readonly loginFailures = new Map<string, { failures: number; lastFailureAt: number }>();\n\n /** The wait imposed after `failures` consecutive failures (0 while within the free attempts). */\n private loginDelayMs(failures: number): number {\n const excess = failures - LOGIN_FREE_ATTEMPTS;\n if (excess <= 0) return 0;\n return Math.min(LOGIN_BACKOFF_START_MS * 2 ** (excess - 1), LOGIN_BACKOFF_CAP_MS);\n }\n\n async login(userId: string, password: string): Promise<{ user: UserInfo; token: string }> {\n const nowMs = this.now().getTime();\n for (const [key, entry] of this.loginFailures) {\n if (nowMs - entry.lastFailureAt > LOGIN_FAILURE_IDLE_MS) this.loginFailures.delete(key);\n }\n const failed = this.loginFailures.get(userId);\n if (failed) {\n const readyAt = failed.lastFailureAt + this.loginDelayMs(failed.failures);\n if (nowMs < readyAt) {\n throw new HttpError(\n 429,\n \"too_many_attempts\",\n `Too many failed sign-in attempts. Try again in ${Math.ceil((readyAt - nowMs) / 1000)}s.`,\n );\n }\n }\n const row = this.deps.users.findById(userId);\n const ok = row !== null && (await verifyPassword(password, row.passwordHash));\n if (!row || !ok) {\n this.loginFailures.set(userId, {\n failures: (failed?.failures ?? 0) + 1,\n lastFailureAt: this.now().getTime(),\n });\n throw new HttpError(401, \"invalid_credentials\", \"Incorrect username or password.\");\n }\n this.loginFailures.delete(userId);\n this.deps.authSessions.deleteExpired(this.now().toISOString());\n return { user: toUserInfo(row), token: this.issueSession(row.userId, \"password\") };\n }\n\n /**\n * Desktop-mode sign-in: issues an admin session WITHOUT a password check — the caller\n * (the desktop-login route) has already redeemed the shell's one-shot token, which is\n * the credential here. Throws if the admin has not been seeded yet (desktop-login runs\n * after startup seeding, so this only trips on a broken deployment).\n */\n loginDesktop(): { user: UserInfo; token: string } {\n const row = this.deps.users.findById(ADMIN_USER_ID);\n if (!row) {\n throw new HttpError(500, \"internal\", \"Built-in admin has not been seeded.\");\n }\n this.deps.authSessions.deleteExpired(this.now().toISOString());\n return { user: toUserInfo(row), token: this.issueSession(row.userId, \"desktop\") };\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 this.deps.onPasswordChanged?.(userId);\n }\n\n /**\n * Desktop-session password set: no old-password check. Only reachable for sessions\n * established via desktop-login (the me route gates on sessionVia) — the seed password\n * of a desktop-created root is random and never shown, so its holder has nothing to\n * type into an old-password field; the shell's token already proved machine ownership.\n */\n async setPasswordDesktop(userId: string, newPassword: string): Promise<void> {\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 this.deps.onPasswordChanged?.(userId);\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 authenticateWithMeta(token: string): { user: UserRow; via: SessionVia } | 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 const user = this.deps.users.findById(session.userId);\n if (!user) return null;\n return { user, via: session.via === \"desktop\" ? \"desktop\" : \"password\" };\n }\n\n private issueSession(userId: string, via: SessionVia): 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 via,\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 * and desktop mode rejects the whole surface (single-user; 403 `desktop_single_user`).\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 { rejectInDesktopMode } from \"./desktop.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(\"*\", rejectInDesktopMode(deps));\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 * Desktop-mode routes: POST /api/desktop/shutdown, plus the shared desktop-mode guard\n * that turns off multi-user surfaces (see rejectInDesktopMode).\n *\n * The shutdown route is authenticated by the shell's Bearer token, not the cookie\n * session (the shell holds no cookie), so it mounts OUTSIDE authMiddleware and only\n * when desktop mode is enabled. Responds 202 first, then triggers the graceful\n * shutdown a beat later so the response isn't cut off by the closing listener.\n */\nimport { Hono } from \"hono\";\nimport type { MiddlewareHandler } from \"hono\";\nimport { HttpError } from \"../errors.js\";\nimport type { AppDeps } from \"../../app.js\";\n\n/**\n * Guard for user-management surfaces (admin users, Project members): the desktop app is\n * single-user, so the whole surface answers 403 with a dedicated code rather than being\n * unmounted — a stray client gets a clear, localizable error instead of a 404. Existing\n * users and memberships in the data root are untouched; only the management routes are\n * closed while the server runs under the desktop shell.\n */\nexport function rejectInDesktopMode(deps: AppDeps): MiddlewareHandler {\n return async (_c, next) => {\n if (deps.desktop !== null) {\n throw new HttpError(\n 403,\n \"desktop_single_user\",\n \"User management is disabled in the desktop app (single-user mode).\",\n );\n }\n await next();\n };\n}\n\n/** Delay between answering 202 and starting shutdown: lets the response flush. */\nconst SHUTDOWN_DELAY_MS = 50;\n\nexport function desktopRoutes(deps: AppDeps): Hono {\n const app = new Hono();\n\n app.post(\"/shutdown\", (c) => {\n const desktop = deps.desktop;\n if (!desktop) throw new HttpError(404, \"not_found\", \"Desktop mode is not enabled.\");\n const header = c.req.header(\"authorization\") ?? \"\";\n const token = header.startsWith(\"Bearer \") ? header.slice(\"Bearer \".length) : \"\";\n if (token === \"\" || !desktop.verifyToken(token)) {\n throw new HttpError(401, \"unauthorized\", \"Invalid desktop token.\");\n }\n setTimeout(() => desktop.requestShutdown(), SHUTDOWN_DELAY_MS).unref();\n return c.body(null, 202);\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 * Admin server-settings routes (admin only, 403 for non-admins):\n * GET|PUT /api/admin/settings — the server-global settings stored in server_settings\n * (currently the proxy settings: the \"application uses the proxy\" and \"agent\n * environment uses the proxy\" switches and their shared explicit address).\n * A PUT applies immediately: everything is validated first (a rejected request writes\n * nothing), then the persisted values are written, then the process dispatcher is\n * rebuilt so new outbound connections follow the change without a restart (the agent\n * switch needs no push — the command-subprocess policy getter re-reads the repo at\n * every spawn).\n */\nimport { Hono } from \"hono\";\nimport type { ServerSettingsResponse } from \"../../api/types.js\";\nimport { HttpError } from \"../errors.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { optionalBoolean, readJson } from \"../validate.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { applyProxySettings, normalizeProxyUrl } from \"../../net/proxy.js\";\n\n/**\n * proxyUrl update value -> stored value: null and empty/whitespace-only clear the\n * address; anything else must normalize (see normalizeProxyUrl) or the whole PUT is\n * rejected with `invalid_proxy_url` — un-normalized values are never stored.\n */\nfunction parseProxyUrl(value: unknown): string | null {\n if (value === null) return null;\n if (typeof value === \"string\") {\n if (value.trim() === \"\") return null;\n const normalized = normalizeProxyUrl(value);\n if (normalized !== null) return normalized;\n }\n throw new HttpError(\n 400,\n \"invalid_proxy_url\",\n \"proxyUrl must be http://host[:port], https://host[:port], or host[:port] (empty or null clears it).\",\n );\n}\n\nexport function adminSettingsRoutes(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 const settings = (): ServerSettingsResponse => ({\n settings: {\n proxyForApp: deps.serverSettingsRepo.getProxyForApp(),\n proxyForAgent: deps.serverSettingsRepo.getProxyForAgent(),\n proxyUrl: deps.serverSettingsRepo.getProxyUrl(),\n },\n });\n\n app.get(\"/\", (c) => c.json(settings()));\n\n app.put(\"/\", async (c) => {\n const body = await readJson(c);\n // Validate every provided field before writing any: a partial PUT with one invalid\n // field must leave the others untouched too.\n const proxyForApp = optionalBoolean(body, \"proxyForApp\");\n const proxyForAgent = optionalBoolean(body, \"proxyForAgent\");\n const proxyUrlProvided = body.proxyUrl !== undefined;\n const proxyUrl = proxyUrlProvided ? parseProxyUrl(body.proxyUrl) : null;\n if (proxyForApp !== undefined) deps.serverSettingsRepo.setProxyForApp(proxyForApp);\n if (proxyForAgent !== undefined) deps.serverSettingsRepo.setProxyForAgent(proxyForAgent);\n if (proxyUrlProvided) deps.serverSettingsRepo.setProxyUrl(proxyUrl);\n // Mirror the app switch + address into the process: rebuilds the global fetch\n // dispatcher (live change, no restart; a no-op when nothing effectively changed).\n applyProxySettings({\n proxyForApp: deps.serverSettingsRepo.getProxyForApp(),\n proxyUrl: deps.serverSettingsRepo.getProxyUrl(),\n });\n return c.json(settings());\n });\n\n return app;\n}\n","/**\n * Auth routes: POST /api/auth/login | logout, GET /api/auth/desktop-login (desktop mode).\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 { HttpError } from \"../errors.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 // Desktop-mode sign-in: the window's FIRST navigation redeems the shell's one-shot\n // token for a standard admin cookie session and lands on the app — the desktop user\n // never sees the login page. 404 outside desktop mode (the route \"doesn't exist\");\n // a wrong or already-used token is a plain 401 with no distinction, so a leaked URL\n // reveals nothing and cannot be replayed.\n app.get(\"/desktop-login\", (c) => {\n const desktop = deps.desktop;\n if (!desktop) throw new HttpError(404, \"not_found\", \"Desktop mode is not enabled.\");\n const token = c.req.query(\"token\") ?? \"\";\n if (token === \"\" || !desktop.redeemLoginToken(token)) {\n throw new HttpError(401, \"unauthorized\", \"Invalid or already-used desktop token.\");\n }\n const { token: session } = deps.authService.loginDesktop();\n setCookie(c, SESSION_COOKIE, session, cookieOptions(c));\n return c.redirect(\"/\", 302);\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 desktopMode: deps.desktop !== null,\n sessionVia: c.var.sessionVia,\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 // Desktop sessions may omit oldPassword: the seed password of a desktop-created root is\n // random and never shown, so its holder has nothing to type — the shell's redeemed\n // token already proved machine ownership.\n app.put(\"/password\", async (c) => {\n const body = await readJson(c);\n const newPassword = requireString(body, \"newPassword\", { label: \"newPassword\" });\n const desktopSession = deps.desktop !== null && c.var.sessionVia === \"desktop\";\n if (desktopSession && body.oldPassword === undefined) {\n await deps.authService.setPasswordDesktop(c.var.user.userId, newPassword);\n } else {\n const oldPassword = requireString(body, \"oldPassword\", { label: \"oldPassword\" });\n await deps.authService.changePassword(c.var.user.userId, oldPassword, newPassword);\n }\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, 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 // Port 0 means \"the listener has not reported its actual port yet\" (PORT=0 picks an\n // ephemeral one; index.ts writes it back once bound). Emitting `:0` would produce a URL\n // no browser will load — Chromium rejects it outright with ERR_UNSAFE_PORT — so degrade\n // to \"no isolated preview\" instead, which the UI already handles by falling back to the\n // sandboxed same-origin preview.\n if (serverBind.port === 0) 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, PATCH|DELETE /api/projects/:p.\n */\nimport { Hono } from \"hono\";\nimport type {\n ProjectCreateResponse,\n ProjectUpdateResponse,\n ProjectsResponse,\n} from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport {\n badRequest,\n optionalString,\n readJson,\n requireString,\n requireValidId,\n} 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 /** Rename (owner): the display name only — the id names the directory and is immutable. */\n app.patch(\"/:projectId\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const body = await readJson(c);\n // Trimmed before the length check, so \" \" is rejected rather than stored as a blank\n // display name (minLen alone counts the spaces). The web client trims too; this makes a\n // direct API call behave the same.\n const name = requireString(body, \"name\", { maxLen: 100, label: \"name\" }).trim();\n if (name === \"\") throw badRequest(\"name must be at least 1 characters.\");\n const project = await deps.projectService.renameProject(c.var.user.userId, projectId, name);\n return c.json({ project } satisfies ProjectUpdateResponse);\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 * Desktop mode rejects the whole surface (single-user; 403 `desktop_single_user`).\n */\nimport { Hono } from \"hono\";\nimport type { MemberAddResponse, MembersResponse } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { rejectInDesktopMode } from \"./desktop.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.use(\"*\", rejectInDesktopMode(deps));\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, PUT /api/projects/:p/models/default,\n * 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 DefaultModelResponse,\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 // Narrow default-model switch (owner): flips the same top-level `default_model` the\n // whole-table PUT above maintains, without resending the table — project settings can\n // change the default without carrying credentials. The pair must name a configured\n // entry (400 otherwise, same rule as the whole-table route's defaultModel). No runtime\n // invalidation and no credentials_updated: existing Sessions pin their model at\n // creation, and no credential changes here.\n app.put(\"/default\", 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 ref: ModelRefDto = {\n provider: requireString(body, \"provider\", { minLen: 1, maxLen: 64 }),\n modelId: requireString(body, \"modelId\", { minLen: 1, maxLen: 200 }),\n };\n const defaultModel = await deps.projectConfigService.setDefaultModelRef(projectId, ref);\n return c.json({ defaultModel } satisfies DefaultModelResponse);\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 * New-chat defaults routes: GET|PUT /api/projects/:p/chat-defaults (the `[default_chat]`\n * block of .project_config.toml). Any member can read (the draft page prefills from it);\n * only the owner can replace it. PUT is declarative whole-block replace: an omitted key\n * clears it, an empty body removes the block. The default MODEL is not part of this block —\n * it stays the top-level `default_model` maintained via the models routes.\n */\nimport { Hono } from \"hono\";\nimport { DEFAULT_CHAT_THINKING_LEVELS, isValidId } from \"@prismshadow/penguin-core\";\nimport type { ChatDefaultsDto } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { HttpError } from \"../errors.js\";\nimport { optionalEnum, optionalString, readJson, requireValidId } from \"../validate.js\";\nimport { APPROVAL_MODES } from \"./sessions.js\";\nimport type { AppDeps } from \"../../app.js\";\n\nexport function chatDefaultsRoutes(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.getChatDefaults(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 body = await readJson(c);\n const req: ChatDefaultsDto = {};\n\n // agentId must reference an existing Agent of this Project (a stale default would make\n // every prefilled draft 404). isValidId first: existence is checked by path\n // (system_config.yaml presence, the same rule the agent routes use), so a junk id must\n // never reach path construction.\n const agentId = optionalString(body, \"agentId\", { minLen: 1, maxLen: 64, label: \"agentId\" });\n if (agentId !== undefined) {\n if (!isValidId(agentId) || !(await deps.agentConfigService.exists(projectId, agentId))) {\n throw new HttpError(\n 400,\n \"unknown_agent\",\n `agentId does not reference an Agent of this Project: ${agentId}.`,\n );\n }\n req.agentId = agentId;\n }\n\n // Not validated as an existing directory on purpose: the default is a prefill, and the\n // directory is (re)checked when a Session is actually created. \"\" = clear (temporary workspace).\n const workspace = optionalString(body, \"workspace\", { maxLen: 4096, label: \"workspace\" });\n if (workspace !== undefined && workspace !== \"\") req.workspace = workspace;\n\n const approvalMode = optionalEnum(body, \"approvalMode\", APPROVAL_MODES);\n if (approvalMode !== undefined) req.approvalMode = approvalMode;\n\n // Only the four selectable tiers — \"none\" is rejected (never offered as a default).\n const thinkingLevel = optionalEnum(body, \"thinkingLevel\", DEFAULT_CHAT_THINKING_LEVELS);\n if (thinkingLevel !== undefined) req.thinkingLevel = thinkingLevel;\n\n return c.json(await deps.projectConfigService.setChatDefaults(projectId, req));\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 MessagesPageInfo,\n MessagesResponse,\n ServerEvent,\n SessionCategory,\n SessionCreateResponse,\n SessionProcessesResponse,\n SessionResponse,\n SessionsResponse,\n RetryNowResponse,\n TaskCreateResponse,\n} from \"../../api/types.js\";\nimport { decodeCursor } from \"../../services/message-window.js\";\nimport type { MessagesPageRequest } from \"../../services/trace-service.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\";\nimport {\n assertAttachmentBudget,\n attachFilesToInput,\n parseAttachmentPart,\n removeAttachments,\n} from \"../../services/task-attachments.js\";\nimport type { TaskAttachment } from \"../../services/task-attachments.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\n/** The four approval modes (shared with the chat-defaults route's validation). */\nexport const 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/** Unit-count bounds for windowed history reads (`tailLimit` / `limit`), and the `before` page's default. */\nconst MESSAGES_PAGE_LIMIT_MAX = 1000;\nconst MESSAGES_PAGE_LIMIT_DEFAULT = 200;\n\n/** Parse one windowed-read unit-count param (positive integer, capped). */\nfunction pageLimit(raw: string, name: string): number {\n if (!/^\\d{1,4}$/.test(raw)) throw badRequest(`${name} must be a positive integer.`);\n const v = Number.parseInt(raw, 10);\n if (v < 1 || v > MESSAGES_PAGE_LIMIT_MAX) {\n throw badRequest(`${name} must be an integer between 1 and ${MESSAGES_PAGE_LIMIT_MAX}.`);\n }\n return v;\n}\n\n/**\n * Parse GET /messages windowed-read params. No params → null: the legacy full-transcript\n * read, byte-identical to the pre-pagination response (other consumers depend on it).\n * `tailLimit=<n>` → the newest n units; `before=<cursor>[&limit=<n>]` → the n units\n * preceding the cursor. The two forms are mutually exclusive, and `limit` belongs to\n * `before` alone — mixing them is a caller bug worth a loud 400 rather than a guess.\n */\n/**\n * Appends the running Task's already-published input messages that the Trace read has not\n * caught up to yet. Duplication is decided by exact envelope-JSON identity — the engine\n * writes the very same envelopes, and the client's overlap dedup uses the same rule — and\n * only the history tail can contain them (inputs are the newest records when this races).\n */\nfunction appendPendingInputs(messages: OmniMessage[], pending: OmniMessage[]): OmniMessage[] {\n if (pending.length === 0) return messages;\n const tail = new Set(messages.slice(-50).map((m) => JSON.stringify(m)));\n const missing = pending.filter((m) => !tail.has(JSON.stringify(m)));\n return missing.length > 0 ? [...messages, ...missing] : messages;\n}\n\nfunction messagesPageQuery(c: Context): MessagesPageRequest | null {\n const rawTail = c.req.query(\"tailLimit\");\n const rawBefore = c.req.query(\"before\");\n const rawLimit = c.req.query(\"limit\");\n if (rawTail === undefined && rawBefore === undefined) {\n if (rawLimit !== undefined) throw badRequest(\"limit requires before.\");\n return null;\n }\n if (rawTail !== undefined) {\n if (rawBefore !== undefined) throw badRequest(\"tailLimit and before are mutually exclusive.\");\n if (rawLimit !== undefined) throw badRequest(\"limit only applies to before requests.\");\n return { kind: \"tail\", limit: pageLimit(rawTail, \"tailLimit\") };\n }\n const cursor = decodeCursor(rawBefore!);\n if (cursor === null) {\n throw badRequest(\"before must be a cursor of the form <shardIndex>:<ordinal>.\");\n }\n return {\n kind: \"before\",\n cursor,\n limit: rawLimit !== undefined ? pageLimit(rawLimit, \"limit\") : MESSAGES_PAGE_LIMIT_DEFAULT,\n };\n}\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/**\n * A base64 `data:` URL of an image, in the exact shape core parses it back out of\n * (`imagesToScratchpadPaths`): one mime type, the `;base64,` marker, a non-empty base64 body.\n * The mime is deliberately unconstrained — core maps the ones it knows to a file extension and\n * falls back to `.bin`, and the image tools sniff the magic bytes rather than trusting either.\n *\n * Checking the body, not just the `data:` prefix, is what keeps the failure here instead of\n * three layers down: core turns a data URL it cannot parse into an \"[an attached image could\n * not be saved and was dropped]\" line, which for an HTTP caller means a 202 followed by a\n * message quietly missing its picture. The file-attachment field has always validated its own\n * payload this way (parseAttachmentPart); this is the same rule for images.\n */\nconst IMAGE_DATA_URL = /^data:[^;,]+;base64,[A-Za-z0-9+/=\\s]+$/;\n\n/**\n * The image-URL rule every image-carrying request field obeys: a `data:` URL the session\n * keeps (inline, or written to the scratchpad without vision) or an http(s) URL it references.\n * `field` names the offending value in the error, so each caller reads as if it validated\n * inline.\n */\nfunction requireImageUrl(url: unknown, field: string): string {\n if (typeof url === \"string\") {\n if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) return url;\n if (IMAGE_DATA_URL.test(url)) return url;\n }\n throw badRequest(\n `${field} must be an http(s) URL or a base64 data: URL (data:<mime>;base64,<bytes>).`,\n );\n}\n\n/**\n * Resolve a scratchpad file name to an absolute path inside `dir`, or null when it could point\n * anywhere else (the caller turns that into the same 404 a missing file gets, so a probe learns\n * nothing either way).\n *\n * A character whitelist is deliberately NOT the guard: an attachment keeps the name the user\n * gave it, `报告.pdf` included, so the check is structural instead — no separators, no control\n * characters, not a relative marker — and then *confirmed* by resolving the path and requiring\n * its parent to be this session's directory exactly. That last step is what actually contains\n * the read: it also rejects the shapes a character class misses, such as a Windows\n * drive-relative `C:evil.png`.\n */\nfunction resolveScratchpadFile(dir: string, fileName: string): string | null {\n if (!fileName || fileName === \".\" || fileName === \"..\") return null;\n for (const ch of fileName) {\n const code = ch.codePointAt(0)!;\n if (code < 0x20 || code === 0x7f || ch === \"/\" || ch === \"\\\\\") return null;\n }\n const resolved = path.resolve(dir, fileName);\n return path.dirname(resolved) === path.resolve(dir) ? resolved : null;\n}\n\n/**\n * A validated Prompt: the message parts that go straight into the run, plus the file\n * attachments, which still have to be written to disk (see attachFilesToInput). Kept apart\n * because validation stays synchronous and side-effect free — nothing touches the filesystem\n * until the request is known to be good, and goal mode can reject files before any bytes land.\n */\ninterface ParsedTaskInput {\n messages: OmniMessage[];\n attachments: TaskAttachment[];\n}\n\n/** Validate Prompt input parts: text, image (data: / http(s) URL), or an uploaded file. */\nfunction parseTaskInput(body: Record<string, unknown>): ParsedTaskInput {\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 const messages: OmniMessage[] = [];\n const attachments: TaskAttachment[] = [];\n input.forEach((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 messages.push(userText(part.text));\n return;\n }\n if (part.type === \"image_url\") {\n messages.push(imageUrlMessage(requireImageUrl(part.imageUrl, `input[${i}].imageUrl`)));\n return;\n }\n if (part.type === \"file\") {\n // Not an OmniMessage of its own: the file becomes an `[attached file: …]` line on the\n // text message once written to the scratchpad, so it carries no payload into the run.\n attachments.push(parseAttachmentPart(part, i));\n // Per-request count / total-bytes caps, re-checked on every part so a hostile `input`\n // is cut off at the item that crosses the line (see assertAttachmentBudget).\n assertAttachmentBudget(attachments);\n return;\n }\n throw badRequest(`input[${i}].type must be one of text / image_url / file.`);\n });\n return { messages, attachments };\n}\n\n/**\n * Validate the optional `images` field of a steer request: a list of `data:` / http(s) URLs\n * (same rule as a task input's `imageUrl`), absent or empty = a text-only steering message.\n */\nfunction parseSteerImages(body: Record<string, unknown>): string[] {\n const images = body.images;\n if (images === undefined) return [];\n if (!Array.isArray(images)) throw badRequest(\"images must be an array.\");\n return images.map((url, i) => requireImageUrl(url, `images[${i}]`));\n}\n\n/**\n * Validate the optional `files` field of a steer request: the same shape and caps as a task\n * input's `{type:\"file\"}` parts (parseAttachmentPart, budget re-checked per item), absent or\n * empty = no attachments.\n */\nfunction parseSteerFiles(body: Record<string, unknown>): TaskAttachment[] {\n const files = body.files;\n if (files === undefined) return [];\n if (!Array.isArray(files)) throw badRequest(\"files must be an array.\");\n const attachments: TaskAttachment[] = [];\n files.forEach((item, i) => {\n if (item === null || typeof item !== \"object\" || Array.isArray(item)) {\n throw badRequest(`files[${i}] must be an object.`);\n }\n attachments.push(parseAttachmentPart(item as Record<string, unknown>, i, \"files\"));\n assertAttachmentBudget(attachments);\n });\n return attachments;\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 // `cli=1` widens the list to CLI-created Sessions (Trace-directory discovery + adoption);\n // the default serves web rows straight from the DB (see SessionService.listSessions).\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 rawCli = c.req.query(\"cli\");\n if (rawCli !== undefined && rawCli !== \"1\") throw badRequest(\"cli 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 ...(rawCli !== undefined ? { includeCli: 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 (input images saved to disk for image-unsupported models, the\n // composer's file attachments, model-generated temp files): read by filename, so the\n // conversation UI can render a message's \"[attached image: <path>]\" attachment line back\n // into an image. Restricted to this session's own scratchpad directory (see\n // resolveScratchpadFile); a name is never reused for different bytes — uploads take a random\n // suffix on collision — 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 const filePath = resolveScratchpadFile(\n path.join(scratchpadDir(deps.config.root, row.projectId, row.agentId), row.sessionId),\n fileName,\n );\n if (!filePath) throw new HttpError(404, \"file_not_found\", \"File does not exist.\");\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 // SECURITY BOUNDARY — do not extend casually. This map is an allowlist of types that are\n // safe to hand a browser inline from the App's own origin, and it is the only reason the\n // bytes below (arbitrary user uploads and Agent-written temp files) cannot become stored\n // XSS. Every image type here is inert when rendered. Adding `.svg`, `.html`, `.pdf` or\n // anything else that a browser parses as a document would look like a one-line convenience\n // and would immediately be same-origin script execution — such a type needs the treatment\n // the Workspace read gives it (plain-text downgrade or a sandbox CSP), not a map entry.\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()];\n return c.body(new Uint8Array(bytes), 200, {\n \"content-type\": mime ?? \"application/octet-stream\",\n // nosniff: the composer's file attachments land in this same directory, so the bytes\n // here are arbitrary user content served from the App's own origin — without it a\n // browser could sniff an `application/octet-stream` upload back into HTML and run it\n // same-origin (the same defense workspace file reads apply).\n \"x-content-type-options\": \"nosniff\",\n // Second, independent layer for everything that fell off the allowlist: the only reason\n // this endpoint is fetched inline is the conversation's <img> tags, so anything that is\n // not one of those images is served as a download and never renders as a document —\n // nosniff alone would be the whole defense otherwise.\n ...(mime === undefined\n ? {\n \"content-disposition\": `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n }\n : {}),\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 page = messagesPageQuery(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 //\n // Windowed reads keep the same contract on TAIL pages only: a tail page ends at the\n // transcript's live edge, so the attachment's semantics are identical. A `before`\n // page is immutable history — attaching in-flight fragments to it would seed them at\n // the wrong position — so it never carries `live`.\n let live: MessagesLiveTail | undefined;\n let pendingInputs: OmniMessage[] = [];\n if (page?.kind !== \"before\") {\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 // The Task's inputs (published at launch) and its streamed bootstrap records\n // (mcp_connect pair / tool_list_ready): the engine's Trace writes for both land\n // only after the first run's connect, so a client rebuilding during that window\n // would otherwise see neither its own message nor the connecting status — a\n // silent blank while a slow MCP server times out. Appended below when the trace\n // read hasn't caught up; `before` pages are immutable history and never carry\n // them (same rule as `live`). NOT gated on running: a run aborted mid-bootstrap\n // wrote nothing to the Trace, and its held input is the only copy a reload can\n // show until the next run persists it (the holds survive idle for exactly that\n // case — see the manager's request_begin clear).\n pendingInputs = [\n ...deps.manager.pendingInputs(row.sessionId),\n ...deps.manager.pendingBootstrap(row.sessionId),\n ];\n }\n if (page !== null) {\n const result = await deps.traceService.readMessagesPage(\n row.projectId,\n row.agentId,\n row.sessionId,\n page,\n );\n const info: MessagesPageInfo = {\n ...(result.before !== undefined ? { before: result.before } : {}),\n earlierTurns: result.prior.turns,\n prior: {\n subagentTokens: result.prior.subagentTokens,\n elapsedMs: result.prior.elapsedMs,\n sessionTokens: result.prior.sessionTokens,\n contextTokens: result.prior.contextTokens,\n },\n };\n return c.json({\n messages: appendPendingInputs(result.messages, pendingInputs),\n ...(live !== undefined ? { live } : {}),\n page: info,\n } satisfies MessagesResponse);\n }\n const messages = await deps.traceService.readMessages(\n row.projectId,\n row.agentId,\n row.sessionId,\n );\n return c.json({\n messages: appendPendingInputs(messages, pendingInputs),\n ...(live !== undefined ? { live } : {}),\n } 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 pendingSteering = deps.manager.pendingSteeringOf(row.sessionId);\n const initialEvents: ServerEvent[] = [\n {\n type: \"task_state\",\n state: deps.manager.statusOf(row.sessionId),\n queued: deps.manager.pendingFollowUpCount(row.sessionId),\n // Undelivered steering rides the snapshot too, so the composer's \"steering queued\"\n // hint (and what it says) survives a reload.\n ...(pendingSteering.length > 0 ? { pendingSteering } : {}),\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 needs non-empty text, since its marker-stripped text becomes the\n // objective that every round re-injects and an image on its own doesn't say what the\n // goal is. Images can come along — core folds them into `[attached image: <path>]` lines\n // inside the objective (whatever the model's vision) so they survive the rounds. File\n // attachments cannot: nothing folds them into the objective, so they are turned away\n // here, before any upload is written to disk.\n const { messages, attachments } = parseTaskInput(body);\n const text = messages\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) {\n throw badRequest(\"goal mode requires a non-empty text objective.\");\n }\n if (attachments.length > 0) {\n throw badRequest(\"goal mode accepts text and images only (no file attachments).\");\n }\n const { sessionId } = await deps.manager.startGoal(row.sessionId, {\n input: messages,\n budget: goal.budget,\n ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),\n });\n return c.json({ sessionId } satisfies TaskCreateResponse, 202);\n }\n const parsed = 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 // Advisory pre-check, so the overwhelmingly common rejection — sending while a Task is\n // running, without queueIfBusy — never writes bytes it would then have to take back. The\n // authoritative check still runs under the Session lock inside startTask; this one is\n // lock-free and may pass on a race, which the cleanup below covers.\n deps.manager.assertCanAcceptTask(row.sessionId, { queueIfBusy });\n // File attachments land in this Session's scratchpad (deleted along with the Session) and\n // are handed to the model as `[attached file: <path>]` lines on the message text. Written\n // even when the task ends up queued as a follow-up: the queued input must be complete, and\n // the queue is drained by this same Session. A Trace-less Session that self-heals into a\n // new id below keeps its files under the id they were written with — the paths in the\n // message stay valid; only the delete-with-the-Session cleanup misses them in that case.\n const { input, written } = await attachFilesToInput(\n parsed.messages,\n parsed.attachments,\n scratchpadDir(deps.config.root, row.projectId, row.agentId),\n row.sessionId,\n );\n try {\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 } catch (err) {\n // The Task never started, so nothing references these files and nothing will ever clean\n // them up — and the Web keeps the chips on failure, so the user's retry would otherwise\n // land a second copy of every one of them.\n await removeAttachments(written);\n throw err;\n }\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, with any images following it as\n // user image messages (the model sees the whole thing without the loop being interrupted).\n // 409 not_running when no Task is in progress — the frontend then falls back to a normal\n // 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 const images = parseSteerImages(body);\n const files = parseSteerFiles(body);\n // Any part can carry the message on its own: an image or a file with no caption is a\n // complete steering message, and so is plain text.\n if (!text && images.length === 0 && files.length === 0) {\n throw badRequest(\"text, images or files must carry the steering message.\");\n }\n // The wire shape becomes core's: a user text message (omitted when the images are the\n // whole message, so the fold's path lines aren't preceded by a blank one) plus one image\n // message each — the same input a normal task would carry. File attachments land in the\n // Session scratchpad exactly as a task's do and ride as `[attached file: <path>]` lines\n // on the steering text (a files-only input becomes a line-only text message).\n const { input, written } = await attachFilesToInput(\n [...(text ? [userText(text)] : []), ...images.map((url) => imageUrlMessage(url))],\n files,\n scratchpadDir(deps.config.root, row.projectId, row.agentId),\n row.sessionId,\n );\n try {\n deps.manager.steer(row.sessionId, input, {\n text,\n images: images.length,\n files: files.length,\n });\n } catch (err) {\n // 409 (not running) or any other refusal: the files must not stay behind — the\n // frontend falls back to a normal task POST, which writes its own copies.\n await removeAttachments(written);\n throw err;\n }\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 // —— Background processes (the details popover's interactive list) ——\n\n // Processes the conversation started (exec_commands promoted to background). Served\n // from the ACTIVE runtime only: an evicted or never-loaded session truthfully reports\n // none — the environment that owned them is gone, and resurrecting an entry could only\n // ever produce an empty list anyway.\n app.get(\"/:sessionId/processes\", (c) => {\n const row = resolveSession(c);\n const processes = deps.manager.listProcesses(row.sessionId).map((p) => ({\n processId: p.processId,\n pid: p.pid,\n cmd: p.cmd,\n cwd: p.cwd,\n startedAt: new Date(p.startedAt).toISOString(),\n running: p.running,\n }));\n return c.json({ processes } satisfies SessionProcessesResponse);\n });\n\n // Stop one background process (SIGTERM to the whole group, SIGKILL after a grace\n // period). 404 when the id is gone — already exited and reaped, or the runtime was\n // evicted; the UI just refreshes its list either way.\n app.post(\"/:sessionId/processes/:processId/kill\", (c) => {\n const row = resolveSession(c);\n const killed = deps.manager.killProcess(row.sessionId, pathParam(c, \"processId\"));\n if (!killed) {\n throw new HttpError(\n 404,\n \"process_not_found\",\n \"Process does not exist or has already exited.\",\n );\n }\n return c.body(null, 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.\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 * Message-window scanner: the pure logic behind cursor pagination of\n * `GET /api/sessions/:id/messages` (TraceService.readMessagesPage).\n *\n * A window **unit** is one Task in the Web reducer's sense: it opens at a main-session\n * user prompt (text or image) and runs until the next such prompt. Cutting only at these\n * boundaries is what keeps every stream-model invariant intact inside a window — a\n * tool_call is never separated from its tool_call_output (outputs land before the next\n * user prompt), a compaction_begin/end span never splits (compaction-internal messages\n * stay skippable), a steering chip keeps the images that ride behind it, and a\n * mid-Task shard rotation (auto compaction with carry-over) stays glued to its Task.\n *\n * The scanner walks one shard's RAW messages (no subagent expansion — origin-carrying\n * messages never appear in a shard) and mirrors, decision for decision, the Web reducer\n * in web/src/lib/omni/stream-model.ts (pushMessage/startTask/finalizeOpenTask) plus the\n * outline-entry rule in web/src/features/chat/outline-model.ts (buildOutline) — the four\n * implementations of \"what is one turn\" (this file, stream-model, outline-model, and\n * trace-service.analyze) must stay in step; tests on both sides pin the shared cases.\n *\n * Two distinct counts come out of one pass:\n * - **unit boundaries** — the safe cut points described above (banner-only prompts and\n * goal-round re-sends included: they start Tasks in the reducer);\n * - **outline turns** — the subset that opens an entry in the Web's conversation\n * outline (`第 N 轮` numbering). Machine-only prompts (handoff / model-switch\n * blocks), goal rounds past 1, steering chips and compaction injections do NOT open\n * entries, and consecutive user messages of one send merge into one entry — the\n * count must match buildOutline exactly, or a paginated outline would mis-number.\n *\n * The pass also accumulates the **prior stats** a partial window needs seeded into the\n * Web's stats tracker so header chips and per-turn cumulative rows keep telling the\n * truth (see task-stats.ts `seedPriorStats`): elapsed time of finished Tasks, subagent\n * token totals (via the caller-provided child expander), and the last main-session\n * session/context token readings.\n *\n * Scan state is carried across shards (a Task can span a rotation). Cached per-shard\n * prefix records (trace_files.page_stats) persist the carry so old shards are read at\n * most once ever; bump CACHE_VERSION whenever any rule in this file changes.\n */\nimport { parseUserSteeringText } from \"@prismshadow/penguin-core\";\nimport {\n parseGoalMessage,\n parseHandoffMessage,\n parseModelSwitchMessage,\n} from \"@prismshadow/penguin-core/markers\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\n\n/** Bump when any counting/boundary rule changes: cached page_stats records with an older version are recomputed. */\nexport const CACHE_VERSION = 1;\n\n/** Cumulative totals at a point in the trace (all values are \"before this point\"). */\nexport interface WindowPriorStats {\n /** Outline entries opened before this point (the Web outline's global numbering offset). */\n turns: number;\n /** Sum of subagent token_usage request totals (all descendant sessions) before this point. */\n subagentTokens: number;\n /** Sum of finished Tasks' elapsed time before this point (the Web's sessionElapsedMs basis). */\n elapsedMs: number;\n /** The last main-session token_usage `session.total` seen before this point (0 = none). */\n sessionTokens: number;\n /** The last main-session NON-compaction token_usage `request.total` before this point (context occupancy basis). */\n contextTokens: number;\n}\n\n/** Open-Task bookkeeping carried across messages (mirrors StreamModel.task* fields). */\ninterface TaskCarry {\n open: boolean;\n /** Task first/latest message timestamps (ms; the degenerate-round elapsed fallback). */\n firstTsMs: number;\n lastTsMs: number;\n /** Last non-compaction request_end (ms); the Task's true end when present. */\n lastReqEndMs: number | null;\n /** Whether this Task saw main usage / non-blank assistant text — decides whether a stats row would be emitted at its close (which breaks a user-item run). */\n sawUsage: boolean;\n sawReply: boolean;\n /** Compaction usage held pending (committed to the Task by a later non-compaction request_end — mirrors task-stats.ts pendingCompaction*). */\n pendingCompactionUsage: boolean;\n}\n\n/** Full scanner state, serializable into a shard's cached prefix record. */\nexport interface ScanState {\n totals: WindowPriorStats;\n task: TaskCarry;\n /** Between a main-session compaction_begin and its compaction_end. */\n compactionActive: boolean;\n /** A steering chip is still collecting its trailing images (mirrors StreamModel.openSteering). */\n steeringOpen: boolean;\n /**\n * Inside an unbroken, entry-carrying run of user items — buildOutline's `lastWasUser`,\n * tracked at the item level: true after an entry-eligible user item, false after any\n * non-user item (a stats row included) AND after banner/goal-round texts (which open no\n * entry and break the run in buildOutline). Both the window-cut and the merge decision\n * key off it, so a cut can never split what the outline merges.\n */\n runOpen: boolean;\n}\n\nexport function initialScanState(): ScanState {\n return {\n totals: { turns: 0, subagentTokens: 0, elapsedMs: 0, sessionTokens: 0, contextTokens: 0 },\n task: {\n open: false,\n firstTsMs: 0,\n lastTsMs: 0,\n lastReqEndMs: null,\n sawUsage: false,\n sawReply: false,\n pendingCompactionUsage: false,\n },\n compactionActive: false,\n steeringOpen: false,\n runOpen: false,\n };\n}\n\n/** One safe cut point: `ordinal` indexes into the shard's parsed message array; `stats` is the cumulative prior AT the cut (the unit itself not included). */\nexport interface UnitBoundary {\n ordinal: number;\n stats: WindowPriorStats;\n}\n\n/**\n * Aggregate a subagent pointer's child subtree without materializing its messages:\n * the token sum feeds `subagentTokens`, the max timestamp advances the open Task's\n * lastTs exactly as routeNested's touchTask would for every expanded child message.\n * Null = child trace missing (the pointer event stays a plain event, contributing nothing).\n */\nexport interface ChildAggregate {\n requestTokens: number;\n maxTsMs: number | null;\n}\n\nfunction tsMs(timestamp: string): number | null {\n const ms = Date.parse(timestamp);\n return Number.isFinite(ms) ? ms : null;\n}\n\n/** Mirrors stream-model touchTask: advance the open Task's latest timestamp (compaction-internal messages excluded). */\nfunction touchTask(state: ScanState, ms: number | null): void {\n if (!state.task.open || state.compactionActive || ms === null) return;\n if (ms > state.task.lastTsMs) state.task.lastTsMs = ms;\n}\n\n/** Mirrors stream-model finalizeOpenTask + task-stats endTask: settle the open Task's elapsed into the cumulative. */\nfunction finalizeTask(state: ScanState): void {\n const t = state.task;\n if (!t.open) return;\n t.open = false;\n const endMs = t.lastReqEndMs ?? t.lastTsMs;\n state.totals.elapsedMs += Math.max(0, endMs - t.firstTsMs);\n t.pendingCompactionUsage = false;\n}\n\n/** Mirrors stream-model startTask: finalize the previous Task and open a new one at `ms`. */\nfunction startTask(state: ScanState, ms: number | null): void {\n finalizeTask(state);\n const t = state.task;\n t.open = true;\n t.firstTsMs = ms ?? 0;\n t.lastTsMs = t.firstTsMs;\n t.lastReqEndMs = null;\n t.sawUsage = false;\n t.sawReply = false;\n t.pendingCompactionUsage = false;\n}\n\n/** A non-user item entered the stream: the user-item run breaks (buildOutline's lastWasUser = false). */\nfunction breakRuns(state: ScanState): void {\n state.runOpen = false;\n}\n\n/**\n * Handle one Task-starting user message (prompt text or non-steering image).\n * `entryEligible` says whether the message would open an outline entry (buildOutline's\n * rule); ineligible messages (banner blocks, goal rounds > 1) still start Tasks — and\n * still cut when the run is broken — but never count and always break the run, exactly\n * as buildOutline resets lastWasUser for them.\n */\nfunction onTaskStart(\n state: ScanState,\n ms: number | null,\n entryEligible: boolean,\n onBoundary: (stats: WindowPriorStats) => void,\n): void {\n // A stats row emitted while closing the previous Task lands BEFORE this user item and\n // breaks the item run (finalizeOpenTask inserts it ahead of the new prompt) — mirror\n // that so two sends merged by the outline are never cut apart, while two sends\n // separated by a stats row cut (and count) as two.\n if (state.task.open && (state.task.sawUsage || state.task.sawReply)) breakRuns(state);\n const boundary = !state.runOpen;\n startTask(state, ms);\n if (boundary) onBoundary({ ...state.totals });\n if (entryEligible) {\n if (boundary) state.totals.turns += 1;\n state.runOpen = true;\n } else {\n state.runOpen = false;\n }\n}\n\n/** buildOutline's eligibility for a user prompt TEXT: machine-only source blocks and goal rounds past 1 open no entry. */\nfunction outlineEligibleText(text: string): boolean {\n if (parseHandoffMessage(text) || parseModelSwitchMessage(text)) return false;\n const goal = parseGoalMessage(text);\n return !(goal !== null && goal.round > 1);\n}\n\n/**\n * Scan one shard's raw messages, mutating `state` in place and reporting every unit\n * boundary through `onBoundary` (with the cumulative priors AT the cut). `expandChild`\n * resolves a subagent pointer's aggregate (may hit a per-request memo); pass null to\n * skip child reads when the caller does not need subagent totals for this span.\n */\nexport async function scanMessages(\n state: ScanState,\n messages: readonly OmniMessage[],\n onBoundary: (ordinal: number, stats: WindowPriorStats) => void,\n expandChild: ((sessionId: string) => Promise<ChildAggregate | null>) | null,\n fromOrdinal = 0,\n toOrdinal = messages.length,\n): Promise<void> {\n for (let i = fromOrdinal; i < toOrdinal; i++) {\n const msg = messages[i]!;\n // Shards never contain origin-carrying messages (core's Writer filters them);\n // defensively skip any that appear rather than mis-shaping the counts.\n if (msg.origin !== undefined && msg.origin.length > 0) continue;\n const ms = tsMs(msg.timestamp);\n const p = msg.payload as Record<string, unknown> & { type?: string; role?: string };\n\n // Mirrors pushMessage's first step: anything that is not a complete user image\n // closes the steering-images window (session_meta and events included).\n const isUserImage = msg.type === \"model_msg\" && p.type === \"image_url\";\n if (!isUserImage) state.steeringOpen = false;\n\n if (msg.type === \"model_msg\") {\n // Compaction-internal model messages: never rendered, never counted (stream-model\n // returns before any item/Task logic; only touchTask advances).\n if (state.compactionActive) {\n touchTask(state, ms);\n continue;\n }\n if (p.type === \"text\" && p.role === \"user\" && typeof p.text === \"string\") {\n const text = p.text;\n // Compaction-summary injection: no item, no Task, no run change.\n if (text.startsWith(\"[context_summary]\") || text.startsWith(\"<context_summary>\")) {\n touchTask(state, ms);\n continue;\n }\n // Mid-run steering: rendered as a user_steering item (not user_text) — it breaks\n // the outline's user-item run but never starts a Task or cuts a window.\n if (parseUserSteeringText(text) !== null) {\n touchTask(state, ms);\n breakRuns(state);\n state.steeringOpen = true;\n continue;\n }\n onTaskStart(state, ms, outlineEligibleText(text), (stats) => onBoundary(i, stats));\n continue;\n }\n if (p.type === \"image_url\") {\n // An image riding behind a steering chip folds into it: no item, no Task.\n if (state.steeringOpen) {\n touchTask(state, ms);\n continue;\n }\n onTaskStart(state, ms, true, (stats) => onBoundary(i, stats));\n continue;\n }\n if (p.type === \"text\" && p.role === \"assistant\" && typeof p.text === \"string\") {\n touchTask(state, ms);\n // Blank fidelity-only messages produce no item (stream-model discards them).\n if (p.text.trim() !== \"\") {\n state.task.sawReply = true;\n breakRuns(state);\n }\n continue;\n }\n if (p.type === \"thinking\") {\n touchTask(state, ms);\n if (typeof p.thinking === \"string\" && p.thinking.trim() !== \"\") breakRuns(state);\n continue;\n }\n if (p.type === \"tool_call\") {\n touchTask(state, ms);\n breakRuns(state);\n continue;\n }\n // tool_call_output updates an existing card (no new item); inline_* render nothing.\n touchTask(state, ms);\n continue;\n }\n\n if (msg.type === \"event_msg\") {\n touchTask(state, ms);\n const t = p.type;\n if (t === \"compaction_begin\") {\n state.compactionActive = true;\n breakRuns(state); // the compaction banner is an item\n continue;\n }\n if (t === \"compaction_end\") {\n // Closes the banner opened by the begin (no new item mid-window).\n state.compactionActive = false;\n continue;\n }\n if (t === \"abort\") {\n breakRuns(state); // the interruption marker is an item\n continue;\n }\n if (t === \"request_end\") {\n if (state.compactionActive) continue; // compaction requests render nothing\n if (ms !== null) state.task.lastReqEndMs = ms;\n // Pending compaction usage commits at a later non-compaction request_end\n // (compaction mid-Task) — from then on the Task WILL show a stats row.\n if (state.task.pendingCompactionUsage) {\n state.task.sawUsage = true;\n state.task.pendingCompactionUsage = false;\n }\n const status = p.status;\n // A retryable end renders a reconnect-hint item.\n if (status === \"failed\" || status === \"timeout\" || status === \"malformed\") {\n breakRuns(state);\n }\n continue;\n }\n if (t === \"token_usage\") {\n const request = p.request as { total?: number } | undefined;\n const session = p.session as { total?: number } | undefined;\n // The session cumulative tracks the provider even during compaction\n // (task-stats trackMainUsage does the same).\n if (typeof session?.total === \"number\") state.totals.sessionTokens = session.total;\n if (state.compactionActive) {\n state.task.pendingCompactionUsage = true;\n } else {\n if (typeof request?.total === \"number\") state.totals.contextTokens = request.total;\n state.task.sawUsage = true;\n }\n continue;\n }\n if (t === \"subagent\" && typeof p.session_id === \"string\" && expandChild !== null) {\n // The pointer expands to the child's messages in the served transcript: their\n // token_usage feeds the parent's subagent totals at every depth, and their\n // timestamps advance the open Task exactly as routeNested's touchTask would.\n const agg = await expandChild(p.session_id);\n if (agg !== null) {\n state.totals.subagentTokens += agg.requestTokens;\n touchTask(state, agg.maxTsMs);\n }\n continue;\n }\n // approval_decision / request_begin / unexpanded pointers: no items, no run change.\n continue;\n }\n // session_meta: no item (steering window already closed above).\n }\n}\n\n/** Finalize the trailing open Task (end of the whole trace): the cumulative then covers every finished Task. */\nexport function finalizeScan(state: ScanState): void {\n finalizeTask(state);\n}\n\n// ---------------------------------------------------------------------------\n// Cursor encoding\n// ---------------------------------------------------------------------------\n\n/** A window cursor: shard file index + message ordinal within that shard's parsed array. */\nexport interface MessageCursor {\n fileIndex: number;\n ordinal: number;\n}\n\n/**\n * `<fileIndex>:<ordinal>` — stable across requests and compaction: rotation always\n * opens a NEW shard, closed shards are immutable, and the active shard is append-only,\n * so a (shard, ordinal) pair never moves.\n */\nexport function encodeCursor(c: MessageCursor): string {\n return `${c.fileIndex}:${c.ordinal}`;\n}\n\n/** Strict parse of a cursor string; null when malformed (callers turn that into a 400). */\nexport function decodeCursor(raw: string): MessageCursor | null {\n const m = /^(\\d{1,9}):(\\d{1,9})$/.exec(raw);\n if (!m) return null;\n return { fileIndex: Number(m[1]), ordinal: Number(m[2]) };\n}\n\n// ---------------------------------------------------------------------------\n// Cached per-shard prefix records\n// ---------------------------------------------------------------------------\n\n/**\n * The persisted shape of trace_files.page_stats: the scan state at the END of a shard\n * (cumulative from the very beginning of the session). Only immutable shards are cached\n * — the newest shard still grows. `v` gates rule evolution: a record from an older\n * CACHE_VERSION is recomputed as if absent.\n */\nexport interface ShardPrefixRecord {\n v: number;\n state: ScanState;\n}\n\nexport function serializePrefix(state: ScanState): string {\n return JSON.stringify({ v: CACHE_VERSION, state } satisfies ShardPrefixRecord);\n}\n\n/** Parse a cached record; null when absent, unparseable, or from another CACHE_VERSION. */\nexport function deserializePrefix(raw: string | null): ScanState | null {\n if (raw === null) return null;\n try {\n const rec = JSON.parse(raw) as ShardPrefixRecord;\n if (rec.v !== CACHE_VERSION || typeof rec.state !== \"object\" || rec.state === null) {\n return null;\n }\n return rec.state;\n } catch {\n return null;\n }\n}\n\n/** Deep-copy a scan state (cached records must not be mutated by a later scan). */\nexport function cloneScanState(state: ScanState): ScanState {\n return {\n totals: { ...state.totals },\n task: { ...state.task },\n compactionActive: state.compactionActive,\n steeringOpen: state.steeringOpen,\n runOpen: state.runOpen,\n };\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 workspace).\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 workspace.`,\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 { WorkspaceFileEntry, 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 /** True when a bounded preview returned only the beginning of the file. */\n truncated?: boolean;\n}\n\nexport interface WorkspaceFileReadOptions {\n /** Return at most this many bytes. Used for bounded text previews. */\n maxBytes?: number;\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 /**\n * List a directory: dirs come first, each group sorted by name. Entries whose\n * canonical target leaves the Workspace are omitted, so listing cannot expose\n * metadata for an out-of-bounds symlink.\n */\n async list(workspace: string, rel: string): Promise<WorkspaceFilesResponse> {\n const dir = await this.resolveRead(workspace, rel);\n const realBase = await this.realBase(workspace);\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 listed = await Promise.all(\n dirents.map(async (d): Promise<WorkspaceFileEntry | null> => {\n try {\n const canonical = await fs.realpath(path.join(dir, d.name));\n this.assertInside(canonical, realBase);\n const stat = await fs.stat(canonical);\n return {\n name: d.name,\n kind: stat.isDirectory() ? \"dir\" : \"file\",\n sizeBytes: stat.size,\n mtime: stat.mtime.toISOString(),\n };\n } catch {\n return null;\n }\n }),\n );\n const entries = listed.filter((entry): entry is WorkspaceFileEntry => entry !== null);\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(\n workspace: string,\n rel: string,\n options?: WorkspaceFileReadOptions,\n ): 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 const maxBytes = options?.maxBytes;\n if (\n maxBytes !== undefined &&\n (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > MAX_READ_BYTES)\n ) {\n throw badRequest(\"maxBytes must be a positive integer within the read limit.\");\n }\n if (maxBytes === undefined && stat.size > MAX_READ_BYTES) {\n throw new HttpError(413, \"file_too_large\", \"File exceeds the 50MB read limit.\");\n }\n let data: Buffer;\n let truncated = false;\n if (maxBytes !== undefined && stat.size > maxBytes) {\n const handle = await fs.open(file, \"r\");\n try {\n const buffer = Buffer.alloc(maxBytes);\n const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);\n data = buffer.subarray(0, bytesRead);\n truncated = true;\n } finally {\n await handle.close();\n }\n } else {\n data = await fs.readFile(file);\n }\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 ...(truncated ? { truncated: true } : {}),\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 * Composer file attachments — the `{type:\"file\"}` variant of TaskInputPart.\n *\n * The browser has no Session while a chat is still a draft, so there is no upload endpoint to\n * call before sending: the file rides the task request itself as a base64 `data:` URL, exactly\n * like a pasted image does. On the way in it is written to the **Session scratchpad**\n * (`<agent>/scratchpad/<sessionId>/`, deleted with the Session, so cleanup is free) and the\n * message text gains one `[attached file: <absolute path>]` line per file — the bytes never\n * enter the conversation, the model opens the file by path with its ordinary file tools.\n *\n * The line format and its placement are not defined here: they are shared with core's\n * `[attached image: …]` producer and the Web renderer that parses both\n * (`@prismshadow/penguin-core/markers` → attachment-lines.ts, plus `appendAttachmentLines`),\n * so the two conventions cannot drift apart.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n appendAttachmentLines,\n attachedFileLine,\n modelVisiblePath,\n} from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\n\n/**\n * Per-file cap. Deliberately below the workspace upload's 14MB: several attachments can ride\n * one task request, and the whole body still has to fit the global 20MB limit (base64 inflates\n * by 4/3), so a smaller per-file ceiling keeps \"one big file\" working while leaving room for\n * \"a handful of ordinary ones\". Oversize is a 413, matching the workspace upload route.\n */\nexport const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;\n\n/**\n * Per-request caps, checked while the parts are validated — before a single byte reaches the\n * disk. They are deliberately NOT delegated to the global body cap: this module has to hold on\n * its own, so that a change to that middleware (or a caller that never goes through it) cannot\n * silently unbound it. Without them a legal 20MB body fits ~350k minimal `file` parts, which is\n * 350k sequential writes into one directory and 350k marker lines on one message.\n *\n * 20 files is far past any plausible composer use — the chip row stops being usable long before\n * that — while still allowing \"drop a folder of small files in\". 12MB of decoded bytes keeps one\n * full-size 10MB attachment usable next to a couple of ordinary ones; base64 inflates by 4/3, so\n * 12MB decoded is ~16MB of body and this cap, not the 20MB body cap, is the one a caller\n * actually reaches.\n */\nexport const MAX_ATTACHMENT_COUNT = 20;\nexport const MAX_TOTAL_ATTACHMENT_BYTES = 12 * 1024 * 1024;\n\n/**\n * Longest stem kept on disk, measured in **UTF-8 bytes**: filesystems cap a name near 255\n * bytes, and a CJK character costs three of them — a character count would let a Chinese name\n * blow the real limit while an English one stayed far under it.\n */\nconst MAX_STEM_BYTES = 80;\n\n/** Windows reserves these device names with or without an extension (`con`, `con.txt`), case-insensitively. */\nconst WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;\n\n/** Format and control characters (Unicode category C) — invisible, and the vector behind right-to-left file-name spoofing. */\nconst INVISIBLE_CHAR = /\\p{C}/u;\n\n/**\n * True when a character must not reach a file name. ASCII keeps the long-standing whitelist:\n * a space or a shell metacharacter inside a path the model is about to paste into a command is\n * a footgun, so anything outside `[A-Za-z0-9._-]` still becomes `-` down there. Above ASCII the\n * rule inverts — the character is kept as typed, so `报告.pdf` reaches the model as `报告.pdf`\n * instead of collapsing to an anonymous `file.pdf` (CJK, accents and emoji are all harmless to\n * a shell). The exception is Unicode category C: invisible controls, and the bidi overrides\n * that let a name render as something it is not.\n */\nfunction unsafeNameChar(ch: string): boolean {\n if (/[A-Za-z0-9._-]/.test(ch)) return false;\n return ch.codePointAt(0)! < 0x80 || INVISIBLE_CHAR.test(ch);\n}\n\n/** Replace every unsafe character with `-`, iterating code points so a surrogate pair survives intact. */\nfunction sanitizeSegment(value: string): string {\n return Array.from(value, (ch) => (unsafeNameChar(ch) ? \"-\" : ch)).join(\"\");\n}\n\n/** Truncate to a UTF-8 byte budget on character boundaries (iterating a string yields whole code points, so a surrogate pair is never split). */\nfunction truncateBytes(value: string, maxBytes: number): string {\n if (Buffer.byteLength(value) <= maxBytes) return value;\n let out = \"\";\n for (const ch of value) {\n if (Buffer.byteLength(out) + Buffer.byteLength(ch) > maxBytes) break;\n out += ch;\n }\n return out;\n}\n\n/** The 6-hex space makes a second collision negligible; the cap only guards against a filesystem stuck on EEXIST. */\nconst MAX_NAME_ATTEMPTS = 16;\n\n/** One validated attachment, bytes already decoded (they are held in memory only until the write below). */\nexport interface TaskAttachment {\n /** Original file name as submitted (validated: non-empty, no path separators, no `..`). */\n fileName: string;\n bytes: Buffer;\n}\n\n/**\n * Validate one `{type:\"file\"}` input part. Shape problems are 400s in the same style as the\n * neighbouring text/image checks; only the size cap answers 413 (`file_too_large`, the code\n * the Web App already has copy for). `index` is the part's position in the request array\n * named by `field` (default `input`; the steer route passes `files`), so the message points\n * at the offending item like the other input errors do.\n */\nexport function parseAttachmentPart(\n part: Record<string, unknown>,\n index: number,\n field = \"input\",\n): TaskAttachment {\n const fileName = part.fileName;\n // Path separators and `..` are rejected rather than sanitized away: the name is the user's,\n // and a name that looks like a path means the caller is confused about the contract (the\n // write below composes the path itself, and sanitization happens there).\n if (\n typeof fileName !== \"string\" ||\n fileName.length === 0 ||\n fileName.includes(\"/\") ||\n fileName.includes(\"\\\\\") ||\n fileName.includes(\"..\") ||\n fileName.includes(\"\\0\")\n ) {\n throw badRequest(\n `${field}[${index}].fileName must be a non-empty file name without path separators or \"..\".`,\n );\n }\n const dataUrl = part.dataUrl;\n // `[^,]*` for the media type, not `[^;,]*`: a browser may hand out parameters\n // (`data:text/plain;charset=utf-8;base64,…`), and only the `;base64,` marker separates the\n // type from the payload. The payload's character class is the actual check that it IS\n // base64 (whitespace tolerated — line-wrapped encoders decode fine).\n const match =\n typeof dataUrl === \"string\" ? /^data:[^,]*;base64,([A-Za-z0-9+/=\\s]+)$/.exec(dataUrl) : null;\n if (!match) {\n throw badRequest(`${field}[${index}].dataUrl must be a base64 data: URL of the file's bytes.`);\n }\n const bytes = Buffer.from(match[1]!, \"base64\");\n if (bytes.length === 0) {\n throw badRequest(`${field}[${index}].dataUrl decodes to an empty file.`);\n }\n if (bytes.length > MAX_ATTACHMENT_BYTES) {\n throw new HttpError(\n 413,\n \"file_too_large\",\n `Attached file exceeds the ${MAX_ATTACHMENT_BYTES / (1024 * 1024)}MB limit.`,\n );\n }\n return { fileName, bytes };\n}\n\n/**\n * Enforce the per-request caps against everything accepted so far. Called after **each** `file`\n * part rather than once at the end, so an oversized `input` stops at the part that crosses the\n * line instead of base64-decoding the whole array first. Both answer 413: the count reuses a\n * dedicated `too_many_files` code, the aggregate the `payload_too_large` the body cap already\n * uses — from the caller's side it is the same \"this request is too big\" outcome.\n */\nexport function assertAttachmentBudget(attachments: TaskAttachment[]): void {\n if (attachments.length > MAX_ATTACHMENT_COUNT) {\n throw new HttpError(\n 413,\n \"too_many_files\",\n `A message may carry at most ${MAX_ATTACHMENT_COUNT} attached files.`,\n );\n }\n let total = 0;\n for (const a of attachments) total += a.bytes.length;\n if (total > MAX_TOTAL_ATTACHMENT_BYTES) {\n throw new HttpError(\n 413,\n \"payload_too_large\",\n `Attached files exceed the ${MAX_TOTAL_ATTACHMENT_BYTES / (1024 * 1024)}MB total limit for one message.`,\n );\n }\n}\n\n/**\n * Map a submitted name onto a name that is safe on disk **and** still recognizably the user's\n * own: `报告 2026.pdf` becomes `报告-2026.pdf` (see unsafeNameChar — the words survive, only the\n * shell-hostile ASCII is replaced), so the model reads a meaningful path and a person looking\n * at the message recognizes what they attached.\n *\n * The rest is Windows-shaped hygiene: trailing dots and spaces are dropped (Windows silently\n * strips them, so `a.` and `a` would be the same file), a reserved device name is prefixed\n * (`con.txt` → `_con.txt`), the stem is capped by UTF-8 bytes, and a stem that sanitizes away\n * entirely falls back to `file` rather than producing a bare extension.\n */\nfunction scratchpadName(fileName: string): string {\n const ext = sanitizeSegment(path.extname(fileName));\n const rawStem = fileName.slice(0, fileName.length - path.extname(fileName).length);\n // Trim after truncating: a cut can expose a trailing dot or space that was mid-name before.\n const stem = truncateBytes(sanitizeSegment(rawStem), MAX_STEM_BYTES).replace(/[. ]+$/, \"\");\n // Nothing but replacement dashes carries no more information than an empty stem did.\n if (!stem || /^-+$/.test(stem)) return `file${ext}`;\n return WINDOWS_RESERVED.test(stem) ? `_${stem}${ext}` : `${stem}${ext}`;\n}\n\n/**\n * Write one attachment into `dir` and return its absolute path. The plain sanitized name is\n * tried first (the model — and the user reading the message — sees `report.pdf`, not an opaque\n * id); \"wx\" makes the create exclusive, so a second upload of the same name lands next to the\n * first as `report-3f9a1c.pdf` instead of overwriting it (same convention as core's image\n * uploads).\n *\n * \"wx\" is O_CREAT|O_EXCL, which also refuses to follow a symlink at the final component: a link\n * planted at `report.pdf` fails with EEXIST and the retry allocates a suffixed name instead of\n * writing through it. The containment check for the *directory* is separate — see openScratchpadDir.\n */\nasync function writeAttachment(dir: string, attachment: TaskAttachment): Promise<string> {\n const base = scratchpadName(attachment.fileName);\n const ext = path.extname(base);\n const stem = base.slice(0, base.length - ext.length);\n for (let attempt = 0; attempt < MAX_NAME_ATTEMPTS; attempt++) {\n const name = attempt === 0 ? base : `${stem}-${randomBytes(3).toString(\"hex\")}${ext}`;\n const file = path.join(dir, name);\n try {\n await fs.writeFile(file, attachment.bytes, { flag: \"wx\" });\n return file;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n }\n }\n throw new Error(\n `failed to allocate a unique attachment file name under ${dir} after ${MAX_NAME_ATTEMPTS} attempts`,\n );\n}\n\n/**\n * Create (or reuse) this Session's scratchpad directory and hand back the path to write into.\n *\n * `fs.mkdir(…, {recursive:true})` succeeds silently when the directory is already a **symlink**\n * to somewhere else, and nothing downstream would notice — so the result is realpath'd and\n * required to still sit inside the Agent's scratchpad root, the same containment rule the\n * Workspace upload path applies (workspace-files-service.resolveWriteParent). Only the Agent\n * process can plant such a link and it runs as this server's uid, so this is consistency rather\n * than a privilege boundary; it costs one resolution per message that carries attachments.\n *\n * The check is on the canonical path but the write stays on the logical one: the path travels\n * into the message text, and the read/delete endpoints address a Session by its logical\n * directory, so canonicalizing here would only make those disagree on hosts where the data root\n * itself sits behind a link (macOS `/var`, a Windows 8.3 temp path).\n */\nasync function openScratchpadDir(root: string, sessionId: string): Promise<string> {\n const dir = path.join(root, sessionId);\n await fs.mkdir(dir, { recursive: true });\n const canonicalRoot = await fs.realpath(root);\n const rel = path.relative(canonicalRoot, await fs.realpath(dir));\n if (rel !== sessionId) {\n throw new Error(\n `session scratchpad ${dir} resolves outside the agent scratchpad root; refusing to write attachments`,\n );\n }\n return dir;\n}\n\n/** Best-effort undo of a batch of writes; errors are swallowed because every caller is already on an error path (a failed cleanup must not replace the original failure). */\nexport async function removeAttachments(files: string[]): Promise<void> {\n await Promise.all(files.map((f) => fs.rm(f, { force: true }).catch(() => {})));\n}\n\n/** Result of a write batch: the Prompt to run, plus the paths written so the caller can undo them if the Task never starts. */\nexport interface AttachedFiles {\n input: OmniMessage[];\n written: string[];\n}\n\n/**\n * Land every attachment in the Session scratchpad under `root` and return the Prompt with one\n * `[attached file: <path>]` line appended per file. Placement follows core's shared rule\n * (after the last user text message; attachments-only input becomes a line-only text\n * message), so a Prompt carrying both images and files still ends in a single trailing block.\n * Returns `messages` untouched when there is nothing to attach — no directory is created.\n *\n * All-or-nothing: a failure part-way through the batch removes what it already wrote, so a 500\n * never leaves files on disk that no message refers to. The caller owns the other half of that\n * guarantee — if starting the Task fails afterwards it must call removeAttachments(written),\n * otherwise the user's retry would land a second copy of every file.\n */\nexport async function attachFilesToInput(\n messages: OmniMessage[],\n attachments: TaskAttachment[],\n root: string,\n sessionId: string,\n): Promise<AttachedFiles> {\n if (attachments.length === 0) return { input: messages, written: [] };\n const dir = await openScratchpadDir(root, sessionId);\n const written: string[] = [];\n try {\n // Sequential on purpose: the exclusive-create retry above resolves collisions against files\n // that already exist, and writing the batch one at a time keeps two same-named uploads in\n // the same message from racing each other for the plain name.\n for (const attachment of attachments) {\n written.push(await writeAttachment(dir, attachment));\n }\n } catch (err) {\n await removeAttachments(written);\n throw err;\n }\n return {\n input: appendAttachmentLines(\n messages,\n written.map((filePath) => attachedFileLine(modelVisiblePath(filePath))),\n ),\n written,\n };\n}\n","/**\n * Vault environment variable routes:\n * GET|PUT /api/projects/:p/agents/:a/vault (Agent-level, agent_state/.vault.toml).\n * POST …/vault/template-placeholder inserts/migrates the {{VAULT}} placeholder in the\n * prompt template.\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 // Insert (or migrate a legacy hardcoded # Vault section to) the {{VAULT}} placeholder —\n // the explicit adoption path mirroring memory's endpoint; idempotent config write,\n // owner-level like this router's other mutation.\n app.post(\"/template-placeholder\", 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 view = await deps.agentConfigService.insertTemplatePlaceholder(\n projectId,\n agentId,\n \"vault\",\n );\n return c.json(view.config.vault);\n });\n\n return app;\n}\n","/**\n * Memory routes (`agent_state/memory/`), all Project-member operations:\n * GET /api/projects/:p/agents/:a/memory # switch + scope groups (user scope first)\n * POST /api/projects/:p/agents/:a/memory/template-placeholder # insert the {{MEMORY}} placeholder into the template\n * GET /api/projects/:p/agents/:a/memory/scopes/:key/files # one scope's topic files\n * GET …/memory/scopes/:key/files/:name # one topic file's content\n * DELETE …/memory/scopes/:key/files/:name # delete + prune its index lines\n *\n * Deliberately read + delete only: content edits go through a chat Session where the model\n * maintains the files and their `MEMORY.md` index together. The `memory.enabled` switch is\n * Agent configuration and lives on PUT …/agents/:a/config.\n *\n * No route accepts an absolute path: a file is addressed by `agentId` + `scopeKey` + a name\n * inside that scope, and MemoryService re-checks that the resolved path stayed inside the\n * Agent's Memory directory.\n */\nimport { Hono } from \"hono\";\nimport type { Context } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport { pathParam, requireValidId } from \"../validate.js\";\n\nexport function memoryRoutes(deps: AppDeps): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n /** Shared preamble: id validation before any path construction (FD-4), then the Project membership check. */\n const scope = (c: Context<AppEnv>) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n return { projectId, agentId };\n };\n\n app.get(\"/\", async (c) => {\n const { projectId, agentId } = scope(c);\n return c.json(await deps.memoryService.overview(projectId, agentId));\n });\n\n // The explicit adoption path for a template that predates Memory (idempotent config write).\n app.post(\"/template-placeholder\", async (c) => {\n const { projectId, agentId } = scope(c);\n return c.json(await deps.memoryService.insertTemplatePlaceholder(projectId, agentId));\n });\n\n app.get(\"/scopes/:scopeKey/files\", async (c) => {\n const { projectId, agentId } = scope(c);\n const key = pathParam(c, \"scopeKey\");\n return c.json(await deps.memoryService.listFiles(projectId, agentId, key));\n });\n\n app.get(\"/scopes/:scopeKey/files/:fileName\", async (c) => {\n const { projectId, agentId } = scope(c);\n const key = pathParam(c, \"scopeKey\");\n const name = pathParam(c, \"fileName\");\n return c.json(await deps.memoryService.readFile(projectId, agentId, key, name));\n });\n\n app.delete(\"/scopes/:scopeKey/files/:fileName\", async (c) => {\n const { projectId, agentId } = scope(c);\n const key = pathParam(c, \"scopeKey\");\n const name = pathParam(c, \"fileName\");\n await deps.memoryService.deleteFile(projectId, agentId, key, name);\n return c.body(null, 204);\n });\n\n return app;\n}\n","/**\n * Schedule routes:\n * GET|POST /api/projects/:p/agents/:a/schedules\n * POST /api/projects/:p/agents/:a/schedules/template-placeholder # insert the {{SCHEDULES}} placeholder\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 // Insert the {{SCHEDULES}} placeholder into the prompt template — the explicit adoption\n // path mirroring memory's endpoint; idempotent config write, owner-level like this\n // router's other mutations. Registered before /:name, though the static path wins anyway.\n app.post(\"/template-placeholder\", 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 view = await deps.agentConfigService.insertTemplatePlaceholder(\n projectId,\n agentId,\n \"schedules\",\n );\n return c.json(view.config.schedules);\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.projectConfigService, 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; a temporary workspace is auto-created 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 { resolveModelRef, scheduleDir } from \"@prismshadow/penguin-core\";\nimport type { ProjectConfig } from \"@prismshadow/penguin-core\";\nimport { cacheable, statMtime } from \"../internal/mtime-gate.js\";\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/** One cached schedule file: its parsed entry plus the stat the parse is valid for. */\ninterface CachedScheduleFile {\n /** Gate-cacheable mtime (a fresh mtime is stored as a never-matching sentinel). */\n mtimeMs: number;\n sizeBytes: number;\n entry: ScheduleFileEntry;\n}\n\n/** One Agent's cached schedule dir: last seen dir mtime (null = did not exist) + files in listing order. */\ninterface CachedScheduleDir {\n mtimeMs: number | null;\n files: Map<string, CachedScheduleFile>;\n}\n\n/**\n * mtime-gated schedule-dir scans (the trace-index house pattern: disk stays the\n * single source of truth, mtime is the change signal, fresh mtimes are never cached\n * as clean, single-flight per Agent). The scheduler tick and the schedules routes\n * both list through here, so the steady state costs stats instead of a full\n * readdir + readFile + TOML parse of every file every 30s:\n *\n * - Unchanged dir mtime → the name set is intact (create/delete/rename all move it);\n * one stat per known file then catches in-place edits (a content write moves only\n * the FILE's mtime, never the dir's). No readdir, no reads.\n * - Changed dir mtime / first look / `force` → one readdir, then re-read only the\n * files whose mtime/size moved; unchanged files reuse their cached parse.\n * - Missing dir → cached as such; a later create is caught by the same stat.\n *\n * Manual edits are picked up by the next pass because every variant moves an mtime\n * this gate stats; the routes' write paths additionally force/invalidate for\n * immediate effect (see Scheduler.reconcileAgent / dropEntry).\n */\nexport class ScheduleFileCache {\n private readonly seen = new Map<string, CachedScheduleDir>();\n private readonly inflight = new Map<string, Promise<ScheduleFileEntry[]>>();\n /** Test observability: gateStats = dir stat calls; dirScans = readdir passes; fileReads = schedule-file content reads. */\n readonly counters = { gateStats: 0, dirScans: 0, fileReads: 0 };\n\n constructor(private readonly root: string) {}\n\n private keyOf(projectId: string, agentId: string): string {\n return `${projectId}\\0${agentId}`;\n }\n\n /**\n * Lists this Agent's schedule files (a missing directory is treated as empty).\n * `force` ignores the dir-mtime gate (write paths' immediate-effect entry); a\n * forced call issued while a scan is in flight chains a fresh pass behind it.\n * The returned entries are shared with the cache — callers must not mutate them.\n */\n list(\n projectId: string,\n agentId: string,\n opts: { force?: boolean } = {},\n ): Promise<ScheduleFileEntry[]> {\n const key = this.keyOf(projectId, agentId);\n const existing = this.inflight.get(key);\n if (existing) {\n return opts.force === true\n ? existing.then(() => this.list(projectId, agentId, opts))\n : existing;\n }\n const run = this.scan(projectId, agentId, opts.force === true).finally(() => {\n this.inflight.delete(key);\n });\n this.inflight.set(key, run);\n return run;\n }\n\n /** Write-time invalidation (route delete path): the next list re-scans regardless of mtimes. */\n invalidate(projectId: string, agentId: string): void {\n this.seen.delete(this.keyOf(projectId, agentId));\n }\n\n /** Drops every cached Agent of the Projects NOT in `live` (deleted Projects stop being ticked, so sweep here). */\n retainProjects(live: ReadonlySet<string>): void {\n for (const key of this.seen.keys()) {\n if (!live.has(key.slice(0, key.indexOf(\"\\0\")))) this.seen.delete(key);\n }\n }\n\n private async scan(\n projectId: string,\n agentId: string,\n force: boolean,\n ): Promise<ScheduleFileEntry[]> {\n const key = this.keyOf(projectId, agentId);\n const dir = scheduleDir(this.root, projectId, agentId);\n const cached = this.seen.get(key);\n this.counters.gateStats += 1;\n const dirMtime = await statMtime(dir);\n if (dirMtime === null) {\n this.seen.set(key, { mtimeMs: null, files: new Map() });\n return [];\n }\n const next: CachedScheduleDir = { mtimeMs: cacheable(dirMtime), files: new Map() };\n let names: string[];\n if (!force && cached !== undefined && cached.mtimeMs !== null && cached.mtimeMs === dirMtime) {\n // Same dir mtime (sentinels never match): the name set is unchanged; only\n // in-place content edits are possible, and the per-file stats below catch those.\n next.mtimeMs = cached.mtimeMs;\n names = [...cached.files.keys()];\n } else {\n this.counters.dirScans += 1;\n let listing: string[];\n try {\n listing = await fs.readdir(dir);\n } catch {\n // Deleted between stat and readdir: treat as missing; a later pass settles it.\n this.seen.set(key, { mtimeMs: null, files: new Map() });\n return [];\n }\n // Sort the full file names before stripping the suffix — the pre-existing listing order.\n names = listing\n .sort()\n .filter((f) => f.endsWith(\".toml\"))\n .map((f) => f.slice(0, -\".toml\".length));\n }\n const entries: ScheduleFileEntry[] = [];\n for (const name of names) {\n const file = await this.revalidate(dir, name, cached?.files.get(name));\n if (file === null) continue; // Deleted during reconciliation: revisit next round.\n next.files.set(name, file);\n entries.push(file.entry);\n }\n this.seen.set(key, next);\n return entries;\n }\n\n /** Reuses the cached parse while the file's stat still matches; re-reads otherwise. */\n private async revalidate(\n dir: string,\n name: string,\n cached: CachedScheduleFile | undefined,\n ): Promise<CachedScheduleFile | null> {\n const file = path.join(dir, `${name}.toml`);\n let stat: { mtimeMs: number; size: number };\n try {\n stat = await fs.stat(file);\n } catch {\n return null;\n }\n if (cached && cached.mtimeMs === stat.mtimeMs && cached.sizeBytes === stat.size) return cached;\n this.counters.fileReads += 1;\n let raw: string;\n try {\n raw = await fs.readFile(file, \"utf8\");\n } catch {\n return null;\n }\n return {\n mtimeMs: cacheable(stat.mtimeMs),\n sizeBytes: stat.size,\n entry: { name, raw, parsed: parseScheduleFile(name, raw) },\n };\n }\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/** Project-config source for model-ref validation (ProjectConfigService: same semantics as core's loadProjectConfig, served from its mtime-gated cache). */\nexport interface ScheduleConfigSource {\n loadConfig(projectId: string): Promise<ProjectConfig>;\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 configs: ScheduleConfigSource,\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 configs.loadConfig(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 * Shared mtime-gate primitives for the server's derived caches (Trace index, schedule\n * scans, project-config reads): disk stays the single source of truth, and a cached\n * value may be served only while the stat'd mtime still matches the one recorded at\n * read time. External edits are therefore caught by one stat instead of a re-read.\n *\n * The freshness guard is the subtle part of the contract: filesystem timestamps are\n * coarse (a write and a later change can land on the same tick), so an mtime younger\n * than FRESH_MS is never recorded as clean — `cacheable` returns a sentinel that can\n * never match a real stat, forcing re-reads until the path has been quiet. Every gate\n * in the server must go through these helpers so the contract stays in one place.\n */\nimport fs from \"node:fs/promises\";\n\n/**\n * mtimes younger than this are treated as UNSTABLE and never cached as clean: an\n * actively-written path costs one extra re-read per pass, and a gate can never wedge\n * on a same-tick change.\n */\nexport const FRESH_MS = 2000;\n\n/** A gate-cacheable mtime: the real value once stable, else a sentinel that never matches. */\nexport function cacheable(mtimeMs: number): number {\n return Date.now() - mtimeMs > FRESH_MS ? mtimeMs : -1;\n}\n\n/** mtimeMs of a path; null when it does not exist. */\nexport async function statMtime(p: string): Promise<number | null> {\n try {\n return (await fs.stat(p)).mtimeMs;\n } catch {\n return null;\n }\n}\n","/**\n * Benchmark scoring routes:\n * GET /api/projects/:p/agents/:a/benchmarks (any member, read-only)\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/files\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/files/content\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/rubric/files\n * GET /api/projects/:p/agents/:a/benchmarks/:benchmarkId/cases/:caseId/rubric/files/content\n * Returns the Agent's Benchmark list (title/description from benchmark_config.toml)\n * along with the evaluations[] from scoreboard.yaml.\n */\nimport { Hono, type Context } from \"hono\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport type { AppDeps } from \"../../app.js\";\nimport type { CaseMaterial } from \"../../api/types.js\";\nimport { requireValidId } from \"../validate.js\";\n\nconst TEXT_PREVIEW_BYTES = 256 * 1024;\n\nfunction listCaseFiles(deps: AppDeps, material: CaseMaterial) {\n return async (c: Context<AppEnv>) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const benchmarkId = requireValidId(c, \"benchmarkId\");\n const caseId = requireValidId(c, \"caseId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(\n await deps.benchmarks.listCaseFiles(\n projectId,\n agentId,\n benchmarkId,\n caseId,\n c.req.query(\"path\") ?? \"\",\n material,\n ),\n );\n };\n}\n\nfunction readCaseFile(deps: AppDeps, material: CaseMaterial) {\n return async (c: Context<AppEnv>) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const benchmarkId = requireValidId(c, \"benchmarkId\");\n const caseId = requireValidId(c, \"caseId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n const download = c.req.query(\"download\") === \"1\";\n const boundedPreview = !download && c.req.query(\"preview\") === \"1\";\n const { data, fileName, contentType, scriptable, truncated } =\n await deps.benchmarks.readCaseFile(\n projectId,\n agentId,\n benchmarkId,\n caseId,\n c.req.query(\"path\") ?? \"\",\n material,\n boundedPreview ? { maxBytes: TEXT_PREVIEW_BYTES } : undefined,\n );\n return new Response(new Uint8Array(data), {\n status: 200,\n headers: {\n \"Content-Type\": !download && scriptable ? \"text/plain; charset=utf-8\" : contentType,\n \"Content-Disposition\": `${download ? \"attachment\" : \"inline\"}; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n \"X-Content-Type-Options\": \"nosniff\",\n ...(truncated ? { \"X-Content-Truncated\": \"1\" } : {}),\n },\n });\n };\n}\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 app.get(\"/:benchmarkId/cases\", async (c) => {\n const projectId = requireValidId(c, \"projectId\");\n const agentId = requireValidId(c, \"agentId\");\n const benchmarkId = requireValidId(c, \"benchmarkId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n await deps.agentConfigService.requireExists(projectId, agentId);\n return c.json(await deps.benchmarks.listCases(projectId, agentId, benchmarkId));\n });\n\n app.get(\"/:benchmarkId/cases/:caseId/files\", listCaseFiles(deps, \"statement\"));\n app.get(\"/:benchmarkId/cases/:caseId/files/content\", readCaseFile(deps, \"statement\"));\n app.get(\"/:benchmarkId/cases/:caseId/rubric/files\", listCaseFiles(deps, \"rubric\"));\n app.get(\"/:benchmarkId/cases/:caseId/rubric/files/content\", readCaseFile(deps, \"rubric\"));\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 * POST /api/projects/:p/agents/:a/skills/template-placeholder # insert/migrate the {{SKILLS}} placeholder (any member)\n * POST /api/projects/:p/agents/:a/skills/archive # install one skill from an uploaded zip (any member)\n * GET /api/projects/:p/agents/:a/skills/:name/archive # export one installed skill as a zip (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 archive routes\n * are symmetric: POST writes every zip file under skills/<name>/ (replace semantics with\n * `overwrite`), GET packs the whole directory back under a single top-level <name>/ so\n * the download round-trips through the POST unchanged. The scope is small enough to skip\n * a service layer — routes call core's disk-writing functions directly.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { unzipSync, strFromU8, zipSync } from \"fflate\";\nimport { Hono } from \"hono\";\nimport {\n installSkill,\n listInstalledSkills,\n removeSkill,\n skillsDir,\n} from \"@prismshadow/penguin-core\";\nimport {\n librarySkill,\n loadSkillGroups,\n parseSkillFrontmatter,\n SKILL_NAME_PATTERN,\n} 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, requireString, requireValidId } from \"../validate.js\";\n\n/** Decoded zip cap: aligned with the Agent snapshot import (stays within the 20MB body limit after base64). */\nconst MAX_ARCHIVE_BYTES = 14 * 1024 * 1024;\n/** Uncompressed limits (guard against zip bombs): entry count / per-file / total. */\nconst MAX_ARCHIVE_FILES = 200;\nconst MAX_FILE_BYTES = 5 * 1024 * 1024;\nconst MAX_TOTAL_BYTES = 20 * 1024 * 1024;\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/**\n * Validates one zip entry path (zip-slip guard): rejects absolute paths (leading \"/\" or a\n * drive letter), backslashes and any \"..\" segment — a malicious archive must never write\n * outside the target Skill directory.\n */\nfunction assertSafeEntryPath(name: string): void {\n if (name.includes(\"\\\\\")) throw badRequest(`Invalid zip entry path (backslash): ${name}`);\n if (name.startsWith(\"/\") || /^[A-Za-z]:/.test(name)) {\n throw badRequest(`Invalid zip entry path (absolute): ${name}`);\n }\n if (name.split(\"/\").some((segment) => segment === \"..\")) {\n throw badRequest(`Invalid zip entry path (traversal): ${name}`);\n }\n}\n\n/** A skill decoded from an uploaded zip: name + file bytes keyed by path relative to the skill directory. */\ninterface ArchiveSkill {\n name: string;\n files: Map<string, Uint8Array>;\n}\n\n/**\n * Decodes and validates an uploaded skill zip. Accepted layouts: SKILL.md at the zip root\n * (name comes from frontmatter), or exactly one top-level directory containing SKILL.md\n * (the directory name is the Skill name, consistent with listInstalledSkills where the\n * directory name always wins). Directory entries are ignored (paths recreate them); every\n * file path is zip-slip-checked and the count/size limits enforced before anything is\n * returned. Frontmatter must parse to a non-null name, and the resolved Skill name must\n * match SKILL_NAME_PATTERN.\n */\nfunction parseSkillArchive(archive: Buffer): ArchiveSkill {\n let entries: Record<string, Uint8Array>;\n try {\n entries = unzipSync(new Uint8Array(archive));\n } catch {\n throw badRequest(\"dataBase64 is not a valid zip archive.\");\n }\n const files = Object.entries(entries).filter(([name]) => !name.endsWith(\"/\"));\n if (files.length === 0) throw badRequest(\"The zip archive contains no files.\");\n if (files.length > MAX_ARCHIVE_FILES) {\n throw badRequest(`The zip archive exceeds the ${MAX_ARCHIVE_FILES}-file limit.`);\n }\n let total = 0;\n for (const [name, data] of files) {\n assertSafeEntryPath(name);\n if (data.byteLength > MAX_FILE_BYTES) {\n throw badRequest(`Zip entry exceeds the 5MB uncompressed limit: ${name}`);\n }\n total += data.byteLength;\n if (total > MAX_TOTAL_BYTES) {\n throw badRequest(\"The zip archive exceeds the 20MB uncompressed limit.\");\n }\n }\n const names = files.map(([name]) => name);\n let prefix = \"\";\n let dirName: string | undefined;\n if (!names.includes(\"SKILL.md\")) {\n const topLevels = new Set(names.map((name) => name.split(\"/\", 1)[0]!));\n dirName = topLevels.size === 1 ? [...topLevels][0] : undefined;\n if (dirName === undefined || !names.includes(`${dirName}/SKILL.md`)) {\n throw badRequest(\n \"The zip must contain SKILL.md at its root, or exactly one top-level directory containing SKILL.md.\",\n );\n }\n prefix = `${dirName}/`;\n }\n const meta = parseSkillFrontmatter(strFromU8(entries[`${prefix}SKILL.md`]!));\n if (meta === null) {\n throw badRequest(\"SKILL.md must start with a frontmatter block that sets `name`.\");\n }\n const name = dirName ?? meta.name;\n if (!SKILL_NAME_PATTERN.test(name)) {\n throw badRequest(\n `Invalid skill name ${JSON.stringify(name)}: only letters, digits, \"_\" and \"-\" are allowed.`,\n );\n }\n return { name, files: new Map(files.map(([n, data]) => [n.slice(prefix.length), data])) };\n}\n\n/**\n * Recursively collects an installed skill directory as zip entries under a single\n * top-level `<name>/` directory (subpaths preserved, \"/\" separators), so the export\n * round-trips through the POST archive route unchanged. Symlinks and other non-regular\n * entries are skipped (nothing outside the directory can leak). The import caps apply\n * on the way out too — a directory exceeding them couldn't be re-imported anyway.\n */\nasync function collectSkillArchive(dir: string, name: string): Promise<Record<string, Uint8Array>> {\n const out: Record<string, Uint8Array> = {};\n let count = 0;\n let total = 0;\n const walk = async (abs: string, rel: string): Promise<void> => {\n for (const entry of await fs.readdir(abs, { withFileTypes: true })) {\n const absChild = path.join(abs, entry.name);\n const relChild = `${rel}/${entry.name}`;\n if (entry.isDirectory()) {\n await walk(absChild, relChild);\n continue;\n }\n if (!entry.isFile()) continue;\n count += 1;\n const data = new Uint8Array(await fs.readFile(absChild));\n total += data.byteLength;\n if (\n count > MAX_ARCHIVE_FILES ||\n data.byteLength > MAX_FILE_BYTES ||\n total > MAX_TOTAL_BYTES\n ) {\n throw new HttpError(\n 413,\n \"skill_too_large\",\n `Skill directory exceeds the archive limits (${MAX_ARCHIVE_FILES} files, 5MB per file, 20MB total).`,\n );\n }\n out[relChild] = data;\n }\n };\n await walk(dir, name);\n return out;\n}\n\n/**\n * Version for the export filename: only an explicit frontmatter `version:` field that\n * parses as a natural number yields a `-v<version>` filename suffix — parseSkillFrontmatter\n * defaults a missing field to 1, which must not be baked into a filename as if declared.\n * Mirrors the parser's frontmatter rules (first `---` block, `key: value` split on the\n * first colon, BOM/CRLF tolerated).\n */\nfunction explicitSkillVersion(skillMd: string): number | null {\n const block = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(skillMd.replace(/^\\uFEFF/, \"\"))?.[1];\n if (block === undefined) return null;\n for (const line of block.split(/\\r?\\n/)) {\n const idx = line.indexOf(\":\");\n if (idx <= 0 || line.slice(0, idx).trim() !== \"version\") continue;\n const version = Number.parseInt(line.slice(idx + 1).trim(), 10);\n return Number.isInteger(version) && version >= 1 ? version : null;\n }\n return null;\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 // Insert (or migrate a legacy hardcoded # Skills section to) the {{SKILLS}} placeholder —\n // the explicit adoption path mirroring memory's endpoint; idempotent config write,\n // member-level like every other mutation on this router.\n app.post(\"/template-placeholder\", 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 view = await deps.agentConfigService.insertTemplatePlaceholder(\n projectId,\n agentId,\n \"skills\",\n );\n return c.json(view.config.skills);\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 // Install one skill from an uploaded zip. Like the library POST this touches only the\n // files (no runtime invalidation): skills are read from disk on demand, so the next\n // prompt assembly already sees the new content.\n app.post(\"/archive\", 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 dataBase64 = requireString(body, \"dataBase64\", { minLen: 1, maxLen: 20 * 1024 * 1024 });\n const overwrite = body.overwrite === 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(\"The zip archive is empty.\");\n if (archive.byteLength > MAX_ARCHIVE_BYTES) {\n throw badRequest(\"The zip archive exceeds the 14MB limit.\");\n }\n const skill = parseSkillArchive(archive);\n const dir = path.join(skillsDir(deps.config.root, projectId, agentId), skill.name);\n // Installed-check uses the same criterion as listInstalledSkills: skills/<name>/SKILL.md exists.\n if (!overwrite) {\n const installed = await fs.access(path.join(dir, \"SKILL.md\")).then(\n () => true,\n () => false,\n );\n if (installed) {\n throw new HttpError(409, \"skill_exists\", `Skill is already installed: ${skill.name}`);\n }\n }\n // Replace semantics (same as reinstalling from the library): drop the old directory\n // first so no stale file survives, then write every archive file (subdirectories kept).\n await fs.rm(dir, { recursive: true, force: true });\n for (const [rel, data] of skill.files) {\n const file = path.join(dir, rel);\n await fs.mkdir(path.dirname(file), { recursive: true });\n await fs.writeFile(file, data);\n }\n return c.json(await listResponse(projectId, agentId), 201);\n });\n\n // Export one installed skill as a zip: served verbatim as an attachment (same shape as\n // the snapshot export / trace download — a direct binary body, not a JSON envelope), so\n // what's downloaded can be re-imported byte-compatibly via the POST archive route.\n app.get(\"/:name/archive\", 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 const dir = path.join(skillsDir(deps.config.root, projectId, agentId), name);\n // Installed-check uses the same criterion as listInstalledSkills: skills/<name>/SKILL.md exists.\n try {\n await fs.access(path.join(dir, \"SKILL.md\"));\n } catch {\n throw new HttpError(404, \"not_found\", `Skill is not installed: ${name}`);\n }\n const archiveFiles = await collectSkillArchive(dir, name);\n // A -v<version> suffix only when the frontmatter declares one explicitly (the header\n // is the authority on the filename — the web tab reads it from Content-Disposition).\n const version = explicitSkillVersion(strFromU8(archiveFiles[`${name}/SKILL.md`]!));\n const fileName = version === null ? `${name}.zip` : `${name}-v${version}.zip`;\n const zip = zipSync(archiveFiles);\n return new Response(new Uint8Array(zip), {\n headers: {\n \"Content-Type\": \"application/zip\",\n // The name is id-validated ([A-Za-z0-9_-]+), so the encoded filename is itself.\n \"Content-Disposition\": `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`,\n \"X-Content-Type-Options\": \"nosniff\",\n },\n });\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 // Trace-index rows describe files the rm above just removed: drop them with the Agent.\n deps.traceIndex.removeAgent(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), POST …/config/kernel-update to smart-merge up to the\n * current defaults generation (customizations kept), and POST …/config/mcp-test to\n * probe one MCP Server entry's reachability. Members can read and write (unrestricted).\n */\nimport { mkdtemp, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { Hono } from \"hono\";\nimport { McpToolProvider, resolveMCPServer } from \"@prismshadow/penguin-core\";\nimport type { MCPServerConfig } from \"@prismshadow/penguin-core\";\nimport type {\n AgentConfigResponse,\n AgentConfigUpdateRequest,\n AgentKernelUpdateResponse,\n McpServerTestResponse,\n} 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\n/** Ceiling on a single mcp-test probe's connect budget (an entry may configure minutes). */\nconst MCP_TEST_TIMEOUT_CAP_MS = 30_000;\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 // Probe one MCP Server entry: connect + discover tools with the entry's own\n // connectTimeoutMs, then close. Runs server-side on purpose — stdio servers spawn on\n // this host, exactly where Sessions run them, and browser-origin HTTP probes would\n // stumble over CORS. A malformed entry is a 400 (same resolver as PUT validation); an\n // unreachable server is a normal `{ ok: false }` result carrying the collected warning\n // (connect error, timeout, stderr tail). Nothing is written to the Agent State.\n app.post(\"/mcp-test\", 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 if (typeof body.name !== \"string\" || body.name.length === 0) {\n throw badRequest(\"name must be a non-empty string.\");\n }\n if (body.config === null || typeof body.config !== \"object\" || Array.isArray(body.config)) {\n throw badRequest(\"config must be an object.\");\n }\n const entry = { name: body.name, config: body.config } as MCPServerConfig;\n let resolved;\n try {\n resolved = resolveMCPServer(entry);\n } catch (err) {\n throw badRequest(err instanceof Error ? err.message : String(err));\n }\n // A probe must not hold a request open arbitrarily long (the bulk test walks every\n // server sequentially): the entry's own connect budget applies, capped for testing.\n if (resolved.connectTimeoutMs > MCP_TEST_TIMEOUT_CAP_MS) {\n entry.config = { ...entry.config, connectTimeoutMs: MCP_TEST_TIMEOUT_CAP_MS };\n }\n const warnings: string[] = [];\n // A fresh empty directory stands in for the Session Workspace (a new session's\n // temporary workspace is exactly that), so a stdio server that resolves relative\n // paths behaves like it will at runtime — not like the server process's own cwd.\n const workspaceDir = await mkdtemp(join(tmpdir(), \"penguin-mcp-test-\"));\n const provider = new McpToolProvider([entry], {\n workspaceDir,\n warn: (m) => warnings.push(m),\n });\n const startedAt = Date.now();\n try {\n const tools = await provider.listTools();\n const latencyMs = Date.now() - startedAt;\n // Verdict comes from the connect outcome itself, not from the warning count:\n // benign warnings (a tool listed twice / an LLM-unusable tool name) must not\n // report a healthy server as failed.\n const outcome = provider.connectResults()[0];\n if (outcome === undefined || outcome.status !== \"completed\") {\n return c.json({\n ok: false,\n error: outcome?.error ?? (warnings.length > 0 ? warnings.join(\"; \") : \"connect failed\"),\n latencyMs,\n } satisfies McpServerTestResponse);\n }\n return c.json({\n ok: true,\n tools: tools.map((t) => t.name),\n latencyMs,\n } satisfies McpServerTestResponse);\n } finally {\n await provider.close();\n void rm(workspaceDir, { recursive: true, force: true }).catch(() => undefined);\n }\n });\n\n // Smart-merge the config up to the current defaults generation (see\n // AgentConfigService.kernelUpdate): non-destructive sibling of /reset, same member-level\n // authorization as /reset and PUT. Responds with the merge report (advanced / kept paths +\n // the new stamp); the client re-GETs the config for the fresh values.\n app.post(\"/kernel-update\", 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 result = await deps.agentConfigService.kernelUpdate(projectId, agentId);\n return c.json(result satisfies AgentKernelUpdateResponse);\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 — Agent-level listing, served from the\n * trace-file index (mtime-gated reconcile, then pure DB — see services/trace-index.ts).\n * Without `limit`: the legacy full drill-down (Agent -> date -> Session -> index,\n * reverse order), never filtered. With optional `offset`/`limit` (+ optional\n * `category`, `cli`): pages Session groups newest-first (within the category when\n * given), stat-ing only the returned page for fresh sizes, and resolves per group a\n * display title (sessions DB title, else the registration-time first-prompt\n * fallback) plus its sidebar category / Workspace and per-category totals.\n * CLI-origin Sessions (no web sessions-table row, not subagent/schedule) are\n * excluded unless `cli=1` — the same \"show CLI sessions\" preference the sessions\n * list honors, applied server-side to rows, counts and workspace groups alike.\n * The listing consults the sessions table read-only (titles, archived, workspace,\n * client); discovery itself still comes from the Trace directory tree via the index.\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 { SessionCategory, TraceImportResponse } from \"../../api/types.js\";\nimport {\n badRequest,\n optionalPagingQuery,\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\n/** Accepted `category` query values of the paginated listing (SessionCategory, spelled out for validation — same as the sessions list route). */\nconst SESSION_CATEGORIES: readonly SessionCategory[] = [\n \"active\",\n \"subagent\",\n \"schedule\",\n \"archived\",\n];\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 // Both params absent -> null -> the legacy full response, byte-for-byte as before.\n const paging = optionalPagingQuery(c);\n // Optional category filter (paging then applies within the category): only meaningful\n // on the paginated session-centric shape — the legacy full drill-down has no category\n // notion, so a filtered-but-unpaged request is a client error, not a silent no-op.\n const rawCategory = c.req.query(\"category\");\n if (\n rawCategory !== undefined &&\n !(SESSION_CATEGORIES as readonly string[]).includes(rawCategory)\n ) {\n throw badRequest(`category must be one of ${SESSION_CATEGORIES.join(\" / \")}.`);\n }\n if (rawCategory !== undefined && paging === null) throw badRequest(\"category requires limit.\");\n // `cli=1` widens the paginated listing to CLI-origin Sessions (mirroring the sessions\n // list's parameter): the default follows the \"show CLI sessions\" preference's OFF\n // state. The legacy unpaged shape is never filtered (back-compat).\n const rawCli = c.req.query(\"cli\");\n if (rawCli !== undefined && rawCli !== \"1\") throw badRequest(\"cli only accepts 1.\");\n return c.json(\n await deps.traceService.agentTraces(projectId, agentId, paging, {\n ...(rawCategory !== undefined ? { category: rawCategory as SessionCategory } : {}),\n ...(rawCli !== undefined ? { includeCli: true } : {}),\n }),\n );\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 * GET /api/projects/:p/usage/errors?offset&limit&from&to&agentId — one page of the error\n * detail table, for paging back past the first page the dashboard already returns.\n */\nimport { Hono } from \"hono\";\nimport type { UsageGroupBy } from \"../../api/types.js\";\nimport type { AppEnv } from \"../../auth/middleware.js\";\nimport { badRequest, optionalDateParam, paginationQuery, 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 // One page of the error detail table, newest first. The dashboard response above already\n // carries the first page; this serves \"show me earlier ones\" without refetching the whole\n // aggregate. Takes the date/agent filter only — the model filter never applied to errors\n // (HTTP and process errors have no Model dimension), so accepting it here would imply a\n // narrowing the summary above does not do.\n app.get(\"/errors\", (c) => {\n const projectId = requireValidId(c, \"projectId\");\n deps.projectService.requireProjectAccess(c.var.user.userId, projectId);\n const { offset, limit } = paginationQuery(c);\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 return c.json(\n deps.usageService.queryErrors(projectId, {\n offset,\n limit,\n // Same admin-only rule as the dashboard: a regular member seeing another tenant's\n // unattributed errors would be a cross-tenant leak.\n includeGlobalErrors: c.var.user.isAdmin,\n ...(from !== undefined ? { from } : {}),\n ...(to !== undefined ? { to } : {}),\n ...(agentId !== undefined && agentId !== \"\" ? { agentId } : {}),\n }),\n );\n });\n\n return app;\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`, and an LLM `failed` the engine went on to retry (the engine\n * reconnects on all three, so a request the ladder carried cost the user nothing); tool\n * execution `failed` / `timeout` (the error is fed back to the model, and the Agent\n * adjusts on 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 `auth` (the credential was rejected and only\n * a human can replace it) and an LLM `failed` the retries did not recover (the run ended\n * on it and the user lost the turn).\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 | \"compaction\"\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 parseUserSteeringText,\n stripLeadingMarkerBlocks,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type {\n ApproveFn,\n BackgroundCommandInfo,\n CompactAvailability,\n OmniMessage,\n ProxyEnvPolicy,\n SessionMetaPayload,\n SessionTitleResult,\n TextPayload,\n ThinkingLevelName,\n} from \"@prismshadow/penguin-core\";\nimport type { PendingSteeringInfo, 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 input (core `Session.steer`); false when no Task is running. */\n steer(input: OmniMessage[]): 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 /** Background command processes owned by the Session's environment (core `Session.listBackgroundCommands`). Optional: test fakes may omit it. */\n listBackgroundCommands?(): BackgroundCommandInfo[];\n /** Kills one background command process (core `Session.killBackgroundCommand`); false when the id is unknown. Optional, like listBackgroundCommands. */\n killBackgroundCommand?(processId: string): boolean;\n /** Releases environment resources — kills the remaining background processes (core `Session.dispose`). Optional, idempotent. */\n dispose?(): void;\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 * `opts.proxyEnv` threads the admin proxy settings into core (a live getter returning the\n * agent-command-subprocess policy: strip the proxy variables, inject the explicit proxy\n * address, or null = pass the environment through).\n */\nexport function createCoreSessionLoader(\n root: string,\n sources?: SessionSources,\n opts: { proxyEnv?: () => ProxyEnvPolicy | null } = {},\n): 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 ...(opts.proxyEnv ? { proxyEnv: opts.proxyEnv } : {}),\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 /**\n * The running Task's input messages, held from launch until the run ends. The engine\n * writes these exact envelopes to the Trace only after the session bootstrap (MCP\n * connect + discovery on the first run), so a history read during that window would\n * miss the user's own message — and the draft flow subscribes to the stream only after\n * the input publish, so the live channel cannot backfill it either. GET /messages\n * appends the ones the Trace has not caught up to yet (exact-envelope dedup).\n */\n pendingInputs: OmniMessage[];\n /**\n * The running Task's streamed bootstrap records (mcp_connect begin/end,\n * tool_list_ready), held until the run ends. Their Trace writes are deferred by the\n * engine until after the input lands (turn attribution), and the draft flow subscribes\n * only after they were published — so a history rebuild during the MCP connect would\n * otherwise show nothing at all (a silent blank while a slow server times out).\n * GET /messages appends whichever of them the Trace has not caught up to.\n */\n pendingBootstrap: OmniMessage[];\n /**\n * Steering messages queued on core but not yet delivered to the model — a display mirror\n * of core's steering queue (same FIFO order), so the composer's \"steering queued\" hint and\n * its content survive reloads: pushed on `steer`, shifted as each `[user_steering]`\n * message appears on the drive stream, dropped wholesale when the run exits (core\n * discards undelivered steering on abort/completion). Broadcast on every `task_state`.\n */\n pendingSteering: PendingSteeringInfo[];\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. */\n/** Whether this main-stream message is a delivered `[user_steering]` user text (one per queued steering entry, see core's steeringMessages). */\nfunction isDeliveredSteering(msg: OmniMessage): boolean {\n const p = msg.payload as { type?: string; role?: string; text?: string };\n if (msg.type !== \"model_msg\" || p.type !== \"text\" || p.role !== \"user\") return false;\n return typeof p.text === \"string\" && parseUserSteeringText(p.text) !== null;\n}\n\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 /** Steering messages queued but not yet delivered to the model (display mirror; see RuntimeEntry.pendingSteering). */\n pendingSteeringOf(sessionId: string): PendingSteeringInfo[] {\n return this.entries.get(sessionId)?.pendingSteering ?? [];\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 /**\n * The running Task's input messages as published at launch; empty when idle. The engine\n * writes these exact envelopes to the Trace only after the first run's bootstrap (MCP\n * connect + discovery), so GET /messages appends whichever of them the Trace read has\n * not caught up to yet — without this, a client rebuilding history during the connect\n * (the draft flow subscribes only after the input publish) loses the user's own message.\n */\n pendingInputs(sessionId: string): OmniMessage[] {\n return this.entries.get(sessionId)?.pendingInputs ?? [];\n }\n\n /** The running Task's streamed bootstrap records (see RuntimeEntry.pendingBootstrap); empty when idle. */\n pendingBootstrap(sessionId: string): OmniMessage[] {\n return this.entries.get(sessionId)?.pendingBootstrap ?? [];\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 pendingInputs: [],\n pendingBootstrap: [],\n pendingSteering: [],\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 * Cheap, lock-free rehearsal of the 409/503 conditions startTask checks, throwing exactly the\n * same HttpErrors. **Advisory only**: it neither takes the Session lock nor loads an entry, so\n * a session that isn't in the active table reads as acceptable and a status change racing this\n * call is not caught — the authoritative check is still the one inside startTask.\n *\n * It exists so a caller that has irreversible work to do first (POST /tasks writes the\n * message's file attachments to disk) can find out about the ordinary \"a Task is already\n * running\" rejection before doing it, instead of undoing it afterwards.\n */\n assertCanAcceptTask(sessionId: string, opts?: { queueIfBusy?: boolean }): void {\n this.assertOpen();\n this.assertAgentNotDeleting(sessionId);\n this.assertSessionNotDeleting(sessionId);\n const entry = this.entries.get(sessionId);\n if (entry && !opts?.queueIfBusy) this.assertIdle(entry);\n }\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 (route-validated to carry text; images may ride along); 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 // `isPlainText` leaves attached images out, so this copy carries no\n // `[attached image: <path>]` lines — which suits its readers, since a status card and a\n // generated title read better without absolute scratchpad paths. Core keeps its own\n // folded copy (Session.runGoal) for what the rounds actually re-inject.\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 // Round-1 objective input: same pendingInputs hold as launchTask (core yields round\n // inputs onto the stream, but the Trace write still waits for the bootstrap);\n // append + bootstrap reset for the same abort-mid-bootstrap reasons as launchTask.\n entry.pendingInputs = [...entry.pendingInputs, ...args.input];\n entry.pendingBootstrap = [];\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. The same envelopes are held as\n // pendingInputs so GET /messages can serve them before the engine's Trace write\n // catches up (delayed by the first run's MCP connect). APPEND, don't replace: inputs\n // of a run aborted mid-bootstrap are still held (nothing reached the Trace; core\n // carries them into this run) and must stay served until this run persists them.\n // The previous attempt's bootstrap records are dropped instead — this run streams\n // its own connect phase, and a stale aborted pair would render as an extra row.\n entry.pendingInputs = [...entry.pendingInputs, ...input];\n entry.pendingBootstrap = [];\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 message to the running Session (core delivers it between\n * turns as a standalone `[user_steering]` user message followed by its images — no SSE\n * event of its own; the messages arrive through the stream the drive loop already\n * publishes). 409 when the Session isn't running a Task (idle / compacting / not loaded)\n * or the run finished in the race window — the caller falls back to submitting a normal\n * task, which carries the same text and images.\n *\n * `info` is the queued message's display summary: mirrored on the entry and broadcast via\n * `task_state` (and the SSE subscribe snapshot) until the delivered `[user_steering]`\n * message is observed on the stream — that is what keeps the composer's \"steering queued\"\n * hint, content included, alive across reloads.\n */\n steer(sessionId: string, input: OmniMessage[], info: PendingSteeringInfo): void {\n const entry = this.entries.get(sessionId);\n if (!entry || entry.status !== \"running\" || !entry.session.steer(input)) {\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.pendingSteering.push(info);\n entry.lastActivityMs = Date.now();\n this.publishState(entry, entry.status);\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 * Background command processes of a LOADED session (empty when the entry is not in\n * the active table — a resumed entry starts with a fresh environment and can only\n * ever report an empty list, so nothing is resurrected just to answer a poll).\n */\n listProcesses(sessionId: string): BackgroundCommandInfo[] {\n return this.entries.get(sessionId)?.session.listBackgroundCommands?.() ?? [];\n }\n\n /** Kills one background command process of a loaded session; false when the session isn't loaded or the id is unknown. */\n killProcess(sessionId: string, processId: string): boolean {\n const entry = this.entries.get(sessionId);\n if (!entry) return false;\n const killed = entry.session.killBackgroundCommand?.(processId) ?? false;\n if (killed) entry.lastActivityMs = Date.now();\n return killed;\n }\n\n /**\n * Disposes a just-removed entry's runtime — after its in-flight drive (if any)\n * settles, so interrupt cleanup never races a dying environment. Deleting a Session /\n * Agent / Project is the one intent that must also end the background processes the\n * conversation started (a dev server surviving its deleted conversation is\n * unreachable from every UI, running forever).\n */\n private disposeRemoved(entry: RuntimeEntry): void {\n const dispose = (): void => entry.session.dispose?.();\n if (entry.running) void entry.running.then(dispose, dispose);\n else dispose();\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 this.disposeRemoved(entry);\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 this.disposeRemoved(entry);\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 this.disposeRemoved(entry);\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 // A live background process (e.g. a dev server the conversation started) pins the\n // entry: eviction would strand the process — a resumed entry starts with a fresh\n // environment, so the process list and its stop control would go blind while the\n // OS process kept running. Exited-but-listed processes don't pin anything.\n if (entry.session.listBackgroundCommands?.().some((p) => p.running)) continue;\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 pendingInputs: [],\n pendingBootstrap: [],\n pendingSteering: [],\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 // Every driven run (task, goal round, compaction) writes Trace lines: flip the row's\n // has_trace cache here, the single choke point, so listing can serve it from the DB\n // without a directory walk (see SessionService.listSessions).\n this.deps.sessions.markHasTrace(entry.sessionId);\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 // Steering delivery: core emits exactly one `[user_steering]` user text per queued\n // entry, in queue order — shift the display mirror and re-broadcast so the\n // composer's \"steering queued\" hint retires the moment the message is on stream.\n if (entry.pendingSteering.length > 0 && isDeliveredSteering(msg)) {\n entry.pendingSteering.shift();\n this.publishState(entry, entry.status);\n }\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 // Bootstrap records (first-run MCP connect + toolset): held for GET /messages\n // until the engine's deferred Trace write catches up (see pendingBootstrap).\n if (!msg.origin || msg.origin.length === 0) {\n const bt = (msg.payload as { type?: string }).type;\n if (bt === \"mcp_connect_begin\" || bt === \"mcp_connect_end\" || bt === \"tool_list_ready\") {\n entry.pendingBootstrap.push(msg);\n }\n // First request of the run: the engine writes input → bootstrap records → tool\n // list to the Trace BEFORE issuing the request, so both holds are persisted by\n // now — end them here rather than at idle. Holding for the whole run would\n // outlive the messages endpoint's tail-window dedup: once the Task appends\n // more records than the window, the input would be judged \"not yet in the\n // Trace\" and served a second time at the end of history.\n if (bt === \"request_begin\") {\n entry.pendingInputs = [];\n entry.pendingBootstrap = [];\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 // The pending holds are NOT cleared here: a run aborted mid-bootstrap wrote nothing\n // to the Trace, so its held input (and the aborted connect pair) are the only copy\n // a reload can show until the next run carries the input forward and persists it.\n // Runs that issued a request already cleared them at their first request_begin.\n entry.approvals.denyAll();\n entry.status = \"idle\";\n entry.abort = null;\n entry.running = null;\n // The run is over, so core has discarded any undelivered steering (see ContextEngine's\n // steeringQueue) — drop the mirror with it; the idle publish below broadcasts the\n // now-empty state.\n entry.pendingSteering = [];\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 // Spawned by this server's run (client NULL = web); its Trace exists by construction.\n hasTrace: true,\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 and the undelivered steering\n // mirror, so subscribers can render both hints without a dedicated event type.\n this.publishEvent(entry, {\n type: \"task_state\",\n state,\n queued: entry.followUps.length,\n ...(entry.pendingSteering.length > 0 ? { pendingSteering: entry.pendingSteering } : {}),\n });\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` / `compaction`), matching\n * 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 * - `auth` → unexpected (`llm_auth`): the credential was rejected, the engine never retries\n * it, and only a human holding the key can fix it. Its own code rather than the failed\n * bucket, because dedup is `(source, code, Project)` over a short window: sharing a code\n * with a class that now fires on every recovered blip would let a real credential failure\n * be swallowed as a duplicate of something already handled.\n * - `failed` → depends on whether the retry ladder carried it. The engine reconnects on\n * `failed` too, so the same status covers both \"a gateway hiccup nobody needed to know\n * about\" and \"the run died on it\":\n * - another attempt followed (a `request_begin` resolved the pending record) → expected\n * (`llm_failed_retried`), same footing as timeout/malformed: recorded for the record,\n * not raised at an operator;\n * - nothing followed but the end of the run (an `abort`, or close) → unexpected\n * (`llm_failed`): the retries did not recover it, the user lost the turn, 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` carries the error detail on\n * non-completed statuses (`error_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 `error_message` detail (e.g. a quota\n * code), falling back to the generic status text when it carried none. This boundary is\n * also what tells a survived `failed` from an exhausted one (see the classification above);\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). Exactly one shape is\n * dropped: a command tool's ordinary non-zero exit, which is how a shell command reports\n * information rather than a fault — see isOrdinaryCommandExit; the same tools' environment\n * faults (killed by a signal, spawn failure, tool timeout) still record.\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/** Error code, classification, and fallback message (the last used when no abort reason is available). */\ninterface FailureSpec {\n code: string;\n kind: ErrorKind;\n text: string;\n}\n\n/** LLM failure state → its spec, for a failure the retry ladder did NOT carry (see the file header). */\nconst LLM_FAILURES: Record<LlmFailure, FailureSpec> = {\n // Reached here only when no further attempt followed: the ladder ran out (or the run ended\n // on it), so the user lost the turn and someone should look. A `failed` that was retried\n // takes LLM_FAILED_RETRIED instead.\n failed: {\n code: \"llm_failed\",\n kind: \"unexpected\",\n text: \"LLM request failed and the retries did not recover it.\",\n },\n // Its own code, not the failed bucket: dedup is `(source, code, Project)` over a short\n // window, and `llm_failed_retried` can now fire on any recovered blip — sharing a bucket\n // would let a genuine credential failure be dropped as a duplicate of one.\n auth: {\n code: \"llm_auth\",\n kind: \"unexpected\",\n text: \"LLM request rejected: the provider did not accept the credentials.\",\n },\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/**\n * A `failed` the engine went on to retry: expected, exactly like timeout/malformed — the\n * engine's defined handling path absorbed it and the user never lost anything, so it belongs\n * in the record but not in an operator's queue. Its own code so the exhausted case keeps\n * `llm_failed` to itself and neither can dedup the other away.\n */\nconst LLM_FAILED_RETRIED: FailureSpec = {\n code: \"llm_failed_retried\",\n kind: \"expected\",\n text: \"LLM request failed (the engine reconnects and retries).\",\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/**\n * The command tools. Both funnel their exit status through core's `resultForExit`, which maps\n * *any* non-zero exit to `failed` — and a non-zero exit is how shell commands return\n * information, not a fault: `grep` exits 1 when nothing matches, `test -f` when the file is\n * absent, `diff` when files differ. Recording those made the cost center's error table mostly a\n * log of ordinary Agent work, burying the real errors and eating the row cap that exists to\n * protect them. (Only the row cap: the recorder's dedup key carries the error code, so this\n * noise could ever have crowded out only *itself*.) The Agent sees the failure in the tool\n * output and adjusts — nothing about it needs a human.\n *\n * Both belong here: `input_command` is how a backgrounded `exec_command` is polled for its\n * eventual exit, so covering only one would record the *same* command's non-zero exit when it\n * finishes in the background and drop it when it finishes in the foreground.\n */\nconst COMMAND_TOOLS: ReadonlySet<string> = new Set([\"exec_command\", \"input_command\"]);\n\n/**\n * `resultForExit`'s terminal marker for an ordinary non-zero exit. Anchored to the end because\n * the marker is a `note`, which Environment appends after the (possibly truncated) output — a\n * command's own stdout may contain anything, including this text.\n */\nconst ORDINARY_EXIT_NOTE = /\\[exit code: [^\\]\\n]*\\]\\s*$/;\n\n/**\n * Whether a command-tool failure is just an ordinary non-zero exit — the one failure shape the\n * error table is better off without (see COMMAND_TOOLS).\n *\n * The discrimination is by note rather than by tool name, because `failed` from these tools is\n * not only \"the command exited non-zero\": it also covers termination by signal (an OOM kill, a\n * segfault), a spawn failure (nonexistent workdir, EMFILE, an unresolvable shell), a missing\n * command session manager, and a tool timeout. Those are config/environment faults — the\n * recorder's own \"needs a human\" category — and no amount of Agent self-correction resolves\n * them, so they must keep landing in the table. `resultForExit` writes the exit code as the\n * output's last note, which tells the two apart precisely.\n *\n * An unknown name (tool_call evicted from the cache, or never seen) still qualifies: only these\n * two tools ever emit that marker, so dropping it beats recording a nameless\n * `tool_failed:unknown` for exactly the noise this exists to remove.\n */\nfunction isOrdinaryCommandExit(name: string | undefined, output: string): boolean {\n if (name !== undefined && !COMMAND_TOOLS.has(name)) return false;\n return ORDINARY_EXIT_NOTE.test(output);\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 error_message?: string;\n };\n const key = originKey(msg);\n if (p.type === \"compaction_end\") {\n this.observeCompactionEnd(msg, key);\n return;\n }\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 error detail (request_end.error_message): the message of record\n // when no abort follows (the retry path).\n ...(typeof p.error_message === \"string\" && p.error_message.trim()\n ? { message: p.error_message }\n : {}),\n });\n }\n return;\n }\n // A new attempt begins (the engine is retrying): no reason text left to wait for the\n // previous failure, persist using the status text — and this is the proof the ladder\n // carried it, which is what downgrades a `failed` from unexpected to expected.\n if (p.type === \"request_begin\") {\n this.flush(key, null, true);\n return;\n }\n // Interrupted/failed exit: reason is core's only failure-reason text. No attempt follows\n // an abort, so a `failed` resolved here is one the retries did not recover.\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, and `retried` says whether another attempt followed\n * (only a `request_begin` proves that). 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 * `retried` defaults to false so every other resolution — an abort, the defensive flush at\n * the next request_end, or close() at the end of the run — is read as \"nothing came after\n * this failure\". That is the conservative direction: at worst a recovered blip is escalated,\n * never a lost turn silently downgraded.\n */\n private flush(key: string, reason?: string | null, retried = false): void {\n const entry = this.pending.get(key);\n if (entry === undefined) return;\n this.pending.delete(key);\n const spec =\n retried && entry.status === \"failed\" ? LLM_FAILED_RETRIED : 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 /**\n * A failed compaction becomes its own error record (source = `compaction`) — a compaction\n * is an ordinary LLM request whose failures (unusable summary and transport alike) core\n * retries under the standard budget, so a `failed` end means the retries ran out\n * (issue #170). This is the single record a failed compaction produces: the compaction\n * request's own request_begin/request_end pair is written to Trace only and never reaches\n * this stream, so the llm-source records above cannot double-count it. `completed` is not\n * an error and `aborted` is a user interrupt — neither is recorded. kind is unexpected for\n * the same reason `llm_failed` is: the budget is exhausted, and while the original context\n * is kept, the trigger still holds — the session keeps re-entering compaction until a\n * human changes something. The message carries the attempts and the last error detail\n * (compaction_end's share of the RetryDetail block) so the cost center shows what failed.\n */\n private observeCompactionEnd(msg: OmniMessage, key: string): void {\n const p = msg.payload as {\n mode?: string;\n reason?: string;\n status?: StopReason;\n attempt?: number;\n error_message?: string;\n };\n if (p.status !== \"failed\") return;\n const attempts =\n typeof p.attempt === \"number\" && p.attempt > 0\n ? ` after ${p.attempt} attempt${p.attempt === 1 ? \"\" : \"s\"}`\n : \"\";\n const detail =\n typeof p.error_message === \"string\" && p.error_message ? `: ${p.error_message}` : \"\";\n this.errors.record({\n source: \"compaction\",\n err: `${p.mode ?? \"summarize\"} compaction failed${attempts}${detail}; trigger ${p.reason ?? \"unknown\"}, original context kept.`,\n ctx: this.ctxFor(key),\n code: \"compaction_failed\",\n kind: \"unexpected\",\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 const output = p.output ?? \"\";\n if (isOrdinaryCommandExit(name, output)) return; // a shell's exit status is information, not a fault\n this.errors.record({\n source: \"environment\",\n err: toolFailureText(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 *\n * The periodic scan is mtime-gated (trace-index house pattern): the agents-dir\n * listing and each Agent's parsed schedule files are cached and revalidated by stat,\n * so an unchanged tree ticks with zero readdir/readFile — manual edits are still\n * picked up next tick because every edit variant moves an mtime the gate stats.\n */\nimport { createHash } from \"node:crypto\";\nimport fs 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 { cacheable, statMtime } from \"../internal/mtime-gate.js\";\nimport type { ErrorSink } from \"./error-recorder.js\";\nimport type { ScheduleDefinition } from \"./schedule-file.js\";\nimport { latestSlotAt, slotInWindow } from \"./schedule-file.js\";\nimport { ScheduleFileCache, readScheduleFile, validateScheduleModelRef } from \"./schedule-store.js\";\nimport type { ScheduleConfigSource } 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 /** Project-config source for model-ref validation (ProjectConfigService's mtime-cached reads). */\n projectConfig: ScheduleConfigSource;\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 /** mtime-gated schedule-file scans (public for test observability of its counters). */\n readonly files: ScheduleFileCache;\n /** Per-Project agents-dir listing gate: dir mtime → Agent ids (creating/removing an Agent dir moves it). */\n private readonly agentDirs = new Map<string, { mtimeMs: number; ids: string[] }>();\n\n constructor(private readonly deps: SchedulerDeps) {\n this.now = deps.now ?? (() => Date.now());\n this.intervalMs = deps.intervalMs ?? TICK_INTERVAL_MS;\n this.files = new ScheduleFileCache(deps.root);\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 const projects = this.deps.projects.listAll();\n for (const project of projects) {\n await this.reconcileProject(project.projectId, project.ownerUserId);\n }\n // A deleted Project simply stops being listed, so its cache entries are swept\n // here (removed Agents inside a live Project are dropped by listAgentIds).\n const live = new Set(projects.map((p) => p.projectId));\n this.files.retainProjects(live);\n for (const projectId of this.agentDirs.keys()) {\n if (!live.has(projectId)) this.agentDirs.delete(projectId);\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 (bypassing the mtime gate) 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, { force: true });\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 this.files.list(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(\n this.deps.projectConfig,\n projectId,\n file.parsed.def,\n );\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 (the unlink moved the dir mtime, but invalidate for immediate effect anyway). */\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 this.files.invalidate(projectId, agentId);\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 /**\n * Enumerate Agents under a Project: scheduling only cares about Agent dirs that exist on\n * disk (no dir → no tasks). mtime-gated: an unchanged agents dir serves the cached ids\n * with one stat and no readdir (creating/removing an Agent dir moves the dir's mtime);\n * removed Agents also drop their schedule-file cache entries here.\n */\n private async listAgentIds(projectId: string): Promise<string[]> {\n const cached = this.agentDirs.get(projectId);\n const mtimeMs = await statMtime(agentsDir(this.deps.root, projectId));\n if (mtimeMs === null) {\n this.agentDirs.delete(projectId);\n return [];\n }\n if (cached && cached.mtimeMs === mtimeMs) return cached.ids;\n let ids: string[];\n try {\n const items = await fs.readdir(agentsDir(this.deps.root, projectId), {\n withFileTypes: true,\n });\n ids = items.filter((d) => d.isDirectory()).map((d) => d.name);\n } catch {\n this.agentDirs.delete(projectId);\n return [];\n }\n for (const gone of cached?.ids.filter((id) => !ids.includes(id)) ?? []) {\n this.files.invalidate(projectId, gone);\n }\n this.agentDirs.set(projectId, { mtimeMs: cacheable(mtimeMs), ids });\n return ids;\n }\n\n private async reconcileOneAgent(\n projectId: string,\n agentId: string,\n ownerUserId: string,\n opts: { force?: boolean } = {},\n ): Promise<void> {\n const files = await this.files.list(projectId, agentId, opts);\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.projectConfig, 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/**\n * Fallback title: take the material's first non-empty line, sanitize and truncate; if\n * sanitizing empties it out (pure punctuation, etc.) fall back to the truncated original\n * text; returns null if all-whitespace. Exported for the Trace listing's title fallback\n * (trace-service derives a title from the first user prompt of Sessions the DB has no\n * title for) so both fallbacks stay one algorithm.\n */\nexport function 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 /**\n * Fired after a password reset. The server wires it to drop the stored\n * initial-password plaintext once it goes stale (a reset re-arms the initial FLAG,\n * but with an admin-chosen value the stored seed no longer matches — see\n * initial-password.ts); test constructions may omit it.\n */\n onPasswordChanged?: (userId: string) => void;\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 this.deps.onPasswordChanged?.(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 * Desktop mode (PENGUIN_DESKTOP_TOKEN): the shell that spawned this server proves itself\n * with a per-launch random token, which backs two endpoints with different consumption\n * rules:\n *\n * - `GET /api/auth/desktop-login?token=…` — ONE-SHOT: the window's first navigation\n * redeems the token for a standard admin cookie session; every later attempt fails,\n * so a leaked URL cannot be replayed.\n * - `POST /api/desktop/shutdown` (Authorization: Bearer <token>) — REUSABLE for the\n * process lifetime: the token here identifies the supervising shell, which may need\n * the endpoint at any point (POSIX quit, and the only graceful path on Windows,\n * where killing a child is a hard TerminateProcess).\n *\n * Comparisons hash both sides first so timingSafeEqual gets equal-length buffers.\n */\nimport { createHash, timingSafeEqual } from \"node:crypto\";\n\nfunction digest(value: string): Buffer {\n return createHash(\"sha256\").update(value).digest();\n}\n\nexport class DesktopService {\n private readonly tokenDigest: Buffer;\n private loginConsumed = false;\n private shutdownHandler: (() => void) | null = null;\n\n constructor(token: string) {\n this.tokenDigest = digest(token);\n }\n\n /** Constant-time token check (no consumption). */\n verifyToken(candidate: string): boolean {\n return timingSafeEqual(digest(candidate), this.tokenDigest);\n }\n\n /** One-shot login redemption: true exactly once, for the correct token. */\n redeemLoginToken(candidate: string): boolean {\n if (this.loginConsumed || !this.verifyToken(candidate)) return false;\n this.loginConsumed = true;\n return true;\n }\n\n /** index.ts registers the actual graceful-shutdown trigger after assembly. */\n onShutdownRequest(handler: () => void): void {\n this.shutdownHandler = handler;\n }\n\n /** Invoked by the shutdown route; false when no handler is registered (tests). */\n requestShutdown(): boolean {\n if (!this.shutdownHandler) return false;\n this.shutdownHandler();\n return true;\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 DEFAULT_MEMORY_PROMPT,\n DEFAULT_MEMORY_WORKSPACE_PROMPT,\n DEFAULT_SCHEDULES_PROMPT,\n DEFAULT_SKILLS_PROMPT,\n DEFAULT_VAULT_PROMPT,\n KERNEL_VERSION,\n LEGACY_SKILLS_SECTION,\n LEGACY_VAULT_SECTION,\n agentsMdPath,\n applyKernelUpdate,\n isKernelOutdated,\n agentStateDir,\n agentStateVersion,\n hasSchedulesPlaceholder,\n hasSkillsPlaceholder,\n hasVaultPlaceholder,\n insertSchedulesPlaceholder,\n insertSkillsPlaceholder,\n insertVaultPlaceholder,\n VAULT_VALUE_MAX_LENGTH,\n isValidVaultKey,\n loadAgentVault,\n resetSystemConfigToDefaults,\n resolveMCPServer,\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 AgentKernelUpdateResponse,\n AgentModelConfigDto,\n AgentCompactionConfigDto,\n AgentMemoryConfigDto,\n AgentSchedulesConfigDto,\n AgentSkillsConfigDto,\n AgentVaultConfigDto,\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<{\n name?: string;\n description?: string;\n toolCount: number;\n version: number;\n kernelOutdated: boolean;\n }> {\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 kernelOutdated: isKernelOutdated(\n typeof parsed.kernel_version === \"string\" ? parsed.kernel_version : null,\n ),\n };\n } catch {\n // A corrupt config carries no readable stamp either: reported outdated, like a pre-stamp one.\n return { toolCount: 0, version: 1, kernelOutdated: true };\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 memory = asRecord(parsed.memory);\n const vault = asRecord(parsed.vault);\n const skills = asRecord(parsed.skills);\n const schedules = asRecord(parsed.schedules);\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 // Memory's effective state, not the literal YAML: core treats anything but an explicit\n // `false` as on and falls back to the built-in prompts, so a config predating the section\n // reports the values its Sessions actually run with (whether anything reaches the prompt\n // still depends on the template carrying {{MEMORY}}, which the Memory tab reports\n // separately).\n const memoryDto: AgentMemoryConfigDto = {\n enabled: memory.enabled !== false,\n prompt: typeof memory.prompt === \"string\" ? memory.prompt : DEFAULT_MEMORY_PROMPT,\n workspacePrompt:\n typeof memory.workspace_prompt === \"string\"\n ? memory.workspace_prompt\n : DEFAULT_MEMORY_WORKSPACE_PROMPT,\n };\n // Effective values like memory's, plus template facts computed from the stored template:\n // whether the section placeholder is present, and — skills/vault only — whether the\n // template still carries the legacy hardcoded section verbatim (the migration case;\n // detection retires with core's LEGACY_* constants).\n const systemPrompt = typeof parsed.system_prompt === \"string\" ? parsed.system_prompt : \"\";\n const vaultDto: AgentVaultConfigDto = {\n enabled: vault.enabled !== false,\n prompt: typeof vault.prompt === \"string\" ? vault.prompt : DEFAULT_VAULT_PROMPT,\n templateHasPlaceholder: hasVaultPlaceholder(systemPrompt),\n legacySectionPresent: systemPrompt.includes(LEGACY_VAULT_SECTION),\n };\n const skillsDto: AgentSkillsConfigDto = {\n enabled: skills.enabled !== false,\n prompt: typeof skills.prompt === \"string\" ? skills.prompt : DEFAULT_SKILLS_PROMPT,\n templateHasPlaceholder: hasSkillsPlaceholder(systemPrompt),\n legacySectionPresent: systemPrompt.includes(LEGACY_SKILLS_SECTION),\n };\n const schedulesDto: AgentSchedulesConfigDto = {\n enabled: schedules.enabled !== false,\n prompt: typeof schedules.prompt === \"string\" ? schedules.prompt : DEFAULT_SCHEDULES_PROMPT,\n templateHasPlaceholder: hasSchedulesPlaceholder(systemPrompt),\n };\n // Kernel stamp: reported literally (null = predates the mechanism), with the current\n // generation and the outdated verdict beside it, so the client renders the update hint\n // without knowing core's KERNEL_VERSION.\n const kernelVersion = typeof parsed.kernel_version === \"string\" ? parsed.kernel_version : null;\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 kernelVersion,\n kernelLatest: KERNEL_VERSION,\n kernelOutdated: isKernelOutdated(kernelVersion),\n systemPrompt,\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 memory: memoryDto,\n vault: vaultDto,\n skills: skillsDto,\n schedules: schedulesDto,\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 /**\n * Smart-merges the config up to the current defaults generation (core's applyKernelUpdate):\n * leaves still carrying a recorded generation's default follow the new defaults, user\n * customizations are kept and reported; the config is stamped with the new kernel version.\n * The destructive full-refresh alternative stays resetConfig.\n */\n async kernelUpdate(projectId: string, agentId: string): Promise<AgentKernelUpdateResponse> {\n await this.requireExists(projectId, agentId);\n return applyKernelUpdate(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.memory !== undefined) {\n // The toggle only decides whether Memory reaches the context and whether Workspace\n // directories are prepared; existing Memory files are never touched by it.\n const memory = asRecord(cfg.memory);\n setIfProvided([\"memory\", \"enabled\"], optionalBoolean(memory, \"enabled\"));\n setIfProvided([\"memory\", \"prompt\"], optionalString(memory, \"prompt\"));\n setIfProvided([\"memory\", \"workspace_prompt\"], optionalString(memory, \"workspacePrompt\"));\n }\n // The vault / skills / schedules toggles only decide whether the section reaches the\n // context: vault values still enter subprocess environments, installed skills stay\n // invocable, and the scheduler keeps firing tasks.\n if (cfg.vault !== undefined) {\n const vault = asRecord(cfg.vault);\n setIfProvided([\"vault\", \"enabled\"], optionalBoolean(vault, \"enabled\"));\n setIfProvided([\"vault\", \"prompt\"], optionalString(vault, \"prompt\"));\n }\n if (cfg.skills !== undefined) {\n const skills = asRecord(cfg.skills);\n setIfProvided([\"skills\", \"enabled\"], optionalBoolean(skills, \"enabled\"));\n setIfProvided([\"skills\", \"prompt\"], optionalString(skills, \"prompt\"));\n }\n if (cfg.schedules !== undefined) {\n const schedules = asRecord(cfg.schedules);\n setIfProvided([\"schedules\", \"enabled\"], optionalBoolean(schedules, \"enabled\"));\n setIfProvided([\"schedules\", \"prompt\"], optionalString(schedules, \"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 /**\n * Inserts a feature's section placeholder into the Agent's prompt template — the explicit\n * adoption path mirroring the Memory tab's endpoint; nothing ever inserts automatically.\n * For skills/vault the insert is migration-first (core's helpers): a stored template still\n * carrying the legacy hardcoded section verbatim gets it replaced in place by the\n * placeholder, otherwise the placeholder is inserted before `# Environment`. Idempotent;\n * returns the refreshed config view (the route picks out the feature's DTO).\n */\n async insertTemplatePlaceholder(\n projectId: string,\n agentId: string,\n feature: \"vault\" | \"skills\" | \"schedules\",\n ): Promise<AgentConfigView> {\n const view = await this.getConfig(projectId, agentId);\n const insert = {\n vault: insertVaultPlaceholder,\n skills: insertSkillsPlaceholder,\n schedules: insertSchedulesPlaceholder,\n }[feature];\n const next = insert(view.config.systemPrompt);\n if (next === view.config.systemPrompt) return view;\n await this.updateConfig(projectId, agentId, { config: { systemPrompt: next } });\n return this.getConfig(projectId, agentId);\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 const seen = new Set<string>();\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 // Transport-level validation through the core resolver — the single source of truth\n // with the runtime: a precise 400 at save time beats a warn-and-skip at the next\n // Session start.\n try {\n resolveMCPServer(s as unknown as MCPServerConfig);\n } catch (err) {\n throw badRequest(`mcpServers[${i}]: ${err instanceof Error ? err.message : String(err)}`);\n }\n if (seen.has(s.name)) {\n throw badRequest(`mcpServers[${i}]: duplicate server name \"${s.name}\".`);\n }\n seen.add(s.name);\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 *\n * Reads are memoized on the file's mtime (see readTable): every consumer of this\n * service — scheduler model-ref validation, the models/schedules routes, usage\n * pricing — shares one parsed table per on-disk version instead of re-reading and\n * re-parsing the TOML per call. The service's own writes all funnel through\n * `writeRaw`, which invalidates synchronously; external edits (CLI, hand edits) are\n * caught by the stat. The cached table is shared between callers and must be treated\n * as immutable — every mutating method here copies before changing.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { parse as parseToml } from \"smol-toml\";\nimport {\n CHAT_APPROVAL_MODES,\n DEFAULT_CHAT_THINKING_LEVELS,\n GenerativeModel,\n catalogEntryFor,\n defaultProjectConfig,\n projectConfigFromTable,\n projectConfigPath,\n renderProjectConfigToml,\n resolveModelEnv,\n userText,\n} from \"@prismshadow/penguin-core\";\nimport type { LLMOutcome, ModelRef, OmniMessage, ProjectConfig } from \"@prismshadow/penguin-core\";\nimport type {\n ChatDefaultsDto,\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 { cacheable } from \"../internal/mtime-gate.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 /**\n * Parsed-table cache, one entry per Project, keyed by the config file's mtime as\n * recorded at read time (a fresh mtime is stored as a never-matching sentinel, see\n * mtime-gate). A repeat read while the stat still matches costs one stat and zero\n * parses; a mismatch (external edit) or a service write (writeRaw deletes the\n * entry) falls back to a full read.\n */\n private readonly cache = new Map<string, { mtimeMs: number; table: RawTable }>();\n\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 return (await this.readTable(projectId)) ?? {};\n }\n\n /**\n * mtime-gated read of the parsed table; null when the file doesn't exist (readRaw\n * flattens that to `{}`, loadConfig to the preset default config — the two\n * pre-existing missing-file behaviors). Serving the shared cached object is safe\n * because no consumer mutates it (see the class header).\n */\n private async readTable(projectId: string): Promise<RawTable | null> {\n const file = this.filePath(projectId);\n let mtimeMs: number;\n try {\n mtimeMs = (await fs.stat(file)).mtimeMs;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n this.cache.delete(projectId); // Config deleted (or the whole Project): drop the stale entry.\n return null;\n }\n const cached = this.cache.get(projectId);\n if (cached && cached.mtimeMs === mtimeMs) return cached.table;\n let raw: string;\n try {\n raw = await fs.readFile(file, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n this.cache.delete(projectId); // Vanished between stat and read: same as never existing.\n return null;\n }\n const table = asTable(parseToml(raw));\n this.cache.set(projectId, { mtimeMs: cacheable(mtimeMs), table });\n return table;\n }\n\n /**\n * Typed view of the same cached table — byte-for-byte the semantics of core's\n * `loadProjectConfig` (missing file → preset default config; legacy format → the\n * same error, via core's shared narrowing) without its per-call readFile + parse.\n * Serves the scheduler's model-ref validation (see schedule-store).\n */\n async loadConfig(projectId: string): Promise<ProjectConfig> {\n const table = await this.readTable(projectId);\n if (table === null) return defaultProjectConfig();\n return projectConfigFromTable(this.filePath(projectId), table);\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 // Every service write funnels through here: invalidate synchronously so the next\n // read re-parses (external writers are caught by readTable's stat instead).\n this.cache.delete(projectId);\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 /**\n * Rewrites the display name, preserving every other field (models, credentials, default\n * model): read-modify-write of the same toml, like ensurePresetModels. The id itself is\n * immutable — only this label changes.\n */\n async setName(projectId: string, name: string): Promise<void> {\n const raw = await this.readRaw(projectId);\n await this.writeRaw(projectId, { ...raw, name });\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 /**\n * Sets the default Model to an already-configured entry (the narrow\n * PUT /models/default route): rewrites only the top-level `default_model` — the SAME key\n * the models page's whole-table PUT maintains, so the two surfaces stay single-sourced —\n * preserving every other field via read-modify-write. The pair must name an entry in\n * `models` (the identical rule updateModels applies to `defaultModel`); a reference\n * outside the config is a 400, since createSession would error on it immediately. No\n * runtime invalidation: existing Sessions pin their model at creation, and this route\n * never touches credentials.\n */\n async setDefaultModelRef(projectId: string, ref: ModelRefDto): Promise<ModelRefDto> {\n const raw = await this.readRaw(projectId);\n if (!asArray(raw.models).some((m) => entryMatches(m, ref.provider, ref.modelId))) {\n throw badRequest(\n `defaultModel must be included in models: ${showRef(ref.provider, ref.modelId)}.`,\n );\n }\n await this.writeRaw(projectId, {\n ...raw,\n default_model: { provider: ref.provider, model_id: ref.modelId },\n });\n return { provider: ref.provider, modelId: ref.modelId };\n }\n\n /**\n * New-chat defaults (`[default_chat]`): read leniently — same tolerance as core's\n * loadProjectConfig (an invalid value drops that key; a missing/malformed block reads as\n * empty). Members may read; only the fields present in the file appear in the DTO.\n */\n async getChatDefaults(projectId: string): Promise<ChatDefaultsDto> {\n const raw = await this.readRaw(projectId);\n const t = asTable(raw.default_chat);\n const agentId = optStr(t.agent_id);\n const workspace = optStr(t.workspace);\n const approval = optStr(t.approval_mode);\n const thinking = optStr(t.thinking_level);\n return {\n ...(agentId !== undefined ? { agentId } : {}),\n ...(workspace !== undefined ? { workspace } : {}),\n ...(approval !== undefined && (CHAT_APPROVAL_MODES as readonly string[]).includes(approval)\n ? { approvalMode: approval as ChatDefaultsDto[\"approvalMode\"] }\n : {}),\n ...(thinking !== undefined &&\n (DEFAULT_CHAT_THINKING_LEVELS as readonly string[]).includes(thinking)\n ? { thinkingLevel: thinking as ChatDefaultsDto[\"thinkingLevel\"] }\n : {}),\n };\n }\n\n /**\n * Replaces the whole `[default_chat]` block (declarative PUT: an omitted key clears it;\n * an empty request removes the block entirely, restoring the pre-existing behavior).\n * Field validation (enums / agent existence) happens at the route; this is a\n * read-modify-write like setName — every other field (models, credentials, name, the\n * default model) is preserved. Returns the stored block as re-read from disk.\n */\n async setChatDefaults(projectId: string, req: ChatDefaultsDto): Promise<ChatDefaultsDto> {\n const raw = await this.readRaw(projectId);\n const block: RawTable = {\n ...(req.agentId !== undefined ? { agent_id: req.agentId } : {}),\n ...(req.workspace !== undefined ? { workspace: req.workspace } : {}),\n ...(req.approvalMode !== undefined ? { approval_mode: req.approvalMode } : {}),\n ...(req.thinkingLevel !== undefined ? { thinking_level: req.thinkingLevel } : {}),\n };\n const next: RawTable = { ...raw };\n if (Object.keys(block).length > 0) next.default_chat = block;\n else delete next.default_chat;\n await this.writeRaw(projectId, next);\n return this.getChatDefaults(projectId);\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 =\n \"errorMessage\" in outcome && outcome.errorMessage ? outcome.errorMessage : 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 * Memory management (`agent_state/memory/`): the Web App's read/delete access to what the Agent\n * remembers between Sessions.\n *\n * The layout is core's (see core's state/memory.ts): one directory per scope holding Markdown\n * topic files and that scope's own `MEMORY.md` index — `memory/user/` for the User scope and\n * `memory/<workspace_key>/` for each Workspace. The User scope is addressed through the same\n * `scopeKey` parameter as any Workspace (`USER_SCOPE_KEY`), so it needs no routes of its own;\n * the one difference is that it may be created on demand, since it belongs to the Agent rather\n * than to a Session that has run.\n *\n * The API is deliberately read + delete only: content changes go through a chat Session where\n * the model edits the same files, keeping frontmatter and index in step. The one write this\n * service performs is mechanical — deleting a topic file also drops its `](<file>)` index lines,\n * so the index never lists a file that is gone.\n *\n * This service never invents paths from client input — a request names an `agentId`, a\n * `scopeKey` and a file name inside that scope, each validated against a character rule and\n * then re-checked for containment after resolution, so neither `..` nor a symlink can reach\n * outside the Agent's Memory directory.\n *\n * Files are the source of truth and are read fresh on every request (they are small, requests\n * are rare, and the model edits the same files from its side).\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n MEMORY_INDEX_FILENAME,\n USER_SCOPE_KEY,\n hasMemoryPlaceholder,\n insertMemoryPlaceholder,\n memoryDir,\n memoryScopeDir,\n parseMemoryFrontmatter,\n readWorkspaceMarker,\n} from \"@prismshadow/penguin-core\";\nimport type {\n MemoryFileInfo,\n MemoryFilesResponse,\n MemoryFileResponse,\n MemoryOverviewResponse,\n MemoryScopeInfo,\n} from \"../api/types.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { badRequest } from \"../http/validate.js\";\nimport type { AgentConfigService } from \"./agent-config-service.js\";\n\n/** Scope directory names: what core's key generator produces (a safe base may start with `_`), plus the leeway of a hand-made directory. Excludes `.`/`..` and any separator, so the name can never climb out of `memory/`. */\nconst SCOPE_KEY_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._-]*$/;\n\n/**\n * Whether a name is a topic file: any Markdown file that is not a dotfile (`.workspace` is the\n * Harness's), carries no path separator, and is not the index under any casing — macOS and\n * Windows resolve `memory.md` to `MEMORY.md`. The model writes these files with the ordinary\n * file tools, so non-ASCII names are as legitimate as kebab-case ones; the same rule guards\n * client-supplied names, with containment re-checked after resolution. Exported so the Agent\n * list's memory count (agent-service) shares this one definition of \"a memory\".\n */\nexport function isTopicFileName(name: string): boolean {\n return (\n name.length > 0 &&\n !name.startsWith(\".\") &&\n !/[/\\\\]/.test(name) &&\n name.toLowerCase().endsWith(\".md\") &&\n name.toLowerCase() !== MEMORY_INDEX_FILENAME.toLowerCase()\n );\n}\n\nexport class MemoryService {\n constructor(\n private readonly root: string,\n private readonly agentConfigService: AgentConfigService,\n ) {}\n\n /** The tab's landing payload: the Agent-level switch and one entry per scope directory. */\n async overview(projectId: string, agentId: string): Promise<MemoryOverviewResponse> {\n const view = await this.agentConfigService.getConfig(projectId, agentId);\n return {\n enabled: view.config.memory.enabled,\n templateHasMemory: hasMemoryPlaceholder(view.config.systemPrompt),\n memoryDir: memoryDir(this.root, projectId, agentId),\n scopes: await this.listScopes(projectId, agentId),\n };\n }\n\n /**\n * Inserts the `{{MEMORY}}` placeholder into the Agent's prompt template — the explicit\n * adoption path for an Agent created before Memory shipped; nothing inserts automatically.\n * Idempotent: a template that already carries it is left as it is (the refreshed overview\n * reports `templateHasMemory` either way).\n */\n async insertTemplatePlaceholder(\n projectId: string,\n agentId: string,\n ): Promise<MemoryOverviewResponse> {\n const view = await this.agentConfigService.getConfig(projectId, agentId);\n const next = insertMemoryPlaceholder(view.config.systemPrompt);\n if (next !== view.config.systemPrompt) {\n await this.agentConfigService.updateConfig(projectId, agentId, {\n config: { systemPrompt: next },\n });\n }\n return this.overview(projectId, agentId);\n }\n\n /**\n * The scope directories under `memory/`: the User scope first — always listed, even before it\n * exists on disk, so a memory can be filed there without waiting for a Session to create it —\n * then the Workspaces, newest activity first (one with no topic file yet sorts last, by key).\n */\n async listScopes(projectId: string, agentId: string): Promise<MemoryScopeInfo[]> {\n const base = memoryDir(this.root, projectId, agentId);\n let entries: string[] = [];\n try {\n entries = (await fs.readdir(base, { withFileTypes: true }))\n .filter((e) => e.isDirectory() && e.name !== USER_SCOPE_KEY)\n .map((e) => e.name);\n } catch {\n // No memory/ directory yet (never initialized): the User scope entry below still stands.\n }\n const workspaces = await Promise.all(\n entries.map((key) => this.scopeInfo(path.join(base, key), key, \"workspace\")),\n );\n workspaces.sort((a, b) => {\n if (a.updatedAt && b.updatedAt) return b.updatedAt.localeCompare(a.updatedAt);\n if (a.updatedAt) return -1;\n if (b.updatedAt) return 1;\n return a.scopeKey.localeCompare(b.scopeKey);\n });\n const userScope = await this.scopeInfo(path.join(base, USER_SCOPE_KEY), USER_SCOPE_KEY, \"user\");\n return [userScope, ...workspaces];\n }\n\n private async scopeInfo(\n dir: string,\n key: string,\n kind: MemoryScopeInfo[\"kind\"],\n ): Promise<MemoryScopeInfo> {\n const files = await this.topicFileNames(dir);\n let latest = 0;\n for (const name of files) {\n try {\n const stat = await fs.stat(path.join(dir, name));\n latest = Math.max(latest, stat.mtimeMs);\n } catch {\n // Raced with a delete: leave it out of the timestamp.\n }\n }\n const workspacePath = kind === \"workspace\" ? await readWorkspaceMarker(dir) : undefined;\n return {\n scopeKey: key,\n kind,\n ...(workspacePath !== undefined ? { workspacePath } : {}),\n fileCount: files.length,\n ...(latest > 0 ? { updatedAt: new Date(latest).toISOString() } : {}),\n };\n }\n\n /** Topic files of one scope: regular Markdown files only — the index, the `.workspace` marker, stray directories and symlinks (dirents that are not regular files) all stay out. */\n private async topicFileNames(dir: string): Promise<string[]> {\n try {\n return (await fs.readdir(dir, { withFileTypes: true }))\n .filter((e) => e.isFile() && isTopicFileName(e.name))\n .map((e) => e.name)\n .sort((a, b) => a.localeCompare(b));\n } catch {\n return [];\n }\n }\n\n async listFiles(\n projectId: string,\n agentId: string,\n scopeKey: string,\n ): Promise<MemoryFilesResponse> {\n const dir = await this.requireScopeDir(projectId, agentId, scopeKey);\n const names = await this.topicFileNames(dir);\n const files: MemoryFileInfo[] = [];\n for (const name of names) {\n const info = await this.fileInfo(dir, name);\n if (info) files.push(info);\n }\n return { scopeKey, files };\n }\n\n async readFile(\n projectId: string,\n agentId: string,\n scopeKey: string,\n fileName: string,\n ): Promise<MemoryFileResponse> {\n const dir = await this.requireScopeDir(projectId, agentId, scopeKey);\n const target = this.resolveFile(dir, fileName);\n try {\n // lstat, not stat: a symlink planted among the topic files (the model can create one with\n // its file tools) must not lead the read outside the Memory directory. A non-regular file\n // — or one deleted between the calls — is a 404, never a 500.\n const stat = await fs.lstat(target);\n if (!stat.isFile()) throw new Error(\"not a regular file\");\n const content = await fs.readFile(target, \"utf8\");\n return {\n scopeKey,\n file: this.describe(fileName, content, stat.size, stat.mtime),\n content,\n };\n } catch {\n throw new HttpError(404, \"memory_file_not_found\", `Memory file not found: ${fileName}`);\n }\n }\n\n /**\n * Deletes a topic file and mechanically drops its lines from the scope's `MEMORY.md`: any\n * line whose Markdown link target is exactly this file (`](<file>)`) goes; a mention of the\n * name in ordinary prose, or a link to a different file, survives. This is the only write the\n * API performs on Memory content — everything else is the model's, through a chat Session.\n */\n async deleteFile(\n projectId: string,\n agentId: string,\n scopeKey: string,\n fileName: string,\n ): Promise<void> {\n const dir = await this.requireScopeDir(projectId, agentId, scopeKey);\n const target = this.resolveFile(dir, fileName);\n try {\n await fs.unlink(target);\n } catch {\n throw new HttpError(404, \"memory_file_not_found\", `Memory file not found: ${fileName}`);\n }\n await this.pruneIndexLines(dir, fileName);\n }\n\n /** Removes index lines linking to a deleted file. Best-effort: a missing or unreadable index is left alone. */\n private async pruneIndexLines(dir: string, fileName: string): Promise<void> {\n const indexPath = path.join(dir, MEMORY_INDEX_FILENAME);\n let content: string;\n try {\n content = await fs.readFile(indexPath, \"utf8\");\n } catch {\n return;\n }\n // Match the mechanical link forms an index line may use: `](file)`, `](./file)`,\n // `](<file>)`, `](file \"title\")`. A prose mention without the link form survives.\n const escaped = fileName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const link = new RegExp(`\\\\]\\\\(\\\\s*<?(?:\\\\./)?${escaped}>?\\\\s*(?:\"[^\"]*\"\\\\s*)?\\\\)`);\n const lines = content.split(\"\\n\");\n const kept = lines.filter((line) => !link.test(line));\n if (kept.length === lines.length) return;\n await fs.writeFile(indexPath, kept.join(\"\\n\"), \"utf8\");\n }\n\n private async fileInfo(dir: string, name: string): Promise<MemoryFileInfo | null> {\n try {\n const [content, stat] = await Promise.all([\n fs.readFile(path.join(dir, name), \"utf8\"),\n fs.stat(path.join(dir, name)),\n ]);\n return this.describe(name, content, stat.size, stat.mtime);\n } catch {\n // Raced with a delete, or not readable: leave it out of the listing rather than failing the request.\n return null;\n }\n }\n\n /** One listing entry: frontmatter (falling back to the file name for an unparsed file) plus file stats. */\n private describe(name: string, content: string, size: number, mtime: Date): MemoryFileInfo {\n const meta = parseMemoryFrontmatter(content, name);\n return {\n name,\n title: meta?.name ?? name,\n description: meta?.description ?? \"\",\n ...(meta?.updatedAt !== undefined ? { updatedAt: meta.updatedAt } : {}),\n size,\n modifiedAt: mtime.toISOString(),\n };\n }\n\n /**\n * Validates a scope key and returns its directory, 404 if it does not exist. The key is only\n * ever a single directory name.\n *\n * A Workspace directory is created by a Session, so this API never creates one — a key with no\n * directory means no Session has run there and there is nothing to list. The User scope is the\n * exception: it belongs to the Agent rather than to any Session, so it is created on demand\n * (Agents that predate Memory have no `memory/user/` until their next Session).\n */\n private async requireScopeDir(\n projectId: string,\n agentId: string,\n scopeKey: string,\n ): Promise<string> {\n await this.agentConfigService.requireExists(projectId, agentId);\n if (!SCOPE_KEY_PATTERN.test(scopeKey)) {\n throw badRequest(\"scopeKey is invalid.\");\n }\n const dir = memoryScopeDir(this.root, projectId, agentId, scopeKey);\n if (scopeKey === USER_SCOPE_KEY) {\n await fs.mkdir(dir, { recursive: true });\n return this.requireRealScopeDir(projectId, agentId, scopeKey, dir);\n }\n try {\n if ((await fs.stat(dir)).isDirectory()) {\n return this.requireRealScopeDir(projectId, agentId, scopeKey, dir);\n }\n } catch {\n // Fall through to the 404 below.\n }\n throw new HttpError(\n 404,\n \"memory_scope_not_found\",\n `No Memory directory for scope: ${scopeKey}`,\n );\n }\n\n /**\n * Symlink hardening for the scope directory itself: a scope smuggled in as a symlink (the\n * model can create one with its file tools) would carry every read and delete outside\n * `memory/`, so the resolved real path must be exactly `<real memory/>/<scopeKey>` — not a\n * link's target, wherever it points.\n */\n private async requireRealScopeDir(\n projectId: string,\n agentId: string,\n scopeKey: string,\n dir: string,\n ): Promise<string> {\n try {\n const realBase = await fs.realpath(memoryDir(this.root, projectId, agentId));\n if ((await fs.realpath(dir)) === path.join(realBase, scopeKey)) return dir;\n } catch {\n // Unresolvable path: treat as absent.\n }\n throw new HttpError(\n 404,\n \"memory_scope_not_found\",\n `No Memory directory for scope: ${scopeKey}`,\n );\n }\n\n /**\n * The absolute path of a topic file inside a scope directory. The name must pass the\n * character rule, must not be the reserved index name, and the joined path is checked\n * for containment — belt and braces, since the rule already excludes separators.\n */\n private resolveFile(dir: string, fileName: string): string {\n if (!isTopicFileName(fileName)) {\n throw badRequest(\n `File name must be a Markdown topic file — no path, no leading dot, and not the ${MEMORY_INDEX_FILENAME} index.`,\n );\n }\n const target = path.join(dir, fileName);\n const rel = path.relative(dir, target);\n if (rel !== fileName || rel.includes(path.sep)) {\n throw badRequest(\"File name must not contain a path.\");\n }\n return target;\n }\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 memoryDir,\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\";\nimport { isTopicFileName } from \"./memory-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 /** Whether the config's kernel stamp is behind the current defaults generation (missing stamp = outdated) — the list card's update hint. */\n kernelOutdated: boolean;\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 /** Number of memory topic files across every scope directory under memory/ (independent of the memory switch, like skillCount). */\n memoryCount: 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, memoryCount] =\n 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 this.memoryCount(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 memoryCount,\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 /** Number of memory topic files: regular `*.md` files (minus each scope's MEMORY.md index) summed over the scope directories under memory/ (0 if the directory doesn't exist). */\n private async memoryCount(projectId: string, agentId: string): Promise<number> {\n const base = memoryDir(this.root, projectId, agentId);\n let scopes: Dirent[];\n try {\n scopes = await fs.readdir(base, { withFileTypes: true });\n } catch {\n return 0;\n }\n const counts = await Promise.all(\n scopes\n .filter((d) => d.isDirectory())\n .map(async (d) => {\n try {\n const files = await fs.readdir(path.join(base, d.name), { withFileTypes: true });\n return files.filter((f) => f.isFile() && isTopicFileName(f.name)).length;\n } catch {\n return 0;\n }\n }),\n );\n return counts.reduce((sum, n) => sum + n, 0);\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 kernelOutdated: meta.kernelOutdated,\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 memoryCount: 0,\n };\n }\n}\n","/**\n * Benchmark score reading (read-only display): walks `benchmarks/<id>/`, reads\n * `benchmark_config.toml` (title, description, per-case run count `runs`) and\n * `scoreboard.yaml` (evaluations[], each case carries its model-written averages\n * and a runs array).\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 * Case and Evaluation averages are authoritative file values. The server validates\n * the current shape but never recomputes aggregates and does not migrate or backfill\n * old Scoreboard formats.\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 BenchmarkCaseSummary,\n BenchmarkCasesResponse,\n BenchmarkEvaluation,\n BenchmarkRunScore,\n BenchmarkSummary,\n BenchmarksResponse,\n CaseMaterial,\n WorkspaceFilesResponse,\n} from \"../api/types.js\";\nimport type {\n WorkspaceFileContent,\n WorkspaceFileReadOptions,\n WorkspaceFilesService,\n} from \"./workspace-files-service.js\";\nimport { HttpError } from \"../http/errors.js\";\n\nconst STATEMENT_TITLE_READ_BYTES = 64 * 1024;\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 scoreOr(v: unknown): number | undefined {\n const value = numberOr(v);\n return value !== undefined && value >= 0 && value <= 100 ? value : undefined;\n}\n\nfunction nonNegativeOr(v: unknown): number | undefined {\n const value = numberOr(v);\n return value !== undefined && value >= 0 ? value : undefined;\n}\n\nfunction nonNegativeIntegerOr(v: unknown): number | undefined {\n const value = nonNegativeOr(v);\n return value !== undefined && Number.isInteger(value) ? value : undefined;\n}\n\n/** `null` is the one valid unknown-cost representation; undefined means invalid input. */\nfunction nullableCostOr(v: unknown): number | null | undefined {\n if (v === null) return null;\n return nonNegativeOr(v);\n}\n\nfunction stringOr(v: unknown): string | undefined {\n return typeof v === \"string\" && v !== \"\" ? v : undefined;\n}\n\nfunction isWithin(parent: string, child: string): boolean {\n const relative = path.relative(parent, child);\n return relative !== \"\" && !relative.startsWith(\"..\") && !path.isAbsolute(relative);\n}\n\nfunction statementTitle(statement: string, fallback: string): string {\n const heading = /^#\\s+(.+)$/m.exec(statement)?.[1]?.trim();\n return heading?.replace(/^Case\\s+\\d+\\s*:\\s*/i, \"\") || fallback;\n}\n\nasync function readStatementTitle(readme: string, fallback: string): Promise<string> {\n const handle = await fs.open(readme, \"r\");\n try {\n const buffer = Buffer.alloc(STATEMENT_TITLE_READ_BYTES);\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);\n return statementTitle(buffer.subarray(0, bytesRead).toString(\"utf8\"), fallback);\n } finally {\n await handle.close();\n }\n}\n\n/** Shapes one current-format Run; a malformed entry invalidates its containing Case. */\nfunction toRun(v: unknown): BenchmarkRunScore | null {\n const r = asRecord(v);\n const score = scoreOr(r.score);\n const cost = nullableCostOr(r.cost);\n const durationMs = nonNegativeIntegerOr(r.duration_ms);\n const sessionId = stringOr(r.session_id);\n if (score === undefined || cost === undefined || durationMs === undefined || !sessionId)\n return null;\n return { score, cost, durationMs, sessionId };\n}\n\n/**\n * Shapes one current-format Case. Its stored aggregates are trusted as written:\n * this parser intentionally performs no average or consistency calculation.\n */\nfunction toCase(v: unknown): BenchmarkCaseScore | null {\n const cr = asRecord(v);\n const caseId = stringOr(cr.case);\n const score = scoreOr(cr.score);\n const cost = nullableCostOr(cr.cost);\n const durationMs = nonNegativeIntegerOr(cr.duration_ms);\n if (\n !caseId ||\n score === undefined ||\n cost === undefined ||\n durationMs === undefined ||\n \"max_score\" in cr ||\n !Array.isArray(cr.runs) ||\n cr.runs.length === 0\n ) {\n return null;\n }\n const parsedRuns = cr.runs.map(toRun);\n if (parsedRuns.some((run) => run === null)) return null;\n const runs = parsedRuns as BenchmarkRunScore[];\n return {\n case: caseId,\n score,\n cost,\n durationMs,\n runs,\n };\n}\n\n/** Shapes one current-format Evaluation and trusts its stored aggregate metrics. */\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 = scoreOr(r.score);\n const cost = nullableCostOr(r.cost);\n const durationMs = nonNegativeIntegerOr(r.duration_ms);\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 const modelId = stringOr(r.model_id);\n const provider = stringOr(r.provider);\n const thinkingLevel = stringOr(r.thinking_level);\n const version = nonNegativeIntegerOr(r.version);\n if (\n typeof time !== \"string\" ||\n time === \"\" ||\n score === undefined ||\n cost === undefined ||\n durationMs === undefined ||\n !modelId ||\n !provider ||\n !thinkingLevel ||\n version === undefined ||\n version < 1 ||\n !Array.isArray(r.cases) ||\n r.cases.length === 0\n ) {\n return null;\n }\n const parsedCases = r.cases.map(toCase);\n if (parsedCases.some((item) => item === null)) return null;\n const cases = parsedCases as BenchmarkCaseScore[];\n return {\n time,\n ...(summaryTitle !== undefined ? { summaryTitle } : {}),\n ...(summary !== undefined ? { summary } : {}),\n modelId,\n provider,\n thinkingLevel,\n score,\n version,\n cost,\n durationMs,\n cases,\n };\n}\n\nexport class BenchmarkService {\n constructor(\n private readonly root: string,\n private readonly workspaceFiles: WorkspaceFilesService,\n ) {}\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 async listCases(\n projectId: string,\n agentId: string,\n benchmarkId: string,\n ): Promise<BenchmarkCasesResponse> {\n const baseDir = benchmarksDir(this.root, projectId, agentId);\n const benchDir = path.join(baseDir, benchmarkId);\n let entries: Array<{ name: string; isDirectory(): boolean }>;\n let realBaseDir: string;\n let realBenchDir: string;\n try {\n [entries, realBaseDir, realBenchDir] = await Promise.all([\n fs.readdir(benchDir, { withFileTypes: true }),\n fs.realpath(baseDir),\n fs.realpath(benchDir),\n ]);\n } catch {\n return { cases: [] };\n }\n if (!isWithin(realBaseDir, realBenchDir)) return { cases: [] };\n\n const cases: BenchmarkCaseSummary[] = [];\n for (const entry of entries\n .filter((item) => item.isDirectory() && item.name.startsWith(\"CASE-\"))\n .sort((a, b) => a.name.localeCompare(b.name))) {\n const fallback: BenchmarkCaseSummary = { id: entry.name, title: entry.name };\n try {\n const statementDir = await this.caseMaterialRoot(\n projectId,\n agentId,\n benchmarkId,\n entry.name,\n \"statement\",\n );\n const realReadme = await fs.realpath(path.join(statementDir, \"README.md\"));\n if (!isWithin(statementDir, realReadme)) throw new Error(\"README escapes Statement\");\n cases.push({\n id: entry.name,\n title: await readStatementTitle(realReadme, entry.name),\n });\n } catch {\n cases.push(fallback);\n }\n }\n return { cases };\n }\n\n async listCaseFiles(\n projectId: string,\n agentId: string,\n benchmarkId: string,\n caseId: string,\n rel: string,\n material: CaseMaterial,\n ): Promise<WorkspaceFilesResponse> {\n const materialRoot = await this.caseMaterialRoot(\n projectId,\n agentId,\n benchmarkId,\n caseId,\n material,\n );\n return this.workspaceFiles.list(materialRoot, rel);\n }\n\n async readCaseFile(\n projectId: string,\n agentId: string,\n benchmarkId: string,\n caseId: string,\n rel: string,\n material: CaseMaterial,\n options?: WorkspaceFileReadOptions,\n ): Promise<WorkspaceFileContent> {\n const materialRoot = await this.caseMaterialRoot(\n projectId,\n agentId,\n benchmarkId,\n caseId,\n material,\n );\n return this.workspaceFiles.read(materialRoot, rel, options);\n }\n\n private async caseMaterialRoot(\n projectId: string,\n agentId: string,\n benchmarkId: string,\n caseId: string,\n material: CaseMaterial,\n ): Promise<string> {\n const benchDir = path.join(benchmarksDir(this.root, projectId, agentId), benchmarkId);\n const caseDir = path.join(benchDir, caseId);\n const materialRoot = path.join(caseDir, material);\n try {\n const [realBenchDir, realCaseDir, realMaterialRoot] = await Promise.all([\n fs.realpath(benchDir),\n fs.realpath(caseDir),\n fs.realpath(materialRoot),\n ]);\n if (\n !isWithin(realBenchDir, realCaseDir) ||\n path.dirname(realMaterialRoot) !== realCaseDir ||\n path.basename(realMaterialRoot) !== material\n ) {\n throw new Error(\"Case material is not canonical\");\n }\n return realMaterialRoot;\n } catch {\n throw new HttpError(404, \"not_found\", `Case ${material} does not exist.`);\n }\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 semantic Case subfolders.\n let caseCount = 0;\n try {\n const entries = await fs.readdir(benchDir, { withFileTypes: true });\n caseCount = entries.filter((e) => e.isDirectory() && e.name.startsWith(\"CASE-\")).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\";\nimport type { TraceIndexService } from \"./trace-index.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 /** Trace-index cleanup on Project destruction (rows describe files removed with the Project dir). */\n traceIndex: TraceIndexService;\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 * Rename a Project's display name (owner): the id stays immutable — it names the directory,\n * the Workspace paths and every stored reference — so only the label in project_config.toml\n * changes. Returns the refreshed summary so the caller can swap it into its list without a\n * reload.\n */\n async renameProject(userId: string, projectId: string, name: string): Promise<ProjectSummary> {\n const row = this.requireProjectOwner(userId, projectId);\n await this.deps.projectConfig.setName(projectId, name);\n // requireProjectOwner already established the role; it returns the plain row.\n return {\n projectId,\n name,\n role: \"owner\",\n ownerUserId: row.ownerUserId,\n createdAt: row.createdAt,\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 this.deps.traceIndex.removeProject(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 default list is served from the DB index alone (web rows: `client` web/NULL) — no\n * Trace directory scanning in steady state (#139). `includeCli` widens it to DB ∪ Trace\n * directory discovery: scans `<agent>/traces/<date>/<session_id>_<index3>.jsonl`; an\n * unmanaged Session (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 marked\n * `client: \"cli\"` (approval_mode defaults, createdAt is taken from the timestamp embedded\n * in session_id) — the default list keeps excluding it, but it becomes individually\n * reachable (deep links).\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 { createAgent, isSessionMeta } from \"@prismshadow/penguin-core\";\nimport type { ProxyEnvPolicy } 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 { TraceIndexService, traceFilePath } from \"./trace-index.js\";\nimport type { ProjectConfigService } from \"./project-config-service.js\";\n\nconst SESSION_ID_TS_RE = /^session-(\\d{4})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-[0-9a-f]{8}$/;\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 /** Trace-file index: discovery / adoption / stats serve from it (mtime-gated reconciler; no per-request walks). */\n traceIndex: TraceIndexService;\n /**\n * Admin proxy-settings threading (same getter the session loader passes, see\n * createCoreSessionLoader): the runtime created here is adopted by the manager and\n * runs the Session's first Task, so it needs the command-subprocess proxy policy too.\n */\n proxyEnv?: () => ProxyEnvPolicy | null;\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(row: SessionRow, hasTrace: boolean): Promise<SessionInfo> {\n const source = await this.sourceOf(row, hasTrace);\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 * index registration); on a miss (a Session created before this process started) the\n * trace index's registration-time facts answer — the reconciler head-read the earliest\n * shard once when the file first appeared, so no file is touched here. A Session with\n * no Trace yet stays unknown and is NOT cached negatively — its meta may appear with\n * the first run.\n */\n private async sourceOf(row: SessionRow, hasTrace: boolean): 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 const facts = this.deps.traceIndex.repo.getSession(row.sessionId);\n if (!facts?.metaRead) return undefined; // Unreadable/unregistered: stay unknown, retry on the next list.\n this.deps.sources.set(row.sessionId, facts.source);\n return facts.source ?? undefined;\n }\n\n /** Whether this Session already has a Trace record (a Task has been run): answered by the index (reconciled first). */\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(row: SessionRow, hasTrace: boolean): Promise<SessionCategory> {\n if ((row.archivedAt ?? null) !== null) return \"archived\";\n return (await this.sourceOf(row, hasTrace)) ?? \"active\";\n }\n\n /**\n * List, sorted by createdAt descending. The default serves **web sessions straight from\n * the DB** (`client` web/NULL rows) with no Trace directory scanning — the answer to\n * many-session sidebar reloads re-walking the filesystem on every request (#139). One\n * lazy discovery walk still runs for a list call that contains rows this process has not\n * classified yet (no in-process source entry): it supplies the Trace locations for the\n * one-time head reads and backfills the `has_trace` cache; once every row is classified,\n * list calls touch only the DB. `includeCli` widens the list to DB ∪ Trace directory\n * discovery (adopting unmanaged CLI Traces as `client: \"cli\"` rows), which inherently\n * scans — that path is opt-in via the \"show CLI sessions\" preference.\n *\n * Optional `paging` returns just that slice (the sidebar pages with limit+1 to detect\n * \"has more\"); slicing happens before toInfo, so per-request source derivation (lazy\n * Trace-head reads) stays bounded by the page size.\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 includeCli?: boolean;\n } = {},\n ): Promise<{\n sessions: SessionInfo[];\n counts?: SessionCategoryCounts;\n workspaceCounts?: Record<string, SessionCategoryCounts>;\n }> {\n const { paging, category, withCounts, includeCli } = opts;\n const rows = new Map(\n this.deps.sessions\n .listByAgent(projectId, agentId, { webOnly: !includeCli })\n .map((r) => [r.sessionId, r]),\n );\n\n let traces: ReadonlySet<string> | undefined;\n if (includeCli) {\n traces = await this.discoverTraces(projectId, agentId);\n // Unmanaged Trace Sessions (the CLI's): backfill an index row from the trace\n // index's registration-time facts, marked client \"cli\" so the default list can\n // exclude them.\n for (const sessionId of traces) {\n if (rows.has(sessionId)) continue;\n const discovered = this.adoptTraceSession(projectId, agentId, sessionId);\n if (discovered) rows.set(sessionId, discovered);\n }\n } else if ([...rows.values()].some((r) => this.deps.sources.get(r.sessionId) === undefined)) {\n // Hydration pass: some rows predate this process and are unclassified — one\n // reconciled index read supplies discovery so sourceOf's facts lookups and the\n // has_trace cache need no per-row work. Steady state (everything classified)\n // skips this.\n traces = await this.discoverTraces(projectId, agentId);\n for (const row of rows.values()) {\n if (!row.hasTrace && traces.has(row.sessionId)) {\n row.hasTrace = true;\n this.deps.sessions.markHasTrace(row.sessionId);\n }\n }\n }\n\n const sorted = [...rows.values()].sort(\n (a, b) => b.createdAt.localeCompare(a.createdAt) || b.sessionId.localeCompare(a.sessionId),\n );\n const rowHasTrace = (row: SessionRow): boolean =>\n traces ? traces.has(row.sessionId) : row.hasTrace === true;\n const toPage = (page: SessionRow[]) =>\n Promise.all(page.map((row) => this.toInfo(row, rowHasTrace(row))));\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, rowHasTrace(row));\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 activity from the index (one mtime-gated reconcile, then a pure DB read —\n // this used to walk the Agent's ENTIRE trace history on every agents-list request):\n // the date is the shard's date directory (local yyyy-mm-dd, core's writing convention).\n await this.deps.traceIndex.reconcileAgent(projectId, agentId);\n for (const f of this.deps.traceIndex.repo.listFilesByAgent(projectId, agentId)) {\n mark(f.date, f.sessionId);\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 ...(this.deps.proxyEnv ? { proxyEnv: this.deps.proxyEnv } : {}),\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 // Everything created through this service is the Web App's (schedule runs included);\n // \"cli\" is stamped only by Trace adoption. NULL means a legacy row, treated as web.\n client: \"web\",\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 await this.deps.traceIndex.reconcileAgent(row.projectId, row.agentId);\n let files = this.deps.traceIndex.repo.listFilesBySession(\n row.projectId,\n row.agentId,\n row.sessionId,\n );\n if (files.length === 0) {\n // Index miss with disk possibly ahead: one forced diff, then retry (the consumers'\n // rule — a stale index costs one extra scan, never a missing resume shard).\n await this.deps.traceIndex.reconcileAgent(row.projectId, row.agentId, { force: true });\n files = this.deps.traceIndex.repo.listFilesBySession(\n row.projectId,\n row.agentId,\n row.sessionId,\n );\n }\n const latest = files.at(-1);\n return latest === undefined ? undefined : traceFilePath(this.deps.root, latest);\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(projectId: string, agentId: string): Promise<Set<string>> {\n await this.deps.traceIndex.reconcileAgent(projectId, agentId);\n const out = new Set<string>();\n for (const f of this.deps.traceIndex.repo.listFilesByAgent(projectId, agentId)) {\n out.add(f.sessionId);\n }\n return out;\n }\n\n /**\n * Adopts a Session that exists only in the Trace directory, from the index's\n * registration-time facts (the reconciler head-read the earliest shard's session_meta\n * once when the file first appeared — adoption itself reads no file).\n */\n private adoptTraceSession(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): SessionRow | null {\n const facts = this.deps.traceIndex.repo.getSession(sessionId);\n if (!facts?.metaRead) return null; // Corrupt/unreadable head: skip (does not block the list; retried by a later reconcile)\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 (facts.provider === null || facts.modelId === null) return null;\n // Registration already narrowed the origin; record it in the registry (single source of truth).\n this.deps.sources.set(sessionId, facts.source);\n const row: SessionRow = {\n sessionId,\n projectId,\n agentId,\n provider: facts.provider,\n modelId: facts.modelId,\n workspace: facts.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 // Adopted = a Trace this server never created, i.e. the CLI's: the default list\n // (web-only) excludes these rows; the \"show CLI sessions\" preference includes them.\n client: \"cli\",\n hasTrace: true,\n createdAt: sessionIdCreatedAt(sessionId) ?? facts.firstTs ?? new Date().toISOString(),\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/** 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","/**\n * Trace-file index: keeps the trace_files / trace_sessions derived cache in step with\n * the on-disk Trace tree so hot request paths never walk directories or head-read\n * files. The DISK remains the single source of truth — every row is rebuildable, and\n * the cache is never authority for absence (consumers force-reconcile and retry on a\n * miss before erroring).\n *\n * Keeping in step happens on three paths:\n * - Write-time registration: server paths that create/delete shards with known\n * identities register synchronously (trace import via registerImportedFile;\n * Session delete via removeSession). Server task runs write through core's Trace\n * Writer without telling the server which shard rotated — those land via the\n * reconciler like external writes (the run bumps the date-dir mtime).\n * - mtime-gated reconciliation (reconcileAgent): the hot path stats the Agent's\n * traces root plus its newest known date dir (TWO stat calls, no readdir; a new\n * date dir bumps the root's mtime, a new file in the current date dir bumps that\n * dir's mtime). Only on a change does it readdir — and only the date dirs whose\n * mtime moved — registering new shards and classifying each new Session ONCE\n * (bounded head-read of the earliest shard for origin/workspace/title). Restart\n * forgets the in-memory gate, so the first request per Agent after an upgrade or\n * restart runs one full diff (this is also how the index first populates: no\n * migration step needed).\n * - Forced reconciliation (`force`): ignores the gate and diffs every date dir —\n * the miss-retry path for gate blind spots (e.g. an external write into an OLD\n * date dir moves neither gated mtime). A stale index therefore degrades to one\n * extra scan, never to a false 404.\n *\n * Single-flight per Agent: concurrent requests share one in-flight reconcile instead\n * of stampeding the directory.\n */\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { agentsDir, isSessionMeta, tracesDir } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport type { TraceFileRow, TraceSessionRow } from \"../db/repos/trace-index.js\";\nimport { TraceIndexRepo } from \"../db/repos/trace-index.js\";\nimport { cacheable, statMtime } from \"../internal/mtime-gate.js\";\nimport { readTraceHead } from \"../internal/trace-head.js\";\nimport { asSessionSource } from \"../runtime/session-sources.js\";\nimport type { SessionSources } from \"../runtime/session-sources.js\";\nimport { fallbackTitle } from \"../runtime/title-generator.js\";\n\nconst TRACE_FILE_RE = /^(.+)_(\\d{3})\\.jsonl$/;\n\n/** Absolute path of an indexed shard (reconstructed — rows never store paths; the data root may move). */\nexport function traceFilePath(root: string, row: TraceFileRow): string {\n const name = `${row.sessionId}_${String(row.fileIndex).padStart(3, \"0\")}.jsonl`;\n return path.join(tracesDir(root, row.projectId, row.agentId), row.date, name);\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\n/** Session facts extracted from a shard's records (registration-time classification). */\nfunction factsFromRecords(\n projectId: string,\n agentId: string,\n sessionId: string,\n records: OmniMessage[],\n): TraceSessionRow {\n const meta = records.find(isSessionMeta);\n let firstPrompt: string | null = null;\n for (const msg of records) {\n if (msg.type !== \"model_msg\") continue;\n const p = msg.payload as { type?: string; role?: string; text?: string };\n if (p.type === \"text\" && p.role === \"user\" && typeof p.text === \"string\") {\n firstPrompt = p.text;\n break;\n }\n }\n return {\n sessionId,\n projectId,\n agentId,\n source: meta ? (asSessionSource(meta.payload.source) ?? null) : null,\n workspace: meta && typeof meta.payload.workspace === \"string\" ? meta.payload.workspace : \"\",\n title: firstPrompt !== null ? fallbackTitle(firstPrompt) : null,\n provider: meta && typeof meta.payload.provider === \"string\" ? meta.payload.provider : null,\n modelId: meta && typeof meta.payload.model_id === \"string\" ? meta.payload.model_id : null,\n firstTs: records[0]?.timestamp ?? null,\n metaRead: meta !== undefined,\n };\n}\n\n/** In-memory reconcile gate of one Agent: last seen mtimes (traces root + per date dir). */\ninterface SeenDirs {\n root: number;\n dates: Map<string, number>;\n}\n\nexport class TraceIndexService {\n private readonly seen = new Map<string, SeenDirs>();\n private readonly inflight = new Map<string, Promise<void>>();\n /**\n * Test observability (asserting the hot path stays readdir/head-read free):\n * gateStats = gate stat calls, dirScans = date-dir readdir passes, headReads =\n * registration-time classification reads.\n */\n readonly counters = { gateStats: 0, dirScans: 0, headReads: 0 };\n\n constructor(\n private readonly root: string,\n readonly repo: TraceIndexRepo,\n /** Shared origin registry: registration-time classification publishes into it (single source of truth for `source`). */\n private readonly sources?: SessionSources,\n ) {}\n\n /**\n * Brings one Agent's index in step with disk. Hot path (nothing changed): two stat\n * calls, zero readdir. `force` ignores the mtime gate and diffs every date dir (the\n * consumers' miss-retry path).\n */\n reconcileAgent(\n projectId: string,\n agentId: string,\n opts: { force?: boolean } = {},\n ): Promise<void> {\n const key = `${projectId}\\0${agentId}`;\n const existing = this.inflight.get(key);\n if (existing) {\n // A forced request must observe disk AFTER the point it was issued: chain a fresh\n // pass behind the in-flight one instead of piggybacking on possibly-gated work.\n return opts.force === true\n ? existing.then(() => this.reconcileAgent(projectId, agentId, opts))\n : existing;\n }\n const run = this.doReconcile(projectId, agentId, opts.force === true).finally(() => {\n this.inflight.delete(key);\n });\n this.inflight.set(key, run);\n return run;\n }\n\n /** Reconciles every Agent of a Project (the subagent-pointer resolver's miss path). */\n async reconcileProject(projectId: string, opts: { force?: boolean } = {}): Promise<void> {\n for (const agentId of await listDirs(agentsDir(this.root, projectId))) {\n await this.reconcileAgent(projectId, agentId, opts);\n }\n }\n\n private async doReconcile(projectId: string, agentId: string, force: boolean): Promise<void> {\n const key = `${projectId}\\0${agentId}`;\n const dir = tracesDir(this.root, projectId, agentId);\n const cached = this.seen.get(key);\n this.counters.gateStats += 1;\n const rootMtime = await statMtime(dir);\n if (rootMtime === null) {\n // No traces directory: whatever the index still holds for this Agent is stale.\n this.repo.deleteByAgent(projectId, agentId);\n this.seen.set(key, { root: -1, dates: new Map() });\n return;\n }\n if (!force && cached && cached.root === rootMtime) {\n // Root unchanged (no date dir created/removed). The one blind spot on this level is\n // a new file inside an EXISTING date dir — in practice always the newest one (the\n // Writer names dirs by current local date) — so gate on that single dir's mtime too.\n // Cached sentinels (-1: the dir was fresh when last seen) never match, forcing a\n // re-diff until the tree has been quiet (see mtime-gate's FRESH_MS).\n const newest = [...cached.dates.keys()].sort().at(-1);\n if (newest === undefined) return;\n this.counters.gateStats += 1;\n const m = await statMtime(path.join(dir, newest));\n if (m !== null && m === cached.dates.get(newest)) return;\n }\n\n // Change detected / first look / force: diff date dirs (readdir only the changed ones).\n this.counters.dirScans += 1;\n const next: SeenDirs = { root: cacheable(rootMtime), dates: new Map() };\n const knownByDate = new Map<string, TraceFileRow[]>();\n for (const row of this.repo.listFilesByAgent(projectId, agentId)) {\n const list = knownByDate.get(row.date) ?? [];\n list.push(row);\n knownByDate.set(row.date, list);\n }\n const newSessions = new Set<string>();\n const dateDirs = await listDirs(dir);\n for (const date of dateDirs) {\n const m = await statMtime(path.join(dir, date));\n if (m === null) continue;\n next.dates.set(date, cacheable(m));\n // Unchanged dir already reflected in the gate cache: its rows are current. A fresh\n // mtime is never treated as unchanged (and was cached as a non-matching sentinel).\n if (!force && cached?.dates.get(date) === m) continue;\n const known = new Map(\n (knownByDate.get(date) ?? []).map((r) => [`${r.sessionId}\\0${r.fileIndex}`, r]),\n );\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 fileIndex = Number(match[2]);\n const fileKey = `${sessionId}\\0${fileIndex}`;\n let size = 0;\n try {\n size = (await fs.stat(path.join(dir, date, file))).size;\n } catch {\n continue; // Vanished between readdir and stat: skip; a later pass settles it.\n }\n this.repo.upsertFile({ projectId, agentId, sessionId, fileIndex, date, sizeBytes: size });\n known.delete(fileKey);\n if (this.repo.getSession(sessionId)?.metaRead !== true) newSessions.add(sessionId);\n }\n // Rows whose files vanished from this dir (external delete / rename).\n for (const row of known.values()) {\n this.repo.deleteFile(projectId, agentId, row.sessionId, row.fileIndex);\n }\n }\n // Whole date dirs deleted from disk.\n for (const [date, rows] of knownByDate) {\n if (next.dates.has(date)) continue;\n for (const row of rows)\n this.repo.deleteFile(projectId, agentId, row.sessionId, row.fileIndex);\n }\n // Registration-time classification: ONCE per newly seen Session (bounded head-read\n // of its earliest shard) — listings afterwards never touch file contents.\n for (const sessionId of newSessions) {\n await this.classifySession(projectId, agentId, sessionId);\n }\n this.seen.set(key, next);\n }\n\n /** Head-reads the Session's earliest indexed shard and stores its facts (origin/workspace/title/model ref). */\n private async classifySession(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<void> {\n const files = this.repo.listFilesBySession(projectId, agentId, sessionId);\n const earliest = files[0];\n if (earliest === undefined) return;\n this.counters.headReads += 1;\n let records: OmniMessage[];\n try {\n records = await readTraceHead(traceFilePath(this.root, earliest));\n } catch {\n records = []; // Unreadable head: facts stay unknown (meta_read=0 → retried by a later reconcile pass).\n }\n const facts = factsFromRecords(projectId, agentId, sessionId, records);\n this.repo.upsertSession(facts);\n if (facts.metaRead) this.sources?.set(sessionId, facts.source);\n }\n\n /**\n * Write-time registration for the Trace import route: the caller has the file's\n * identity AND parsed records in hand, so the row and facts are stored synchronously\n * with zero additional IO.\n */\n registerImportedFile(args: {\n projectId: string;\n agentId: string;\n sessionId: string;\n fileIndex: number;\n date: string;\n sizeBytes: number;\n records: OmniMessage[];\n }): void {\n this.repo.upsertFile({\n projectId: args.projectId,\n agentId: args.agentId,\n sessionId: args.sessionId,\n fileIndex: args.fileIndex,\n date: args.date,\n sizeBytes: args.sizeBytes,\n });\n const facts = factsFromRecords(args.projectId, args.agentId, args.sessionId, args.records);\n this.repo.upsertSession(facts);\n if (facts.metaRead) this.sources?.set(args.sessionId, facts.source);\n // The write moved the directory mtimes: drop the gate so the next reconcile re-syncs\n // (cheap — the import's own rows are already upserted; the pass just confirms).\n this.seen.delete(`${args.projectId}\\0${args.agentId}`);\n }\n\n /** Write-time removal for Session deletion (files are being rm'ed by the caller). */\n removeSession(projectId: string, agentId: string, sessionId: string): void {\n this.repo.deleteBySession(sessionId);\n this.seen.delete(`${projectId}\\0${agentId}`);\n }\n\n /** Agent deletion: its whole tree is going away with it. */\n removeAgent(projectId: string, agentId: string): void {\n this.repo.deleteByAgent(projectId, agentId);\n this.seen.delete(`${projectId}\\0${agentId}`);\n }\n\n /** Project deletion. */\n removeProject(projectId: string): void {\n this.repo.deleteByProject(projectId);\n for (const key of this.seen.keys()) {\n if (key.startsWith(`${projectId}\\0`)) this.seen.delete(key);\n }\n }\n}\n","/**\n * Bounded Trace-head reads (shared by the Session index's session_meta backfill and the\n * Trace listing's first-prompt title fallback): parse only the head window of a shard\n * instead of pulling a whole multi-MB file into memory.\n */\nimport { open } from \"node:fs/promises\";\nimport { parseTraceLines, readTraceTolerant } from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\n\n/** Head window for Trace head reads: generous for a long system prompt, far below a whole multi-MB shard. */\nexport const 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 (and the first user prompt follows right behind it in the earliest shard),\n * so a bounded read finds them without pulling the whole file into memory.\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 */\nexport async function readTraceHead(filePath: string): Promise<OmniMessage[]> {\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 * Trace service.\n *\n * History messages: all of the Session's index files concatenated in order\n * (readTraceTolerant, tolerating a truncated last line and skipping malformed\n * middle lines), containing only the complete messages and events that were\n * actually written to Trace (naturally excluding partial_*); in-flight\n * 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 isSessionMeta,\n parseTraceLines,\n parseUserSteeringText,\n readTraceTolerant,\n tracesDir,\n} from \"@prismshadow/penguin-core\";\nimport type { OmniMessage } from \"@prismshadow/penguin-core\";\nimport type {\n AgentTraceSessionEntry,\n AgentTracesResponse,\n RequestSpan,\n SessionCategory,\n SessionCategoryCounts,\n ToolCallSpan,\n TraceAnalysisResponse,\n TraceEventsResponse,\n TraceFileInfo,\n TraceImportResponse,\n TraceModelSegment,\n TraceOtherSpan,\n TraceTaskStats,\n TraceToolSpan,\n UsageTrendPointInTrace,\n} from \"../api/types.js\";\nimport type { SessionRow } from \"../db/repos/sessions.js\";\nimport type { TraceFileRow, TraceSessionRow } from \"../db/repos/trace-index.js\";\nimport { HttpError } from \"../http/errors.js\";\nimport { formatLocalDate } from \"../internal/dates.js\";\nimport type { SessionSources } from \"../runtime/session-sources.js\";\nimport {\n cloneScanState,\n deserializePrefix,\n encodeCursor,\n finalizeScan,\n initialScanState,\n scanMessages,\n serializePrefix,\n} from \"./message-window.js\";\nimport type {\n ChildAggregate,\n MessageCursor,\n ScanState,\n WindowPriorStats,\n} from \"./message-window.js\";\nimport { TraceIndexService, traceFilePath } from \"./trace-index.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 /** Last observed size from the index row (the page-stats cache write guard). */\n sizeBytes: number;\n}\n\n/** A windowed history read's request shape (see readMessagesPage). */\nexport type MessagesPageRequest =\n { kind: \"tail\"; limit: number } | { kind: \"before\"; cursor: MessageCursor; limit: number };\n\n/** A windowed history read's result (route maps it onto MessagesResponse.page). */\nexport interface MessagesPageResult {\n messages: OmniMessage[];\n /** Cursor of the window's first unit; absent = the window reaches the beginning. */\n before?: string;\n /** Cumulative stats before the window (earlierTurns = prior.turns). */\n prior: WindowPriorStats;\n}\n\n/**\n * Per-request expansion context: `projectScanned` caps the miss-path force reconcile at\n * one per request; `ancestry` guards cycles; `raw` memoizes each child session's\n * concatenated shard messages so the window expansion and the subagent-token\n * aggregation never read the same child twice within one request.\n */\ninterface ExpandCtx {\n projectScanned: boolean;\n ancestry: Set<string>;\n depth: number;\n raw: Map<string, OmniMessage[] | null>;\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\n/**\n * Read-only slice of the Session index the paginated listing consults (SessionsRepo\n * structurally satisfies it; injected so service tests need no full app): one indexed\n * query answers title / archived / workspace / client for every DB-tracked Session of\n * the Agent.\n */\nexport interface TraceSessionIndex {\n listByAgent(projectId: string, agentId: string): SessionRow[];\n}\n\n/** TraceService wiring (the trace-file index is required: every listing/locating path serves from it — no directory walks). */\nexport interface TraceServiceDeps {\n index: TraceIndexService;\n /** Optional (narrow tests may omit): DB rows supplying titles / archived / workspace / client. */\n sessions?: TraceSessionIndex;\n /** Optional (narrow tests may omit): the shared in-process Session-origin registry (single source of truth for `source`). */\n sources?: SessionSources;\n /**\n * Test observability: called with the path of every Trace shard this service reads\n * from disk (windowed-read tests assert an old-window request never touches the\n * newest shard and vice versa). Production wiring omits it.\n */\n observeShardRead?: (path: string) => void;\n}\n\n/** One Session's classification result (see classify): its sidebar category + Workspace path (\"\" = unknown). */\ninterface TraceSessionFacts {\n category: SessionCategory;\n workspace: string;\n}\n\nexport class TraceService {\n constructor(\n private readonly root: string,\n private readonly deps: TraceServiceDeps,\n ) {}\n\n /**\n * All of this Session's Trace files (sorted by index ascending), served from the\n * index. An empty answer force-reconciles once and retries before being believed:\n * disk is the source of truth, and a gate blind spot (an external write into an old\n * date dir) must cost one extra scan, never a false 404.\n */\n private async locateAll(\n projectId: string,\n agentId: string,\n sessionId: string,\n ): Promise<LocatedFile[]> {\n await this.deps.index.reconcileAgent(projectId, agentId);\n let rows = this.deps.index.repo.listFilesBySession(projectId, agentId, sessionId);\n if (rows.length === 0) {\n await this.deps.index.reconcileAgent(projectId, agentId, { force: true });\n rows = this.deps.index.repo.listFilesBySession(projectId, agentId, sessionId);\n }\n return rows.map((r) => ({\n path: traceFilePath(this.root, r),\n date: r.date,\n index: r.fileIndex,\n sizeBytes: r.sizeBytes,\n }));\n }\n\n /** All shard reads funnel through here (deps.observeShardRead is the windowed-read tests' proof of which files were touched). */\n private async readShard(path: string): Promise<OmniMessage[]> {\n this.deps.observeShardRead?.(path);\n return readTraceTolerant(path);\n }\n\n /** Deletes all of this Session's Trace files (called when the Session is deleted); the index rows go with them. */\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 this.deps.index.removeSession(projectId, agentId, sessionId);\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 const ctx: ExpandCtx = {\n projectScanned: false,\n ancestry: new Set([sessionId]),\n depth: 0,\n raw: new Map(),\n };\n const files = await this.locateAll(projectId, agentId, sessionId);\n const out: OmniMessage[] = [];\n for (const file of files) {\n out.push(...(await this.expandMessages(projectId, await this.readShard(file.path), ctx)));\n }\n return out;\n }\n\n /**\n * Locates a subagent pointer's owning Agent within the Project: a single index\n * lookup (the old implementation walked EVERY Agent's whole traces tree to build a\n * sessionId → agentId map per readMessages call). On a miss, the whole Project is\n * force-reconciled ONCE per readMessages call and the lookup retried — a child\n * Trace written by an external process is one scan away, never invisible.\n */\n private async resolveChildAgent(\n projectId: string,\n childSid: string,\n ctx: { projectScanned: boolean },\n ): Promise<string | null> {\n const hit = this.deps.index.repo.findAgentBySession(projectId, childSid);\n if (hit !== null || ctx.projectScanned) return hit;\n ctx.projectScanned = true;\n await this.deps.index.reconcileProject(projectId, { force: true });\n return this.deps.index.repo.findAgentBySession(projectId, childSid);\n }\n\n /** A child session's concatenated raw shard messages, memoized per request; null = no Trace located. */\n private async readChildRaw(\n projectId: string,\n childSid: string,\n ctx: ExpandCtx,\n ): Promise<OmniMessage[] | null> {\n const cached = ctx.raw.get(childSid);\n if (cached !== undefined) return cached;\n const childAgent = await this.resolveChildAgent(projectId, childSid, ctx);\n let raw: OmniMessage[] | null = null;\n if (childAgent) {\n const files = await this.locateAll(projectId, childAgent, childSid);\n if (files.length > 0) {\n raw = [];\n for (const file of files) raw.push(...(await this.readShard(file.path)));\n }\n }\n ctx.raw.set(childSid, raw);\n return raw;\n }\n\n /**\n * Expand subagent pointers within one message span (the whole transcript on the full\n * path, one window on the paged path — same recursive shape either way).\n */\n private async expandMessages(\n projectId: string,\n messages: readonly OmniMessage[],\n ctx: ExpandCtx,\n ): Promise<OmniMessage[]> {\n const out: OmniMessage[] = [];\n for (const msg of messages) {\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 const raw = await this.readChildRaw(projectId, childSid, ctx);\n let nested: OmniMessage[] = [];\n if (raw !== null) {\n ctx.ancestry.add(childSid);\n nested = await this.expandMessages(projectId, raw, { ...ctx, depth: ctx.depth + 1 });\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 return out;\n }\n\n /**\n * A subagent pointer's subtree aggregate for the window scanner (message-window.ts):\n * descendant token_usage request totals plus the subtree's max timestamp — the same\n * contributions the expanded messages make to the Web's parent-level stats tracker.\n * The recursion mirrors expandMessages' depth/ancestry rules exactly, so an\n * unexpandable pointer contributes nothing on both paths.\n */\n private async aggregateChild(\n projectId: string,\n childSid: string,\n ctx: ExpandCtx,\n ): Promise<ChildAggregate | null> {\n if (ctx.depth >= MAX_SUBAGENT_DEPTH || ctx.ancestry.has(childSid)) return null;\n const raw = await this.readChildRaw(projectId, childSid, ctx);\n if (raw === null || raw.length === 0) return null;\n let requestTokens = 0;\n let maxTsMs: number | null = null;\n ctx.ancestry.add(childSid);\n for (const msg of raw) {\n const ms = Date.parse(msg.timestamp);\n if (Number.isFinite(ms) && (maxTsMs === null || ms > maxTsMs)) maxTsMs = ms;\n const p = msg.payload as { type?: string; request?: { total?: number } };\n if (msg.type === \"event_msg\" && p.type === \"token_usage\") {\n requestTokens += typeof p.request?.total === \"number\" ? p.request.total : 0;\n continue;\n }\n const grandchild = subagentPointer(msg);\n if (grandchild !== null) {\n const agg = await this.aggregateChild(projectId, grandchild, {\n ...ctx,\n depth: ctx.depth + 1,\n });\n if (agg !== null) {\n requestTokens += agg.requestTokens;\n if (agg.maxTsMs !== null && (maxTsMs === null || agg.maxTsMs > maxTsMs)) {\n maxTsMs = agg.maxTsMs;\n }\n }\n }\n }\n ctx.ancestry.delete(childSid);\n return { requestTokens, maxTsMs };\n }\n\n /**\n * End-of-shard scan states for files[0..upto] (cumulative from the transcript's\n * beginning), served from the trace_files.page_stats cache. A missing/stale record\n * costs one read of THAT shard — once ever: every shard here is immutable (rotation\n * opens a new shard; the newest shard never appears in a prefix, because windows are\n * suffixes and their first shard is always read anyway). The write-back is guarded on\n * the indexed size, so an externally rewritten shard can only invalidate, never\n * poison, the cache.\n */\n private async prefixStates(\n projectId: string,\n agentId: string,\n sessionId: string,\n files: LocatedFile[],\n upto: number,\n ctx: ExpandCtx,\n ): Promise<ScanState[]> {\n const states: ScanState[] = [];\n for (let j = 0; j <= upto; j++) {\n const file = files[j]!;\n const cached = deserializePrefix(\n this.deps.index.repo.getPageStats(projectId, agentId, sessionId, file.index),\n );\n if (cached !== null) {\n states.push(cached);\n continue;\n }\n const state = cloneScanState(j === 0 ? initialScanState() : states[j - 1]!);\n const messages = await this.readShard(file.path);\n await scanMessages(\n state,\n messages,\n () => {},\n (sid) => this.aggregateChild(projectId, sid, ctx),\n );\n this.deps.index.repo.setPageStats(\n projectId,\n agentId,\n sessionId,\n file.index,\n file.sizeBytes,\n serializePrefix(state),\n );\n states.push(state);\n }\n return states;\n }\n\n /**\n * Windowed history read (the `tailLimit` / `before` forms of GET /messages). The\n * window is a run of whole units — cut points and unit semantics live in\n * message-window.ts — assembled by reading ONLY the shards the window overlaps\n * (plus, once ever per old shard, the prefix-cache backfill above). Subagent\n * pointers are expanded exactly as the full path expands them, but only within the\n * window: children referenced by older windows load when those windows do.\n */\n async readMessagesPage(\n projectId: string,\n agentId: string,\n sessionId: string,\n req: MessagesPageRequest,\n ): Promise<MessagesPageResult> {\n const files = await this.locateAll(projectId, agentId, sessionId);\n const empty = (): MessagesPageResult => ({\n messages: [],\n prior: initialScanState().totals,\n });\n if (files.length === 0) return empty();\n const ctx: ExpandCtx = {\n projectScanned: false,\n ancestry: new Set([sessionId]),\n depth: 0,\n raw: new Map(),\n };\n\n // The window's exclusive end: the newest shard's end (tail), or the cursor (before).\n let endPos: number;\n let endOrdinal: number | null = null; // null = to the shard's end\n if (req.kind === \"tail\") {\n endPos = files.length - 1;\n } else {\n endPos = files.findIndex((f) => f.index === req.cursor.fileIndex);\n // Cursor shard no longer on disk (external deletion — locateAll already\n // force-retried): the history it pointed into is gone; report end-of-history\n // rather than a guess at what used to precede it.\n if (endPos < 0) return empty();\n endOrdinal = req.cursor.ordinal;\n }\n\n const prefixes = await this.prefixStates(projectId, agentId, sessionId, files, endPos - 1, ctx);\n\n // Walk backward from the end, scanning whole shards (each from its cached carry-in)\n // until the window has more units than requested or the beginning is reached.\n const shardMessages = new Map<number, OmniMessage[]>();\n let boundaries: Array<{ pos: number; ordinal: number; stats: WindowPriorStats }> = [];\n let startPos = endPos + 1;\n while (boundaries.length <= req.limit && startPos > 0) {\n startPos -= 1;\n const messages = await this.readShard(files[startPos]!.path);\n shardMessages.set(startPos, messages);\n const state = cloneScanState(startPos === 0 ? initialScanState() : prefixes[startPos - 1]!);\n const shardBoundaries: Array<{ pos: number; ordinal: number; stats: WindowPriorStats }> = [];\n const to = startPos === endPos && endOrdinal !== null ? endOrdinal : messages.length;\n await scanMessages(\n state,\n messages,\n (ordinal, stats) => shardBoundaries.push({ pos: startPos, ordinal, stats }),\n (sid) => this.aggregateChild(projectId, sid, ctx),\n 0,\n Math.min(to, messages.length),\n );\n boundaries = [...shardBoundaries, ...boundaries];\n }\n\n // Window start: the last `limit` units, or the very beginning (preamble included)\n // when the whole remaining history fits — then there is no `before` cursor.\n let start: { pos: number; ordinal: number };\n let before: string | undefined;\n let prior: WindowPriorStats;\n if (boundaries.length > req.limit) {\n const wb = boundaries[boundaries.length - req.limit]!;\n start = { pos: wb.pos, ordinal: wb.ordinal };\n before = encodeCursor({ fileIndex: files[wb.pos]!.index, ordinal: wb.ordinal });\n prior = wb.stats;\n } else {\n start = { pos: 0, ordinal: 0 };\n prior = initialScanState().totals;\n }\n\n const windowRaw: OmniMessage[] = [];\n for (let pos = start.pos; pos <= endPos; pos++) {\n const messages = shardMessages.get(pos) ?? (await this.readShard(files[pos]!.path));\n const from = pos === start.pos ? start.ordinal : 0;\n const to =\n pos === endPos && endOrdinal !== null\n ? Math.min(endOrdinal, messages.length)\n : messages.length;\n for (let i = from; i < to; i++) windowRaw.push(messages[i]!);\n }\n const expanded = await this.expandMessages(projectId, windowRaw, ctx);\n return { messages: expanded, ...(before !== undefined ? { before } : {}), prior };\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 otherSpans: TraceOtherSpan[] = [];\n const openSpansById = new Map<string, TraceToolSpan>();\n // Open mcp_connect_begin (first-run MCP connect + discovery), closed by its end event\n // into a synthetic tool span so the timeline shows the wait ahead of the next Task.\n let openMcpConnect: { beginTs: string } | null = null;\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 /** Inside the image run that follows a `[user_steering]` text (see the turn-start rule below). */\n let steeringImages = false;\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 // Images sent with a steering message ride immediately behind its text, exactly as a\n // Prompt's images ride behind theirs — and they inherit its exclusion: still the same\n // Task, so `steeringImages` keeps the window open across the whole run of them and\n // anything else on the main session closes it (an images-only Prompt after a steering\n // message is a genuine new turn). A subagent's messages pass through without closing it:\n // they belong to another session's stream and say nothing about this one's grouping.\n // The Web answers the same \"what is one Task\" question over the live stream — see\n // `openSteering` in web/src/lib/omni/stream-model.ts; the two need to stay in step.\n const isImage = !hasOrigin && msg.type === \"model_msg\" && p.type === \"image_url\";\n if (!hasOrigin && !isSteeringText && !(isImage && steeringImages)) steeringImages = false;\n if (isSteeringText) steeringImages = true;\n const startsUserTurn =\n !hasOrigin &&\n !compactionActive &&\n !isSteeringText &&\n !steeringImages &&\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 a retryable status (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 // A status core reconnects on within the same run (context-engine's retry\n // loop) leaves the resent Request in **the same user turn**: it must\n // continue the turn, otherwise a single blip would split that turn's\n // Tokens/duration/TPS across two Tasks and inflate the Task count.\n //\n // This list must track the engine's, and the engine's differs by loop:\n // the turn loop retries `failed` too, while compaction deliberately fails\n // fast and stops on it (core's TURN_RETRY_STATUSES vs\n // COMPACTION_RETRY_STATUSES). Hence the compactionActive guard — a failed\n // compaction request really is the end of that request, and counting it\n // as a reconnect would invent an attempt that never happened.\n const retryable =\n status === \"timeout\" ||\n status === \"malformed\" ||\n (status === \"failed\" && !compactionActive);\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 === \"mcp_connect_begin\") {\n if (!hasOrigin) openMcpConnect = { beginTs: msg.timestamp };\n } else if (p.type === \"mcp_connect_end\") {\n // The connect pair becomes an \"other\" span (not a tool span — nothing was\n // called) attached to the FOLLOWING Task (taskIndex + 1: Task 0 at file start;\n // after an in-file resume, the next Task), so the timeline renders the\n // pre-request connect wait inside that Task's group under its own lane.\n if (!hasOrigin && openMcpConnect) {\n const q = p as { status?: string };\n const failed = q.status !== \"completed\";\n otherSpans.push({\n key: `mcp-connect-${openMcpConnect.beginTs}`,\n name: \"mcp connect\",\n startTs: openMcpConnect.beginTs,\n endTs: msg.timestamp,\n taskIndex: taskIndex + 1,\n ...(failed ? { failed: true } : {}),\n });\n openMcpConnect = 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 otherSpans,\n reconnectCount,\n compactionCount,\n usageTrend,\n };\n }\n\n /**\n * Agent-level browsing, served from the trace-file index (one mtime-gated reconcile,\n * then pure DB — no directory walks, no head-reads). Without `paging`: the legacy full\n * drill-down (newest first: Agent -> date -> Session -> Trace files; sizes are the\n * index's last observed values). With `paging`: session-group-centric — Sessions\n * ordered by id descending (ids embed a timestamp, so that is reverse chronological),\n * optionally filtered to one sidebar `category`, CLI-origin Sessions excluded unless\n * `includeCli` (the \"show CLI sessions\" preference; the legacy shape is never\n * filtered — back-compat), and only the returned slice gets per-file `fs.stat` for\n * fresh sizes (written back to the index).\n */\n async agentTraces(\n projectId: string,\n agentId: string,\n paging?: { offset: number; limit: number } | null,\n opts: { category?: SessionCategory; includeCli?: boolean } = {},\n ): Promise<AgentTracesResponse> {\n await this.deps.index.reconcileAgent(projectId, agentId);\n const files = this.deps.index.repo.listFilesByAgent(projectId, agentId);\n if (paging) return this.agentTracesPage(projectId, agentId, files, paging, opts);\n const byDate = new Map<string, Map<string, { index: number; sizeBytes: number }[]>>();\n for (const f of files) {\n const sessions =\n byDate.get(f.date) ?? new Map<string, { index: number; sizeBytes: number }[]>();\n const list = sessions.get(f.sessionId) ?? [];\n list.push({ index: f.fileIndex, sizeBytes: f.sizeBytes });\n sessions.set(f.sessionId, list);\n byDate.set(f.date, sessions);\n }\n return {\n dates: [...byDate.keys()]\n .sort()\n .reverse()\n .map((date) => ({\n date,\n // session_id embeds a timestamp, so reverse lexicographic order is reverse chronological order.\n sessions: [...byDate.get(date)!.entries()]\n .sort((a, b) => b[0].localeCompare(a[0]))\n .map(([sessionId, list]) => ({\n sessionId,\n files: list.sort((a, b) => a.index - b.index),\n })),\n })),\n };\n }\n\n /**\n * Classification (no IO): `archived` comes exactly from the DB row; the origin comes\n * from the shared sources registry, else from the Session's registration-time facts\n * (trace_sessions — the reconciler head-read its earliest shard once when the file\n * first appeared, so by listing time every indexed Session is classified exactly).\n */\n private classify(\n sessionId: string,\n row: SessionRow | undefined,\n facts: TraceSessionRow | undefined,\n ): TraceSessionFacts {\n const known = this.deps.sources?.get(sessionId);\n // Registry answer (including null = known user-created) wins — it can be fresher\n // (subagent registration happens at spawn, before any reconcile); else the stored facts.\n const source = known !== undefined ? known : (facts?.source ?? undefined);\n const category: SessionCategory =\n (row?.archivedAt ?? null) !== null\n ? \"archived\"\n : source === \"subagent\" || source === \"schedule\"\n ? source\n : \"active\";\n return { category, workspace: row?.workspace ?? facts?.workspace ?? \"\" };\n }\n\n /** The paginated listing behind agentTraces: pure index reads; per-file stat only for the returned slice (sizes written back). */\n private async agentTracesPage(\n projectId: string,\n agentId: string,\n files: TraceFileRow[],\n paging: { offset: number; limit: number },\n opts: { category?: SessionCategory; includeCli?: boolean },\n ): Promise<AgentTracesResponse> {\n const bySession = new Map<string, TraceFileRow[]>();\n for (const f of files) {\n const list = bySession.get(f.sessionId) ?? [];\n list.push(f);\n bySession.set(f.sessionId, list);\n }\n // One indexed query per source: the Agent's DB rows (title / archived / workspace /\n // client — CLI rows included) and the registration-time facts.\n const rows = new Map(\n (this.deps.sessions?.listByAgent(projectId, agentId) ?? []).map((r) => [r.sessionId, r]),\n );\n const factsBySession = new Map(\n this.deps.index.repo.listSessionsByAgent(projectId, agentId).map((r) => [r.sessionId, r]),\n );\n /**\n * CLI-origin = no web-created DB row AND not a subagent/schedule Session. Server-created\n * Sessions always have a row (creation / subagent registration insert one), so rowless =\n * external CLI; adopted CLI rows carry client='cli'. The default listing excludes these —\n * the same \"show CLI sessions\" preference the sidebar applies — while subagent/schedule\n * Sessions stay in their folders regardless of which process ran them.\n */\n const cliOrigin = (sessionId: string, facts: TraceSessionFacts): boolean => {\n if (facts.category === \"subagent\" || facts.category === \"schedule\") return false;\n const row = rows.get(sessionId);\n return !(\n row !== undefined &&\n (row.client === null || row.client === undefined || row.client === \"web\")\n );\n };\n const ids = [...bySession.keys()].sort((a, b) => b.localeCompare(a));\n // Classify every group once; the same result drives the CLI/category filters, the\n // counts AND the returned fields, so a row can never appear in a bucket its own\n // `category` denies. Hidden CLI-origin groups are excluded from counts too.\n const counts: SessionCategoryCounts = { active: 0, subagent: 0, schedule: 0, archived: 0 };\n const workspaceCounts: Record<string, SessionCategoryCounts> = {};\n const factsById = new Map<string, TraceSessionFacts>();\n const visible: string[] = [];\n for (const id of ids) {\n const facts = this.classify(id, rows.get(id), factsBySession.get(id));\n if (opts.includeCli !== true && cliOrigin(id, facts)) continue;\n factsById.set(id, facts);\n visible.push(id);\n counts[facts.category] += 1;\n const ws = (workspaceCounts[facts.workspace] ??= {\n active: 0,\n subagent: 0,\n schedule: 0,\n archived: 0,\n });\n ws[facts.category] += 1;\n }\n const filtered =\n opts.category === undefined\n ? visible\n : visible.filter((id) => factsById.get(id)!.category === opts.category);\n const page = filtered.slice(paging.offset, paging.offset + paging.limit);\n const sessions: AgentTraceSessionEntry[] = [];\n for (const sessionId of page) {\n const shard = bySession.get(sessionId)!.sort((a, b) => a.fileIndex - b.fileIndex);\n // Fresh sizes for the returned page only (an actively-appended shard grows without\n // moving any directory mtime): bounded metadata stats, written back to the index.\n const withSize = await Promise.all(\n shard.map(async (f) => {\n let sizeBytes = f.sizeBytes;\n try {\n sizeBytes = (await fs.stat(traceFilePath(this.root, f))).size;\n if (sizeBytes !== f.sizeBytes) {\n this.deps.index.repo.updateFileSize(\n projectId,\n agentId,\n sessionId,\n f.fileIndex,\n sizeBytes,\n );\n }\n } catch {\n /* Vanished mid-request: serve the last observed size; the next reconcile settles the rows. */\n }\n return { index: f.fileIndex, date: f.date, sizeBytes };\n }),\n );\n const facts = factsById.get(sessionId)!;\n // Title: DB row first (?? sends both \"no row\" and \"row with NULL title\" onward);\n // else the registration-time first-prompt fallback stored in trace_sessions.\n const title = rows.get(sessionId)?.title ?? factsBySession.get(sessionId)?.title ?? undefined;\n sessions.push({\n sessionId,\n ...(title !== undefined ? { title } : {}),\n category: facts.category,\n workspace: facts.workspace,\n files: withSize,\n });\n }\n return { dates: [], sessions, totalSessions: filtered.length, counts, workspaceCounts };\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 // Strict parse: import is the gate for user-supplied files, so a malformed middle\n // line is reported as 400 rather than silently dropped (read paths skip it instead).\n records = parseTraceLines(content, { onMalformed: \"throw\" });\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 const body = content.replace(/\\n+$/, \"\") + \"\\n\";\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, body, { encoding: \"utf8\", flag: \"wx\" });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") throw duplicate();\n throw err;\n }\n // Write-time registration: this path knows the file's identity and already holds the\n // parsed records, so the index row + session facts land synchronously (no re-read).\n this.deps.index.registerImportedFile({\n projectId,\n agentId,\n sessionId,\n fileIndex: index,\n date,\n sizeBytes: Buffer.byteLength(body, \"utf8\"),\n records,\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 UsageErrorsPage,\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/**\n * Number of most-recent entries kept in the error detail table. Also the page size the whole\n * feature runs on, but nothing needs to hard-code it: `errors.recent` is exactly this many rows\n * whenever a second page exists, so the client derives its page size from the response instead\n * of holding a constant that could drift out of step with this one.\n */\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/** One page of the error detail table (see {@link UsageService.queryErrors}). */\nexport interface UsageErrorsQuery {\n offset: number;\n limit: number;\n from?: string;\n to?: string;\n agentId?: string;\n /** Admin only: include errors with no Project attribution (see the ErrorsRepo file header). */\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 /**\n * One page of the error detail table, newest first. The dashboard's own response already\n * carries the first page (`errors.recent`); this serves the \"show me earlier ones\" paging,\n * where refetching the whole aggregate to move one page would be wasteful. `total` is the\n * filtered row count, so the caller knows when it has reached the end.\n *\n * Takes the same filter the dashboard applies — date + agent, and admin-only visibility of\n * unattributed errors — so a page never widens what the summary above it counted.\n *\n * Paging is by offset into a newest-first table that grows at its head, so errors recorded\n * between two page requests shift every row down and a page can re-show rows already seen.\n * Accepted deliberately: this is a diagnostic table read at a moment in time, not a feed to\n * be walked exhaustively, and keying off the newest row's id would cost a cursor the caller\n * has no other use for.\n */\n queryErrors(projectId: string, q: UsageErrorsQuery): UsageErrorsPage {\n const f: ErrorFilter = {\n ...(q.agentId !== undefined ? { agentId: q.agentId } : {}),\n ...(q.from !== undefined ? { from: q.from } : {}),\n ...(q.to !== undefined ? { to: q.to } : {}),\n ...(q.includeGlobalErrors === true ? { includeGlobal: true } : {}),\n };\n return {\n items: this.errors.recent(projectId, f, q.limit, q.offset),\n total: this.errors.summary(projectId, f).total,\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 */\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 { randomBytes } from \"node:crypto\";\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 /**\n * Listen port. `0` asks the OS for an ephemeral port (the desktop shell always does),\n * in which case the value is only a request: index.ts writes the ACTUAL bound port back\n * here once listening, because preview URLs are built from the server's own port rather\n * than the browser's (dev serves the SPA on a different port; see resolvePreviewTarget).\n */\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.\n */\n previewOrigin: string | null;\n /**\n * Fixed initial password for the seeded built-in admin (PENGUIN_SEED_ADMIN_PASSWORD),\n * used by automated tests and e2e; null (the norm) makes the seed generate a random\n * `penguin-<4 digits>` password, printed once to the server console. In desktop mode\n * an unpinned value resolves to a FULLY random password instead (never printed):\n * sign-in there goes through the shell's one-shot token, so nobody needs to read the\n * seed.\n */\n seedAdminPassword: 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 * Desktop mode (PENGUIN_DESKTOP_TOKEN): the per-launch token minted by the desktop\n * shell. Non-null enables the one-shot desktop-login and Bearer-token shutdown\n * endpoints and requires a loopback HOST — desktop mode passes the token through a\n * URL, which must never leave the machine.\n */\n desktopToken: string | null;\n /**\n * Port announcement file (PENGUIN_PORT_FILE): after the listener is up, the actual\n * bound port is written here — the supervising process's way to learn the port when\n * it starts the server with PORT=0.\n */\n portFile: string | null;\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 / PENGUIN_SEED_ADMIN_PASSWORD / PENGUIN_DESKTOP_TOKEN / PENGUIN_PORT_FILE). */\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 const host = env.HOST ?? \"127.0.0.1\";\n const desktopToken = env.PENGUIN_DESKTOP_TOKEN?.trim() || null;\n // Desktop mode redeems its token through a URL: never allow it off loopback.\n if (desktopToken !== null && host !== \"127.0.0.1\" && host !== \"localhost\") {\n throw new Error(`Desktop mode requires a loopback HOST (got HOST=${host})`);\n }\n return {\n root,\n host,\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 // An empty/whitespace value is treated as unset (→ random seed password). Desktop\n // mode without a pinned value seeds a FULLY random password rather than the\n // printable penguin-<4 digits>: desktop sign-in goes through the shell's token, so\n // the seed never needs to be read — and index.ts deliberately does not print it.\n seedAdminPassword:\n env.PENGUIN_SEED_ADMIN_PASSWORD?.trim() ||\n (desktopToken !== null ? randomBytes(24).toString(\"base64url\") : null),\n authSessionTtlMs: 7 * DAY_MS,\n authSessionRenewMs: 6 * DAY_MS,\n desktopToken,\n portFile: env.PENGUIN_PORT_FILE?.trim() || null,\n };\n}\n"],"mappings":"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","names":["fs","path","fs","path","Hono","mapRow","mapRow","mapRow","mapRow","randomBytes","deps","randomBytes","Hono","deps","app","deps","app","Hono","Hono","deps","app","Hono","Hono","getCookie","deps","app","Hono","getCookie","Hono","randomBytes","timingSafeEqual","app","deps","app","Hono","current","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","Hono","isValidId","fs","path","Hono","fs","path","fs","path","fs","path","randomBytes","path","randomBytes","fs","path","deps","app","Hono","fs","deps","app","Hono","isValidId","Hono","deps","app","Hono","Hono","deps","app","Hono","createHash","Hono","isValidId","ms","fs","path","fs","fs","path","isValidId","deps","app","Hono","createHash","Hono","deps","app","Hono","fs","path","Hono","name","fs","path","app","Hono","deps","installed","fs","Hono","MAX_ARCHIVE_BYTES","deps","app","Hono","fs","Hono","deps","app","Hono","fs","path","Hono","deps","app","Hono","path","fs","Hono","deps","app","Hono","Hono","SESSION_CATEGORIES","deps","app","Hono","Hono","deps","app","Hono","Hono","deps","app","Hono","fs","path","isSessionMeta","parseUserSteeringText","open","frag","path","path","fs","parseUserSteeringText","deps","isSessionMeta","path","createHash","fs","userText","deps","fs","createHash","userText","deps","isEventMessage","isSessionMeta","isSessionMeta","originSessionId","isEventMessage","randomBytes","deps","createHash","timingSafeEqual","fs","resolveMCPServer","fs","path","parseToml","DEFAULT_CHAT_THINKING_LEVELS","userText","fs","parseToml","path","DEFAULT_CHAT_THINKING_LEVELS","userText","next","THINKING_LEVELS","fs","config","path","resolveMCPServer","fs","path","fs","path","fs","path","agentsDir","agentsMdPath","isValidId","loadAgentVault","memoryDir","scheduleDir","skillsDir","systemConfigPath","fs","agentsDir","isValidId","systemConfigPath","loadAgentVault","scheduleDir","skillsDir","path","memoryDir","agentsMdPath","fs","path","parseToml","parseYaml","asRecord","path","fs","config","parseToml","parseYaml","randomBytes","fs","path","parseYaml","agentDir","agentStateDir","agentStateVersion","systemConfigPath","path","fs","systemConfigPath","parseYaml","agentStateVersion","randomBytes","agentDir","current","agentStateDir","fs","path","fs","deps","createAgent","isSessionMeta","fs","path","agentsDir","isSessionMeta","tracesDir","path","tracesDir","fs","isSessionMeta","agentsDir","deps","createAgent","isSessionMeta","fs","path","isSessionMeta","parseTraceLines","parseUserSteeringText","readTraceTolerant","tracesDir","deps","path","readTraceTolerant","fs","parseUserSteeringText","parseTraceLines","isSessionMeta","tracesDir","BUILD_DATE","VERSION","refKey","Hono","deps","app","Hono","config","deps","app","Hono","fs","CONTENT_TYPES","path","randomBytes","fs","path","fs","path"]}