feat(v2): builder-agent self-modification WIP + container-config as per-group file

Checkpoints the builder-agent dev-agent/worktree/swap flow (create_dev_agent,
request_swap, classifier, deadman, promote) before pivoting to a unified
draft-activate approach with OS-level RO enforcement. Lifts container_config
out of the agent_groups row into groups/<folder>/container.json so install_packages,
add_mcp_server, and rebuild flows can eventually route through the same draft
path as source edits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-15 18:42:10 +03:00
parent c54c779834
commit 75c2fde2b5
48 changed files with 4385 additions and 134 deletions

View File

@@ -12,7 +12,6 @@ export const migration001: Migration = {
name TEXT NOT NULL,
folder TEXT NOT NULL UNIQUE,
agent_provider TEXT,
container_config TEXT,
created_at TEXT NOT NULL
);

View File

@@ -0,0 +1,44 @@
import type { Migration } from './index.js';
/**
* `pending_swaps` — backs the builder-agent self-modification flow. One row
* per in-flight swap request from a dev agent. Everything swap-lifecycle fits
* on one row: approval state, classification, pre-swap git SHA for rollback,
* DB snapshot path, deadman timer, handshake state.
*
* Status transitions: pending_approval → awaiting_confirmation →
* (finalized | rolled_back | rejected).
*
* Handshake state (only meaningful while status = awaiting_confirmation):
* pending_restart → message1_sent → confirmed | rolled_back.
*/
export const migration006: Migration = {
version: 6,
name: 'pending-swaps',
up(db) {
db.exec(`
CREATE TABLE pending_swaps (
request_id TEXT PRIMARY KEY,
dev_agent_id TEXT NOT NULL REFERENCES agent_groups(id),
originating_group_id TEXT NOT NULL REFERENCES agent_groups(id),
dev_branch TEXT NOT NULL,
commit_sha TEXT NOT NULL,
classification TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending_approval',
summary_json TEXT NOT NULL,
pre_swap_sha TEXT,
db_snapshot_path TEXT,
deadman_started_at TEXT,
deadman_expires_at TEXT,
handshake_state TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX idx_pending_swaps_originating_status
ON pending_swaps(originating_group_id, status);
CREATE INDEX idx_pending_swaps_status
ON pending_swaps(status);
`);
},
};

View File

@@ -0,0 +1,43 @@
import type { Migration } from './index.js';
/**
* Retroactive schema fix: earlier migration 003 was edited after it had
* already been applied in the wild, adding `title` and `options_json`
* columns to its CREATE TABLE statement. Installs that ran 003 before the
* edit don't have those columns, and `createPendingApproval` (which
* inserts into both) fails with "no such column" at runtime.
*
* This migration adds the missing columns via ALTER TABLE so old installs
* catch up. On a fresh install that runs 003 at its current definition,
* the ALTER statements will fail harmlessly (column already exists) and
* we swallow the error per-column.
*/
export const migration007: Migration = {
version: 7,
name: 'pending-approvals-title-options',
up(db) {
const addIfMissing = (col: string, sql: string): void => {
try {
db.exec(sql);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (msg.includes('duplicate column') || msg.includes('already exists')) {
// Fresh install — column already added by the current 003
// definition. Nothing to do.
return;
}
throw err;
}
void col;
};
addIfMissing(
'title',
`ALTER TABLE pending_approvals ADD COLUMN title TEXT NOT NULL DEFAULT ''`,
);
addIfMissing(
'options_json',
`ALTER TABLE pending_approvals ADD COLUMN options_json TEXT NOT NULL DEFAULT '[]'`,
);
},
};

View File

@@ -6,6 +6,8 @@ import { migration002 } from './002-chat-sdk-state.js';
import { migration003 } from './003-pending-approvals.js';
import { migration004 } from './004-agent-destinations.js';
import { migration005 } from './005-pending-credentials.js';
import { migration006 } from './006-pending-swaps.js';
import { migration007 } from './007-pending-approvals-title-options.js';
export interface Migration {
version: number;
@@ -13,7 +15,15 @@ export interface Migration {
up: (db: Database.Database) => void;
}
const migrations: Migration[] = [migration001, migration002, migration003, migration004, migration005];
const migrations: Migration[] = [
migration001,
migration002,
migration003,
migration004,
migration005,
migration006,
migration007,
];
export function runMigrations(db: Database.Database): void {
db.exec(`