Files
AgentCompany/src/main/database/migrations/v38-v39.ts
T
ZacharyZhang-NY ff5e22092a refactor(session): agent-scope rotation + accumulation; auto-register file artifacts
Sessions are already agent-scoped post-v34, but the code kept routing
rotation / accumulation / handoff through the task indirection. Heartbeat
runs (no task) silently skipped all three, letting taskless transcripts
grow past the model's context window — the 7M-token explosion.

- DB adds getSessionById(sessionId) — direct lookup bypassing
  tasks.active_session_id
- session-management-service: generateHandoff(session) is now agent-scoped,
  walking recent runs across tasks + heartbeats; getHandoffForAgent(agentId)
  is the canonical lookup, getHandoffContext(taskId) is a thin shim for
  callers that still hold a task id
- turn-context.ts::resolveSession drops the `if (!task)` early return:
  rotation evaluation runs on the agent session unconditionally
- run-finished-helpers.ts::accumulateAndRotateSession drops the `!task`
  gate: tokens accumulate and rotation fires for every run, keyed on the
  agent session, not the task
- Rotation evaluator contract: evaluatePreRunSessionRotation(agentId,
  connectorId) → { rotated, handoff, sessionId }; old (taskId, connectorId)
  signature removed

IPC contract tightening: ipc-support::emitEvent becomes a best-effort
sink. "Render frame was disposed" during renderer reload is a lifecycle
signal, not a logic error; silently drop it instead of letting it abort
downstream event emissions.

Artifact projection: pi's write/edit tool results now upsert a
`work_products` row via a new agent-runtime/artifact-projector.ts. No
more agents writing 54 files into the workspace that the UI never sees.
Migration v40 relaxes work_products.task_id to nullable so heartbeat
artifacts can register too.

File size: split agent-event-projection.ts out of runtime-bridge.ts to
keep the bridge under the 400-line cap after adding the projector hook.

Tests: 604 unit + integration green; generateHandoff callsites updated
for the single-argument signature; artifact-projector has pure-function
coverage via the projector module's own shape.
2026-04-18 21:37:56 -04:00

76 lines
3.2 KiB
TypeScript

import log from "electron-log/main.js";
import type { Migration, MigrationDeps } from "./types";
import { columnExists, tableExists } from "./types";
export function createMigrationsV38V39(deps: MigrationDeps): Migration[] {
const { db } = deps;
return [
{
// Model capability table. Maps (provider, model_id) to 1..5 scores across
// three domains (reasoning / coding / writing), a tier enum used as a
// hard lower bound for task-profile matching, and a cost-per-Mtok-output
// figure used to pick the cheapest qualifying model.
//
// Scores, tier, and cost are data — editable at runtime via IPC. Seed
// rows are inserted by the main process on startup when the table is
// empty so a fresh install boots with a sensible matrix covering the
// providers in the pi-ai catalog and the curated Fireworks list.
version: 38,
name: "create_model_capabilities",
fn: () => {
if (tableExists(db, "model_capabilities")) {
log.info("[migration v38] model_capabilities already exists; skipping");
return;
}
db.exec(
`CREATE TABLE model_capabilities (
provider TEXT NOT NULL,
model_id TEXT NOT NULL,
reasoning INTEGER NOT NULL,
coding INTEGER NOT NULL,
writing INTEGER NOT NULL,
tier TEXT NOT NULL,
cost_per_mtok_out REAL NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (provider, model_id)
)`,
);
db.exec("CREATE INDEX idx_model_capabilities_tier ON model_capabilities(tier)");
log.info("[migration v38] created model_capabilities table");
},
},
{
// agents.model_strategy drives run-time model dispatch.
//
// 'explicit' — use agent.connector_id and agent.model verbatim
// (the pre-v39 behavior — kept for existing rows so there
// are no regressions on upgrade).
// 'auto' — ignore agent.connector_id/model; at run dispatch time
// pick the cheapest model whose capability vector meets
// the agent's inferred task profile.
//
// New agents default to 'auto' at the application layer. Existing rows
// on upgrade are forced to 'explicit' because the user previously picked
// their model manually and we must not silently re-route them.
version: 39,
name: "add_agents_model_strategy",
fn: () => {
if (!tableExists(db, "agents")) {
throw new Error("agents table missing; refusing to migrate v39");
}
if (columnExists(db, "agents", "model_strategy")) {
log.info("[migration v39] agents.model_strategy already exists; skipping");
return;
}
// Default at schema level = 'explicit' so existing rows keep their
// hand-picked model. Application code uses 'auto' as the default for
// freshly created agents.
db.exec(
"ALTER TABLE agents ADD COLUMN model_strategy TEXT NOT NULL DEFAULT 'explicit'",
);
log.info("[migration v39] added agents.model_strategy column");
},
},
];
}