/** Fork a tree root, record its starter, and admit the first message exactly once. */ import { bool, int, obj, opt, str } from '@earendil-works/pi-schema' import { defineTool, ROOT_CONVERSATION_ID } from '@earendil-works/pi-durable' import { ModelsApi } from '../models/api.ts' import { configureChild } from '../subagents/children.ts' import { LineageApi, Roots } from '../lineage/api.ts' import { imagesParameter } from './images.ts' import { send } from './send.ts' export const branchTool = (host: any) => defineTool({ name: 'branch', replay: 'safe', description: 'Fork a root for one task in an explicit repository. Its first answer returns to you. Worktree isolation requires workspaces.', parameters: obj({repo: str({minLength: 1}), prompt: str({minLength: 1}), title: opt(str({minLength: 1, maxLength: 60})), root: opt(int({minimum: 1})), isolated: opt(bool()), images: opt(imagesParameter)}), async execute(input, api, ctx) { let id = await api.memo('branch-id', ctx) if (id === undefined) { const lineage = host.need(LineageApi) const workspaces = input.isolated ? host.need({name:'workspaces'}) : undefined const roots = (await api.snapshot(Roots, ROOT_CONVERSATION_ID, ctx))?.roots ?? [] const root = input.root ?? await lineage.rootOf(api.conversationId, ctx) ?? (roots.length === 1 ? roots[0] : undefined) if (root === undefined || !roots.includes(root)) throw new Error('Choose a listed root') const {definitions} = await host.need(ModelsApi).discover(input.repo) const coordinator = definitions.find((definition: any) => definition.name === 'coordinator') if (!coordinator) throw new Error('Missing coordinator definition') const rootAgent = await (await host.harness().conversation(root, ctx))!.agent(ctx) const branch = await lineage.branch({from: root, repo: input.repo, prompt: input.prompt, title: input.title, deliver: false, by: api.conversationId, receipt: {taskId: api.taskId, callId: api.callId}}, ctx) const existing = await workspaces?.get(branch.id, ctx) const cwd = existing ? (await (await host.harness().conversation(branch.id, ctx)).agent(ctx)).cwd : input.repo const ancestors = workspaces ? (await workspaces.chain(branch.id, ctx)).slice(1) : [] await host.harness().commit(async (tx: any) => { await configureChild(host, tx, branch.id, {definition: coordinator, parentAgent: rootAgent, cwd}) if (workspaces && !existing) await workspaces.create(tx, branch.id, input.repo, ancestors) ;(await tx.doc(host.metadata, branch.id)).label = 'coordinator' }, ctx) id = await api.memo('branch-id', branch.id, ctx) } const message = await send(host, api.conversationId, {to: `#${id}`, text: input.prompt, reply: true, images: input.images, imagesFrom: api.conversationId}, ctx, {taskId: api.taskId, callId: api.callId}) return {output: [{type: 'text', text: `Branch #${id} started; message ${message} sent.`}]} }, })