# Changelog All notable changes to CortexPrism are documented here. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)\ Versioning: [Semantic Versioning](https://semver.org/) ## [Unreleased] ### Fixed - **MCP gateway audit + approvals lost on restart** — `logAudit()`, `createApproval()`, `approveGatewayRequest()`, and `denyGatewayRequest()` now persist to `cortex.db` via write-through to the `mcp_gateway_audit` and `mcp_gateway_approvals` tables (migration 055). Previously all gateway state was in-memory only and lost on every server restart. (`packages/server/src/mcp-gateway/gateway.ts`, migration `055_mcp_gateway_approvals.sql`) - **MCP gateway registry DB errors silently swallowed** — `registerServer()`, `updateServer()`, and `removeServer()` now log DB write failures via the `mcp:gateway:registry` logger instead of silently discarding errors. Previously a failed DB write would leave in-memory state out of sync with the database with no indication anything went wrong. (`packages/server/src/mcp-gateway/registry.ts`) - **`updateServer()` field protection** — only `name`, `endpoint`, `transport`, and `tags` can now be updated via `updateServer()`. Previously any caller could overwrite protected fields like `id`, `createdAt`, `status`, or `tools` by spreading arbitrary `Partial`. (`packages/server/src/mcp-gateway/registry.ts`) - **`registerBuiltinTools()` called on every HTTP request** — guarded with a module-level boolean flag so `loadConfig()` and tool registration run once, not on every `/mcp` request. (`src/mcp/server.ts`) - **Test server entries leaking into production DB** — MCP gateway tests now track all registered server IDs and run a cleanup step that removes test entries from both memory and the database, including retroactive cleanup of entries leaked by prior test runs. (`tests/mcp_test.ts`) - **Agent workspace boundary escape** — non-admin agents could bypass workspace isolation by specifying `workspace: "global"` in file tools (`file_list`, `file_tree`) and shell commands, accessing the host filesystem outside their agent workspace directory. Added a new `workspace` `PolicyKind` with a `default_deny_workspace_global` deny rule (priority 150, migration 056) that blocks `workspace:global` access unless an explicit allow rule exists. The shell tool now defaults to agent workspace and validates `cwd` via `resolveWorkspacePath`. When compliance classifies a turn as `critical` risk, subsequent turns in that session are now blocked rather than only logged. (`src/security/policy.ts`, `src/security/validator.ts`, `packages/gate/src/security/policy.ts`, `packages/gate/src/security/validator.ts`, `src/tools/builtin/shell.ts`, `src/pipeline/builtin.ts`, migration `056_workspace_policy.sql`, `tests/workspace_policy_test.ts`) - **Workspace path resolution allowed global access from agent scope** — `resolveWorkspacePath()` included `globalDir` in the allowed roots for `workspace: 'agent'`, letting agents read and write anywhere on the host even when confined to agent workspace. Removed `globalDir` from the agent-scope allowed roots so `workspace: 'agent'` only resolves paths within `~/.cortex/data/workspaces//`. Also fixed `file_read` and `file_read_enhanced` catch-block fallbacks that silently bypassed workspace boundary on resolution failure. (`src/workspace/paths.ts`, `src/tools/builtin/file_read.ts`, `src/tools/builtin/file_read_enhanced.ts`) - **Node directives bypassed workspace policy** — `validateNodeDirective()` checked path tier restrictions but never enforced workspace policy for file tools or shell commands. Added workspace policy checks to `validateNodeDirective()` for both file tools (Layer 3) and shell/code_exec tools (Layer 2), preventing swarm/hub node directives from circumventing workspace boundary enforcement. (`src/security/validator.ts`) - **Stop/cancel did not kill sub-agent processes** — `stopDaemons()` only killed supervisor, validator, executor, and scheduler daemons, leaving orphaned `sub-agent-entry` processes running indefinitely. Added `'sub-agent-entry'` to kill patterns. Additionally, when an agent turn is cancelled via AbortSignal, running sub-agent child processes are now tracked via `childPids` on `TurnContext` and killed via `killProcessById()` in the abort handler. Sub-agent spawning tools register their child PIDs through the new `registerChildPid` callback on `ToolContext`. (`src/cli/daemon.ts`, `src/agent/loop.ts`, `src/agent/stages/setup.ts`, `src/agent/sub-agent.ts`, `src/agent/pipeline/context.ts`, `src/tools/types.ts`, `src/tools/builtin/sub_agent.ts`, `src/tools/builtin/sub_agent_spawn.ts`) - **Orchestration resume delivery failed on jobs table constraint** — when background sub-agents completed, the executor daemon's `checkPendingResumes()` tried to insert a resume job but the `INSERT INTO jobs` statement was missing `schedule_kind` and `schedule_config` columns (both `NOT NULL`), causing a `SQLITE_CONSTRAINT_NOTNULL` error. This left parent sessions permanently stuck in yielded state waiting for sub-agents that had already completed. Fixed by including `schedule_kind='adhoc'` and `schedule_config='{}'` in the insert. (`src/scheduler/orchestration-resume.ts`) - **Wait barrier expiry never called for yielded sessions** — `expelExpiredWaitBarriers()` was only invoked from inside the `sub_agent_wait` tool's `execute()` method, meaning sessions that yielded their turn (via `yieldTurn: true`) never had their wait barriers expired. Barriers older than the 30-minute threshold remained `active` indefinitely, preventing `checkPendingResumes()` from delivering the resume (the expired barriers blocked cascade expiry of the associated `orchestration_resume_bundles`). Added `expelAllExpiredWaitBarriers()` — a session-agnostic variant called from the scheduler poll cycle every 30 seconds — so yielded sessions are no longer a blind spot. (`packages/core/src/db/subagent-runs.ts`, `src/db/subagent-runs.ts`, `src/scheduler/orchestration-resume.ts`) - **Stuck sub-agent children blocked resume delivery forever** — `checkPendingResumes()` polled child run terminal status via `checkAllChildrenTerminal()` but never detected children that had been in `running` status past a reasonable timeout. Silently crashed child processes (fire-and-forget spawn pattern in `sub_agent_spawn`) left runs stuck in `running` with no error, which `isTerminalStatus()` treated as non-terminal, causing the resume bundle to be skipped every poll cycle — a permanent deadlock. Added `failStaleSubagentRuns()` which is called at the start of each scheduler poll to mark runs as `failed` if they have been `running` longer than 15 minutes. Combined with the barrier expiry fix, this breaks the deadlock: stuck children → auto-failed → terminal → resume delivered. (`packages/core/src/db/subagent-runs.ts`, `src/db/subagent-runs.ts`, `src/scheduler/orchestration-resume.ts`) - **Scheduler dropped `orchestrationResume` config when dispatching agent turns** — the `runDueJobs()` dispatcher in the scheduler daemon only destructured `prompt` and `agent_id` from `job.action_config`, silently dropping the `session_id` and `orchestrationResume` fields that `checkPendingResumes()` had included in the adhoc job payload. This meant even when a resume job was successfully created, it ran as a fresh ephemeral agent turn instead of resuming the yielded session with the collected sub-agent results. Extended the config destructuring to pass `session_id` and `orchestrationResume` through to `createJob()`. The trigger job creator (`createTriggerJobCreator()`) now accepts an optional `resumeOpts` parameter: when `sessionId` is provided, it reuses the existing session DB (via `openExistingSession()`) instead of creating a new ephemeral session, passes `orchestrationResume` through to `agentTurn()`, and keeps the session open after the turn (`keepSessionOpen: true`). Standard trigger-driven jobs (no `sessionId`) follow the original code path unchanged. (`packages/infra/src/processes/scheduler-process.ts`, `src/triggers/job-creator.ts`) ### Added - **Container workspace isolation** — when Docker is available, agents now get containerized workspaces backed by persistent Docker containers (or gVisor if available). Two workspace implementations: `HostWorkspace` (current behavior, direct host filesystem) and `ContainerWorkspace` (Docker container with `--read-only` root, `--cap-drop=ALL`, `--network=none`, bind-mounted workspace at `/workspace`). The `AgentWorkspace` abstraction provides `readFile`, `writeFile`, `readFileRaw`, `stat`, `readDir`, `mkdir`, `remove` methods plus `exec()` for shell commands. All 15+ file tools now route I/O through the workspace abstraction — `docker exec` inside the container for contained mode, `Deno.*` APIs for host mode. Workspaces are cached per-agent via `getOrCreateWorkspace()` and reused across turns. Docker absence falls back transparently to `HostWorkspace`. (`src/workspace/agent-workspace.ts`, `src/workspace/file-io.ts`, `src/agent/stages/setup.ts`, `src/agent/pipeline/context.ts`, `src/tools/types.ts`, `src/tools/builtin/shell.ts`, all file tools under `src/tools/builtin/file_*.ts` and `src/tools/builtin/workspace/file_*.ts`) - **Host filesystem access escape hatch** — agents can still access the host filesystem when explicitly permitted. The existing `workspace` parameter on file tools (`'agent'` vs `'global'`) serves as the selector. `workspace: 'global'` is policy-gated via the `workspace` policy kind (migration 056, denied by default) and can be enabled per-agent via `cortex policy add --kind workspace --effect allow --pattern 'global'`. When `workspace: 'global'` is used, file I/O goes directly to the host (via `Deno.*` APIs), bypassing the container. This enables use cases like configuring Apache on the host system. (`src/security/policy.ts`, migration `056_workspace_policy.sql`) ### Changed - **Gateway health-retry endpoint now functional** — `POST /api/mcp-gateway/health-retry` previously returned a static acknowledgment without running a health check. It now calls `healthCheck()`, returns the real `HealthCheckResult`, and updates the server's status and `toolCount` in the registry if they changed. (`src/server/routes/mcp-gateway-routes.ts`) - **MCP route file split into three concerns** — the 387-line `mcp-connections.ts` route file handled MCP connections, the gateway API, and chrome-bridge in one file. Split into: `mcp-connections.ts` (connection CRUD), `mcp-gateway-routes.ts` (gateway servers, audit, approvals, health), and `chrome-bridge-routes.ts` (chrome-bridge integration). (`src/server/routes/{mcp-connections,mcp-gateway-routes,chrome-bridge-routes}.ts`, `src/server/new-router.ts`) - **Gateway server listing includes live connection state** — `GET /api/mcp-gateway/servers` now includes a `connected` field for each server, bridged from the live MCP client connections via `listConnections()`. (`src/server/routes/mcp-gateway-routes.ts`) - **Scheduler package migration** — the canonical scheduler, cron parser, and daemon process moved from `src/scheduler/` and `src/processes/scheduler-process.ts` into `packages/infra/src/scheduler/` and `packages/infra/src/processes/scheduler-process.ts`, completing the `@cortex/infra` package boundary. All 10 consumers (tools, CLI, API routes across `src/` and `packages/`) now import from the packages/infra location. Removed duplicate trigger files (`watcher.ts`, `webhook.ts`) from `packages/infra/src/triggers/` — the canonical versions remain in `src/triggers/`. (`packages/infra/src/scheduler/scheduler.ts`, `packages/infra/src/scheduler/cron.ts`, `packages/infra/src/processes/scheduler-process.ts`, `src/main.ts`, `deno.json`, all routes/tools/CLI consumers) ### Added - **Logger env-var overrides** — file transport can now be configured entirely via environment variables without touching `config.json`. `CORTEX_LOG_FILE` sets the log file path (and enables file logging), `CORTEX_LOG_FILE_MAX_BYTES` sets the max file size before rotation, and `CORTEX_LOG_FILE_MAX_FILES` sets the rotation count. All three combine with the existing `CORTEX_LOG_LEVEL` for a complete env-driven logging setup. (`src/utils/logger.ts`) - **JSON-structured stdout logging** — set `CORTEX_LOG_JSON=1` (env) or `jsonStdout: true` (config.json `logging` block) to emit JSON-line log entries to stdout instead of pretty-printed text. Designed for log aggregators (Datadog, Loki, Grafana Cloud, container stdout collectors). Each line includes `ts`, `level`, `msg`, `ns`, `reqId`, `data`, and `stack` fields. (`src/utils/logger.ts`) - **`resetLogger()` utility** — clears all logger state (level, transports, request ID) for test isolation. Tests can now `resetLogger()` then `configureLogger()` with a clean slate without leaking state between test cases. (`src/utils/logger.ts`) - **Logger unit tests** — 23 tests covering the full public API: level gating (trace through silent), namespace hierarchy via `.child()`, request ID propagation, error stack traces, env-var overrides, JSON stdout mode, transport broadcasting, concurrent emit safety, and realistic production scenarios (agent loop turn logging, server request lifecycle). (`tests/logger_test.ts`) - **Metrics unit tests** — 17 tests for the Prometheus metrics module: counter increment accumulation across labels, gauge set replacement, histogram value aggregation, `renderPrometheus` output validation (HELP/TYPE header correctness, label tuple formatting, `_sum`/`_count` suffix), `resetMetrics` clearing all state, custom metric registration, and production scenarios simulating agent turn metrics and node swarm directives. (`tests/metrics_test.ts`) - **Eval framework tests** — 32 tests for the scoring and regression detection logic: `regex:`, `contains:`, `not_contains:`, and fuzzy pattern matching with edge cases (empty output, unicode, emoji, multi-line code blocks, special regex characters), `scoreFileContent` with and without `shouldContain`, `checkRegression` with threshold boundaries (including floating-point tolerance), and realistic agent output scenarios (code generation, bug fixes with partial failures, TypeScript file checks). (`tests/eval_framework_test.ts`) - **Trigger persistence** — triggers (webhooks, file watchers, git hooks) were previously stored only in an in-memory `Map` and lost on server restart. A new `triggers` DB table (migration 048) stores the full trigger configuration as JSON. `registerTrigger()` and `unregisterTrigger()` now persist to the database, and `initTriggers()` loads persisted triggers at server startup. The enable/disable route also persists the enabled state. (`packages/infra/src/triggers/db.ts`, `src/triggers/manager.ts`, `src/server/server.ts`, `src/server/routes/triggers.ts`, migration `048_triggers_persistence.sql`) - **Scheduled agent-task support** — the job scheduler can now dispatch agent turns on a cron schedule in addition to shell commands. Jobs with `action_kind: 'agent_turn'` and an `action_config` containing `{prompt, agent_id}` are executed by the scheduler daemon via `createTriggerJobCreator()`, creating an ephemeral agent session and dispatching a fire-and-forget agent turn. The schedule tool gained `agent_prompt` and `agent_id` parameters, and `POST /api/jobs` accepts `actionKind` and `actionConfig` fields. (`packages/infra/src/scheduler/ scheduler.ts`, `packages/infra/src/processes/scheduler-process.ts`, `src/tools/builtin/schedule.ts`, `src/server/routes/jobs-crud.ts`) - **Job description field** — `CreateJobOptions` and the jobs DB table INSERT/UPDATE now accept an optional `description` field. `POST /api/jobs` forwards it, and the schedule tool's `formatJob()` displays it in listings. (`packages/infra/src/scheduler/scheduler.ts`, `src/server/routes/jobs-crud.ts`, `src/tools/builtin/schedule.ts`) - **Codegraph: DEFINES_METHOD, INHERITS, IMPLEMENTS, USES_TYPE edge extraction** — the indexer previously extracted only `CALLS` and `IMPORTS` edges (2 of 18 defined types), leaving most nodes as disconnected orphans. Now extracts `DEFINES_METHOD` edges from classes to their contained methods (walking through `class_body`/`declaration_list` wrapper nodes), `INHERITS` edges from subclass to superclass via `superclass`/`interfaces` field lookups, `IMPLEMENTS` edges from class to interface from `implements` clauses, and `USES_TYPE` edges from function/method signatures scanning return types, parameter types, and typed variable declarations for all referenced type identifiers (including generic, union, intersection, and optional compound types). (`src/codegraph/indexer.ts`) - **Codegraph: CONTAINS_FILE structural edges** — creates a `CodeFile` container node for every file that yields parsed symbols, then inserts `CONTAINS_FILE` edges from each file node to every symbol node within that file. These edges use explicit `sourceId`/`targetId` (bypassing the resolver) with 0.99 confidence. This guarantees every symbol node in the graph connects to at least one structural parent, eliminating the island-node problem regardless of whether cross-symbol edges resolve. (`src/codegraph/sync.ts`) - **Codegraph: improved edge resolution** — the resolver gained four new resolution strategies to reduce dropped edges: file-qualified target matching (`sourceFile:targetName` → direct lookup), same-file candidate preference when import-mapped names collide across files, relative import path resolution (`./foo`, `../bar` → file node lookup by normalized path), same-directory scoring for multi-candidate disambiguation, and fuzzy substring fallback against indexed simple names. For `IMPORTS` edges specifically, module paths are now resolved to file nodes via path normalization and containment matching. (`src/codegraph/resolver.ts`) - **Background sub-agent orchestration v2** — extends the v1 spawn/wait/apply lifecycle with six new capabilities: (1) **Multiple wait barriers** — `sub_agent_wait` now supports `await_mode` (`all`/`any`/`count`), `barrier_label` for disambiguation, and concurrent barriers per session via the new `subagent_wait_barriers` table. (2) **Nested orchestration** — background children can spawn grandchildren up to depth 3 (configurable), with max 9 concurrent children per session and orchestration tools stripped from child tool lists. `write_staged` mode is blocked at depth ≥ 2. (3) **Partial apply** — `sub_agent_apply` gained `include_paths`, `include_patterns`, and `exclude_patterns` params for selective change bundle application with glob filtering and skip tracking. (4) **Detached runner leases** — the scheduler daemon now polls `orchestration_resume_bundles` for pending barriers where all children are terminal, creating `agent_turn` jobs with `orchestrationResume` injected for automatic resume delivery without a live WebSocket connection. (5) **Write-capable child workspace support** — `write_staged` mode now provisions workspace snapshots via `captureBaseSnapshot`/`captureChangeBundle` in the new `src/agent/orchestration/isolation.ts` module, capturing pre- and post-run state to produce structured change bundles. (6) **Auto-apply with policy matrix** — `sub_agent_spawn` accepts `auto_apply` and `auto_apply_policy` (allow_delete, file_patterns, max_files, require_supervisor) to automatically apply child change bundles on completion without parent intervention. (4 migrations: `051_wait_barriers.sql`, `052_nested_orchestration.sql`, `053_detached_resume.sql`, `054_auto_apply.sql`. Modules: `src/agent/orchestration/isolation.ts`, `src/scheduler/orchestration-resume.ts`, `packages/gate/src/sandbox/merge.ts`. Tools: `sub_agent_spawn.ts`, `sub_agent_wait.ts`, `sub_agent_apply.ts`, `sub_agent_gate.ts`. Data layer: `src/db/subagent-runs.ts`, agent loop: `src/agent/loop.ts`, scheduler: `packages/infra/src/processes/scheduler-process.ts`) - **Canonical isolation module** — moved the orchestration workspace isolation provider (`isIsolationAvailable`, `captureBaseSnapshot`, `captureChangeBundle`) from the composition layer (`src/agent/orchestration/`) into the `@cortex/ai` package, so the canonical package tool registry can reach it directly. (`packages/ai/src/agent/orchestration/isolation.ts`) - **Merge strategy support in sub_agent_apply** — the `sub_agent_apply` tool now accepts a `merge_strategy` parameter (`"exact"` / `"three_way"`). In `three_way` mode, changes are merged via the existing `packages/gate/src/sandbox/merge.ts` module with inline conflict markers (`<<<<<<< parent` / `>>>>>>> child`). (`packages/ai/src/tools/builtin/sub_agent_apply.ts`, `src/tools/builtin/sub_agent_apply.ts`) - **Patch-based apply** — `sub_agent_apply` now supports `file.patch` entries in change bundles via a unified-diff parser (`parseUnifiedPatch` / `applyPatch`), replacing the previous hard-reject error. (`packages/ai/src/tools/builtin/sub_agent_apply.ts`, `src/tools/builtin/sub_agent_apply.ts`) - **Supervisor gate for auto-apply** — `autoApplyChangeBundle()` now checks the `require_supervisor` policy and loads `packages/gate/src/security/supervisor.ts` to call `checkAutoApply()` before applying changes. Falls back gracefully if the supervisor module is unavailable. (`packages/ai/src/tools/builtin/sub_agent_spawn.ts`, `src/tools/builtin/sub_agent_spawn.ts`) - **ToolContext turn/tool-call wiring** — `ToolContext` gains optional `turnId` and `toolCallId` fields, populated by the tool-executor stage. `sub_agent_spawn` now records the actual parent turn and tool-call IDs in `subagent_runs` instead of empty strings. (`packages/ai/src/tools/types.ts`, `src/tools/types.ts`, `src/agent/stages/tool-executor.ts`, `packages/ai/src/tools/builtin/sub_agent_spawn.ts`, `src/tools/builtin/sub_agent_spawn.ts`) - **Orchestration unit tests** — 13 tests covering three-way merge (clean apply, conflict, delete, new file, same-change), terminal status checks, glob-to-regex conversion, file-path glob matching, unified-diff patch application (insert, add, delete), and CSV parsing. (`tests/orchestration_pure_functions_test.ts`) ### Fixed - **Background orchestration tools: package/mirror divergence resolved** — the canonical `packages/ai/src/tools/builtin/` implementations (registered in the tool registry) were significantly behind the `src/tools/builtin/` mirror versions. Synced all four tools: `sub_agent_spawn` now fully supports `write_staged` mode with snapshot isolation, `auto_apply`, `auto_apply_policy`, and tiered gate checking; `sub_agent_gate` gained mode-aware `isBackgroundOrchestrationEnabled(mode?)` where `read_only` is always allowed; `sub_agent_wait` uses mode-aware gating; `sub_agent_apply` gained `merge_strategy` and patch support. (`packages/ai/src/tools/builtin/sub_agent_spawn.ts`, `packages/ai/src/tools/builtin/sub_agent_gate.ts`, `packages/ai/src/tools/builtin/sub_agent_wait.ts`, `packages/ai/src/tools/builtin/sub_agent_apply.ts`) - **Wait/apply event ordering fixed** — `sub_agent_spawn` now emits `spawn_accepted` and `started` lifecycle events before transitioning the run status to `running`, matching the expected append-only event log order. (`packages/ai/src/tools/builtin/sub_agent_spawn.ts`, `src/tools/builtin/sub_agent_spawn.ts`) - **DDL duplication removed from agent loop** — `persistResumeBundle()` in `src/agent/loop.ts` had an inline `CREATE TABLE IF NOT EXISTS orchestration_resume_bundles` that duplicated migration 053. Removed the inline DDL; the table is now only created by the migration. (`src/agent/loop.ts`) - **Unused merge module wired** — `packages/gate/src/sandbox/merge.ts` (`threeWayMerge`) was implemented but never imported by any orchestration code. Now wired into `sub_agent_apply` via the `merge_strategy` parameter. (`packages/ai/src/tools/builtin/sub_agent_apply.ts`, `src/tools/builtin/sub_agent_apply.ts`) - **Settings panes and workspace tree noise** — restored the settings page DOM structure so the General, AI & Models, Tools & Integrations, System, and Debug panes render as sibling sections, and filtered the editor/workspace tree to skip hidden/tooling directories (`.kilo`, `.deno`, `node_modules`, `target`, etc.) so the browser no longer spams 404s for cache paths. (`src/server/ui/js/12_settings.ts`, `packages/server/src/server/ui/js/12_settings.ts`, `src/server/ui/js/14_editor.ts`, `packages/server/src/server/ui/js/14_editor.ts`, `src/server/routes/workspace.ts`, `packages/server/src/server/routes/workspace.ts`) - **Job API input validation** — `POST /api/jobs` now validates that `name` is present, a string, non-empty, and ≤200 characters; `command` is required for shell jobs. Returns clean 400 errors instead of silently creating broken jobs. (`src/server/routes/jobs-crud.ts`) - **Codegraph: indexed projects deleted from dropdown** — the `GET /api/codegraph/projects` route cross-referenced indexed projects against filesystem projects and called `deleteCodeProject()` on any mismatch, treating every indexed project without a matching `cortex-project.json` as "stale" and permanently removing it from the DB. Removed the stale cleanup filter — codegraph projects now always appear in the dropdown regardless of filesystem state. Additionally, `POST /api/codegraph/index` now calls `createProject()` before indexing, ensuring every indexed repo gets a filesystem project entry that persists across server restarts. (`src/server/routes/codegraph.ts`) - **Codegraph: incrementalSync produced disconnected nodes** — `incrementalSync()` created symbol nodes for changed files but never built `CodeFile` container nodes or `CONTAINS_FILE` edges, so all nodes from watcher-triggered or manual incremental syncs became orphans. Now mirrors the full `indexRepository` logic: a `CodeFile` container node is created per file, and `CONTAINS_FILE` edges (confidence 0.99) connect it to every symbol node in that file. Additionally, `buildResolutionContext()` was populating `fileNodeMap` with an arbitrary first-symbol-node ID per file instead of the actual CodeFile node ID. The function now accepts an explicit `fileNodeIdMap` parameter from callers, ensuring edge source-ID fallbacks point to real CodeFile containers rather than unrelated symbol nodes. (`src/codegraph/sync.ts`, `src/codegraph/resolver.ts`) - **Database corruption defenses** — four-layer protection against SQLite corruption from concurrent server instances, crashes, and unclean shutdowns: 1. **Pre-start integrity check** — `runMigrations()` now runs `PRAGMA integrity_check` on all 5 databases before applying migrations. If corruption is detected, the server refuses to start with a clear error message pointing to the backups directory. 2. **Single-instance PID lock** — a `server.pid` file is checked before any initialization. If another cortex server process is alive and holding the lock, the new instance exits immediately with the running PID. Stale PID files from dead processes are cleaned up automatically. 3. **WAL checkpoint on graceful shutdown** — the shutdown handler now checkpoints all databases with `PRAGMA wal_checkpoint(TRUNCATE)`, ensuring WAL data is merged into the main database file before exit. 4. **Enhanced `tryRecover()`** — recovery uses `PRAGMA integrity_check` instead of `SELECT 1` for corruption detection, catches `malformed database schema` errors, verifies restored backups pass integrity checks before accepting them, and falls through to older backups when the latest is also corrupted. Recovery instructions are printed when no healthy backup exists. (`src/db/migrate.ts`, `src/server/server.ts`) ## [0.53.1] - 2026-06-24 ### Fixed - **Instance admin assignment bug** — `createUser()` with `isAdmin: true` was overwriting all existing administrators with a singleton array, destroying prior admin assignments. Now reads the existing admins list and appends the new user. (`src/server/auth.ts`) - **Session persistence gap** — sessions were stored only in an in-memory `Map`, lost on server restart. Sessions now also persist to the `sessions` DB table via `createSession()`, and new functions `listUserSessions()` / `destroyAllUserSessions()` enable per-user session management. (`src/server/auth.ts`) - **Multi-user password change** — `changePassword()` previously only handled legacy vault-stored passwords. New `changeUserPassword()` verifies the user's individual PBKDF2 hash and updates it. `adminResetUserPassword()` lets instance admins reset any user's password. The `/api/auth/change-password` route now detects authenticated multi-user sessions and uses the correct handler. (`src/server/auth.ts`, `src/server/routes/public-auth.ts`) - **Duplicate password-change route removed** — `src/server/routes/password-change.ts` was a duplicate of the `/api/auth/change-password` handler in `public-auth.ts` and lacked session checks. Deleted and removed from `src/server/new-router.ts`. - **Federation pairing token security** — pairing tokens were stored without expiry and never validated. Tokens now carry a 1-hour TTL (stored as JSON `{token, expiresAt, used}` in the `config` table), are validated during pair, and are single-use with a `used` flag. Replay and expired tokens are rejected with 401. (`src/server/routes/federation.ts`) - **Federation agent discovery** — `GET /api/federation/peers/:id/agents` was a hardcoded stub returning `"Remote agent discovery pending"`. Now fetches the remote peer's `.well-known/ agent-card.json` with fallback to the A2A transport protocol. (`src/server/routes/federation.ts`) - **Team DELETE cascade** — deleting a team only removed `team_memberships` rows, leaving orphaned references in `agents`, `sessions`, `services`, `nodes`, `channels`, and `workspace_config`. Now clears `team_id` to NULL on all scoped resources before deleting the team. (`src/server/routes/teams.ts`) - **Input validation on teams routes** — `join_policy` and `role` values are now validated against allowed enum values before hitting the DB, returning clean 400 errors instead of cryptic SQL constraint failures. (`src/server/routes/teams.ts`) - **`requireResourceOwner` guard coverage** — only handled 5 resource types (agents, sessions, services, nodes, channels). Added support for `workspace_config`, `triggers`, `workflows`, `projects`, and `glossary`. (`src/server/guards.ts`) - **Remove dead `mcp-gateway-cmd.ts` CLI file** — deprecated command orphan that was not registered in the CLI command tree. The gateway functionality moved to `cortex mcp gateway`. (`src/cli/mcp-gateway-cmd.ts`, `packages/cli/src/cli/mcp-gateway-cmd.ts`) - **Sync migrations to `@cortex/core` package** — migrations 044–047 (users/teams, vault scoping, memory scoping, core scoping) existed in `src/db/migrations/` but were missing from `packages/core/src/db/migrations/`, causing a pack sync gap for consumers of `@cortex/core`. (`packages/core/src/db/migrations/044_users_teams.sql` through `047_core_scoping.sql`) - **Split `eval-routes.ts` catch-all** — 831-line monolithic route file holding 7 different API areas (memori, eval, cost optimizer, observability, benchmarks, PKM, glossary, promptlab, memory benchmark) was split into dedicated route files. Prompts routes moved to `src/server/routes/promptlab.ts`, PKM routes to `src/server/routes/pkm.ts`, glossary routes to `src/server/routes/glossary-routes.ts`. All three registered in `src/server/new-router.ts`. ### Added - **User lifecycle management endpoints** — `GET /api/users/:id`, `PATCH /api/users/:id` (update display_name/email), `DELETE /api/users/:id` (cascades: tokens, memberships, shares, admin list). `POST /api/users/:id/reset-password` for admin-initiated password resets. (`src/server/routes/public-auth.ts`, `src/server/auth.ts`) - **Self-service user profile** — `GET /api/auth/profile` and `PATCH /api/auth/profile` let authenticated users view and update their display_name and email without admin intervention. `updateUserProfile()` function added to auth layer. (`src/server/routes/public-auth.ts`, `src/server/auth.ts`) - **Session management API** — `GET /api/auth/sessions` lists all active sessions for the current user. `DELETE /api/auth/sessions/:id` revokes a specific session. `DELETE /api/auth/sessions` revokes all other sessions (keeps current). (`src/server/routes/public-auth.ts`, `src/server/auth.ts`) - **Federation instance identity** — `GET /api/federation/identity` returns the instance's ECDSA P-256 public key and instance name, auto-generating the key pair on first access. `POST /api/federation/identity/rotate` rotates the key pair. `GET /api/federation/status` returns peer count, instance identity, and connected swarm nodes. Federation pairing now performs mutual public key exchange. (`src/server/routes/federation.ts`) - **Federation peer management** — `GET /api/federation/peers/:id` fetches a single peer. `POST /api/federation/peers/:id/ping` tests connectivity to a peer with a 5-second timeout. Paired peers register with the swarm coordinator for resource tracking. (`src/server/routes/federation.ts`) - **Team join endpoint** — `POST /api/teams/:id/join` allows self-service enrollment into open teams. Respects join policy: open teams auto-enroll as member, invite teams reject with "invitation needed", closed teams reject. (`src/server/routes/teams.ts`) - **User-level team listing** — `GET /api/users/:id/teams` returns all teams a user belongs to with their role and join date. Accessible to the user themselves or instance admins. (`src/server/routes/teams.ts`) - **Team member detail** — `GET /api/teams/:id/members/:userId` fetches a single member's details including display_name, email, role, and join date. (`src/server/routes/teams.ts`) - **Resource share detail + update** — `GET /api/shares/:id` fetches a single share (accessible to sender, recipient, or admin). `PATCH /api/shares/:id` allows the sender to change the permission level (read/write/admin). (`src/server/routes/shares.ts`) - **Team management UI** — Teams page now includes a Create Team form (name, description, join policy dropdown), Add Member form with user search dropdown and role selector, Toggle Role and Remove Member buttons per member, a Delete Team button with confirmation, and a Back button from detail view. Team cards display member count and join policy labels. (`src/server/ui/js/27_teams.ts`) - **User management UI enhancements** — Users page now includes email field and Instance Admin checkbox in the create user form, an ADMIN badge on admin users, a Reset Password button with inline form, and a Delete User button with confirmation. Admin list is fetched from the config API. (`src/server/ui/js/28_users.ts`) ## [0.53.0] - 2026-06-24 ### Added - **Multi-user collaboration** — users, teams, API tokens, and resource scoping across the entire platform. New `users` table with PBKDF2 password hashing, `teams` table with join policies, `team_memberships` join table with admin/member roles, `user_tokens` table for API access (SHA-256 hashed), `agents` DB table for agent storage (moved from `config.json`), `resource_shares` table for cross-user sharing, `instance_identity` and `federation_peers` tables for instance-to-instance federation. (`packages/core/src/db/migrations/044_users_teams.sql` through `047_core_scoping.sql`, `src/server/auth.ts`) - **Database migrations 044–047** — identity tables (users, teams, memberships, tokens, agents, federation, resource_shares), vault scoping columns (`owner_user_id`, `owner_team_id`), memory scoping columns on all memory tables, and resource scoping columns on services/nodes/channels/ workspace_config. Auto-admin creation on first run with backfill of existing resource rows. (`src/db/migrate.ts`) - **Agent storage moved from `config.json` to `agents` DB table** — full DB-based CRUD with user/team/instance scope filtering. Config.json agents preserved as fallback for backward compatibility during transition. Built-in agents seeded into DB as instance-scoped. (`src/db/agents.ts`, `src/agent/manager.ts`, `src/agent/builtin-agents.ts`) - **Request identity system** — `RequestIdentity` interface (`user`/`instance`/`anonymous`) with userId, teamIds, currentTeamId, isInstanceAdmin fields. Extracted from session cookies or `Authorization: Bearer` API tokens via `extractIdentity()`. (`src/server/identity.ts`, `src/server/auth.ts`) - **Authorization guards** — `requireInstanceAdmin()`, `requireTeamAdmin()`, `requireTeamMember()`, `requireResourceOwner()` functions for coarse permission checks. Authorization enforced on agent detail endpoints (GET/PUT/DELETE) and agent creation with team membership validation. (`src/server/guards.ts`, `src/server/routes/agents.ts`) - **API token management** — `POST /api/auth/tokens` to create tokens, `GET /api/auth/tokens` to list, `DELETE /api/auth/tokens/:id` to revoke. Tokens support team-scoping via `team_ids` JSON column, expiration dates, and last-used tracking. (`src/server/routes/public-auth.ts`) - **Team management API** — `GET/POST /api/teams`, `GET/PATCH/DELETE /api/teams/:id`, `GET/POST/PATCH/DELETE /api/teams/:id/members`, `GET/POST /api/teams/:id/agents`. Team-scoped agent creation with membership validation. (`src/server/routes/teams.ts`) - **Resource sharing API** — `POST /api/shares` to share resources between users, `GET /api/shares/given` and `GET /api/shares/received` to list shares, `DELETE /api/shares/:id` to revoke. Ownership validation enforced before sharing. (`src/server/routes/shares.ts`) - **Federation API** — `POST /api/federation/generate-pairing-token`, `POST /api/federation/pair`, `GET /api/federation/peers`, `DELETE /api/federation/peers/:id` for instance-to-instance trust establishment. (`src/server/routes/federation.ts`) - **User management API** — `GET/POST /api/users`, `POST /api/users/:id/disable` and `POST /api/users/:id/enable` (instance admin only). (`src/server/routes/public-auth.ts`) - **Multi-user web UI** — login page with username+password fields (`src/server/ui-auth.ts`), team selector dropdown in header (`src/server/ui/shell.ts`), Teams page with member management, Users page with create/disable/enable (instance admin). (`src/server/ui/js/27_teams.ts`, `src/server/ui/js/28_users.ts`, `src/server/ui/pages/login.ts`, `src/server/ui/pages/teams.ts`) - **CLI commands for multi-user** — `cortex login` (username+password or API token), `cortex logout`, `cortex whoami`, `cortex users list/create/disable/enable`, `cortex teams list/create`. Auth token stored in `~/.cortex/auth.json`. (`src/cli/user-cmd.ts`, `src/cli/registry.ts`) - **Locale translations** — all 10 non-English locale files (ar, de, es, fr, hi, ja, ko, pt, ru, zh) fully translated from English source. Preserves `{variable}` placeholders, Unicode symbols, CLI commands, and JSON structure. (`locales/*.json`) ### Changed - **Login flow** — `/api/auth/login` now accepts `{ username, password }` for multi-user authentication. Falls back to legacy vault-based password verification when username is omitted (`src/server/routes/public-auth.ts`) - **Session model** — `Session` interface gained `userId` and `username` fields. Sessions are still in-memory (7-day expiry) but track the authenticated user for downstream scoping. (`src/server/auth.ts`) - **Auth middleware** — `requireAuth()` now extracts `RequestIdentity` with user/team/admin context and returns it alongside the authenticate flag. `authGuard` stores identity in a `WeakMap` for downstream route handlers. (`src/server/auth.ts`, `src/server/routes/auth-guard.ts`) - **Agent CRUD scoped** — `listAgents()` accepts optional `userId` and `teamIds` for three-layer filtering (user → team → instance). Agent routes pass identity context for scope-aware operations. (`src/agent/manager.ts`, `src/server/routes/agents.ts`) - **Settings page** — Removed "Web Authentication" section (password setup/change, require-auth toggle) now that user management is handled through the Users page. (`src/server/ui/js/12_settings.ts`) ### Fixed - **Migration version collision** — four-part migration 044 (identity, vault, memory, core scoping) now uses unique version numbers 044–047 to prevent skip of subsequent migrations after the first sub-migration is applied. (`src/db/migrate.ts`) - **Team agent listing** — `listAgents()` now correctly returns team-scoped agents when called with `teamIds` but without `userId`. (`src/db/agents.ts`) - **Agent authorization** — GET/PUT/DELETE on individual agents now validates the authenticated user owns or has team access to the agent, preventing unauthorized access to private agents. (`src/server/routes/agents.ts`) - **Agent creation scoping** — `POST /api/agents` now validates team membership before accepting a `teamId` parameter, preventing agent injection into arbitrary teams. (`src/server/routes/agents.ts`) - **Per-user default agent isolation** — `selectAgent()` no longer overwrites the global `defaultAgent` when a user selects a personal default. (`src/agent/manager.ts`) - **Share ownership validation** — `POST /api/shares` now verifies the sender owns the resource before creating the share. (`src/server/routes/shares.ts`) - **Federation pairing token** — `POST /api/federation/generate-pairing-token` now returns the actual stored token instead of a mismatched new UUID. (`src/server/routes/federation.ts`) - **Auth per-request DB query** — `requireAuth()` now caches the user-existence check using a module-level flag with invalidation on user create/disable/enable, eliminating a `COUNT(*)` query on every API request. (`src/server/auth.ts`) - **Teams/Users page rendering** — fixed incorrect DOM target (`main-panel` → `teams-content` / `users-content`) and wrong escape function (`escHtml` → `esc`) in teams and users page JS. (`src/server/ui/js/27_teams.ts`, `src/server/ui/js/28_users.ts`) ### Removed - **`src/server/precedence.ts`** — dead file with no consumers (29 lines). Resource precedence resolution will be re-added when needed by callers. - **`getAgentsForConfigFallback()`** — unused export from `src/db/agents.ts`. - **`getUserScopeFilter()`** — unused export from `src/server/guards.ts`. - **`extractIdentity` import** — removed dead import from `src/server/routes/auth-guard.ts`. ## [Unreleased] ### Added - **Glossary REST API** — `GET/POST /api/glossary`, `GET /api/glossary/:term`, `GET /api/glossary/categories` endpoints for term definition, lookup, listing, and category browsing. (`src/server/routes/glossary.ts`, `src/server/new-router.ts`) - **AgentLint CLI agent-id support** — `cortex agent lint check ` now accepts an optional agent ID for per-agent linting. (`packages/cli/src/cli/agentlint-cmd.ts`, `src/cli/agentlint-cmd.ts`) - **/soul TUI slash command** — added `/soul` to TUI completions for showing agent soul/prompt context. (`packages/cli/src/tui/completions.ts`, `src/tui/completions.ts`) - **Tool toggle/stats endpoints** — `POST /api/tools/:name/toggle` and `GET /api/tools/:name/stats` route handlers for enabling/disabling tools and querying usage stats. (`src/server/routes/tools-list.ts`) - **MCP Gateway approval workflow** — `createApproval()`, `approveGatewayRequest()`, `denyGatewayRequest()`, `getPendingGatewayApprovals()` functions with `POST /api/mcp-gateway/approvals`, `/:id/approve`, `/:id/deny`, `GET /api/mcp-gateway/approvals` REST endpoints. (`packages/server/src/mcp-gateway/gateway.ts`, `src/server/routes/mcp-connections.ts`) ### Changed - **Documentation overhaul** — updated all version references from 0.51.0 to 0.53.0 across root docs (AGENTS.md, README.md, CONTRIBUTING.md, SECURITY.md), wiki pages (Home, AGENTS, Sidebar, Architecture, CLI-Reference, Configuration, Distributed-Nodes, Security, Plugin-System, Changelog, LLM-Providers, Agent-Loop, Built-in-Agents), and active docs (ARCHITECTURE.md, AGENT_OS_ALIGNMENT). Created 5 new wiki pages (Swarm, Multi-User-Collaboration, API-Tokens, Federation, Login-&-Auth). Updated provider count 24→30, route modules 62→69, UI modules 74→78, migrations 42→47, and DB migration paths to reflect the packages/core/ source layout. ### Fixed - **Wiki accuracy sweep** — fixed 40+ documentation discrepancies across 16 wiki pages: corrected Configuration.md defaults (maxTurns, modelSelection, logging, webAuth, supervisor), Home.md feature counts (9 channel adapters, 11 sub-agent types, 21 DLP scanners), CLI-Reference.md slash commands, Security-Supervisor.md classification default, Code-Sandbox.md Docker images and output limits, Code-Intelligence.md language count, AgentLint.md check count (29), Database-Migrations.md migration count (47) and file paths, Channel-Adapter-API.md shared modules and table name, Model-Routing/Quartermaster.md provider counts, Sub-Agents.md tool lists, Built-in-Tools.md missing file_diff tool, A2A-Protocol.md task states and CLI commands, Architecture.md stale pre-modularization paths, and REST-API.md phantom endpoints (36 marked as planned/🔜). ### Removed ### Security ## [0.52.0] - 2026-06-23 ### Added - **WASM plugin ABI versioning** — new `plugin_get_abi_version()` export and `host_get_abi_version()` host function allow forward/backward compatible plugin loading. Host rejects WASM modules with unsupported ABI versions. Current ABI version: 1. (`src/plugins/wasm-runtime.ts`) - **WASM linear memory allocator** — replaced hardcoded offset magic numbers with a bump allocator (`host_alloc`/`host_free` host functions). Memory layout: 64KB host scratch → 64KB allocator metadata → 896KB managed heap → WASM static data. Bounds-checked with auto-grow. (`src/plugins/wasm-runtime.ts`) - **WASM synchronous HTTP** — `host_http_request` now blocks synchronously via a dedicated `Worker` thread + `SharedArrayBuffer` + `Atomics.wait`. 30-second timeout, proper status/body return. Replaces the previous broken fire-and-forget `fetch().then()` pattern. (`src/plugins/wasm-runtime.ts`, `src/plugins/wasm-worker-http.ts`) - **WASM tool parameter schemas** — capabilities JSON now supports full `params[]` with `name`, `type` (`string`|`number`|`boolean`|`object`|`array`), `description`, and `required`. LLMs can now understand WASM tool signatures. (`src/plugins/wasm-runtime.ts`) - **WASM PluginContext integration** — `loadWasmPlugin()` now accepts `PluginContext`, giving WASM plugins access to logging (`host_log`), state persistence (`host_set_state`/`host_get_state` via SQLite-backed in-memory cache with dirty tracking), and configuration (`host_get_config` via `CORTEX_PLUGIN_{NAME}_{KEY}` env vars). (`src/plugins/wasm-runtime.ts`) - **WASM permission enforcement** — `host_http_request` gates on `network:fetch` or `net:outbound` capability. Returns 403 if permission not declared. (`src/plugins/wasm-runtime.ts`) - **WASM execution timeouts** — 120-second maximum per tool execution (`MAX_TOOL_EXECUTION_MS`). (`src/plugins/wasm-runtime.ts`) - **WASM supply-chain binary scanning** — `scanWasmBinary()` parses WASM section headers to detect: suspicious imports (`wasi_snapshot_preview1.proc_exit`, `args_get`, `environ_get`, `sock_*`), excessive memory requests (>4GB), unknown `env` imports, and WASM version mismatches. Called automatically during plugin installation. (`src/plugins/supply-chain.ts`) - **WASM diagnostics API** — `getWasmDiagnostics(name)` returns memory usage, heap pointer position, ABI version, and tool count for debugging loaded WASM plugins. (`src/plugins/wasm-runtime.ts`) - **WASM plugin SDK** — C-compatible header (`src/plugins/sdk/wasm-plugin.h`) declaring all host functions, memory layout constants, and required exports. TypeScript client library (`src/plugins/sdk/client.ts`) with `defineTool()`, `definePlugin()`, `generateCapabilitiesJson()`, and `generateWasmPluginModule()` helpers. - **WASM plugin test suite** — 7 tests covering binary encoding, module compilation, ABI version checking, supply-chain scanning, memory layout, capabilities JSON parsing, and worker code integrity. (`tests/wasm_plugin_test.ts`) - **Plugin files now have a single source of truth** — `packages/core/src/plugins/` files replaced with re-exports to `src/plugins/`, eliminating 3900+ lines of stale duplicate code. ### Fixed - **WASM `http_request` was broken** — the host function launched `fetch().then(...)` but returned immediately. The WASM module read garbage from memory because the response hadn't arrived. Now synchronous via `Atomics.wait`. (`src/plugins/wasm-runtime.ts`) - **WASM memory layout corruption** — hardcoded offsets (tool name at 1024, args at random positions, 64KB output at arbitrary offset) could collide with WASM stack/heap data. Replaced with bounded scratch area and proper allocation. (`src/plugins/wasm-runtime.ts`) - **WASM `plugin_destroy` never called** — unload/reload leaked `WebAssembly.Memory` and instance. `destroyWasmPlugin()` now called from `unloadPlugin()` in the loader. Cleans up memory cache too. (`src/plugins/loader.ts`, `src/plugins/wasm-runtime.ts`) - **WASM state lost on restart** — `_wasmState` was an in-memory `Map`. Now per-plugin cache + dirty-tracking flush to SQLite via `PluginContext.state`. (`src/plugins/wasm-runtime.ts`) - **WASM plugins had no PluginContext** — `loadWasmPlugin(row)` didn't accept `ctx`, so WASM plugins couldn't log, persist state, or access config. `ctx` parameter is now optional (backward compatible). (`src/plugins/wasm-runtime.ts`, `src/plugins/loader.ts`) ### Changed - **WASM host function names** — renamed with `host_` prefix for clarity and to avoid conflicts with WASM module internals: `log` → `host_log`, `get_config` → `host_get_config`, `set_state` → `host_set_state`, `get_state` → `host_get_state`, `http_request` → `host_http_request`. New functions: `host_alloc`, `host_free`, `host_get_abi_version`, `host_get_time_ms`, `host_random`. (`src/plugins/wasm-runtime.ts`) - **WASM `get_config` priority** — now checks `CORTEX_PLUGIN_{NAME}_{KEY}` first (PluginContext-aware), falling back to `CORTEX_WASM_{KEY}`. (`src/plugins/wasm-runtime.ts`) - **Supply-chain verifier** — non-WASM files still get text-based malware pattern scanning; WASM binaries get dedicated binary analysis. (`src/plugins/supply-chain.ts`) - **Telegram webhook method named `handleWebhookUpdate`, not `handleWebhook`** — the webhook route dispatches to `channel.plugin.handleWebhook()`, but Telegram's method was `handleWebhookUpdate`. Added `handleWebhook(data)` that delegates to `handleWebhookUpdate`. (`src/channels/telegram.ts`) - **Channel bridge missing tool registration and `toolContext`** — `createChannelEventHandler()` called `agentTurn()` without `registry`, `toolContext`, workspace directory, system prompt, or plugin loading. The agent would have had zero tools available. Added `registerAllBuiltins`, `pluginManager.loadAll()`, `loadAgentIdentity` + `buildSystemPrompt`, workspace dir creation, and full `toolContext` with `workingDir`/`agentId`/`workspaceDir`/`model`/`provider`. Also now sends error responses back to the channel when `agentTurn()` throws. (`src/channels/bridge.ts`) kernel process trees and resource accounting across machines using the A2A protocol as the wire transport. Cortex instances can now form a swarm: register as nodes, discover peers, dispatch directives, and aggregate resource usage across the entire fleet. - **Swarm contracts** (`packages/infra/contracts/swarm.ts`) — `ISwarmNode`, `ISwarmCoordinator`, `ISwarmTransport`, plus types for directives, resource reports, node registration payloads, and runtime metrics (`NodeMetrics`). - **Node registry** (`packages/infra/src/swarm/node-registry.ts`) — CRUD over the existing `nodes` table (migration 015); periodic heartbeat with metrics snapshots into new `swarm_resource_snapshots` table; stale-node eviction; peer discovery via A2A agent cards, config seed nodes, or shared DB fallback. - **A2A-based transport** (`packages/infra/src/swarm/transport.ts`) — maps swarm directives to A2A `SendMessage` JSON-RPC calls; agent card caching; `Promise.allSettled` broadcast fan-out. - **Swarm coordinator** (`packages/infra/src/swarm/coordinator.ts`) — `swarm` singleton: self-registration, peer discovery, directive dispatch (tracked in new `swarm_directives` table), broadcast to node groups, aggregated resource reporting, 30s heartbeat loop with CPU/memory/token metrics, drain/seal lifecycle. - **Directive handler** (`packages/infra/src/swarm/directive-handler.ts`) — receiving side: processes all 5 directive kinds (`spawn_agent`, `execute_task`, `query_resources`, `forward_message`, `sync_state`) on the target node. - **Remote kernel** (`packages/infra/src/swarm/remote-kernel.ts`) — proxy remote processes into the local `OsKernel` process tree (synthetic PIDs ≥900000); aggregated resource accounting across all connected nodes. - **A2A server integration** (`packages/server/src/a2a/server.ts`) — `registerSwarmHandler()` and `SwarmDirectiveHandler` interface; `handleSendMessage` detects `metadata.swarmKind` and routes swarm directives to the directive handler instead of the normal Cortex executor. - **Migration 043** (`src/db/migrations/043_swarm.sql`) — `swarm_directives` and `swarm_resource_snapshots` tables plus metrics/labels/a2a_endpoint columns on `nodes`. - **CLI** (`src/cli/swarm-cmd.ts`) — `cortex swarm` command with `init`, `nodes`, `topology`, `report`, `drain`, and `seal` subcommands. - **Config** (`src/config/config.ts`) — `SwarmConfig` interface with `seedNodes`, `group`, and `enabled` fields under `config.swarm`. - **Web UI — Nodes page** (`src/server/ui/pages/nodes.ts`, `src/server/ui/js/15_nodes.ts`) — new swarm fleet summary cards (CPU avg, memory, sessions, processes, tokens today) computed from per-node heartbeat metrics; enhanced node cards with CPU/memory color-coded bars, active session/process counts, A2A endpoint, and key=value labels; view-mode toggle between Node List, Swarm Topology (process tree + fleet token/cost report), and Directive History table. - **Swarm API routes** (`src/server/routes/swarm.ts`) — `GET /api/swarm/topology`, `GET /api/swarm/report`, `GET /api/swarm/directives`, `GET /api/swarm/nodes/metrics`, `GET /api/swarm/nodes/:id/snapshots`; registered in `new-router.ts` under protected routes. - **Nodes API enrichment** (`src/server/routes/nodes.ts`) — `GET /api/nodes` now includes swarm fields (`cpu_percent`, `memory_used_mb`, `memory_total_mb`, `active_sessions`, `active_processes`, `a2a_endpoint`, `labels`) alongside existing hub node data. - **DeepInfra provider** — OpenAI-compatible provider for `api.deepinfra.com/v1/openai`. Default model: `meta-llama/Llama-3.3-70B-Instruct`. Supports all standard parameters plus repetition penalty. Includes pricing for 6 popular models. (`packages/ai/src/llm/deepinfra.ts`, `src/llm/deepinfra.ts`) - **Hyperbolic provider** — OpenAI-compatible provider for `api.hyperbolic.xyz/v1`. Default model: `deepseek-ai/DeepSeek-V3`. 80% cheaper than traditional cloud providers. Includes pricing for 4 models. (`packages/ai/src/llm/hyperbolic.ts`, `src/llm/hyperbolic.ts`) - **MiniMax provider** — OpenAI-compatible provider for `api.minimax.chat/v1`. Ships the MiniMax M3 model (80.5% SWE-bench Verified at $0.30/$1.20 per 1M tokens), the cheapest 80%+ coding model available through a hosted API. Includes pricing for 4 models. (`packages/ai/src/llm/minimax.ts`, `src/llm/minimax.ts`) - **Zhipu (GLM) provider** — OpenAI-compatible provider for `open.bigmodel.cn/api/paas/v4`. Default model: `glm-4-flash` (free tier). Includes pricing for 5 models. (`packages/ai/src/llm/zhipu.ts`, `src/llm/zhipu.ts`) - **Replicate provider** — custom REST API provider for `api.replicate.com/v1`. Uses the predictions-based API with polling for non-streaming completions and SSE streaming for streaming mode. Includes pricing for 4 popular open-source models. (`packages/ai/src/llm/replicate.ts`, `src/llm/replicate.ts`) - **Cloudflare Workers AI provider** — custom REST API provider for Cloudflare's edge inference platform (`api.cloudflare.com/client/v4/accounts/{account_id}/ai/run`). Requires both API token and Account ID. Supports SSE streaming. Includes pricing for 4 models. `accountId` field added to `IProviderConfig` and `ProviderConfig`; flow plumbed through config save route, model fetch route, UI provider modal (extra field), and router factory. (`packages/ai/src/llm/cloudflare.ts`, `src/llm/cloudflare.ts`, `packages/core/contracts/config.ts`, `src/config/config.ts`, `src/server/routes/config-routes.ts`, `src/server/routes/providers.ts`, `src/server/ui/js/12_settings.ts`) ### Fixed - **Migration 043 placed in wrong directory** — `043_swarm.sql` was created in `packages/core/src/db/migrations/` but `migrate.ts` reads from `src/db/migrations/`, causing `server start` to crash with `NotFound: No such file or directory`. Copied to `src/db/migrations/043_swarm.sql`. - **Novita models endpoint returned 404** — the model fetcher used `api.novita.ai/openai/v1/models` but the provider's base URL is `api.novita.ai/v3/openai`. Fixed to `api.novita.ai/v3/openai/models` to match the provider's API version. (`src/server/models.ts`, `packages/server/src/server/models.ts`) - **Alibaba models fetcher used wrong regional domain** — used `dashscope-intl.aliyuncs.com` (international endpoint) while the Alibaba provider uses `dashscope.aliyuncs.com` (China mainland). Unified to the China endpoint to match the provider. (`src/server/models.ts`, `packages/server/src/server/models.ts`) - **`fetchModelsForModal` required API key for localhost providers** — the model fetch button in the Add/Edit Provider modal required an API key for all providers except Ollama. LM Studio and LiteLLM (localhost providers) were also blocked. Added `lmstudio` and `litellm` to the bypass list. (`src/server/ui/js/12_settings.ts`, `packages/server/src/server/ui/js/12_settings.ts`) - **LM Studio dead `numCtx`/`keepAlive` configuration fields** — the UI showed context window and keep-alive fields for LM Studio in the provider modal, but LM Studio's OpenAI-compatible chat API doesn't support these as request parameters (they're server-side model-loading settings). Removed from `PROVIDER_EXTRA_FIELDS` to avoid misleading users. (`src/server/ui/js/12_settings.ts`, `packages/server/src/server/ui/js/12_settings.ts`) - **Cloudflare model fetch had no access to Account ID** — the providers route (`/api/providers/{kind}/models`) only passed `baseUrl` to model fetchers, but Cloudflare needs the Account ID. Added special-case routing: when `kind === 'cloudflare'`, passes `stored.accountId` instead of `stored.baseUrl` as the second argument. Also added `accountId` to the config save route handler body interface. (`src/server/routes/providers.ts`, `packages/server/src/server/routes/providers.ts`, `src/server/routes/config-routes.ts`, `packages/server/src/server/routes/config-routes.ts`) - **Codegraph: call edges attributed to wrong source node** — `extractCalls()` was setting `sourceQName: ''` for every call, causing all edges in a file to point to the first indexed node via `fileNodeMap` fallback. Now tracks parent function/method context through the AST walk and emits `${filePath}:${containingFunction}` sourceQName, matching the node qualified-name format. (`src/codegraph/indexer.ts`) - **Codegraph: edge sourceQName absolute↔relative path mismatch** — edge sourceQNames carried absolute paths while node qualified-names used relative paths from `indexFile` normalisation, causing `resolveEdges` to miss valid source nodes. Now transforms absolute `filePath` to `relPath` in edge sourceQNames during `indexFile`. (`src/codegraph/sync.ts`) - **Codegraph: `bulkInsertNodes` returned wrong IDs causing edge FK violations** — used `BEGIN`/`INSERT`/`SELECT last_insert_rowid()`/`COMMIT` across separate `db.run()` calls. The libSQL client may use different connections per call, so `last_insert_rowid()` returned stale/zero values and computed IDs drifted from actual DB rowids by 2+. Replaced with single `db.insert()` call that captures `lastInsertRowid` from the same execute result. (`src/codegraph/graph.ts`) - **Codegraph: post-insert DELETE removed all edges** — after each chunk's edge INSERT, a cleanup `DELETE … WHERE source_id NOT IN (SELECT id FROM code_nodes …)` ran. Due to connection state issues the subquery returned empty and deleted all edges including freshly inserted ones. Removed; the pre-insert `validEdges` filter already guarantees referential integrity. (`src/codegraph/graph.ts`) - **Codegraph: auto-index infinite loop + data corruption** — the architecture endpoint re-triggered auto-index on every page load when `node_count === 0`. A failed prior auto-index left `node_count` stuck at 0 (nodes persisted but edges failed), causing each subsequent load to delete-and-retry, corrupting data indefinitely. Now only auto-indexes when the project truly does not exist; if `node_count` is stale but actual nodes are present, fixes the counter without re-indexing; on index failure clears `p` to return "Project not found" instead of serving corrupted state. (`src/server/routes/codegraph.ts`) - **Codegraph: stale projects from deleted workspaces persisted** — `deleteProject()` in `projects/manager.ts` only removed the filesystem directory; codegraph data in memory.db was never cleaned up. Added `deleteCodeProject()` that deletes the `code_projects` row (child tables cascade via `ON DELETE CASCADE` FK). The project list endpoint now cross-references with workspace directories, excludes stale entries, and fire-and-forget cleans them from the DB. (`src/codegraph/graph.ts`, `src/server/routes/codegraph.ts`) - **GitHub clone missing token authentication** — `POST /api/projects/import-github` called `git clone` with `repo.html_url` (bare `https://github.com/owner/name`) without embedding the GitHub token, so private repos and rate-limited public access failed. Now constructs `https://{token}@github.com/{fullName}.git` and passes it to `git clone`. (`src/server/routes/projects.ts`) - **`decryptValue()` returned encrypted `enc:` string on failure** — the config decryption helper caught errors but returned the raw `enc:…` ciphertext instead of `null`. Since the encrypted string is truthy, the `?? null` fallback never triggered, and corrupted encrypted blobs flowed through `loadConfig()` into the vault migration code. Now returns `null` on decryption failure. Added belt-and-suspenders `startsWith('enc:')` guards in `getGitHubToken()` and `loadGitHubToken()` to skip any value that survived. (`src/config/config.ts`, `src/workspace/github.ts`, `src/server/ui/js/12_settings.ts`) - **Six CLI aliases silently `Deno.exit(1)` instead of delegating** — `cortex chat`, `tui`, `serve`, `start`, `stop`, and `restart` were registered as top-level wrapper commands that printed a deprecation warning and exited with code 1, preventing any actual work. The real implementations in `chat.ts`, `tui-cmd.ts`, `serve.ts`, `start.ts`, and `stop.ts` were fully functional but unreachable through these aliases. Removed the dead alias stubs from `src/main.ts`; users now reach the canonical paths (`cortex agent chat`, `cortex agent tui`, `cortex server start`, `cortex daemon start|stop|restart`) directly. - **Six fully-implemented CLI commands never registered** — `cortex run`, `cortex update`, `cortex migrate`, `cortex service`, `cortex qm`, and `cortex mqm` had complete implementations (10–293 lines each) but were missing from the active command registry in `src/cli/registry.ts`. Three were in the stale `packages/cli/src/cli/registry.ts` (never imported); three were registered nowhere. All six now registered in the active registry. (`src/cli/registry.ts`) - **`PUT /api/workflows/:id` parsed update body but never applied mutations** — the handler deserialised `body.name` and `body.description`, validated the workflow exists, then returned `{ ok: true }` without modifying anything. Now applies `description` and `name` updates directly on the `Workflow` instance; renames atomically (delete old name, register under new name) and returns a 409 if the target name is already taken. (`src/server/routes/workflows.ts`) - **Pipeline stages `pre-reflect` and `post-reflect` defined but never wired** — both were listed in the `PipelineStage` union type but had zero `runHooksForStage()` calls anywhere in the agent loop. The reflection logic in `src/agent/post/background.ts` ran `reflectOnTurn()` and `adversarialReflection()` directly, bypassing the hook system. Now wraps reflection with `pre-reflect` and `post-reflect` hook invocations, building an `AgentState` from the runtime `TurnContext` and passing reflection results (standard + adversarial JSON) into the `post-reflect` stage. (`src/agent/post/background.ts`) - **Stale `packages/cli/src/cli/registry.ts` removed** — the file was byte-for-byte identical to the active `src/cli/registry.ts` at one point but had drifted (missing 2 entries, held 3 stale entries), and was never imported by anything. Deleted. - **`AGENTS.md` contained wrong migration paths and count** — claimed migrations live at `packages/core/src/db/migrations/`, registration at `packages/core/src/db/migrate.ts`, and "currently 41 migrations." Corrected to `src/db/migrations/`, `src/db/migrate.ts`, and 42 respectively. - **Three test files used legacy `deno.land/std@0.203.0/testing/asserts.ts` imports** — `tests/phase2_endpoints_test.ts`, `tests/phase2_endpoints_all_test.ts`, and `tests/phase2_pages_metadata_test.ts` imported from the old Deno CDN URL instead of the project-consistent `@std/assert` import map entry. Updated to `import { assertEquals } from '@std/assert'`. - **Phase2 dev-mode endpoints now annotated with `TODO(phase2)`** — the three `/api/phase2/` endpoint handlers returned hardcoded placeholder `
` strings; now carry explicit `TODO(phase2)` comments describing the planned real analytics/config/state/stats data. (`src/server/routes/health.ts`) ### Changed - **`ProviderKind` consolidated to single source of truth** — the 24-provider union type was defined identically in three places: `packages/core/contracts/config.ts` (contract layer), `src/config/config.ts` (implementation), and `packages/core/src/config/config.ts` (package implementation). Both implementation files now `import type { ProviderKind }` from the contracts file and re-export it. Adding a new provider now requires changes in only the contracts file. (`src/config/config.ts`, `packages/core/src/config/config.ts`) - **`packages/server/src/server/server.ts` replaced with canonical re-export** — the 321-line packages copy was missing 98 lines of startup logic (install manifest detection, vault availability check, pre-start sanity checks, auto-tunnel, auto-channels, vault guard in UI auth) compared to the canonical `src/server/server.ts`. Since zero files import the packages copy, replaced the entire file with a 1-line `export { startServer, type ServeOptions }` re-export of the canonical version. - **Sandbox contracts documented as aspirational** — `ISandboxProvider` and `ISandboxBackend` in `packages/gate/contracts/sandbox.ts` had zero implementations (the active executor uses direct functions). Added a header doc comment noting these are aspirational design contracts for a future multi-backend sandbox system (Docker / subprocess / gVisor / E2B / Daytona). - **Removed unused `@std/datetime` and bare `@std/encoding` from `deno.json` import map** — `@std/datetime` had zero imports across all 1000+ `.ts` files. The bare `@std/encoding` entry was never used; all actual encoding imports go through `@std/encoding/base64`. Both removed. (`deno.json`) ## [0.51.0] - 2026-06-23 ### Added - **Checkpoint Time-Travel UI** — the Memori page (`/memori`) now renders a full two-panel timeline: a session-grouped checkpoint list on the left and a rich detail view on the right. Each checkpoint shows turn number, goals, message count, tool calls, and workspace snapshot. Two action buttons — **Resume here** (restore the checkpoint into the current session) and **Branch from here** (fork into a new child session) — are available on every checkpoint. Helper functions `fmtTokens`, `fmtTimeAgo`, and `memoriStat` power the compact summary cards. (`src/server/ui/pages/memori.ts`, `src/server/ui/js/22_mcp_memori.ts`) - **Runtime Tool Forging** — agents can now create, test, and export custom tools at runtime via three new built-in tools: - `tool_forge` — takes `name`, `description`, and TypeScript `code`; runs a static safety scan against `UNSAFE_PATTERNS`; optionally calls an LLM security judge; executes pure-compute code in a Deno Worker (no net/read/write permissions) or shell-touching code in the existing Docker sandbox; registers the result in a session-scoped forged-tool registry. - `forged_call` — invokes a previously forged tool by name with arbitrary arguments. - `tool_export` — promotes a forged tool to the persistent skills system (lifecycle: `candidate`) so it survives across sessions. - `tool_list_forged` — lists all forged tools registered in the current session. (`src/tools/builtin/tool_forge.ts`, `src/tools/registry.ts`) - **Multi-Agent Orchestration — `orchestrate` tool with 6 strategies** — a single `orchestrate` tool now exposes six composable multi-agent execution strategies, all backed by `spawnSubAgent`: - `sequential` — chains agents; each receives the previous agent's output as context. - `parallel` — runs agents concurrently via `Promise.allSettled`; a synthesiser agent merges outputs. - `debate` — N agents argue assigned positions for R rounds; an impartial judge synthesises the final answer. - `review-loop` — a writer agent drafts, a reviewer agent critiques, iterating up to `max_iterations` times until the reviewer emits an approval keyword. - `hierarchical` — a coordinator agent decomposes the task, worker agents execute sub-tasks in parallel, the coordinator synthesises results. - `graph` — user-defined DAG of `{id, task, dependsOn[]}` nodes; topological execution with dependency context injection. (`src/agent/orchestration/strategies.ts`, `src/tools/builtin/orchestrate.ts`, `src/tools/registry.ts`) - **HEXACO Personality System** — agents can now be configured with a six-factor HEXACO personality (`h`, `e`, `x`, `a`, `c`, `o` ∈ [0, 1]). The personality drives: - **System prompt injection** — `buildPersonalityPrompt()` generates a natural-language paragraph describing the agent's voice, honesty, emotional tone, extraversion, agreeableness, conscientiousness, and openness, prepended to the system prompt on every turn. - **Memory retrieval bias** — `getMemoryBiasWeights()` returns per-tier multipliers (episodic, semantic, procedural, preference) and BM25/vector balance weights derived from personality scores. - **Response style hints** — `buildResponseStyleHints()` produces brief post-processing nudges (structured output, warmth, perspective acknowledgement, creative alternatives). - **MQM routing hints** — `getMqmPersonalityHints()` returns `accuracyWeight`, `creativityWeight`, and `preferFast` signals for the Model Quartermaster. The `personality` field is optional on `AgentConfig`; absent or neutral scores (0.5) produce no change in behaviour. (`src/agent/personality.ts`, `src/config/config.ts`, `src/agent/types.ts`, `src/agent/stages/prompt-builder.ts`, `src/server/ws.ts`) - **Memory Benchmark Runner — LongMemEval-S compatible** — a new benchmarking subsystem evaluates the agent's memory recall against a question-answer suite: - `src/eval/memory-bench.ts` — core runner with configurable concurrency, token-overlap + Jaccard scoring, per-category aggregation, and JSON persistence to `~/.cortex/data/memory_bench_results.json` and `memory_bench_history.json`. - `cortex eval memory` CLI command — supports `--suite `, `--sample `, `--full`, and `--json` flags. (`src/cli/eval-memory-cmd.ts`, `src/cli/registry.ts`) - REST API — `GET /api/eval/memory/results`, `GET /api/eval/memory/history`, `POST /api/eval/memory/run`. (`src/server/routes/eval-routes.ts`) - Web UI — new **Memory Benchmark** page (`page-eval-memory`) with summary stat cards, per-category accuracy bar chart, per-question result table, and historical run trend table. One-click **▶ Run Benchmark** button triggers a live run via the API. (`src/server/ui/pages/eval-memory.ts`, `src/server/ui/js/26_eval_memory.ts`, `src/server/ui/mod.ts`) - CI workflow — `.github/workflows/memory-bench.yml` runs the benchmark weekly (Monday 06:00 UTC) and on manual dispatch; results are uploaded as a GitHub Actions artifact and summarised in the job step summary. - **10 built-in agents (5 new, 5 refined)** — the agent roster now ships with 10 selectable built-in agents. Five new specialist agents join the existing five: **Writer** ✍️ (technical documentation, changelogs, READMEs, API references), **DevOps** 🚀 (Docker, Kubernetes, Terraform, CI/CD pipelines), **Security** 🔐 (OWASP Top 10 auditing, CVE scanning, compliance review — read-only), **Code Reviewer** 👁️ (structured BLOCKER/SUGGESTION/NITPICK/QUESTION review format — read-only), and **QA / Tester** 🧪 (test generation, coverage analysis, regression discipline). All five existing agents (Assistant, Developer, Researcher, Architect, Analyst) received deep soul rewrites adding Capabilities, Guardrails, and Limitations sections, explicit sub-agent delegation hints, and improved output format specs. (`src/agent/builtin-agents.ts`) - **Two new sub-agent types** — `reviewer` (Code Reviewer) and `writer` (Technical Writer) added to the sub-agent type system. `reviewer` produces structured review reports with BLOCKER/SUGGESTION/NITPICK/QUESTION labels and a per-finding rationale/suggestion format. `writer` produces audience-appropriate documentation following Keep a Changelog and API doc conventions with accuracy-first constraints. Both are accessible via `sub_agent` tool with their respective type strings. (`src/agent/sub-agent-types.ts`) ### Changed - **Sub-agent prompts refined across all 11 types** — targeted improvements to every existing sub-agent system prompt: - `explore`: added explicit scope boundary (stay within task, don't roam) - `general`: added scope escalation rule (report unexpected expansion rather than acting unilaterally) - `plan`: steps now require explicit IDs (S1, S2, …) for dependency tracking; constraint wording tightened - `code`: verification is now mandatory — "run tests; do not skip verification"; fix test failures before reporting done - `research`: added source-quality hierarchy (official docs > peer-reviewed > reputable news > blog posts); constraint softened from "no commands" to "no commands unless needed for research" - `security`: dependency checklist expanded with dependency confusion, typosquatting, and supply-chain risks; summary format made explicit; severity uncertainty defaults to higher - `debug`: git history check promoted to step 1 (before reproduce); regression test added to step 7 - `architect`: "Extend, don't replace" principle promoted to top-level design principle - `devops`: Kubernetes and Terraform capabilities added explicitly; "show commands before running destructive ops" constraint added - `data`: explicit causation caveat added; "never hide gaps" added to caveats format - `ui`: no prompt change (already well-structured) (`src/agent/sub-agent-types.ts`) - **`INIT_SOUL_TEMPLATE` sub-agent type list updated** — the default soul now documents all 13 sub-agent types (added `reviewer` and `writer` entries, updated descriptions for `plan`, `code`, `security`, `debug`, `architect`, and `devops` to reflect refined capabilities). (`src/agent/soul.ts`) - **Extensions top-nav category** — plugins now have a dedicated **Extensions** top-nav tab (sixth tab in the header). Plugin-contributed panels appear as first-class sub-nav items under Extensions rather than being buried in a Panels tab. Clicking a panel item navigates directly via `showPluginPanel()` with full URL hash deep-link support (`#pluginpanel::`). Page restore on reload handles `pluginpanel:` hashes correctly. (`src/server/ui/shell.ts`, `src/server/ui/js/05_nav_pre.ts`, `src/server/ui/js/07_nav_post.ts`, `src/server/ui/js/11_pages.ts`, `src/server/ui/js/24_deferred.ts`) - **Plugin sidebar slot injection** — plugins declaring `ui:panel` now have their panels registered in the `ui-slots` registry at load time. A new `GET /api/plugins/slots` endpoint exposes live slot registrations. `loadPluginSidebarSlots()` fetches slots at boot and injects `sidebar`-slot plugins as clickable items above the sidebar footer; clicking opens the plugin HTML in an inline modal iframe. (`src/plugins/loader.ts`, `src/server/routes/plugins.ts`, `src/server/ui/js/11_pages.ts`) - **Plugin middleware pipeline hooks** — ESM plugins can now export `middlewarePre` and `middlewarePost` functions alongside their capabilities declaration. When loaded, plugins declaring `middleware:pre`/`middleware:post` capabilities have their functions automatically registered as `pre-tool`/`post-tool` pipeline hooks. Hooks are fully unregistered when the plugin is disabled or removed. (`src/plugins/types.ts`, `src/plugins/loader.ts`) - **Plugin event bus wiring** — the plugin event bus now receives live agent lifecycle events: `agent:turn-start` (after user message is persisted), `tool:pre-execute` (before each tool call), `tool:post-execute` (after each tool result), and `agent:turn-end` (in background after response is complete). All emissions are fire-and-forget and never block the response. (`src/agent/stages/setup.ts`, `src/agent/stages/tool-executor.ts`, `src/agent/post/background.ts`) ### Fixed - **`host.registerTool` / `host.unregisterTool` were no-ops** — `PluginContext.host` stubs silently discarded tool registrations made from lifecycle hooks. Both methods now delegate to `globalRegistry.register()` / `globalRegistry.unregister()` so plugins can register tools dynamically from `onActivate` and unregister them from `onDeactivate`. (`src/plugins/context.ts`) - **Plugin panel navigation moved to Extensions category** — `extensions` page removed from `system` category and given its own `extensions` category in `CATEGORY_PAGES`. The old sidebar `Plugin Panels` section (a duplicate nav mechanism) removed from shell HTML. (`src/server/ui/shell.ts`, `src/server/ui/js/05_nav_pre.ts`) - **`unloadPlugin` left dangling pipeline hooks and UI slot registrations** — disabling or removing a plugin now calls `unregisterAllForPlugin()` (pipeline hooks) and `unregisterUIPlugin()` (UI slots) in addition to unregistering tools. (`src/plugins/loader.ts`) ### Changed - **Navigation consolidation — 9 pages merged into 5 tabbed hubs** — eliminated duplicate and fragmented pages by merging related UI into unified tabbed interfaces: - **Sandbox** now includes a **Code Runner** tab (previously standalone `coderunner` page), alongside Snapshots, Workspace, Dev Env, and Bug Repro. - **Remote & Computer** merges the former `remote` (Remote Agents) and `computer` (Computer Use) pages into a single page with two tabs. - **MCP** merges `mcp` (Connections) and `mcp-gateway` (Gateway) into one page with two tabs. - **System Health** (formerly Daemons) merges daemon process monitoring and OS health metrics into one page with two tabs. - **Automation** expands to a 5-tab hub: Hooks, Triggers, Workflows, Jobs, and Eval — replacing four separate nav entries. - **Extensions** gains a **Panels** tab, absorbing the standalone Plugin Panels page. - **Activity (Lens)** moved from the Knowledge category to System, where audit/observability tooling belongs. - Removed 9 retired page entries from `PAGES`, `CATEGORY_PAGES`, `mod.ts` imports, and command palette. (`src/server/ui/js/05_nav_pre.ts`, `src/server/ui/js/07_nav_post.ts`, `src/server/ui/js/11_pages.ts`, `src/server/ui/js/13_command.ts`, `src/server/ui/js/20_extensions.ts`, `src/server/ui/js/22_mcp_memori.ts`, `src/server/ui/js/23_sandbox.ts`, `src/server/ui/mod.ts`, `src/server/ui/pages/automation.ts`, `src/server/ui/pages/daemons.ts`, `src/server/ui/pages/extensions.ts`, `src/server/ui/pages/mcp.ts`, `src/server/ui/pages/remote.ts`, `src/server/ui/pages/sandbox.ts`) ## [0.50.1] - 2026-06-23 ### Added - **Secure tunnel UI — Tailscale & Cloudflare Zero Trust** — new dedicated **Tunnels** page (`Settings → Tunnels`) with full lifecycle management: provider selector cards (Tailscale / Cloudflare), per-provider option forms (Funnel vs Serve mode, binary path, named-tunnel credentials), auto-start toggle, live status bar with public URL chip (click-to-copy), diagnostics grid, and real-time output log. Accessible via the System category in the sidebar and via the shortcut card in Settings → Tools & Integrations. (`src/server/ui/pages/tunnel.ts`, `src/server/ui/js/25_tunnel.ts`) - **Tunnel page wired into navigation** — `tunnel` added to `PAGES` array, `CATEGORY_PAGES.system` (intermediate level), `showPage` loader table, `settingsGroup` highlight map, `tabbed` subnav map, and the Tools sub-navigation bar. (`src/server/ui/js/05_nav_pre.ts`, `src/server/ui/js/07_nav_post.ts`, `src/server/ui/js/08_subnav.ts`, `src/server/ui/mod.ts`) - **Tunnel step in web onboarding** — new **Step 7/9: Remote Access** inserted between Advanced Features and Telemetry. Users can choose Tailscale Funnel, Cloudflare quick-tunnel, or skip. On continue the config is saved and the tunnel is started immediately; if a public URL is obtained it is shown on the completion screen. `TOTAL_STEPS` updated from 8 to 9. (`src/server/ui-auth.ts`) ### Fixed - **CLI setup channel credentials discarded** — channel credentials collected during `cortex setup` (Discord, Slack, Telegram, Teams, Mattermost, Rocket.Chat, WhatsApp, Google Chat, Lark) were stored in a local `Map` but never persisted to vault or database. Now saved to vault (`channel:` entries) and `channels` DB table during setup. (`src/cli/setup.ts`) - **Web onboarding provider test always returned success** — `POST /api/onboarding/provider` hardcoded `connected: true` without testing the provider connection. Now runs an actual `provider.complete("Hi")` test and returns the real connection status. The web UI correctly shows success/failure. (`src/server/routes/onboarding.ts`) - **Channels never auto-started on server boot** — channel configurations saved during onboarding were never loaded at runtime. Added `initChannelsFromDb()` that reads enabled channels from the `channels` DB table, instantiates the adapter plugin, and calls `startChannel()` during server bootstrap. Channels now auto-start after restart. (`src/server/server.ts`) - **Missing `CORTEX_VAULT_KEY` silently disabled all authentication** — when the vault encryption key env var was unset, `hasPassword()` returned `false` (any vault error), causing `requireAuth()` to return `authenticated: true` — bypassing all auth. Now: `checkVaultAvailability()` runs at server startup (logs warning), `isVaultUnavailable()` tracked globally, `requireAuth()` returns 503 when vault is unavailable, and UI routes show a clear error message instead of silently granting access. (`src/server/auth.ts`, `src/server/server.ts`) - **Two parallel channel config systems with no bridge** — web onboarding saved channels to `config.plugins.channels` (plaintext in config.json) while the runtime manager used a separate `channels` DB table with vault-encrypted credentials. Now: `POST /api/onboarding/channels` bridges both systems — saves to config.json AND persists to DB+vault. (`src/server/routes/onboarding.ts`) - **Server started with zero pre-start sanity checks** — the server bootstrap performed no validation of: config file existence, provider API key configuration, vault key availability, or web password status. Now emits startup warnings for each missing element. Config `loadConfig()` also catches corrupted JSON and file read errors gracefully instead of crashing. (`src/server/server.ts`, `src/config/config.ts`) - **`printSetupHint()` was dead code** — the function was defined but never imported or called. Removed and replaced with inline console warnings in `buildProvider()` and `getActiveProvider()` that display the setup hint when a provider is not configured. (`src/cli/setup.ts`, `src/llm/router.ts`, `src/config/config.ts`) - **Web onboarding ignored `onboarding.completed` status** — the onboarding page checked `/api/onboarding/status` but only read `hasPassword`, ignoring the `completed` field. Users with completed onboarding could revisit `/onboarding` and overwrite config. Now: JS init redirects to `/` when `completed` is true, restores last step from progress data, and calls the progress endpoint on each step change. (`src/server/ui-auth.ts`) - **CLI setup never set a web password** — the CLI setup wizard had no password step. Users who ran `cortex setup` via CLI and later started the web UI had no password protection. Added optional web password step (step 4/7) with complexity validation and retry on mismatch. Credentials stored in vault via `setupPassword()`. (`src/cli/setup.ts`) - **CLI onboarding lost all progress on Ctrl+C** — `SIGINT`/`SIGTERM` handlers called `Deno.exit(0)` immediately with no config save. Now saves progress (current step, completed steps) after each major step (provider, personality, password, channels). On restart, prompts user to resume from the last saved step. (`src/cli/setup.ts`) - **Web onboarding progress endpoint existed but was unused** — `POST /api/onboarding/progress` saved step state but the web UI JS never called it. Now wired into `showStep()` to persist step position, and init reads `currentStep` from status to restore position on page reload. (`src/server/ui-auth.ts`) - **Web AI personalization was a single hardcoded question** — `POST /api/onboarding/profile/start` returned one fixed question ("What do you do?"). Now uses the configured LLM provider to generate contextual questions, extract structured profile data, and ask intelligent follow-ups. Falls back to hardcoded question if provider is unavailable. Web UI now supports multi-turn LLM conversation. (`src/server/routes/onboarding.ts`) - **CLI provider test failed with no retry option** — the connection test ran once and the wizard continued regardless of result. Now offers retry prompt on failure, looping until connection succeeds or user declines. (`src/cli/setup.ts`) - **Serve command description was hardcoded English** — `cortex serve` description was a literal string not using i18n. Now uses `i18n.t('cli.serve.commandDescription')`. (`src/cli/serve.ts`) - **Install manifest not created on non-update startup** — `install.json` was only created during self-update checks. Added `loadManifest()` call during server bootstrap to auto-detect and persist install type on first server start. (`src/server/server.ts`) ## [0.50.0] - 2026-06-22 ### Added - **`cortex import` CLI command** — full data migration system (openclaw, hermes, zeroclaw, transcripts) was implemented (314 lines) but not registered in the CLI registry. Now accessible via `cortex import `. (`packages/cli/src/cli/registry.ts`, `packages/cli/src/cli/import-cmd.ts`) - **`cortex qm` / `cortex mqm` CLI commands** — Quartermaster (tool orchestration learning, 293 lines) and Model Quartermaster (model selection intelligence, 243 lines) were fully implemented but not registered. Now accessible via `cortex qm` and `cortex mqm` with subcommands for patterns, weights, stats, decisions, accuracy, and reset. (`packages/cli/src/cli/registry.ts`, `packages/cli/src/cli/quartermaster-cmd.ts`, `packages/cli/src/cli/model-qm-cmd.ts`) - **`cortex service` CLI command** — full micro-service CRUD (list, show, create, update, delete, start, stop — 186 lines) registered. Previously only `service install`/`uninstall` were accessible. (`packages/cli/src/cli/registry.ts`, `packages/cli/src/cli/service-cmd.ts`) - **`file_diff` tool registered** — the 476-line file diff tool (unified diffs, side-by-side, syntax hints) was fully implemented and tested but never registered in the tool registry. Agents can now diff files. (`packages/ai/src/tools/registry.ts`, `packages/ai/src/tools/builtin/workspace/file_diff.ts`) - **Search cache eviction route** — `clearSearchCache()` was exported but never called. Added `DELETE /api/cache/search` endpoint to flush the web search cache. (`src/server/routes/eval-routes.ts`) - **Memory graph entity detail panel** — clicking a graph node now opens a side panel showing full entity information (name, type, description, importance, sensitivity, aliases, metadata), all inbound/outbound relations with strength percentages and relation type breakdowns, and entity ID/creation date. Includes `GET /api/memory/graph/entity` endpoint with `name` and optional `type` query params. Graph nodes and relation rows are clickable to navigate between entities. (`src/memory/graph.ts`, `src/server/routes/memory-graph.ts`, `src/server/ui/pages/memory.ts`, `src/server/ui/js/11_pages.ts`, `src/server/ui/css.ts`) - **Prompt Lab A/B testing and generation** — major expansion of the prompt engineering workspace: adds A/B test creation with variant comparison (avg score, latency, tokens, winner detection with confidence), prompt generation from structured parameters (role, tone, style, length, constraints, examples), automatic prompt variation generation (5 strategies: restructure, clarity, specificity, format, persona), `{{variable}}` interpolation and extraction, test run recording with score/latency/tokens, template CRUD with delete, and a redesigned three-tab UI (Templates, A/B Tests, Generator). 14 API endpoints replace the original 2: `GET/POST/PUT/DELETE /api/prompts`, `GET /api/prompts/:id`, `POST /api/prompts/runs`, `GET/POST /api/prompts/ab-tests`, `GET/PUT /api/prompts/ab-tests/:id`, `POST /api/prompts/generate`, `POST /api/prompts/variations`. Run buffer increased from 100 to 500. (`src/prompt-lab.ts`, `src/server/routes/eval-routes.ts`, `src/server/ui/pages/promptlab.ts`, `src/server/ui/js/11_pages.ts`) - **Prompt Lab UI integrity tests** — three new tests in the UI JS integrity suite: validates all 26 prompt lab functions exist in the generated output, all 18 DOM element IDs are present in the page HTML, and `split(/\\n/)` (the correct template-literal-safe regex pattern for newline splitting) exists in the output. Catches the exact class of escaping bugs where `/\n/` inside a template literal produces a literal newline breaking the regex. (`tests/ui_js_integrity_test.ts`) - **OpenClaw config import (`cortex import config`)** — new subcommand that converts an OpenClaw `openclaw.json` configuration to Cortex `config.json` settings. Maps providers (apiKey, baseUrl, model), agents (id, description, tools from skills), default provider/model, auto model selection pool from provider model lists, plugin configs (firecrawl, litellm, etc.), web search provider, voice/talk config, server settings, and MCP server entries. Supports `--dry-run` for preview. (`src/cli/import/config/types.ts`, `src/cli/import/config/openclaw.ts`, `src/cli/import-cmd.ts`) - **Unified `cortex import openclaw` command** — rebuilt from a memory-only export importer into a comprehensive migration entry point. Imports config (providers, agents, model pool), session transcripts (from `agents//sessions/*.jsonl` and `transcripts/*/*/transcript.jsonl`), session metadata (`sessions.json`), and memory files (MEMORY.md, memory/*.md, SOUL.md, USER.md) in a single command. Supports `--config-only`, `--sessions-only`, `--memory-only`, and `--dry-run` flags. (`src/cli/import-cmd.ts`) - **Enhanced JSONL transcript parser** — the transcript importer now extracts `tool_calls` and `tool_result` from event metadata, populating the `session_messages.tool_calls` and `session_messages.tool_result` columns. Handles `custom_message` events (extension-injected messages visible to model context) and stores `model_change` events as episodic memories. Adds `importOpenClawSessions()` function that recursively discovers transcripts across both agent session directories and historical transcript directories. (`src/cli/import/jsonl.ts`) - **Config mapper framework** — extensible `ConfigMapper` type and `PROVIDER_NAME_MAP` (25 providers) enabling new source-system adapters to be plugged in. Each mapper receives the source config and existing Cortex config, returning a partial config object and warnings array. (`src/cli/import/config/types.ts`, `src/cli/import/config/openclaw.ts`) - **Hermes config import** — new config mapper reads Hermes `config.yaml` (via `@std/yaml`) and maps to Cortex config: `model.default` → default provider/model, `model.provider` / `model.base_url` → provider config, `agent.personalities` → Cortex agents, `agent.max_turns` → agent runtime, `terminal.docker_image` → sandbox, `memory.*` → memory config, `mcp_servers` → MCP server entries. (`src/cli/import/config/hermes.ts`, `deno.json`) - **Hermes state.db direct reader** — new `importHermesStateDb()` reads Hermes' SQLite `state.db` directly (no export step required). Queries sessions (24+ columns: source, user_id, model, parent_session_id, started_at, end_reason, token counts, costs) and messages (18+ columns: role, content, tool_calls, token_count, finish_reason, timestamp). Creates Cortex sessions with `hermes_` naming and writes fully populated `session_messages`. (`src/cli/import/hermes.ts`) - **Hermes memory file import** — new `importHermesMemoryFiles()` imports `SOUL.md` (copies to Cortex config dir as agent identity), `MEMORY.md` (parsed into episodic memories by `##` heading sections), `USER.md` (copied as user profile), and the `skills/` directory (recursively copied). (`src/cli/import/hermes.ts`) - **Enhanced `cortex import hermes` command** — rebuilt from a JSONL-only importer into a comprehensive migration entry point. Supports `--config-only` (config.yaml), `--sessions-only` (auto-detects state.db vs JSONL exports), `--memory-only` (SOUL.md, MEMORY.md, USER.md, skills/), and `--dry-run`. Auto-discovers Hermes' config.yaml, state.db, and memory files from the detected directory. (`src/cli/import-cmd.ts`) - **UI overhaul — horizontal top navigation** — replaced the 33-item flat sidebar with a horizontal top bar (5 categories: Chat, Development, Knowledge, Infrastructure, System) containing logo, 5 nav tabs, command palette trigger, experience level toggle, theme toggle, and WebSocket badge. Clicking a category tab shows its contextual sub-nav in the sidebar. (`src/server/ui/shell.ts`, `src/server/ui/css.ts`) - **UI overhaul — contextual sidebar sub-nav** — sidebar is now dynamically populated by `renderSubNav()` based on the active top nav category. Category-to-page mapping defines 40 pages across 5 categories, each with icon, label, tooltip, and experience level. Sidebar search (`filterNav()`) now searches within the visible category. (`src/server/ui/js/05_nav_pre.ts`, `src/server/ui/js/07_nav_post.ts`, `src/server/ui/js/13_command.ts`) - **UI overhaul — experience levels with 3-button segmented control** — `[B] [I] [A]` mode toggle in header filters visible navigation by experience level. Beginner sees 10 core pages, Intermediate sees 29, Advanced sees all 40. Persisted in `localStorage` as `cortex_experience_level`. Navigating to a hidden page via URL hash shows a level gate overlay with upgrade button. Command palette also filters by experience level. (`src/server/ui/shell.ts`, `src/server/ui/js/00_init.ts`, `src/server/ui/js/01_helpers.ts`, `src/server/ui/js/05_nav_pre.ts`, `src/server/ui/js/07_nav_post.ts`, `src/server/ui/js/13_command.ts`, `src/server/ui/css.ts`) - **UI overhaul — JS tooltip system** — replaced the CSS-only `[data-tip]::after` pseudo-element hack with a proper JavaScript tooltip implementation. Uses event delegation on `[data-tooltip]` attributes, creates a single reusable `#global-tooltip` element with `role="tooltip"` and `aria-describedby`, supports both mouse (250ms delay, instant hide) and keyboard (focusin/focusout), smart positioning (flip above/below, clamp horizontal), and Escape to dismiss. Deployed on all nav items, mode toggle buttons, and theme toggle. (`src/server/ui/js/01_helpers.ts`, `src/server/ui/css.ts`) - **UI overhaul — dark/light theme toggle** — adds CSS custom property system with dark theme as default (`:root`) and full light theme overrides (`[data-theme="light"]`). Toggle button in header switches between modes, respects `prefers-color-scheme` media query on first load, persists choice in `localStorage` as `cortex_theme`. (`src/server/ui/shell.ts`, `src/server/ui/css.ts`, `src/server/ui/js/00_init.ts`, `src/server/ui/js/01_helpers.ts`) ### Security - **Comprehensive security policy audit and hardening** — reviewed all 6 layers of built-in security policies (DB rules, validator, guardrails, DLP, capability tiers, auxiliary modules). Identified and resolved 18 issues across critical, high, medium, and low priority tiers. - **SSRF protection wired into shell command validation** — the existing SSRF module (`resolveAndCheck()` with private IP/DNS blocking) was never called from the validator. Shell commands containing URLs (e.g., `curl http://169.254.169.254/`) now undergo SSRF checks, blocking cloud metadata endpoints, loopback addresses, and RFC 1918 private IPs. (`packages/gate/src/security/validator.ts`) - **Session isolation enforced at tool-call boundary** — `isPathAllowed()` from the isolation module was registered but never consulted by the validator. File tool path arguments now checked against registered session boundaries, preventing cross-session file access. (`packages/gate/src/security/validator.ts`) - **Policy table CHECK constraint widened** — migration 009 only allowed `('tool', 'shell', 'domain', 'capability')` kinds, but the validator also checks `'path'` and `'computer'` kinds which could never be inserted. Migration 042 recreates the table with the full set. (`src/db/migrations/042_policy_review.sql`, `packages/core/src/db/migrations/042_policy_review.sql`) - **16 new default deny rules seeded** — 5 shell rules (`mkfs`, `/proc/sys/` writes, `iptables`/`ufw`, `crontab -`, `git push`), 7 path rules (`/etc/shadow`, `/root/.ssh/`, `.gnupg/`, `.env`, `id_rsa`, `sshd_config`, `sudoers`), 3 domain rules (AWS/GCP metadata endpoints, loopback), 1 computer action rule (`type`). All use `INSERT OR IGNORE` to avoid overwriting user customizations. (`042_policy_review.sql`) - **4 existing shell regex patterns hardened** — `rm -rf` now catches `-r -f`, `--recursive --force`, and `-fr` variants; fork bomb pattern matches actual `:(){ :|: & };:` syntax; `dd` catches bare device names (`/dev/sda`); `chmod 777` catches `-R 777` and non-root paths. Updates only apply when the original pattern is unmodified. (`042_policy_review.sql`) - **12 chrome_* and codegraph tool risk profiles added** — `chrome_execute_js`, `chrome_http_auth`, and `chrome_network_rules` set to `'high'` with confirmation required; `chrome_navigate`, `chrome_create_tab`, `chrome_upload_file`, `chrome_save_page`, `chrome_manage_downloads`, `chrome_fill_form`, and `chrome_type_text` set to `'medium'` with appropriate guardrails; `code_index` and `code_pilot` profiled. Previously all fell through to a blanket `'medium'`. (`packages/gate/src/security/dynamic-grant.ts`) - **`CORTEX_VAULT_KEY` removed from safe environment variables** — the vault encryption key was listed as accessible from any session, creating a path for agents to exfiltrate the master key. Removed from the safe-var set. (`packages/gate/src/security/isolation.ts`) - **Guardrail shell injection patterns narrowed** — backtick and `$()` patterns matched empty content and blocked legitimate code examples. Changed to `{1,200}` quantifier requiring 1+ characters and bounded to inline code length. (`packages/gate/src/security/guardrails.ts`) - **Data classification default relaxed from `'sensitive'` to `'normal'`** — the security-first default classified all non-empty content as sensitive, triggering excessive supervisor LLM calls. Following the defense-in-depth review, only content matching explicit SENSITIVE_PATTERNS or SECRET_PATTERNS is now elevated. (`packages/gate/src/security/classification.ts`) ### Fixed - **`cortex import` command was not registered** — the `import` command entry existed in `packages/cli/src/cli/registry.ts` but was missing from `src/cli/registry.ts`, the actual registry consumed by `src/main.ts`. Added the entry so `cortex import` is now accessible. (`src/cli/registry.ts`) - **Import subcommand detection functions were swapped** — `cortex import openclaw` called `detectZeroClawDir()` and `cortex import zeroclaw` called `detectOpenClawDir()`. Fixed in both `src/` and `packages/` trees. (`src/cli/import-cmd.ts`, `packages/cli/src/cli/import-cmd.ts`) - **Daemon restart was a no-op** — `POST /api/daemons/*/restart` returned `{ok: true}` without actually restarting anything. Daemon processes now write PID files on spawn (`src/processes/supervisor-process.ts`), and the restart handler reads the PID, sends SIGTERM, and waits up to 15s for the supervisor to auto-restart the process. (`src/server/routes/daemons.ts`, `src/processes/supervisor-process.ts`) - **Workflow approvals always returned empty** — `GET /api/workflows/approvals` hardcoded `json([])`. Now queries all registered workflows for pending approval state. Added `POST /api/workflows/approvals/:name` route for approve/reject actions. UI updated to use `name` instead of `id` for approval actions. (`src/server/routes/workflows.ts`, `src/server/ui/js/11_pages.ts`) - **Memori preview returned stub** — `GET /api/memori/preview` always returned `{checkpoints: []}`. Now queries the actual checkpoint store with a limit of 5. (`src/server/routes/eval-routes.ts`) - **Observability traces and embeddings pipeline endpoints marked 501** — `GET /api/observability/traces` and `GET /api/embeddings/pipeline` returned hardcoded empty data. Now return HTTP 501 Not Implemented to signal these features are pending. (`src/server/routes/eval-routes.ts`) - **Memory graph entity detail panel — XSS via inline onclick** — the `esc()` function converts `'` to `'`, which the browser HTML parser decodes back to `'` before evaluating inline `onclick` handlers. Entity names containing single quotes could break out of the JS string context and execute arbitrary code. Fixed by using `escJs()` for values inside JavaScript string contexts. (`src/server/ui/js/11_pages.ts`) - **Memory graph entity detail — uncaught URIError on malformed name** — `decodeURIComponent(name)` in the `/api/memory/graph/entity` route threw `URIError` on invalid percent-encoding with no try/catch, causing an unhandled rejection. Added try/catch returning HTTP 400. (`src/server/routes/memory-graph.ts`) - **Memory graph entity detail — non-unique entity name** — `getEntityDetail()` matched only on `name` with `LIMIT 1`, returning an arbitrary entity when duplicates exist (different types share the same name). Added optional `type` query parameter; frontend now passes `d.type` from graph nodes and `r.entity.type` from relation rows. Query uses `WHERE name = ? AND type = ?` when type is provided. (`src/memory/graph.ts`, `src/server/routes/memory-graph.ts`, `src/server/ui/js/11_pages.ts`) - **Memory graph entity detail — total counts included deleted peers** — `totalInbound`/`totalOutbound` counted raw DB rows including relations to deleted entities, while the displayed relations list filters them out. Counts now computed from the filtered `relations` array. (`src/memory/graph.ts`) - **Memory graph entity detail — unbounded relation queries** — outbound and inbound `SELECT` queries on `graph_relations` had no `LIMIT`, unlike the existing `traverseGraph` which uses `LIMIT 10`. Added `LIMIT 200` to both queries. (`src/memory/graph.ts`) - **Memory graph entity detail — unbounded IN clause** — peer entity lookup used `WHERE id IN (...)` with no cap on placeholders, risking SQLite's 999-parameter limit for heavily-connected entities. Capped `peerIds` to 900 before constructing the IN clause. (`src/memory/graph.ts`) - **Prompt Lab AB test onclicks — raw comma after backslash-escaped quote** — inline `onclick` handlers using `\\'` (backslash-escaped quote boundary) followed by bare `,` produced a `SyntaxError: Unexpected string` because `,` is invalid JS outside a string context. Fixed by replacing complex ternary-in-onclick handlers with simple helper functions (`plPauseABTest`, `plResumeABTest`, `plCompleteABTest`) that call `updateABTestStatus` internally, avoiding quote-escaping entirely. (`src/server/ui/js/11_pages.ts`) - **Prompt Lab generator — `split(/\n/)` produced literal newline in template literal** — the `11_pages.ts` file is a TypeScript template literal export (`export const JS_11_PAGES = \`...\``). Inside it, `\n` is interpreted as a template literal escape producing an actual newline character, breaking the regex literal across lines. Fixed by using `split(/\\n/)` (double-escaped backslash) which produces the correct `split(/\n/)` in the output. (`src/server/ui/js/11_pages.ts`) ### Changed - **Misfiled routes reorganized** — `session-links.ts` contained 6 unrelated route groups (security approvals bulk, settings compressor, codegraph pilot-config, agentlint check, agent preferences, sessions links). Routes moved to their semantically correct files: `security.ts`, `config-routes.ts`, `codegraph.ts`, `agents.ts`, `memory-config.ts`. (`src/server/routes/session-links.ts`, `src/server/routes/security.ts`, `src/server/routes/config-routes.ts`, `src/server/routes/codegraph.ts`, `src/server/routes/agents.ts`, `src/server/routes/memory-config.ts`) - **UI overhaul — CSS rewrite with brand palette** — complete CSS rewrite (599→1000+ lines) with CortexPrism brand colors (cyan `#06b6d4`, indigo `#6366f1`), spacing scale (`--space-1` through `--space-8`), updated typography (Inter 14px/1.6, JetBrains Mono 13px), and 80+ new component classes for header, top nav, sidebar, mode toggle, tooltips, and level gating. All existing component styles (chat, cards, buttons, CodeMirror, agent panel, dashboard, graph) preserved with refreshed values. (`src/server/ui/css.ts`) - **UI overhaul — shell restructure** — replaced sidebar-centric flex layout with header+body column layout: `
` (48px, containing logo, top nav, controls) above `
` (sidebar + main flex). Removed all 33 hardcoded nav items from `SIDEBAR_HTML`. Sidebar now houses `#sidebar-subnav` container populated dynamically by JS. (`src/server/ui/shell.ts`) ### Removed - **Dead `packages/core/src/db/migrate.ts`** — 409-line duplicate of `src/db/migrate.ts` that was never imported (all 13+ consumers resolve to `src/db/migrate.ts`). Contained a broken import (`../security/backfill.ts` resolving to a non-existent path). Removed to prevent confusion and staleness risk. (`packages/core/src/db/migrate.ts`) - **Orphaned `working_memory` table** — the `working_memory` table in the per-session schema (006_session.sql) had zero runtime references. Removed from the session schema. (`src/db/migrations/006_session.sql`, `packages/core/src/db/migrations/006_session.sql`) - **Orphaned `channel_sessions` and `channel_messages` tables** — these tables in `cortex.db` had zero runtime references (incomplete channel message persistence feature). Dropped via migration 041. (`src/db/migrations/041_cleanup_orphaned.sql`, `packages/core/src/db/migrations/041_cleanup_orphaned.sql`) - **Dead `savePartialProfile` import** — `workspace-snapshots.ts` imported `savePartialProfile` from `_helpers.ts` but never called it. Import removed. (`src/server/routes/workspace-snapshots.ts`) - **Remote Agents deploy modal tier mismatch** — the deploy modal dropdown listed nonexistent tiers `operator` and `observer`. Fixed to use the actual capability tiers: `unprivileged`, `sudo`, `root`. (`src/server/ui/pages/modals.ts`, `packages/server/src/server/ui/pages/modals.ts`) - **A2A remote agents were dead config** — `createA2AToolWrapper()` was defined and exported but never called by any code path. Remote A2A agents configured under `a2a.remoteAgents` in `config.json` were never registered as tools, making the entire feature a no-op. Added registration at the end of `registerAllBuiltins()` that reads `config.a2a.remoteAgents` and registers each agent as a tool (`a2a_`). Gracefully skips when config or remote agents are absent. (`src/tools/registry.ts`) - **A2A config contract type was `Record`** — the `a2a` field in `ICortexConfig` (contracts) used a bare generic object type instead of a typed interface. Added `IA2ARemoteAgentConfig` and `IA2AConfig` interfaces with proper fields (`enabled`, `server`, `remoteAgents`). (`packages/core/contracts/config.ts`) - **`renderThinkingForRestore` regex escapes broken + TS type annotation in browser JS** — 5 regex patterns in the thinking-tag restoration function used single backslashes (`\s`, `\S`, `\/`) inside the template literal export, which TypeScript consumed as escape sequences. `\/` became `/` in the output, terminating the regex literal early and exposing `(?:think)` as raw JS code, causing `SyntaxError: Unexpected token '?'`. Additionally, `const thinkBlocks: string[] = []` had a TypeScript type annotation (`: string[]`) that is invalid in the browser's JS engine, causing `SyntaxError: Missing initializer in const declaration`. Fixed by double-escaping all regex backslashes and removing the type annotation. Added `new Function(js)` syntax validation to prevent future regressions. (`src/server/ui/js/04_chat_ui.ts`) - **Node agent TLS fields were dead code** — `NodeAgentOptions` declared `tlsCert` and `tlsKey` fields that were never consumed by `createWebSocket()`. Removed both fields from the interface and destructuring. (`src/remote/agent.ts`) - **Node `rekey` handler was a no-op** — the Hub-to-Node `rekey` message handler only logged the event. Now stores the rotated token in mutable state and closes the WebSocket to trigger an automatic reconnect using the new credential. (`src/remote/agent.ts`) - **Node `config_update` handler was a no-op** — the Hub-to-Node `config_update` message logged the allow-list but never applied it. Now stores `toolsAllowList` and `blockedTools` in mutable config overrides that `localPolicyCheck()` checks before tier-based rules. The `config_update` message type and `pushConfigUpdate()` signature updated to carry `blockedTools`. (`src/remote/agent.ts`, `src/remote/types.ts`, `src/hub/ws-node.ts`) - **Dead `RemoteAgentManager` removed** — `src/remote/manager.ts` (47 lines of pure `Map` wrappers) was not imported by any file in the codebase. Superseded by the persisted `hub/node-registry.ts`. Removed from both `src/remote/` and `packages/server/src/remote/`. (`src/remote/manager.ts`, `packages/server/src/remote/manager.ts`) - **Unused packages/ duplicates cleaned up** — removed 9 dead duplicate files under `packages/server/src/` and `packages/ai/src/` that existed as migration scaffold but were never imported (all imports resolve to `src/`). Fixed broken references in the dead-but-kept `packages/server/src/server/ui/mod.ts`. (`packages/server/src/remote/types.ts`, `packages/server/src/remote/agent.ts`, `packages/server/src/hub/node-registry.ts`, `packages/server/src/hub/ws-node.ts`, `packages/server/src/hub/capability-tiers.ts`, `packages/server/src/hub/session-routing.ts`, `packages/server/src/server/ui/pages/remote.ts`, `packages/server/src/server/ui/js/19_devtools.ts`, `packages/ai/src/agent/node-context.ts`) - **Directive cancellation not audited** — `cancelPending()` in the session routing layer deleted the directive map entry without logging a lens event. Now logs `node_directive_cancelled`. Added `node_directive_cancelled` to the `EventType` union. (`src/hub/session-routing.ts`, `src/db/lens.ts`) - **Computer Use Xvfb start/kill per action** — `executeComputerAction()` created a new `ComputerUseExecutor` (starting Xvfb) for every single tool call, then destroyed it. Every mouse click, keypress, and screenshot incurred ~1s Xvfb startup overhead. Replaced with a module-level singleton executor that persists across tool calls and auto-shuts down after 5 minutes of inactivity. Also eliminated a redundant second `loadConfig()` call. (`src/tools/builtin/computer.ts`, `packages/ai/src/tools/builtin/computer.ts`) - **Computer Use screenshot API returned full base64 blob per request** — the screenshot gallery API loaded every PNG file (~5MB for 24 screenshots) into memory, base64-encoded them all, and sent them inline in the JSON response. Split into a metadata-only list (`GET /api/computer/screenshots`) plus a per-file endpoint (`GET /api/computer/screenshots/:name`). Thumbnails now lazy-load via `fetch()` + data URIs. Config endpoint expanded from 3 fields to 8 (enabled, runtime, screenshot format/quality, action timeout). (`src/server/routes/computer-use.ts`, `src/server/routes/_helpers.ts`, `src/server/ui/js/19_devtools.ts`, `packages/server/src/server/routes/computer-use.ts`, `packages/server/src/server/routes/_helpers.ts`) - **Remote agent dead code removed** — removed 7 unused type exports from `src/remote/types.ts` (`RemoteAgentStatus`, `RemoteAgentInfo`, `RemoteAgentConfig`, `RemoteDirective`, `RemoteResult`, `StreamChunk`, `RemoteMessage`) and the dead `runRemoteAgent()` wrapper. Extraneous `ws.onclose` handler removed. (`src/remote/types.ts`, `src/remote/agent.ts`) - **Dead duplicate computer-use files removed** — 6 files under `packages/server/src/computer-use/` were identical duplicates of `src/computer-use/` with adjusted import paths, never imported by any file. (`packages/server/src/computer-use/`) - **DuckDuckGo "Related" sidebar content confused the LLM** — the `web_search` tool's `instantAnswers()` function labeled DuckDuckGo's `RelatedTopics` API field simply as `**Related:**`, causing the LLM to interpret algorithmically-suggested Wikipedia sidebar snippets as conversation context. This could trigger a recursive tool-call feedback loop where the LLM chased noise through 12 rounds of search before delivering a confused error. Now labeled `**DuckDuckGo Sidebar (algorithmically suggested — may be unrelated to your query):**` with an explicit ignore instruction. (`src/tools/builtin/web_search.ts`, `packages/ai/src/tools/builtin/web_search.ts`) - **Recursive self-referential tool calls in LLM stream** — the agent loop had no guard against the LLM generating search queries that recycled text from its own prior responses. Added detection: if any search/fetch tool query matches a >30-char substring of recent assistant output, a `[SYSTEM WARNING]` is injected telling the LLM to reread the user's original message. (`src/agent/stages/llm-stream.ts`, `packages/ai/src/agent/stages/llm-stream.ts`) - **Confusion spiral detection in agent loop** — added a counter tracking consecutive rounds where all tool calls are search/fetch tools. At 3+ rounds with no user-facing output, a `[SYSTEM WARNING]` interrupts the loop telling the LLM it is chasing tangents and to produce results from already-collected data. (`src/agent/stages/llm-stream.ts`, `packages/ai/src/agent/stages/llm-stream.ts`) - **`` tags rendered inline on page refresh** — during live streaming, `...` blocks were extracted into a reasoning accordion and stripped from display text. But when messages were restored from the database on page refresh (`restoreSession()`) or session switch (`loadSessionMessages()`), raw thinking tags were passed directly to the markdown parser, causing garbled display. Added `renderThinkingForRestore()` helper that extracts thinking blocks into reasoning accordions from restored messages, mirroring the live streaming behavior. (`src/server/ui/js/04_chat_ui.ts`, `src/server/ui/js/02_chat_setup.ts`, `src/server/ui/js/16_agent_panel.ts`) - **Template literal newline escaping in UI JS export** — a `'\n\n'` in the `renderThinkingForRestore` function was processed as actual newline characters by the TypeScript template literal export, breaking string literals in the concatenated browser-side JS. Fixed to `'\\n\\n'`. (`src/server/ui/js/04_chat_ui.ts`) - **Model Quartermaster (MQM) prediction & accuracy system overhaul** — 8 fixes to the 6-signal model selection intelligence: - **Reflection signal squeezed out by normalization** — `learn.ts` only reinforced 3 of 6 signals (`historical`, `quality`, `reflection`) on good choices, causing the ignored signals (`cost`, `episodic`, `trajectory`) to shrink toward zero after each normalization pass. Now all 6 signals receive proportional reinforcement/punishment, preserving the full signal portfolio over time. - **Accuracy threshold inconsistency** — accuracy trend query used `was_correct >= 0.7` while session state accuracy used raw `correctCount` (updated by a separate reflection path). Added `CORRECTNESS_THRESHOLD = 0.7` constant; `observeModel()` now updates both `was_correct` on decisions and `correctCount` in session state from a single source. - **Race condition in observe→active mode transition** — both `incrementSessionObservations()` (store) and `observeModel()` (mod.ts) independently set `mode = 'active'` at the 50-observation threshold, risking duplicate mode-change events. Removed mode-setting from `incrementSessionObservations()`; only `observeModel()` handles the transition. - **Normalization inflation for single-signal models** — `fusion.ts` computed `confidence = weightedSum / activeWeightSum`, so a model matching only the `reflection` signal (weight 0.05, score 0.9) got confidence `0.9 * 0.05 / 0.05 = 0.9` — same as a model matching all 6 signals. Added coverage penalty: `confidence *= 0.7 + 0.3 * (signalCount / 6)`. A model with 1/6 signals now gets 0.733× multiplier. - **Heuristic model tier detection missed modern models** — `estimateModelCost()` and `estimateModelQuality()` used a 3-tier list (`opus|gpt-4|o1`, `sonnet|gpt-3.5`, `haiku|flash|mini`) dating to early 2025. Expanded to 6 tiers covering `gpt-4o`, `gpt-4.1`, `gemini-2.x`, `nova-*`, `llama-3.x`, `mistral`, `phi`, `o3`, `o4-mini` with per-tier cost and quality baselines. - **No recency decay on model statistics** — `mqm_model_stats` accumulated forever with equal weight. Added 2%/day decay (floor 40%) on `historical`, `quality`, and `cost` signal scores based on `last_used` timestamp. - **Episodic signal relied on fragile regex extraction** — the episodic signal used `(?:model|using|with)\s+([\w-]+)` regex to extract model names from memory hit text, failing when memory entries didn't match this exact pattern. Replaced with direct substring search: scans each memory hit for candidate model name and provider strings. - **Cost signal compared per-call, not per-task** — raw `avg_cost_usd` was compared across models without normalizing for task size, penalizing models used for complex tasks. Cost is now divided by `taskComplexity` to produce a cost-per-complexity-unit metric for fair cross-model comparison. (`src/model-quartermaster/signals.ts`, `src/model-quartermaster/fusion.ts`, `src/model-quartermaster/learn.ts`, `src/model-quartermaster/mod.ts`, `src/model-quartermaster/store.ts`, `src/model-quartermaster/monitor.ts`, `src/db/migrations/019_model_quartermaster.sql`, plus shadow copies under `packages/infra/src/model-quartermaster/` and `packages/core/src/db/migrations/`) - **Default MQM signal weights rebalanced** — `historical` 0.25→0.22, `quality` 0.25→0.23, `trajectory` 0.10→0.12, `reflection` 0.05→0.08. Gives trajectory and reflection more initial influence while still prioritizing historical performance and quality. - **Quartermaster (QM) tool orchestration system overhaul** — 10 fixes to the 5-signal tool prediction intelligence: - **Race condition in observe()** — `observe()` read `observationCount` from session state, computed `+1` in JS, then upserted. Two concurrent observations could both read the same value, miss an increment, and both trigger mode transitions. Replaced with `incrementSessionObservations()`; mode transition checked separately against `newCount >= OBSERVE_THRESHOLD`. - **`learn()` corrupted `predictionCount`** — `learn()` overwrote `predictionCount` with `sessionState.predictionCount + decisions.length`, but `predict()` had already incremented it for each call. Removed the overwrite; `learn()` now only writes `correctCount`. - **`learn()` set mode at wrong threshold** — `learn()` wrote `mode = predictionCount >= 50 ? 'active' : 'observe'`, but QM's real observe→active threshold is 10. Mode is now set exclusively by `observe()`. - **Trajectory signal dead — exact-match on full-turn sequences** — `findPatterns(last3)` searched for `JSON.stringify(last3)` but stored patterns contained `JSON.stringify(allToolsInTurn)`. A full-turn sequence like `["read","edit","write","shell"]` never matched a prefix search for `["edit","write","shell"]`. Added `prefix` mode to `findPatterns()` using SQL `LIKE ? || '%'`; `computeTrajectorySignal()` extracts `nextTool = seq[prefix.length]`; `learn.ts` stores `prefix_3_tools + actualTool` instead of the full turn. - **Fusion never reached suggest threshold** — unlike MQM, QM just summed `weight * score` without dividing by activeWeightSum. A tool matching only `taskContext` (weight 0.15, score 0.8) got `0.12` — 5× below the 0.6 suggest threshold. Added `rawTotal / activeWeightSum` normalization plus `coveragePenalty = 0.7 + 0.3 * (signalCount / 5)`. - **Reflection confidence hardcoded to 0.5** — `predict()` always passed `0.5` to `gatherSignalScores()` regardless of actual reflection quality. Now accepts `reflectionConfidence` parameter (default 0.5) so callers can pass real reflection confidence. - **`avg_confidence` incremental average formula broken** — `upsertPattern()` computed `(confidence + success_count) / (hit_count + 1)`, mixing a 0-1 score with an integer count. Added `avg_confidence` to the SELECT; formula corrected to `(avg_confidence * hit_count + confidence) / newHitCount`. - **Only 3/5 signals penalized on bad predictions** — `updateWeightsFromDecision()` only penalized `trajectory`, `episodic`, and `taskContext` on wrong predictions, leaving `toolStats` and `reflection` immune to penalty. Now all 5 signals receive proportional penalties using `confidenceFloor` enforcement. - **`confidenceFloor` stored but never enforced** — `qm_signal_weights.confidence_floor` existed in schema and migration but `updateWeightsFromDecision()` ignored it. Now enforces `Math.max(floor, newWeight)` on every update. - **Hardcoded candidate tool list missed 50+ tools** — `collectCandidateTools()` listed only 10 tools. Modern tools (`web_search`, `web_fetch`, `brave_search`, `computer`, `sandbox_exec`, `task`, `a2a`, `mcp`, `semantic_search`, `codebase_search`, `git_commit`, `git_stash`, `web_scrape`) were excluded from prediction. Expanded to 24 tools. (`src/quartermaster/signals.ts`, `src/quartermaster/fusion.ts`, `src/quartermaster/learn.ts`, `src/quartermaster/mod.ts`, `src/quartermaster/store.ts`, plus shadow copies under `packages/infra/src/quartermaster/`) - **Episodic signal regex fragility fixed in QM** — the QM episodic signal used `(?:tool|call|used|ran|executed)\s+(\w+)` regex identical to the MQM issue. Replaced with direct `text.includes(toolName)` search across candidate tools. (`src/quartermaster/signals.ts`, `packages/infra/src/quartermaster/signals.ts`) ### Added - **`cortex mcp a2a remote` CLI command** — new subcommand that lists all configured remote A2A agents with endpoint, auth status, timeout, and tool name. Shows a config example when no agents are configured. The main `cortex mcp a2a` help text now includes a full `config.json` example for adding remote agents. (`src/cli/a2a-cmd.ts`, `packages/cli/src/cli/a2a-cmd.ts`) - **Sessions page — tree view with token metrics** — the sessions list is now a hierarchical tree showing parent sessions with indented child sub-agent sessions. Added an enriched endpoint (`GET /api/sessions/enriched`) that joins lens_events token data per session, plus `GET /api/sessions/:id/stats` for single-session stats. Each row displays: status dot, sub-agent connector, name, truncated ID, agent/channel/sub-agent type badges, child count chip, `← parent` link on children, turn count, total tokens, cost, tool calls, and average LLM duration. Parent cards get an accent left border; children get an amber left border. The detail view now fetches and displays token metrics alongside parent/child navigation. Archival opacity transitions smoothly on hover. **Fixed template-literal escaping** — the file lives inside a TypeScript template literal export; inline `onclick` handlers using JS string concatenation (`\'')` patterns) were broken by the dual-layer escaping (TS template → browser JS). Replaced concatenation with browser-side template literals for the main card HTML and DOM-based `addEventListener` for child links, and switched special characters to Unicode escapes (`\u2514`, `\u2190`, etc.) to avoid encoding ambiguity. (`src/db/sessions.ts`, `src/server/routes/sessions.ts`, `src/server/ui/js/10_sessions.ts`, `src/server/ui/pages/sessions.ts`, `src/server/ui/css.ts`) ## [0.49.1] - 2026-06-22 ### Changed - **Tauri desktop app rebuilt** — the `desktop/` Tauri 2.x application was fully restructured with a proper Rust IPC backend, dedicated desktop frontend, and server lifecycle management: - **Rust backend** (`desktop/src-tauri/src/`): modular architecture with `commands.rs` (8 IPC commands: `get_system_info`, clipboard read/write, server start/stop/status, `open_external`), `tray.rs` (system tray with Tauri 2.x `TrayIconBuilder` API, dynamic server status, quick-ask trigger), and `main.rs` (auto-starts Cortex server on launch, manages `AppState` with child process tracking, close-to-tray behavior) - **Desktop frontend** (`desktop/src/`): dedicated shell UI with toolbar, quick-ask bar (`Ctrl+Shift+K`), server health indicator, iframe-hosted Cortex dashboard, and splash/loading screen. Communicates with Tauri backend via IPC and Cortex server via REST API - **Icons**: generated SVG logo + PNG sizes (32×32, 128×128, 256×256) + ICO + placeholder ICNS via `rsvg-convert` - **Build**: new `build-desktop.ts` script inlines CSS/JS into single `desktop/dist/index.html`; added `deno task build-desktop` - **Dependencies**: `sysinfo` (system monitoring), `arboard` (clipboard), `open` (URL handler), `hostname`; version synced to 0.49.1 - (`desktop/src-tauri/src/main.rs`, `desktop/src-tauri/src/commands.rs`, `desktop/src-tauri/src/tray.rs`, `desktop/src-tauri/Cargo.toml`, `desktop/src-tauri/tauri.conf.json`, `desktop/src-tauri/icons/*`, `desktop/src/index.html`, `desktop/src/app.css`, `desktop/src/app.js`, `desktop/build-desktop.ts`, `deno.json`) - **Runtime timeouts, limits, and CDN endpoints made configurable** — 30+ previously hardcoded values are now configurable via `config.json` with sensible defaults preserved: - **Agent loop** — `agentRuntime.maxToolRounds` (12), `agentRuntime.subAgentTimeoutMs` (120000), `agentRuntime.streamTimeoutMs` (180000). `setup.ts` and `llm-stream.ts` read from config, falling back to module-level defaults. - **Sandbox** — `sandbox.timeoutMs` (30000), `sandbox.maxOutputBytes` (65536), `sandbox.scrollAmount` (3), optional `sandbox.dockerImages` overrides. - **Approval workflow** — `approvals.autoApproveRiskBelow` (low), `approvals.defaultTimeoutMs` (300000), `approvals.maxTimeoutMs` (3600000). Added `initApprovalWorkflowFromCortexConfig()`. - **Job scheduler** — `scheduler.runningJobTimeoutMs` (600000). `recoverStaleJobs()` resolves timeout from config. - **Chrome Bridge** — `chromeBridge.healthCheckMs` (30000), `chromeBridge.maxRetries` (5), `chromeBridge.initialBackoffMs` (100), `chromeBridge.maxBackoffMs` (1600). Wired through `startChromeBridge()`. - **UI CDN endpoints** — `uiCdn.cdnBase` (`cdn.jsdelivr.net`), `uiCdn.googleFontsBase` (`fonts.googleapis.com`), `uiCdn.d3Base` (`d3js.org`). `serveUi()` accepts `UICdnOptions` from config. - **Code graph** — `codeGraph.maxGrammarSize` (5242880), `codeGraph.ignoreDirs`, `codeGraph.ignoreFiles`. - All new config sections use deep merging so partial overrides don't wipe defaults. (`packages/core/contracts/config.ts`, `packages/core/src/config/config.ts`, `packages/ai/src/agent/stages/setup.ts`, `packages/ai/src/agent/stages/llm-stream.ts`, `packages/ai/src/tools/builtin/chrome_bridge_manager.ts`, `packages/infra/src/scheduler/scheduler.ts`, `packages/gate/src/security/approval-workflow.ts`, `packages/server/src/server/ui/mod.ts`, `packages/server/src/server/server.ts`) ## [0.49.0] - 2026-06-22 ### Added - **Codebase modularization** — three of the largest monoliths were decomposed into cohesive modules with no behavior change: - **Router split** (`src/server/router.ts`, 6,075 lines → 62 route modules + `new-router.ts`): every `// ──` section extracted into its own `src/server/routes/.ts` file exporting `RouteHandler[]` tuples (`{ method, pattern, handler }`). Helper functions (`json`, `notFound`, `err`, rate limiter, CORS) moved to `_helpers.ts`. `new-router.ts` iterates a flat `publicRoutes`/`protectedRoutes` table with the auth guard between them. The original `router.ts` is replaced in `server.ts` by the new-router. (`src/server/routes/*.ts`, `src/server/new-router.ts`) - **UI split** (`src/server/ui.ts`, 17,740 lines → 74 modular files): CSS extracted to `css.ts`, 41 page `
` templates to `pages/*.ts`, 25 JavaScript blocks to `js/*.ts`, shared utilities to `shared/`. `mod.ts` assembles all pieces via string concatenation into a single `