style: apply prettier formatting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-15 18:43:07 +03:00
parent 75c2fde2b5
commit 20a24dfd13
21 changed files with 75 additions and 231 deletions

View File

@@ -68,9 +68,7 @@ export async function sendSwapApprovalCard(
// Host-level swaps target the owner only. Group-level uses the normal
// approver ladder (scoped admin → global admin → owner).
const approvers = isHostLevel
? getOwners().map((r) => r.user_id)
: pickApprover(swap.originating_group_id);
const approvers = isHostLevel ? getOwners().map((r) => r.user_id) : pickApprover(swap.originating_group_id);
if (approvers.length === 0) {
notifyDevAgent(
@@ -85,7 +83,8 @@ export async function sendSwapApprovalCard(
// Origin channel kind drives tie-break preference (same as existing
// install_packages / request_rebuild approvals).
const originChannelType = originatingSession.messaging_group_id
? (await import('../db/messaging-groups.js')).getMessagingGroup(originatingSession.messaging_group_id)?.channel_type ?? ''
? ((await import('../db/messaging-groups.js')).getMessagingGroup(originatingSession.messaging_group_id)
?.channel_type ?? '')
: '';
const target = await pickApprovalDelivery(approvers, originChannelType);
@@ -230,9 +229,7 @@ async function sendSwapReviewMessages(
};
// 1. Intro message
const headerPrefix = isHostLevel
? '⚠️ ⚠️ ⚠️ **HOST-LEVEL CODE CHANGE PROPOSED**'
: '🔧 **Code change proposed**';
const headerPrefix = isHostLevel ? '⚠️ ⚠️ ⚠️ **HOST-LEVEL CODE CHANGE PROPOSED**' : '🔧 **Code change proposed**';
const intro =
`${headerPrefix} by agent "${originatingName}".\n\n` +
`**What it does:** ${summary.overallSummary || '(no summary)'}\n\n` +

View File

@@ -22,29 +22,19 @@ describe('classifyPath', () => {
const r = classifyPath('container/agent-runner/src/index.ts', OPTS);
expect(r).not.toBeNull();
expect(r!.classification).toBe('group');
expect(r!.target).toBe(
path.join('/repo/data/v2-sessions/grp-abc/agent-runner-src/index.ts'),
);
expect(r!.target).toBe(path.join('/repo/data/v2-sessions/grp-abc/agent-runner-src/index.ts'));
});
it('routes nested runner edits correctly', () => {
const r = classifyPath('container/agent-runner/src/mcp-tools/agents.ts', OPTS);
expect(r!.classification).toBe('group');
expect(r!.target).toBe(
path.join(
'/repo/data/v2-sessions/grp-abc/agent-runner-src/mcp-tools/agents.ts',
),
);
expect(r!.target).toBe(path.join('/repo/data/v2-sessions/grp-abc/agent-runner-src/mcp-tools/agents.ts'));
});
it('routes skills edits to the per-group private skills dir', () => {
const r = classifyPath('container/skills/browser/SKILL.md', OPTS);
expect(r!.classification).toBe('group');
expect(r!.target).toBe(
path.join(
'/repo/data/v2-sessions/grp-abc/.claude-shared/skills/browser/SKILL.md',
),
);
expect(r!.target).toBe(path.join('/repo/data/v2-sessions/grp-abc/.claude-shared/skills/browser/SKILL.md'));
});
it('routes originating group folder edits to their repo path', () => {
@@ -131,10 +121,7 @@ describe('isMigrationPath', () => {
describe('classifyDiff — overall classification', () => {
it('is "group" when all changes land in originating group targets', () => {
const d = classifyDiff(
['groups/main/CLAUDE.md', 'container/agent-runner/src/index.ts'],
OPTS,
);
const d = classifyDiff(['groups/main/CLAUDE.md', 'container/agent-runner/src/index.ts'], OPTS);
expect(d.overall).toBe('group');
expect(d.hostPaths).toHaveLength(0);
expect(d.runnerOrSkillsPaths).toHaveLength(1);
@@ -148,28 +135,19 @@ describe('classifyDiff — overall classification', () => {
});
it('is "combined" when host AND runner/skills are both changed', () => {
const d = classifyDiff(
['src/delivery.ts', 'container/agent-runner/src/poll-loop.ts'],
OPTS,
);
const d = classifyDiff(['src/delivery.ts', 'container/agent-runner/src/poll-loop.ts'], OPTS);
expect(d.overall).toBe('combined');
expect(d.hostPaths).toHaveLength(1);
expect(d.runnerOrSkillsPaths).toHaveLength(1);
});
it('is "combined" for host + skills change', () => {
const d = classifyDiff(
['Dockerfile', 'container/skills/browser/SKILL.md'],
OPTS,
);
const d = classifyDiff(['Dockerfile', 'container/skills/browser/SKILL.md'], OPTS);
expect(d.overall).toBe('combined');
});
it('flags migrations regardless of other paths', () => {
const d = classifyDiff(
['src/db/migrations/007-new.ts', 'src/delivery.ts'],
OPTS,
);
const d = classifyDiff(['src/db/migrations/007-new.ts', 'src/delivery.ts'], OPTS);
expect(d.touchesMigrations).toBe(true);
expect(d.overall).toBe('host');
});
@@ -180,9 +158,7 @@ describe('classifyDiff — overall classification', () => {
});
it('throws on excluded paths in the diff', () => {
expect(() => classifyDiff(['.env'], OPTS)).toThrow(
/unreachable or excluded path/,
);
expect(() => classifyDiff(['.env'], OPTS)).toThrow(/unreachable or excluded path/);
});
it('throws on data/ paths in the diff', () => {
@@ -190,13 +166,7 @@ describe('classifyDiff — overall classification', () => {
});
it('preserves original paths in output files', () => {
const d = classifyDiff(
['groups/main/CLAUDE.md', 'src/delivery.ts'],
OPTS,
);
expect(d.files.map((f) => f.path)).toEqual([
'groups/main/CLAUDE.md',
'src/delivery.ts',
]);
const d = classifyDiff(['groups/main/CLAUDE.md', 'src/delivery.ts'], OPTS);
expect(d.files.map((f) => f.path)).toEqual(['groups/main/CLAUDE.md', 'src/delivery.ts']);
});
});

View File

@@ -98,13 +98,7 @@ export function classifyPath(
const rel = norm.slice(runnerPrefix.length);
return {
classification: 'group',
target: path.join(
opts.dataDir,
'v2-sessions',
opts.originatingGroupId,
'agent-runner-src',
rel,
),
target: path.join(opts.dataDir, 'v2-sessions', opts.originatingGroupId, 'agent-runner-src', rel),
};
}
@@ -114,14 +108,7 @@ export function classifyPath(
const rel = norm.slice(skillsPrefix.length);
return {
classification: 'group',
target: path.join(
opts.dataDir,
'v2-sessions',
opts.originatingGroupId,
'.claude-shared',
'skills',
rel,
),
target: path.join(opts.dataDir, 'v2-sessions', opts.originatingGroupId, '.claude-shared', 'skills', rel),
};
}
@@ -146,10 +133,7 @@ export function classifyPath(
/** True iff a classified file's worktree path is under runner or skills template. */
export function isRunnerOrSkillsPath(relPath: string): boolean {
const norm = relPath.replace(/\\/g, '/');
return (
norm.startsWith('container/agent-runner/src/') ||
norm.startsWith('container/skills/')
);
return norm.startsWith('container/agent-runner/src/') || norm.startsWith('container/skills/');
}
/** True iff a changed path is a schema migration. */
@@ -168,9 +152,7 @@ export function classifyDiff(changedPaths: string[], opts: ClassifyOptions): Cla
for (const p of changedPaths) {
const result = classifyPath(p, opts);
if (!result) {
throw new Error(
`builder-agent: diff contains unreachable or excluded path: ${p}`,
);
throw new Error(`builder-agent: diff contains unreachable or excluded path: ${p}`);
}
files.push({
path: p,

View File

@@ -34,12 +34,7 @@ import { log } from '../log.js';
import type { PendingSwap } from '../types.js';
import { maybeSendPromotePrompt } from './promote.js';
import { removeDevWorktree } from './worktree.js';
import {
isHostLevelSwap,
parseSwapSummary,
restoreDbFromSnapshot,
rollbackSwapFiles,
} from './swap.js';
import { isHostLevelSwap, parseSwapSummary, restoreDbFromSnapshot, rollbackSwapFiles } from './swap.js';
const DEADMAN_INITIAL_MS = 2 * 60 * 1000;
const DEADMAN_HARD_CAP_MS = 10 * 60 * 1000;

View File

@@ -272,10 +272,7 @@ export async function handleCreateDevAgent(
} catch {
/* best effort */
}
notifyAgent(
session,
`create_dev_agent failed: ${err instanceof Error ? err.message : String(err)}`,
);
notifyAgent(session, `create_dev_agent failed: ${err instanceof Error ? err.message : String(err)}`);
return;
}
@@ -355,10 +352,7 @@ export async function handleRequestSwap(
originatingGroupFolder: getAgentGroup(pending.originating_group_id)?.folder ?? '',
});
} catch (err) {
notifyAgent(
session,
`Code change submission failed: ${err instanceof Error ? err.message : String(err)}`,
);
notifyAgent(session, `Code change submission failed: ${err instanceof Error ? err.message : String(err)}`);
return;
}

View File

@@ -38,9 +38,7 @@ describe('swapTouchedRunnerOrSkills', () => {
});
it('is true when container/agent-runner/src is touched', () => {
const swap = makeSwap([
{ path: 'container/agent-runner/src/poll-loop.ts', classification: 'group' },
]);
const swap = makeSwap([{ path: 'container/agent-runner/src/poll-loop.ts', classification: 'group' }]);
expect(swapTouchedRunnerOrSkills(swap)).toBe(true);
});
@@ -76,16 +74,12 @@ describe('sourceForTemplate', () => {
it('maps nested runner paths correctly', () => {
const src = sourceForTemplate('container/agent-runner/src/mcp-tools/agents.ts', 'ag-abc');
expect(src).toBe(
path.join(DATA_DIR, 'v2-sessions', 'ag-abc', 'agent-runner-src', 'mcp-tools', 'agents.ts'),
);
expect(src).toBe(path.join(DATA_DIR, 'v2-sessions', 'ag-abc', 'agent-runner-src', 'mcp-tools', 'agents.ts'));
});
it('maps skills template paths to the per-group skills dir', () => {
const src = sourceForTemplate('container/skills/browser/SKILL.md', 'ag-abc');
expect(src).toBe(
path.join(DATA_DIR, 'v2-sessions', 'ag-abc', '.claude-shared', 'skills', 'browser', 'SKILL.md'),
);
expect(src).toBe(path.join(DATA_DIR, 'v2-sessions', 'ag-abc', '.claude-shared', 'skills', 'browser', 'SKILL.md'));
});
});

View File

@@ -57,9 +57,7 @@ export function setPromoteDelivery(adapter: PromoteDelivery): void {
export function swapTouchedRunnerOrSkills(swap: PendingSwap): boolean {
const summary = parseSwapSummary(swap);
return summary.classifiedFiles.some(
(f) =>
f.path.startsWith('container/agent-runner/src/') ||
f.path.startsWith('container/skills/'),
(f) => f.path.startsWith('container/agent-runner/src/') || f.path.startsWith('container/skills/'),
);
}
@@ -77,9 +75,7 @@ export async function maybeSendPromotePrompt(swap: PendingSwap): Promise<void> {
}
const isHostLevel = swap.classification === 'host' || swap.classification === 'combined';
const approvers = isHostLevel
? getOwners().map((r) => r.user_id)
: pickApprover(swap.originating_group_id);
const approvers = isHostLevel ? getOwners().map((r) => r.user_id) : pickApprover(swap.originating_group_id);
if (approvers.length === 0) {
log.info('Skipping promote prompt: no approvers configured', { requestId: swap.request_id });
@@ -187,9 +183,7 @@ async function applyToTemplate(swapRequestId: string): Promise<void> {
const summary = parseSwapSummary(swap);
const runnerOrSkills = summary.classifiedFiles.filter(
(f) =>
f.path.startsWith('container/agent-runner/src/') ||
f.path.startsWith('container/skills/'),
(f) => f.path.startsWith('container/agent-runner/src/') || f.path.startsWith('container/skills/'),
);
if (runnerOrSkills.length === 0) return;

View File

@@ -19,10 +19,7 @@
import fs from 'fs';
import path from 'path';
import {
getAwaitingConfirmationSwaps,
getPendingSwap,
} from '../db/pending-swaps.js';
import { getAwaitingConfirmationSwaps, getPendingSwap } from '../db/pending-swaps.js';
import { log } from '../log.js';
import { resumeDeadman } from './deadman.js';
import { removeDevWorktree } from './worktree.js';
@@ -59,10 +56,7 @@ function cleanupOrphanWorktrees(): void {
// Orphaned if: no row, or row in a terminal state.
const terminal =
!swap ||
swap.status === 'finalized' ||
swap.status === 'rolled_back' ||
swap.status === 'rejected';
!swap || swap.status === 'finalized' || swap.status === 'rolled_back' || swap.status === 'rejected';
if (terminal) {
log.info('Cleaning up orphan worktree', { requestId, status: swap?.status ?? 'missing' });

View File

@@ -2,12 +2,7 @@ import path from 'path';
import { describe, expect, it } from 'vitest';
import {
isHostLevelSwap,
parseSwapSummary,
requiresFullHostRebuild,
targetRepoRelPath,
} from './swap.js';
import { isHostLevelSwap, parseSwapSummary, requiresFullHostRebuild, targetRepoRelPath } from './swap.js';
import type { PendingSwap } from '../types.js';
function makeSwap(overrides: Partial<PendingSwap> = {}): PendingSwap {
@@ -99,14 +94,10 @@ describe('requiresFullHostRebuild', () => {
expect(requiresFullHostRebuild([abs('groups/main/CLAUDE.md')])).toBe(false);
});
it('does not flag per-group runner dir changes', () => {
expect(
requiresFullHostRebuild([abs('data/v2-sessions/ag-1/agent-runner-src/poll-loop.ts')]),
).toBe(false);
expect(requiresFullHostRebuild([abs('data/v2-sessions/ag-1/agent-runner-src/poll-loop.ts')])).toBe(false);
});
it('returns true if any path requires rebuild even if others do not', () => {
expect(
requiresFullHostRebuild([abs('groups/main/CLAUDE.md'), abs('src/delivery.ts')]),
).toBe(true);
expect(requiresFullHostRebuild([abs('groups/main/CLAUDE.md'), abs('src/delivery.ts')])).toBe(true);
});
});
@@ -118,9 +109,9 @@ describe('targetRepoRelPath', () => {
});
it('maps nested runner paths correctly', () => {
expect(
targetRepoRelPath('container/agent-runner/src/mcp-tools/agents.ts', 'ag-abc'),
).toBe('data/v2-sessions/ag-abc/agent-runner-src/mcp-tools/agents.ts');
expect(targetRepoRelPath('container/agent-runner/src/mcp-tools/agents.ts', 'ag-abc')).toBe(
'data/v2-sessions/ag-abc/agent-runner-src/mcp-tools/agents.ts',
);
});
it('maps skills paths to the per-group skills dir', () => {
@@ -136,8 +127,8 @@ describe('targetRepoRelPath', () => {
});
it('handles Windows-style separators by normalizing', () => {
expect(
targetRepoRelPath('container\\agent-runner\\src\\index.ts', 'ag-abc'),
).toBe('data/v2-sessions/ag-abc/agent-runner-src/index.ts');
expect(targetRepoRelPath('container\\agent-runner\\src\\index.ts', 'ag-abc')).toBe(
'data/v2-sessions/ag-abc/agent-runner-src/index.ts',
);
});
});

View File

@@ -18,11 +18,7 @@ import path from 'path';
import { DATA_DIR } from '../config.js';
import { getAgentGroup } from '../db/agent-groups.js';
import { getDb } from '../db/connection.js';
import {
getPendingSwap,
resetSwapForRetry,
setSwapPreSwapState,
} from '../db/pending-swaps.js';
import { getPendingSwap, resetSwapForRetry, setSwapPreSwapState } from '../db/pending-swaps.js';
import { log } from '../log.js';
import type { PendingSwap } from '../types.js';
import { classifyDiff, type ClassifiedFile } from './classifier.js';
@@ -132,10 +128,7 @@ export function applySwapFiles(requestId: string): string[] {
// Enumerate every path that changed in the reviewed commit relative
// to main. Pairs each path with its A/M/D status. --no-renames keeps
// the parsing simple (a rename shows up as D+A).
const nameStatus = git(
['diff', '--name-status', '--no-renames', `main..${swap.commit_sha}`],
worktreePath,
);
const nameStatus = git(['diff', '--name-status', '--no-renames', `main..${swap.commit_sha}`], worktreePath);
const changes: Array<{ status: 'A' | 'M' | 'D'; path: string }> = [];
for (const line of nameStatus.split('\n')) {
@@ -158,9 +151,7 @@ export function applySwapFiles(requestId: string): string[] {
},
);
const statusByPath = new Map<string, 'A' | 'M' | 'D'>(
changes.map((c) => [c.path, c.status]),
);
const statusByPath = new Map<string, 'A' | 'M' | 'D'>(changes.map((c) => [c.path, c.status]));
const touchedAbs: string[] = [];
for (const file of classified.files) {
@@ -182,9 +173,7 @@ export function applySwapFiles(requestId: string): string[] {
});
} catch (err) {
throw new Error(
`git show ${swap.commit_sha}:${file.path} failed: ${
err instanceof Error ? err.message : String(err)
}`,
`git show ${swap.commit_sha}:${file.path} failed: ${err instanceof Error ? err.message : String(err)}`,
);
}
const dir = path.dirname(dst);
@@ -269,10 +258,7 @@ export function rollbackSwapFiles(swap: PendingSwap): void {
// Record a forward-only revert commit so main's history shows what reverted.
try {
git(['add', '--', ...relPaths], PROJECT_ROOT);
git(
['commit', '-m', `rollback ${swap.request_id}: deadman timeout`, '--', ...relPaths],
PROJECT_ROOT,
);
git(['commit', '-m', `rollback ${swap.request_id}: deadman timeout`, '--', ...relPaths], PROJECT_ROOT);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (!(msg.includes('nothing to commit') || msg.includes('no changes added'))) {
@@ -291,10 +277,7 @@ export function rollbackSwapFiles(swap: PendingSwap): void {
*
* Exported so tests can lock the mapping against the classifier's rules.
*/
export function targetRepoRelPath(
worktreeRelPath: string,
originatingGroupId: string,
): string {
export function targetRepoRelPath(worktreeRelPath: string, originatingGroupId: string): string {
const norm = worktreeRelPath.replace(/\\/g, '/');
if (norm.startsWith('container/agent-runner/src/')) {
const rel = norm.slice('container/agent-runner/src/'.length);
@@ -302,14 +285,7 @@ export function targetRepoRelPath(
}
if (norm.startsWith('container/skills/')) {
const rel = norm.slice('container/skills/'.length);
return path.posix.join(
'data',
'v2-sessions',
originatingGroupId,
'.claude-shared',
'skills',
rel,
);
return path.posix.join('data', 'v2-sessions', originatingGroupId, '.claude-shared', 'skills', rel);
}
return norm;
}

View File

@@ -71,9 +71,7 @@ export function assertGitCleanEnoughForSwap(): void {
const rebaseDir = path.join(gitDir, 'rebase-merge');
const rebaseApply = path.join(gitDir, 'rebase-apply');
if (fs.existsSync(rebaseDir) || fs.existsSync(rebaseApply)) {
throw new Error(
'cannot start swap: git repo is mid-rebase. resolve it in the terminal first.',
);
throw new Error('cannot start swap: git repo is mid-rebase. resolve it in the terminal first.');
}
}
@@ -86,10 +84,7 @@ export function assertGitCleanEnoughForSwap(): void {
* or crash), it is removed first via `git worktree remove --force` so the
* creation is clean.
*/
export function createDevWorktree(
requestId: string,
originatingGroupId: string,
): string {
export function createDevWorktree(requestId: string, originatingGroupId: string): string {
assertGitCleanEnoughForSwap();
if (!fs.existsSync(WORKTREES_DIR)) {
@@ -128,14 +123,8 @@ export function createDevWorktree(
// what the originating group is actually running, not the pristine
// template.
const sessDir = path.join(DATA_DIR, 'v2-sessions', originatingGroupId);
overlayDir(
path.join(sessDir, 'agent-runner-src'),
path.join(worktreePath, 'container', 'agent-runner', 'src'),
);
overlayDir(
path.join(sessDir, '.claude-shared', 'skills'),
path.join(worktreePath, 'container', 'skills'),
);
overlayDir(path.join(sessDir, 'agent-runner-src'), path.join(worktreePath, 'container', 'agent-runner', 'src'));
overlayDir(path.join(sessDir, '.claude-shared', 'skills'), path.join(worktreePath, 'container', 'skills'));
// Shadow the .env with an empty placeholder so the dev agent can't read
// credentials from a committed-but-gitignored file if one snuck into the

View File

@@ -95,10 +95,7 @@ export function writeContainerConfig(folder: string, config: ContainerConfig): v
* result. Convenient for append-style changes like `install_packages` and
* `add_mcp_server` handlers.
*/
export function updateContainerConfig(
folder: string,
mutate: (config: ContainerConfig) => void,
): ContainerConfig {
export function updateContainerConfig(folder: string, mutate: (config: ContainerConfig) => void): ContainerConfig {
const config = readContainerConfig(folder);
mutate(config);
writeContainerConfig(folder, config);

View File

@@ -105,9 +105,7 @@ export function deleteDestination(agentGroupId: string, localName: string): void
*/
export function deleteAllDestinationsTouching(agentGroupId: string): void {
getDb()
.prepare(
'DELETE FROM agent_destinations WHERE agent_group_id = ? OR (target_type = ? AND target_id = ?)',
)
.prepare('DELETE FROM agent_destinations WHERE agent_group_id = ? OR (target_type = ? AND target_id = ?)')
.run(agentGroupId, 'agent', agentGroupId);
}

View File

@@ -22,10 +22,7 @@ export function getAllAgentGroups(): AgentGroup[] {
return getDb().prepare('SELECT * FROM agent_groups ORDER BY name').all() as AgentGroup[];
}
export function updateAgentGroup(
id: string,
updates: Partial<Pick<AgentGroup, 'name' | 'agent_provider'>>,
): void {
export function updateAgentGroup(id: string, updates: Partial<Pick<AgentGroup, 'name' | 'agent_provider'>>): void {
const fields: string[] = [];
const values: Record<string, unknown> = { id };

View File

@@ -31,13 +31,7 @@ export const migration007: Migration = {
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 '[]'`,
);
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

@@ -189,7 +189,9 @@ describe('pending-swaps bulk lookups', () => {
createPendingSwap(makeSwap({ request_id: 'req-t3', status: 'rejected' }));
createPendingSwap(makeSwap({ request_id: 'req-active', status: 'awaiting_confirmation' }));
const terminal = getTerminalSwaps().map((s) => s.request_id).sort();
const terminal = getTerminalSwaps()
.map((s) => s.request_id)
.sort();
expect(terminal).toEqual(['req-t1', 'req-t2', 'req-t3']);
});
});

View File

@@ -18,9 +18,7 @@ export function createPendingSwap(swap: PendingSwap): void {
}
export function getPendingSwap(requestId: string): PendingSwap | undefined {
return getDb().prepare('SELECT * FROM pending_swaps WHERE request_id = ?').get(requestId) as
| PendingSwap
| undefined;
return getDb().prepare('SELECT * FROM pending_swaps WHERE request_id = ?').get(requestId) as PendingSwap | undefined;
}
/**
@@ -60,9 +58,7 @@ export function getSwapForDevAgent(devAgentId: string): PendingSwap | undefined
* unexpected for group-level crashes).
*/
export function getAwaitingConfirmationSwaps(): PendingSwap[] {
return getDb()
.prepare(`SELECT * FROM pending_swaps WHERE status = 'awaiting_confirmation'`)
.all() as PendingSwap[];
return getDb().prepare(`SELECT * FROM pending_swaps WHERE status = 'awaiting_confirmation'`).all() as PendingSwap[];
}
/** All terminal-status swaps — used by the startup worktree-orphan sweep. */
@@ -76,11 +72,7 @@ export function updatePendingSwapStatus(requestId: string, status: SwapStatus):
getDb().prepare('UPDATE pending_swaps SET status = ? WHERE request_id = ?').run(status, requestId);
}
export function setSwapPreSwapState(
requestId: string,
preSwapSha: string,
dbSnapshotPath: string,
): void {
export function setSwapPreSwapState(requestId: string, preSwapSha: string, dbSnapshotPath: string): void {
getDb()
.prepare(
`UPDATE pending_swaps
@@ -109,17 +101,11 @@ export function startSwapDeadman(
}
export function extendSwapDeadman(requestId: string, expiresAt: string): void {
getDb().prepare('UPDATE pending_swaps SET deadman_expires_at = ? WHERE request_id = ?').run(
expiresAt,
requestId,
);
getDb().prepare('UPDATE pending_swaps SET deadman_expires_at = ? WHERE request_id = ?').run(expiresAt, requestId);
}
export function setSwapHandshakeState(requestId: string, state: SwapHandshakeState): void {
getDb().prepare('UPDATE pending_swaps SET handshake_state = ? WHERE request_id = ?').run(
state,
requestId,
);
getDb().prepare('UPDATE pending_swaps SET handshake_state = ? WHERE request_id = ?').run(state, requestId);
}
export function deletePendingSwap(requestId: string): void {

View File

@@ -506,7 +506,9 @@ async function handleSwapRequestApproval(
await startDeadman(swapRequestId);
if (isHostLevelSwap(swap)) {
notifyDev('Code change applied and committed. Triggering host restart so the new code takes effect. Awaiting user confirmation after restart.');
notifyDev(
'Code change applied and committed. Triggering host restart so the new code takes effect. Awaiting user confirmation after restart.',
);
log.warn('Host-level swap triggering process exit for supervisor respawn', {
requestId: swapRequestId,
});
@@ -520,7 +522,9 @@ async function handleSwapRequestApproval(
if (originatingSession) {
killContainer(originatingSession.id, 'swap applied');
}
notifyDev('Code change applied and committed. The originating agent will restart on its next message. Awaiting user confirmation.');
notifyDev(
'Code change applied and committed. The originating agent will restart on its next message. Awaiting user confirmation.',
);
}
} catch (err) {
const errMsg = err instanceof Error ? err.message : String(err);

View File

@@ -155,13 +155,7 @@ export async function routeInbound(event: InboundEvent): Promise<void> {
// as soon as the agent goes idle — not when the container eventually
// exits. Container-runner also calls stopTypingRefresh on exit as a
// fast-path cleanup.
startTypingRefresh(
session.id,
session.agent_group_id,
event.channelType,
event.platformId,
event.threadId,
);
startTypingRefresh(session.id, session.agent_group_id, event.channelType, event.platformId, event.threadId);
// 8. Wake container
const freshSession = getSession(session.id);
@@ -206,9 +200,10 @@ function extractAndUpsertUser(event: InboundEvent): string | null {
// `senderId` or `sender` directly at the top level. Check all three.
const senderIdField = typeof content.senderId === 'string' ? content.senderId : undefined;
const senderField = typeof content.sender === 'string' ? content.sender : undefined;
const author = typeof content.author === 'object' && content.author !== null
? (content.author as Record<string, unknown>)
: undefined;
const author =
typeof content.author === 'object' && content.author !== null
? (content.author as Record<string, unknown>)
: undefined;
const authorUserId = typeof author?.userId === 'string' ? (author.userId as string) : undefined;
const senderName =
(typeof content.senderName === 'string' ? content.senderName : undefined) ??

View File

@@ -189,12 +189,7 @@ export type SwapClassification = 'group' | 'host' | 'combined';
* pending_approval → awaiting_confirmation → (finalized | rolled_back | rejected)
* `rejected` is also reachable directly from pending_approval.
*/
export type SwapStatus =
| 'pending_approval'
| 'awaiting_confirmation'
| 'finalized'
| 'rolled_back'
| 'rejected';
export type SwapStatus = 'pending_approval' | 'awaiting_confirmation' | 'finalized' | 'rolled_back' | 'rejected';
/**
* Deadman handshake state — only meaningful while status = awaiting_confirmation.