feat(coder): re-key agent_sessions to (chat_id, agent) + worktrees table (P1.5-b)
The tab (a chat) is the context unit: two opencode tabs in one session are two independent agent contexts sharing one worktree. agent_sessions re-keys from (session_id, agent) to (chat_id, agent) — chat_id FK ON DELETE CASCADE (closing a tab ends its context); worktree_id and session_id become informational SET NULL columns. New worktrees table (one-per-session, survives session delete via session_id SET NULL) supersedes session_worktrees, which is defanged (CASCADE dropped) not yet removed. chat_id is threaded end-to-end: tasks.chat_id added, written by the coder message + skills routes from the frontend tab, read by runOpenCodeServerTask which falls back to resolve-or-create a chat for session-less creators (arena/MCP/new_task/generic) so ensureSession never gets a null key. Idempotent migration with a backfill-verify gate (0-row assertion after the test session was deleted). config_hash fingerprint logic preserved; one-worktree-per-session unchanged; runExternalAgent untouched. Column rename worktree_path -> path repointed at all five readers (server delete-guard, risk/stash endpoints, ensureSessionWorktree). Supersedes the earlier (worktree_id) draft. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -423,9 +423,12 @@ export class OpenCodeServerBackend implements AgentBackend {
|
||||
if (!this.client) throw new Error('opencode-server: client not ready after ensureServer');
|
||||
|
||||
const configHash = sessionConfigHash(opts.model);
|
||||
// P1.5-b: agent_sessions is keyed (chat_id, agent) — the tab/chat is the
|
||||
// context unit (two tabs in one session = two contexts sharing one worktree).
|
||||
// session_id + worktree_id are retained as informational (SET NULL) columns.
|
||||
const [row] = await this.sql<{ agent_session_id: string | null; status: string; config_hash: string | null }[]>`
|
||||
SELECT agent_session_id, status, config_hash FROM agent_sessions
|
||||
WHERE session_id = ${sessionId} AND agent = ${opts.agent}
|
||||
WHERE chat_id = ${opts.chatId} AND agent = ${opts.agent}
|
||||
`;
|
||||
let agentSessionId = row?.agent_session_id ?? null;
|
||||
|
||||
@@ -447,10 +450,12 @@ export class OpenCodeServerBackend implements AgentBackend {
|
||||
agentSessionId = created.data.id;
|
||||
await this.sql`
|
||||
INSERT INTO agent_sessions
|
||||
(session_id, agent, backend, agent_session_id, server_port, status, last_active_at, config_hash)
|
||||
(chat_id, session_id, worktree_id, agent, backend, agent_session_id, server_port, status, last_active_at, config_hash)
|
||||
VALUES
|
||||
(${sessionId}, ${opts.agent}, 'opencode_server', ${agentSessionId}, ${this.port}, 'active', clock_timestamp(), ${configHash})
|
||||
ON CONFLICT (session_id, agent) DO UPDATE SET
|
||||
(${opts.chatId}, ${sessionId}, ${opts.worktreeId}, ${opts.agent}, 'opencode_server', ${agentSessionId}, ${this.port}, 'active', clock_timestamp(), ${configHash})
|
||||
ON CONFLICT (chat_id, agent) DO UPDATE SET
|
||||
session_id = EXCLUDED.session_id,
|
||||
worktree_id = EXCLUDED.worktree_id,
|
||||
backend = 'opencode_server',
|
||||
agent_session_id = EXCLUDED.agent_session_id,
|
||||
server_port = EXCLUDED.server_port,
|
||||
@@ -462,7 +467,7 @@ export class OpenCodeServerBackend implements AgentBackend {
|
||||
await this.sql`
|
||||
UPDATE agent_sessions
|
||||
SET status = 'active', last_active_at = clock_timestamp(), server_port = ${this.port}, config_hash = ${configHash}
|
||||
WHERE session_id = ${sessionId} AND agent = ${opts.agent}
|
||||
WHERE chat_id = ${opts.chatId} AND agent = ${opts.agent}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -498,6 +503,8 @@ export class OpenCodeServerBackend implements AgentBackend {
|
||||
sessionId,
|
||||
agent: opts.agent,
|
||||
backend: 'opencode_server',
|
||||
chatId: opts.chatId,
|
||||
worktreeId: opts.worktreeId,
|
||||
agentSessionId: ocSessionId,
|
||||
serverPort: this.port,
|
||||
};
|
||||
@@ -593,7 +600,7 @@ export class OpenCodeServerBackend implements AgentBackend {
|
||||
}
|
||||
await this.sql`
|
||||
UPDATE agent_sessions SET status = 'closed'
|
||||
WHERE session_id = ${handle.sessionId} AND agent = ${handle.agent}
|
||||
WHERE chat_id = ${handle.chatId} AND agent = ${handle.agent}
|
||||
`.catch(() => {});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user