Files
AgentCompany/src/main/database/schema.ts
T
ZacharyZhang-NY f041336721 refactor: nuke dead exports, dead files, dead DB column, dead zero-value wrappers
Full-codebase ts-prune + manual audit sweep. Every change below is a pure
deletion (or a 2-line re-wire where a dead function was supposed to be
alive). No scope additions.

Dead wrappers / phantom features
  - shouldAutoApproveApproval: zero-value wrapper over shouldAutoApprove.
    Four call sites now import shouldAutoApprove from execution-policy
    directly.
  - evaluateRotation(session, connectorId, thresholdsOverride): the
    connectorId arg was never read (underscored), and thresholdsOverride
    was only exercised by the test suite — no production caller. Signature
    collapses to evaluateRotation(session). DEFAULT_THRESHOLDS /
    RotationThresholds exports and their tests go with it.
  - installLogRedactor: defensive console wrapper that was never wired
    from worker-entry. buildRedactor handles the known threat (env-value
    leaks); the pattern-based redactTokens stays for tests and future
    composition.

Dead files
  - resume-interrupted-tasks.ts: helper function never imported.
  - service-graph-orchestration-deps.ts: three factory closures
    superseded by the inline closures in create-orchestration-service.

Dead exports
  - listConnectorsByAuthKind (shared/connector-catalog)
  - getLocalePromptInstruction + whole localization.ts file
  - ImportantDocType
  - main-side ConnectorAuthKind (duplicated the shared one; not referenced)
  - CheckpointEngineService, RunHousekeepingService types
  - WorkspaceResolution type
  - cmdOrCtrlSymbol
  - AgentEventPush, CredentialPayload type aliases
  - parseApprovalPayloadJson, parseEnvValue, ProviderCard
  - getSOP — buildSOPSection reads the SOPS map directly; the individual
    getter had no callers. Deduped to a flat array.
  - renderWakeReason in wake-reasons/en.ts: byte-for-byte duplicate of the
    one in wake-reasons/index.ts.

Forgotten wiring (not cleanup, but adjacent)
  - registerAgentStandupRoute: the GET /api/agents/me/standup endpoint is
    advertised in every system prompt and wake-reason doc, but the route
    handler was never registered in api-server/index.ts. Agents have been
    hitting 404 since Pi Mono. Registered.

Dead DB column
  - v44 migration drops runs.context_snapshot_json. Column was added by
    v16-v25, never written or read, and v43's deliberate preservation
    was an interim measure. Same idempotent table-rebuild pattern as
    v41/v43.

Test tree
  - Phantom-feature threshold override tests removed (3). The rest of
    the rotation suite re-expressed against the real maxRuns=20 floor.

495/495 unit tests pass. typecheck + build clean.
2026-04-18 23:37:37 -04:00

208 lines
16 KiB
TypeScript

import type { DatabaseSync } from "node:sqlite";
import { createMigrationsV1V15 } from "./migrations/v1-v15";
import { createMigrationsV16V25 } from "./migrations/v16-v25";
import { createMigrationsV26V34 } from "./migrations/v26-v34";
import { createMigrationsV35V37 } from "./migrations/v35-v37";
import { createMigrationsV38V39 } from "./migrations/v38-v39";
import { createMigrationsV40V40 } from "./migrations/v40-v40";
import { createMigrationsV41V41 } from "./migrations/v41-v41";
import { createMigrationsV42V42 } from "./migrations/v42-v42";
import { createMigrationsV43V43 } from "./migrations/v43-v43";
import { createMigrationsV44V44 } from "./migrations/v44-v44";
import {
columnExists,
nowIso,
tableExists,
type Migration,
type ReadAll,
type Transaction,
} from "./migrations/types";
export function ensureOptionalColumns(db: DatabaseSync) {
const addColumnIfMissing = (table: string, column: string, sql: string) => {
if (!columnExists(db, table, column)) db.prepare(sql).run();
};
addColumnIfMissing("connectors", "updated_at", "alter table connectors add column updated_at text");
addColumnIfMissing("approvals", "payload_json", "alter table approvals add column payload_json text");
addColumnIfMissing("agents", "heartbeat_enabled", "alter table agents add column heartbeat_enabled integer not null default 0");
addColumnIfMissing("agents", "heartbeat_interval_sec", "alter table agents add column heartbeat_interval_sec integer not null default 120");
addColumnIfMissing("agents", "last_heartbeat_at", "alter table agents add column last_heartbeat_at text");
addColumnIfMissing("agents", "session_id", "alter table agents add column session_id text");
db.exec("create index if not exists idx_agents_session_id on agents(session_id)");
addColumnIfMissing("companies", "auto_approve_hires", "alter table companies add column auto_approve_hires integer not null default 0");
addColumnIfMissing("companies", "review_deliverables", "alter table companies add column review_deliverables integer not null default 0");
addColumnIfMissing("companies", "execution_mode", "alter table companies add column execution_mode text not null default 'ai_ceo'");
addColumnIfMissing("tasks", "task_type", "alter table tasks add column task_type text not null default 'general'");
addColumnIfMissing("tasks", "requires_user_review", "alter table tasks add column requires_user_review integer not null default 0");
addColumnIfMissing("tasks", "retry_count", "alter table tasks add column retry_count integer not null default 0");
addColumnIfMissing("tasks", "last_failure_at", "alter table tasks add column last_failure_at text");
addColumnIfMissing("tasks", "checkout_run_id", "alter table tasks add column checkout_run_id text");
addColumnIfMissing("tasks", "execution_locked_at", "alter table tasks add column execution_locked_at text");
addColumnIfMissing("tasks", "active_session_id", "alter table tasks add column active_session_id text");
addColumnIfMissing("cost_entries", "project_id", "alter table cost_entries add column project_id text");
db.exec(`
create table if not exists task_comments (id text primary key, company_id text not null, task_id text not null, author_agent_id text, author_name text not null, body text not null, created_at text not null);
create index if not exists idx_comments_task on task_comments(task_id);
create index if not exists idx_comments_company on task_comments(company_id);
`);
db.exec(`
create table if not exists social_accounts (id text primary key, company_id text not null references companies(id) on delete cascade, platform text not null default 'other', account_name text not null, display_name text not null default '', profile_url text not null default '', credential_secret_id text, status text not null default 'login_required', require_approval integer not null default 1, metadata_json text not null default '{}', last_used_at text, created_at text not null default (datetime('now')), updated_at text not null default (datetime('now')));
create index if not exists idx_social_accounts_company on social_accounts(company_id);
`);
db.exec(`
create table if not exists browser_actions (id text primary key, company_id text not null references companies(id) on delete cascade, social_account_id text not null references social_accounts(id) on delete cascade, agent_id text not null references agents(id) on delete cascade, task_id text, action_type text not null default 'custom_script', payload_json text not null default '{}', status text not null default 'queued', result_summary text not null default '', screenshot_path text, approval_id text, error_message text, started_at text, finished_at text, created_at text not null default (datetime('now')), updated_at text not null default (datetime('now')));
create index if not exists idx_browser_actions_company on browser_actions(company_id);
create index if not exists idx_browser_actions_account on browser_actions(social_account_id);
`);
if (tableExists(db, "agent_messages")) {
db.exec(`
create index if not exists idx_messages_company on agent_messages(company_id);
create index if not exists idx_messages_channel on agent_messages(channel, channel_target_id);
`);
}
if (tableExists(db, "automation_rules")) {
db.exec(`create index if not exists idx_automation_rules_company_status on automation_rules(company_id, status);`);
}
if (tableExists(db, "automation_log")) {
db.exec(`create index if not exists idx_automation_log_company on automation_log(company_id);`);
}
}
export function migrateSecretsTable(db: DatabaseSync, readAll: ReadAll) {
const columns = readAll<{ name: string }>("pragma table_info(secrets)");
if (columns.some((column) => column.name === "company_id")) {
db.prepare("create unique index if not exists secrets_company_name_idx on secrets(company_id, name)").run();
return;
}
const fallbackCompanyId =
((db.prepare("select value from settings where key = 'current_company_id'").get() as { value: string } | undefined)?.value) ??
((db.prepare("select id from companies order by created_at asc limit 1").get() as { id: string } | undefined)?.id) ??
"00000000-0000-0000-0000-000000000000";
db.exec(`
alter table secrets rename to secrets_legacy;
create table secrets (id text primary key, company_id text not null, name text not null, description text not null, backend text not null, ciphertext blob not null, updated_at text not null, created_at text not null, unique(company_id, name));
`);
db.prepare("insert into secrets (id, company_id, name, description, backend, ciphertext, updated_at, created_at) select id, ?, name, description, backend, ciphertext, updated_at, created_at from secrets_legacy").run(fallbackCompanyId);
db.exec("drop table secrets_legacy;");
}
export function initializeDatabase(db: DatabaseSync, ra: ReadAll, tx: Transaction): void {
db.exec(`
create table if not exists settings (key text primary key, value text not null);
create table if not exists companies (id text primary key, name text not null, description text not null, status text not null, created_at text not null, updated_at text not null);
create table if not exists connectors (id text primary key, label text not null, description text not null, status text not null, command text not null, version text, auth_state text not null, last_checked_at text, last_error text, model text, env_binding_text text not null, notes text not null, updated_at text);
create table if not exists workspaces (id text primary key, company_id text not null, name text not null, local_path text not null, repo_url text not null, repo_ref text not null, project_id text, state text not null, is_primary integer not null default 0, created_at text not null, updated_at text not null);
create table if not exists agents (id text primary key, company_id text not null, name text not null, role text not null, title text not null, status text not null, reports_to text, connector_id text not null, workspace_id text, model text, capabilities text not null, budget_monthly_usd real not null default 0, spent_monthly_usd real not null default 0, metadata_json text not null default '{}', created_at text not null, updated_at text not null);
create table if not exists goals (id text primary key, company_id text not null, title text not null, description text not null, parent_id text, owner_agent_id text, status text not null, created_at text not null, updated_at text not null);
create table if not exists projects (id text primary key, company_id text not null, goal_id text, name text not null, description text not null, lead_agent_id text, status text not null, target_date text, created_at text not null, updated_at text not null);
create table if not exists tasks (id text primary key, company_id text not null, project_id text, goal_id text, parent_id text, title text not null, description text not null, assignee_agent_id text, workspace_id text, status text not null, priority text not null, active_run_id text, session_json text, session_display_id text, cost_usd real not null default 0, created_at text not null, updated_at text not null);
create table if not exists approvals (id text primary key, company_id text not null, related_task_id text, requested_by_agent_id text, type text not null, payload_summary text not null, impact_summary text not null, payload_json text, decision_note text not null default '', state text not null, created_at text not null, updated_at text not null);
create table if not exists runs (id text primary key, company_id text not null, task_id text not null, agent_id text not null, workspace_id text, connector_id text not null, status text not null, summary text not null, error_message text, exit_code integer, signal text, model text, session_display_id text, cost_usd real, unattributed_cost integer not null default 0, started_at text, finished_at text, log_path text, created_at text not null, updated_at text not null);
create table if not exists cost_entries (id text primary key, company_id text not null, run_id text not null, task_id text, agent_id text, connector_id text not null, amount_usd real, unattributed integer not null default 0, created_at text not null);
create table if not exists activity (id text primary key, company_id text not null, actor text not null, action text not null, entity_type text not null, entity_id text not null, detail text not null, created_at text not null);
create table if not exists secrets (id text primary key, company_id text not null, name text not null, description text not null, backend text not null, ciphertext blob not null, updated_at text not null, created_at text not null, unique(company_id, name));
`);
db.exec(`
create index if not exists idx_workspaces_company on workspaces(company_id);
create index if not exists idx_agents_company on agents(company_id);
create index if not exists idx_goals_company on goals(company_id);
create index if not exists idx_projects_company on projects(company_id);
create index if not exists idx_tasks_company on tasks(company_id);
create index if not exists idx_tasks_assignee on tasks(assignee_agent_id);
create index if not exists idx_tasks_project on tasks(project_id);
create index if not exists idx_approvals_company on approvals(company_id);
create index if not exists idx_runs_company on runs(company_id);
create index if not exists idx_runs_task on runs(task_id);
create index if not exists idx_runs_agent on runs(agent_id);
create index if not exists idx_cost_entries_company on cost_entries(company_id);
create index if not exists idx_cost_entries_run on cost_entries(run_id);
create index if not exists idx_activity_company on activity(company_id);
create index if not exists idx_tasks_company_status on tasks(company_id, status);
create index if not exists idx_agents_company_status on agents(company_id, status);
create index if not exists idx_agents_reports_to on agents(reports_to);
create index if not exists idx_runs_company_status on runs(company_id, status);
create index if not exists idx_approvals_company_state on approvals(company_id, state);
`);
db.exec(`
create table if not exists pending_wakes (id text primary key, company_id text not null, agent_id text not null, trigger_reason text not null, created_at text not null);
create index if not exists idx_pending_wakes_agent on pending_wakes(agent_id);
`);
db.exec(`
CREATE TABLE IF NOT EXISTS wakeup_requests (id TEXT PRIMARY KEY, company_id TEXT NOT NULL, agent_id TEXT NOT NULL, source TEXT NOT NULL, trigger_detail TEXT, reason TEXT, payload TEXT, status TEXT NOT NULL DEFAULT 'queued', coalesced_count INTEGER NOT NULL DEFAULT 0, requested_by_actor_type TEXT, requested_by_actor_id TEXT, idempotency_key TEXT, run_id TEXT, requested_at TEXT NOT NULL, claimed_at TEXT, finished_at TEXT, error TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS idx_wakeup_requests_agent ON wakeup_requests(agent_id, company_id, status);
`);
migrateSecretsTable(db, ra);
db.exec(`create index if not exists idx_secrets_company on secrets(company_id);`);
runMigrations(db, ra, tx);
ensureOptionalColumns(db);
db.exec(`
create index if not exists idx_agents_department on agents(company_id, department);
create index if not exists idx_runs_company_created_at on runs(company_id, created_at desc);
create index if not exists idx_cost_entries_company_created_at on cost_entries(company_id, created_at desc);
create index if not exists idx_activity_company_created_at on activity(company_id, created_at desc);
create index if not exists idx_agent_messages_company_created_at on agent_messages(company_id, created_at desc);
`);
seedSettings(db);
}
export function seedSettings(db: DatabaseSync) {
const getSetting = (key: string): string | null => {
const row = db.prepare("select value from settings where key = ?").get(key) as { value: string } | undefined;
return row?.value ?? null;
};
const setSetting = (key: string, value: string) => {
db.prepare("insert into settings (key, value) values (?, ?) on conflict(key) do update set value = excluded.value").run(key, value);
};
if (!getSetting("theme")) setSetting("theme", "system");
if (!getSetting("updates_enabled")) setSetting("updates_enabled", "false");
if (!getSetting("log_retention_days")) setSetting("log_retention_days", "90");
}
export function runMigrations(
db: DatabaseSync,
readAll: ReadAll,
transaction: Transaction,
) {
db.exec(`create table if not exists schema_migrations (version integer primary key, name text not null, applied_at text not null);`);
const applied = new Set(readAll<{ version: number }>("select version from schema_migrations").map((row) => row.version));
const deps = { db, readAll };
const migrations: Migration[] = [
...createMigrationsV1V15(deps),
...createMigrationsV16V25(deps),
...createMigrationsV26V34(deps),
...createMigrationsV35V37(deps),
...createMigrationsV38V39(deps),
...createMigrationsV40V40(deps),
...createMigrationsV41V41(deps),
...createMigrationsV42V42(deps),
...createMigrationsV43V43(deps),
...createMigrationsV44V44(deps),
];
for (const migration of migrations) {
if (applied.has(migration.version)) continue;
try {
transaction(() => {
if (migration.fn) {
migration.fn();
} else if (migration.sql) {
for (const stmt of migration.sql.split(";").map((s) => s.trim()).filter(Boolean)) {
db.prepare(stmt).run();
}
}
db.prepare("insert into schema_migrations (version, name, applied_at) values (?, ?, ?)").run(migration.version, migration.name, nowIso());
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Migration ${migration.version} (${migration.name}) failed: ${message}`);
}
}
}