bazilion 0.14.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1752 -825
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +16624 -9803
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +218 -3
- package/dist/web/client/assets/ChatPane-CQZzxOir.js +76 -0
- package/dist/web/client/assets/{ConfirmDialog-_bmG-wVG.js → ConfirmDialog-MicTTwdy.js} +1 -1
- package/dist/web/client/assets/{FieldRow-Do1cBkRc.js → FieldRow-DJ2Gx4Du.js} +1 -1
- package/dist/web/client/assets/{ProductionTeamPolicyEditor-XP-QJul4.js → ProductionTeamPolicyEditor-DqUnN_ce.js} +1 -1
- package/dist/web/client/assets/{ProfileCommunicationEditor-CwdFPdpk.js → ProfileCommunicationEditor-DArXub0b.js} +1 -1
- package/dist/web/client/assets/ResultCard-Cps_lXVN.js +1 -0
- package/dist/web/client/assets/ResultSourceConversation-CtzJ8QA-.js +1 -0
- package/dist/web/client/assets/TeamTabs-DbIk5vvT.js +1 -0
- package/dist/web/client/assets/{TemplatesTabs-D4WHvs6b.js → TemplatesTabs-CatJIjGi.js} +1 -1
- package/dist/web/client/assets/{UnsavedChangesGuard-B1aLqiK5.js → UnsavedChangesGuard-BCsLw-mU.js} +1 -1
- package/dist/web/client/assets/_id-B5Ov7cdO.js +1 -0
- package/dist/web/client/assets/{_id-BH3v_vI_.js → _id-BEDmIXVf.js} +1 -1
- package/dist/web/client/assets/_id-BFM_tn_s.js +1 -0
- package/dist/web/client/assets/_id-CrnEVcw8.js +1 -0
- package/dist/web/client/assets/{_id-3w4PFfma.js → _id-DxopIW-y.js} +1 -1
- package/dist/web/client/assets/{activity-A_HrG1Tv.js → activity-CrG8Bg60.js} +1 -1
- package/dist/web/client/assets/{agents-C8OVKq3N.js → agents-CMPOFcs-.js} +1 -1
- package/dist/web/client/assets/{agents-BeeFCt2c.js → agents-O4soKZND.js} +1 -1
- package/dist/web/client/assets/{approvals-BIdZ9BSn.js → approvals-CwDu_lrA.js} +1 -1
- package/dist/web/client/assets/{attention-B9bUrxiU.js → attention-BY8MMuM_.js} +1 -1
- package/dist/web/client/assets/chevron-right-Cf6vKeLq.js +1 -0
- package/dist/web/client/assets/{config-By86S7k-.js → config-Cn4iBJFz.js} +1 -1
- package/dist/web/client/assets/{inbox-DlXQCJLe.js → inbox-CWXql0K3.js} +1 -1
- package/dist/web/client/assets/{index-CLdmsmiN.js → index-Bq2zrG2_.js} +2 -2
- package/dist/web/client/assets/{learning-BqZIw2nA.js → learning-DAm4dyyc.js} +1 -1
- package/dist/web/client/assets/login-BsABI8Ux.js +1 -0
- package/dist/web/client/assets/{mcp-DKSY2yVa.js → mcp-loLB0wuh.js} +1 -1
- package/dist/web/client/assets/{members-9GuOiq6J.js → members-BJ0ikkTl.js} +1 -1
- package/dist/web/client/assets/{memory-DiGMI6FC.js → memory-Drn9Gv53.js} +1 -1
- package/dist/web/client/assets/{policy-TBGQffw5.js → policy-pQadZyk0.js} +1 -1
- package/dist/web/client/assets/results-9exU6f7m.js +1 -0
- package/dist/web/client/assets/routes-jw1PuqiR.js +1 -0
- package/dist/web/client/assets/{services-1Z398CES.js → services-BLso1BhA.js} +1 -1
- package/dist/web/client/assets/{skills-BP2gciFt.js → skills-BodqX3OI.js} +1 -1
- package/dist/web/client/assets/styles-d1yjBpud.css +2 -0
- package/dist/web/client/assets/{teams-DBIivGT7.js → teams-ByQedXZi.js} +1 -1
- package/dist/web/client/assets/{teams-tNoMDJ_M.js → teams-SYsF1bOR.js} +1 -1
- package/dist/web/client/assets/telegram-DC9PLFbB.js +1 -0
- package/dist/web/client/assets/{tokens-eDWCGQQ2.js → tokens-DGy8Ijpo.js} +1 -1
- package/dist/web/client/assets/{triggers-DZME3IcK.js → triggers-BXpVmHDG.js} +1 -1
- package/dist/web/client/assets/{welcome-B49sh_uf.js → welcome-BKsnsCxJ.js} +1 -1
- package/dist/web/client/assets/wire-constants-CBD-lTaa.js +1 -0
- package/dist/web/client/assets/{x-CHc7_-TH.js → x-BK4xNCzS.js} +1 -1
- package/dist/web/server/assets/ChatPane-CS5oI7ef.js +2666 -0
- package/dist/web/server/assets/ResultCard-B3zD9N0t.js +143 -0
- package/dist/web/server/assets/ResultSourceConversation-BpVWBRYq.js +64 -0
- package/dist/web/server/assets/{TeamTabs-Co3n2exj.js → TeamTabs-TCKjCvMA.js} +4 -0
- package/dist/web/server/assets/{_id-sNu5aXZt.js → _id-5pgKoid8.js} +2 -1
- package/dist/web/server/assets/_id-BDEpe6k0.js +6 -0
- package/dist/web/server/assets/{_id-DxCnEFpm.js → _id-BG2nkGxa.js} +3 -4
- package/dist/web/server/assets/{_id-BqWTB8Wl.js → _id-B_-CDHOi2.js} +1 -1
- package/dist/web/server/assets/{_id-BeKhPKw3.js → _id-BnqodC1c2.js} +7 -3
- package/dist/web/server/assets/{_id-DvlDkk04.js → _id-CeyHEsCZ.js} +1 -1
- package/dist/web/server/assets/{_id-BYKpT2Tu.js → _id-DFHB9JFQ.js} +1 -1
- package/dist/web/server/assets/{_id-DYU6uIBx.js → _id-DOnInzLb.js} +1 -1
- package/dist/web/server/assets/{_id-HMy7jM3P.js → _id-DrB9uR1b.js} +1 -1
- package/dist/web/server/assets/{_id-BAA9KJBU.js → _id-DtIYuJJX.js} +1 -1
- package/dist/web/server/assets/_id-DwZXhLlL.js +779 -0
- package/dist/web/server/assets/_id-wyjrf0JG.js +17 -0
- package/dist/web/server/assets/{_tanstack-start-manifest_v-Duhf0z7H.js → _tanstack-start-manifest_v-D7gKv6m6.js} +97 -70
- package/dist/web/server/assets/{activity-9VP6QDTH.js → activity-0VUkStOu.js} +1 -1
- package/dist/web/server/assets/{activity-BWv1goK6.js → activity-5fWKPDxj.js} +2 -2
- package/dist/web/server/assets/{activity-LjpblbDL.js → activity-C88qrNqv.js} +1 -1
- package/dist/web/server/assets/{agents-DjyQk94u.js → agents-C86K6HvS.js} +1 -1
- package/dist/web/server/assets/{agents-BJAv919x.js → agents-CSXLaM2W.js} +1 -1
- package/dist/web/server/assets/{approvals-BYi84gwl.js → approvals-C3MysI6l.js} +1 -1
- package/dist/web/server/assets/{approvals-7JOoBchn.js → approvals-DTdrltyk.js} +1 -1
- package/dist/web/server/assets/{approvals-Bz123liE.js → approvals-DXoN-T57.js} +15 -1
- package/dist/web/server/assets/{attention-UhqPK_Ro.js → attention-DLeLCKah.js} +1 -1
- package/dist/web/server/assets/{auth-7BHTH-KS.js → auth-CDZp9aEk.js} +1 -1
- package/dist/web/server/assets/{config-BMiajTZP.js → config-BVTHvljR.js} +1 -1
- package/dist/web/server/assets/{daemon-client-CI5NHwNC.js → daemon-client-xWyYKAcx.js} +59 -0
- package/dist/web/server/assets/{inbox-Dv55rGYq.js → inbox-PQTrjtOw.js} +1 -1
- package/dist/web/server/assets/{learning-C2upa1CE.js → learning-K0cmDsZR.js} +1 -1
- package/dist/web/server/assets/{login-BNj3U6ok.js → login-B_-9L8TU.js} +7 -2
- package/dist/web/server/assets/{login-Be8eP4Jp.js → login-rDox7PRD.js} +5 -2
- package/dist/web/server/assets/{mcp-BvnvlVFd.js → mcp-Rtraochg.js} +1 -1
- package/dist/web/server/assets/{members-DiYtr-zm.js → members-CD3GiTfF.js} +1 -1
- package/dist/web/server/assets/{members-DHYTVgwb.js → members-cc5SeEaW.js} +2 -2
- package/dist/web/server/assets/{members-BP3UTqu1.js → members-grAKFWgv.js} +1 -1
- package/dist/web/server/assets/{memory-CkXUgf5s.js → memory-C5i_GxlY.js} +2 -2
- package/dist/web/server/assets/{memory-DTFEmfF3.js → memory-CVqnyUaM.js} +1 -1
- package/dist/web/server/assets/{memory-BqV-Afy1.js → memory-egFpBD1g.js} +1 -1
- package/dist/web/server/assets/{policy-Bvvav8fh.js → policy-Dgjb0_gK.js} +2 -2
- package/dist/web/server/assets/{policy-BddK4xom.js → policy-bLKwvnja.js} +1 -1
- package/dist/web/server/assets/{policy-18fdTFO4.js → policy-ooSBgiDS.js} +1 -1
- package/dist/web/server/assets/results-CfNsFncJ.js +173 -0
- package/dist/web/server/assets/results-DXdYtHaq.js +12 -0
- package/dist/web/server/assets/results-N-kP4iFT.js +12 -0
- package/dist/web/server/assets/{router-CY_pCgP1.js → router-B6g_s7W3.js} +60 -43
- package/dist/web/server/assets/{routes-COtkyZG1.js → routes-BqSaZvlu.js} +3 -3
- package/dist/web/server/assets/{routes-BgHisAxH.js → routes-Dsh8F_7i.js} +2 -2
- package/dist/web/server/assets/{routes-DAU2g7Uz.js → routes-pcRy_Sf8.js} +1 -1
- package/dist/web/server/assets/{services-DozuJtzX.js → services-DlZA8fRL.js} +1 -1
- package/dist/web/server/assets/{skills-DfGSJTCB.js → skills-i2duvgW-.js} +1 -1
- package/dist/web/server/assets/{teams-be05ci17.js → teams-6RYIj1Xm.js} +1 -1
- package/dist/web/server/assets/{teams-DPnAX4wD.js → teams-B1AI3NfN.js} +1 -1
- package/dist/web/server/assets/{telegram-1AWL6lFw.js → telegram-C4q0YTVn.js} +1 -1
- package/dist/web/server/assets/{telegram-Dj4Wwhd6.js → telegram-CoNeOgqZ.js} +465 -1
- package/dist/web/server/assets/{telegram-DbOFpTFm.js → telegram-Zd1rIZxg.js} +1 -1
- package/dist/web/server/assets/{tokens-DjM8GsgS.js → tokens-RFquA2Bx.js} +1 -1
- package/dist/web/server/assets/{triggers-Nezfw-Bd.js → triggers-DVD-iE-N.js} +1 -1
- package/dist/web/server/server.js +31 -27
- package/dist/worker.js +1765 -710
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
- package/dist/web/client/assets/TeamTabs-DIRiKcqx.js +0 -1
- package/dist/web/client/assets/_id-Cx7o4Zmf.js +0 -1
- package/dist/web/client/assets/_id-DIiVVxBl.js +0 -1
- package/dist/web/client/assets/chevron-right-VWrk_dTX.js +0 -1
- package/dist/web/client/assets/login-CCMPXiny.js +0 -1
- package/dist/web/client/assets/routes-g3yUOhCb.js +0 -1
- package/dist/web/client/assets/styles-B5vcgXln.css +0 -2
- package/dist/web/client/assets/telegram-CJG_DFdI.js +0 -1
- package/dist/web/client/assets/wire-constants-zZ7Wjb9C.js +0 -74
- package/dist/web/server/assets/ChatPane-CgSe7tsy.js +0 -1429
- package/dist/web/server/assets/_id-ndxFHx0t.js +0 -381
package/dist/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../daemon/src/runtime/worker/entry.ts","../../daemon/src/core/agent/delete.ts","../../daemon/src/core/agent/move.ts","../../daemon/src/core/agent/resolve.ts","../../daemon/src/core/profile/identity.ts","../../daemon/src/core/repos/agentLessonProposals.ts","../../daemon/src/core/agent/spawn.ts","../../daemon/src/core/profile/load.ts","../../daemon/src/core/profile/seed.ts","../../daemon/src/core/team/register.ts","../../daemon/src/core/profile/create.ts","../../daemon/src/core/skills/discover.ts","../../daemon/src/core/db/client.ts","../../daemon/src/core/db/migrate.ts","../../daemon/src/core/paths.ts","../../daemon/src/core/profile/delete.ts","../../daemon/src/core/repos/teamTemplates.ts","../../daemon/src/core/profile/update.ts","../../daemon/src/core/repos/agentReviews.ts","../../daemon/src/core/repos/communicationApprovals.ts","../../daemon/src/core/services.ts","../../daemon/src/core/repos/config.ts","../../daemon/src/core/repos/mcpServers.ts","../../daemon/src/core/repos/messages.ts","../../daemon/src/core/repos/secrets.ts","../../daemon/src/core/repos/telegram-pairing.ts","../../daemon/src/core/repos/triggerDispatches.ts","../../daemon/src/core/repos/triggers.ts","../../daemon/src/core/repos/webSessions.ts","../../daemon/src/core/repos/webTokens.ts","../../daemon/src/core/secrets.ts","../../daemon/src/core/skills/import.ts","../../daemon/src/core/skills/parse.ts","../../daemon/src/core/team/delete.ts","../../daemon/src/core/team-policy/adopt.ts","../../daemon/src/core/team-policy/authorization.ts","../../daemon/src/core/team-policy/source.ts","../../daemon/src/core/team-policy/spawn.ts","../../daemon/src/core/team-policy/rm-with-retry.ts","../../daemon/src/runtime/memory/qmd.ts","../../daemon/src/lib/native-module-error.ts","../../daemon/src/runtime/pi/events.ts","../../daemon/src/runtime/pi/session.ts","../../daemon/src/runtime/providers/pi-runtime.ts","../../daemon/src/runtime/auth/openai-codex.ts","../../daemon/src/runtime/providers/pi-adapter.ts","../../daemon/src/runtime/session/prompt.ts","../../daemon/src/runtime/safe-files.ts","../../daemon/src/runtime/shell/tooling.ts","../../daemon/src/runtime/shell/approval.ts","../../daemon/src/runtime/shell/security.ts","../../daemon/src/runtime/shell/docker.ts","../../daemon/src/runtime/worker/runtime.ts","../../daemon/src/runtime/pi/tools.ts","../../daemon/src/runtime/tools/bootstrap.ts","../../daemon/src/runtime/tools/browser.ts","../../daemon/src/runtime/tools/deliver-file.ts","../../daemon/src/runtime/tools/home.ts","../../daemon/src/runtime/tools/mcp.ts","../../daemon/src/runtime/tools/memory.ts","../../daemon/src/runtime/tools/messaging.ts","../../daemon/src/runtime/tools/user-md.ts","../../daemon/src/runtime/tools/web.ts","../../daemon/src/runtime/tools/web-extract.ts","../../daemon/src/runtime/tools/web-ssrf.ts","../../daemon/src/runtime/worker/api-key-refresh.ts","../../daemon/src/runtime/worker/ipc-client.ts"],"sourcesContent":["// One-shot worker entry. Configured operator HTTP retains its legacy runtime;\n// protected normal turns and restricted reviews use closed typed inputs.\n\nimport { randomUUID } from 'node:crypto'\nimport type { ChatFrame } from '@bazilion/api-types'\nimport type { AgentSession, ToolDefinition } from '@earendil-works/pi-coding-agent'\nimport { Type } from 'typebox'\nimport { resolvePaths } from '../../core/index.ts'\nimport { qmdBackend } from '../memory/qmd.ts'\nimport { piMessagesToProviderView, translatePiEvent } from '../pi/events.ts'\nimport {\n type BazilionSessionHandle,\n createBazilionSession,\n createProtectedBazilionSession,\n createRestrictedReviewSession,\n} from '../pi/session.ts'\nimport type { BashApprovalHost as ShellBashApprovalHost } from '../shell/approval.ts'\nimport { createIpcApiKeyRefresher } from './api-key-refresh.ts'\nimport { createIpcClient, type WorkerIpcCall } from './ipc-client.ts'\nimport type {\n BashApprovalResult,\n BrowserHost,\n McpHost,\n MessagingHost,\n UserMdGetResult,\n UserMdHost,\n UserMdWriteResult,\n} from './ipc-protocol.ts'\nimport {\n parseWorkerInput,\n protectedRuntimeSecrets,\n redactJsonValue,\n validateMinimalWorkerProcessEnv,\n type WorkerInput,\n} from './runtime.ts'\n\ninterface ReviewWorkerProposal {\n scope: 'private' | 'shared'\n text: string\n evidenceEntryIds: Array<{ sessionId: string; entryOrdinal: number }>\n}\n\ntype WorkerFrame = ChatFrame | { kind: 'review_result'; proposals: ReviewWorkerProposal[] }\n\nconst activeAccessTokens: string[] = []\n\nfunction emit(frame: WorkerFrame): void {\n process.stdout.write(`${JSON.stringify(redactJsonValue(frame, activeAccessTokens))}\\n`)\n}\n\nasync function readInput(): Promise<WorkerInput> {\n const chunks: Buffer[] = []\n for await (const chunk of process.stdin) chunks.push(chunk as Buffer)\n const raw = Buffer.concat(chunks).toString('utf8').trim()\n if (!raw) throw new Error('worker: empty stdin input')\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n throw new Error('worker: stdin is not valid JSON')\n }\n return parseWorkerInput(parsed)\n}\n\nfunction createWorkerIpcCall(): WorkerIpcCall {\n return createIpcClient({\n send: process.send\n ? (ipcMessage, done) => {\n process.send?.(ipcMessage, undefined, undefined, done)\n }\n : undefined,\n onMessage: (listener) => process.on('message', listener),\n onDisconnect: (listener) => process.on('disconnect', listener),\n })\n}\n\nfunction createTrackedIpcApiKeyRefresher(\n call: WorkerIpcCall,\n context: { providerName: string; agentId: string; turnId: string },\n): (providerName: string) => Promise<string> {\n const refresh = createIpcApiKeyRefresher(call, context)\n return async (providerName) => {\n const token = await refresh(providerName)\n if (token && !activeAccessTokens.includes(token)) activeAccessTokens.push(token)\n return token\n }\n}\n\nfunction createIpcMessagingHost(call: WorkerIpcCall): MessagingHost {\n return {\n agentExists: (agentId) => call<boolean>('agentExists', { agentId }),\n sendMessage: (input) => call<{ messageId: string }>('sendMessage', input),\n listInbox: (agentId, opts) => call('listInbox', { agentId, unreadOnly: opts.unreadOnly }),\n markRead: async (messageId) => {\n await call<null>('markRead', { messageId })\n },\n findReplies: (agentId, replyTo) => call('findReplies', { agentId, replyTo }),\n approvalStatus: (agentId, approvalId) => call('approvalStatus', { agentId, approvalId }),\n }\n}\n\nfunction createIpcUserMdHost(call: WorkerIpcCall): UserMdHost {\n return {\n get: (teamId) => call<UserMdGetResult>('userMdGet', { teamId }),\n write: (teamId, content, ifMatch) =>\n call<UserMdWriteResult>('userMdWrite', { teamId, content, ifMatch }),\n }\n}\n\nfunction createIpcBrowserHost(call: WorkerIpcCall): BrowserHost {\n return {\n invoke: (agentId, action, args) => call('browserInvoke', { agentId, action, args }),\n }\n}\n\nfunction createIpcMcpHost(call: WorkerIpcCall): McpHost {\n return {\n invoke: (serverId, toolName, args) => call('mcpInvoke', { serverId, toolName, args }),\n }\n}\n\nfunction createIpcBashApprovalHost(\n call: WorkerIpcCall,\n input: {\n agentId: string\n teamId: string\n turnId: string\n mode: 'interactive' | 'auto_deny'\n },\n): ShellBashApprovalHost {\n return {\n async requestApproval(request) {\n const result = await call<BashApprovalResult>('bashApproval', {\n id: randomUUID(),\n turnId: input.turnId,\n toolCallId: request.toolCallId,\n agentId: input.agentId,\n teamId: input.teamId,\n command: request.command,\n risks: [...request.risks],\n mode: input.mode,\n })\n return result.decision === 'allow' ? 'approved' : 'denied'\n },\n }\n}\n\ninterface ReviewState {\n proposals: ReviewWorkerProposal[]\n submitted(): boolean\n validationFailures(): number\n recordValidationFailure(): void\n tools: ToolDefinition[]\n}\n\nfunction createReviewState(\n evidenceItems: Array<{ sessionId: string; entryOrdinal: number }>,\n): ReviewState {\n const proposals: ReviewWorkerProposal[] = []\n let proposalBatchSubmitted = false\n let reviewValidationFailures = 0\n const allowedEvidence = new Set(\n evidenceItems.map((item) => `${item.sessionId}:${item.entryOrdinal}`),\n )\n const tools: ToolDefinition[] = [\n {\n name: 'propose_lesson',\n label: 'propose_lesson',\n description:\n 'Submit zero to five evidence-backed lesson proposals in one final batch. This is the only available action.',\n parameters: Type.Object({\n proposals: Type.Array(\n Type.Object({\n scope: Type.Union([Type.Literal('private'), Type.Literal('shared')]),\n text: Type.String({ minLength: 1, maxLength: 500 }),\n evidenceEntryIds: Type.Array(\n Type.Object({ sessionId: Type.String(), entryOrdinal: Type.Integer({ minimum: 0 }) }),\n { minItems: 1 },\n ),\n }),\n { maxItems: 5 },\n ),\n }),\n async execute(_toolCallId, params) {\n if (proposalBatchSubmitted) throw new Error('propose_lesson may be called only once')\n const input = params as { proposals: ReviewWorkerProposal[] }\n const validated: ReviewWorkerProposal[] = []\n for (const candidate of input.proposals) {\n const text = candidate.text.trim()\n if (!text || text.length > 500) throw new Error('proposal text must be 1-500 characters')\n for (const evidence of candidate.evidenceEntryIds) {\n if (!allowedEvidence.has(`${evidence.sessionId}:${evidence.entryOrdinal}`)) {\n throw new Error('proposal cites evidence outside the supplied digest')\n }\n }\n validated.push({ ...candidate, text })\n }\n proposalBatchSubmitted = true\n proposals.push(...validated)\n return {\n content: [{ type: 'text', text: `accepted ${proposals.length} proposals` }],\n details: {},\n terminate: true,\n }\n },\n },\n ]\n return {\n proposals,\n submitted: () => proposalBatchSubmitted,\n validationFailures: () => reviewValidationFailures,\n recordValidationFailure: () => {\n reviewValidationFailures += 1\n },\n tools,\n }\n}\n\nasync function createSessionForInput(\n input: WorkerInput,\n ipcCall: WorkerIpcCall,\n): Promise<{ handle: BazilionSessionHandle; reviewState?: ReviewState }> {\n if (input.kind === 'configured_operator_http') {\n const paths = resolvePaths()\n const memory = qmdBackend(`${input.agent.team.path}/memory`)\n await memory.init()\n const messagingHost = createIpcMessagingHost(ipcCall)\n const userMdHost = createIpcUserMdHost(ipcCall)\n const browserHost = input.browserEnabled ? createIpcBrowserHost(ipcCall) : undefined\n const mcpHost = input.mcpTools?.length ? createIpcMcpHost(ipcCall) : undefined\n const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {\n agentId: input.agent.agent.id,\n teamId: input.agent.team.id,\n turnId: input.turnId,\n mode: input.bashApprovalMode,\n })\n const refreshApiKey = input.apiKeyRefreshEnabled\n ? createTrackedIpcApiKeyRefresher(ipcCall, {\n providerName: input.agent.model.split(':', 1)[0] ?? '',\n agentId: input.agent.agent.id,\n turnId: input.turnId,\n })\n : undefined\n const handle = await createBazilionSession({\n agent: input.agent,\n paths,\n env: process.env,\n memory,\n enabledProviders: new Set(input.enabledProviders),\n messagingHost,\n userMdHost,\n apiKey: input.apiKey,\n refreshApiKey,\n browserHost,\n mcpHost,\n mcpTools: input.mcpTools,\n bashApprovalHost,\n fileSink: (file) => emit({ kind: 'event', event: { type: 'file', ...file } }),\n })\n return { handle }\n }\n\n const refreshApiKey = createTrackedIpcApiKeyRefresher(ipcCall, {\n providerName: input.runtime.providerName,\n agentId: input.kind === 'protected' ? input.agent.agent.id : input.agentId,\n turnId: input.turnId,\n })\n if (input.kind === 'restricted_review') {\n const reviewState = createReviewState(input.review.evidence)\n const handle = await createRestrictedReviewSession({\n runtime: input.runtime,\n scratch: input.scratch,\n systemPrompt: REVIEW_SYSTEM_PROMPT,\n tools: reviewState.tools,\n refreshApiKey,\n })\n return { handle, reviewState }\n }\n\n const memory = qmdBackend(input.paths.memoryDir)\n await memory.init()\n const messagingHost = createIpcMessagingHost(ipcCall)\n const userMdHost = createIpcUserMdHost(ipcCall)\n const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {\n agentId: input.agent.agent.id,\n teamId: input.agent.team.id,\n turnId: input.turnId,\n mode: 'auto_deny',\n })\n const handle = await createProtectedBazilionSession({\n agent: input.agent,\n runtime: input.runtime,\n paths: input.paths,\n scratch: input.scratch,\n docker: input.docker,\n memory,\n messagingHost,\n userMdHost,\n bashApprovalHost,\n refreshApiKey,\n fileSink: (file) => emit({ kind: 'event', event: { type: 'file', ...file } }),\n })\n return { handle }\n}\n\nasync function main(): Promise<void> {\n let aborted = false\n let session: AgentSession | null = null\n const onSignal = (): void => {\n aborted = true\n if (session) void session.abort()\n else {\n try {\n emit({ kind: 'event', event: { type: 'error', error: 'cancelled' } })\n } catch {}\n process.exit(0)\n }\n }\n process.on('SIGTERM', onSignal)\n process.on('SIGINT', onSignal)\n\n const input = await readInput()\n if (input.kind !== 'configured_operator_http') {\n validateMinimalWorkerProcessEnv(process.env, input.scratch)\n }\n const initialAccessTokens =\n input.kind === 'configured_operator_http'\n ? input.apiKey\n ? [input.apiKey]\n : []\n : protectedRuntimeSecrets(input.runtime)\n activeAccessTokens.push(...initialAccessTokens.filter(Boolean))\n const ipcCall = createWorkerIpcCall()\n const { handle, reviewState } = await createSessionForInput(input, ipcCall)\n session = handle.session\n\n const unsubscribe = session.subscribe((piEvent) => {\n if (\n reviewState &&\n piEvent.type === 'tool_execution_end' &&\n piEvent.toolName === 'propose_lesson' &&\n piEvent.isError\n ) {\n reviewState.recordValidationFailure()\n if (reviewState.validationFailures() >= 2) void session?.abort()\n }\n for (const event of translatePiEvent(piEvent)) emit({ kind: 'event', event })\n })\n\n try {\n const promptMessage = redactJsonValue(input.message, activeAccessTokens)\n const sanitizedImages =\n input.kind === 'restricted_review'\n ? []\n : redactJsonValue(input.images ?? [], activeAccessTokens)\n const promptImages =\n input.kind === 'restricted_review'\n ? []\n : sanitizedImages.map((image) => ({\n type: 'image' as const,\n data: image.data,\n mimeType: image.mimeType,\n }))\n await session.prompt(\n promptMessage,\n promptImages.length > 0 ? { images: promptImages } : undefined,\n )\n await session.agent.waitForIdle()\n\n if (reviewState) {\n if (reviewState.validationFailures() >= 2) {\n throw new Error('review output remained invalid after one schema-repair attempt')\n }\n if (!reviewState.submitted()) throw new Error('review output did not submit a proposal batch')\n emit({ kind: 'review_result', proposals: reviewState.proposals })\n } else {\n emit({ kind: 'done', messages: piMessagesToProviderView(session.agent.state.messages) })\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (!aborted) emit({ kind: 'event', event: { type: 'error', error: message } })\n emit({ kind: 'fatal', error: message })\n process.exitCode = 1\n } finally {\n unsubscribe()\n process.off('SIGTERM', onSignal)\n process.off('SIGINT', onSignal)\n handle.dispose()\n try {\n process.disconnect?.()\n } catch {}\n }\n}\n\nmain().catch((error) => {\n try {\n emit({ kind: 'fatal', error: error instanceof Error ? error.message : String(error) })\n } catch {}\n process.exit(1)\n})\n\nconst REVIEW_SYSTEM_PROMPT = `You are a restricted learning reviewer. Analyze only the supplied\ntranscript digest and approved-lesson index. You cannot act on the world. Call propose_lesson once\nwith zero to five proposals, then stop.\n\nIf propose_lesson rejects malformed output, correct the schema exactly once. If that repair is also\nrejected, stop; the review will fail.\n\nClassify before proposing:\n1. Stable human preference or biography belongs to USER.md: do not propose it.\n2. Agent-specific verified behavior or strategy is private.\n3. Reusable verified Team project knowledge, decisions, or procedures are shared.\n4. Everything else produces no proposal.\n\nZero proposals is a successful result. Never save transient or environment-dependent failures,\nunresolved attempts, guesses, contradicted claims, one-off narratives, status updates, secrets,\ncredentials, sensitive personal data, raw logs, copied text, or duplicates. Each proposal must cite\nat least one supplied evidence entry and one observation may appear in only one proposal.`\n","import { randomUUID } from 'node:crypto'\nimport { existsSync, renameSync, rmSync } from 'node:fs'\nimport type { BazilionDb } from '../db/client.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport { pruneBindingAndEmptyCohort } from './move.ts'\n\nexport function deleteAgent(db: BazilionDb, id: string, expectedTeamRevision: number): void {\n const agent = agentRepo.get(db, id)\n if (!agent) throw new Error(`agent not found: ${id}`)\n const fullId = agent.id\n const stagedDir = `${agent.dir}.deleting-${randomUUID()}`\n const hadDir = existsSync(agent.dir)\n if (hadDir) renameSync(agent.dir, stagedDir)\n\n try {\n db.raw.transaction(() => {\n const teamPolicy = teamPolicyRepo.get(db, agent.teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${agent.teamId}`)\n if (teamPolicy.revision !== expectedTeamRevision) {\n throw new Error(\n `team_revision_conflict: expected ${expectedTeamRevision}, current ${teamPolicy.revision}`,\n )\n }\n // messages.from_agent_id and to_agent_id reference agents(id) with no ON\n // DELETE rule, and messages.reply_to references messages(id) the same way,\n // so a naive DELETE of the agent fails if it has any mailbox history.\n // Null out inbound reply pointers to this agent's messages, then purge the\n // messages themselves, then let agentRepo.remove cascade the rest\n // (agent_skills, runs, events). agents.team_id is `ON DELETE RESTRICT`\n // from the team side, but the agent row itself goes away freely.\n db.raw.run(\n `UPDATE messages SET reply_to = NULL\n WHERE reply_to IN (SELECT id FROM messages WHERE from_agent_id = ? OR to_agent_id = ?)`,\n [fullId, fullId],\n )\n db.raw.run('DELETE FROM messages WHERE from_agent_id = ? OR to_agent_id = ?', [\n fullId,\n fullId,\n ])\n pruneBindingAndEmptyCohort(db, fullId, agent.teamId)\n db.raw.run(\n `DELETE FROM team_policy_edges WHERE team_id = ? AND\n ((source_kind = 'agent' AND source_id = ?) OR\n (target_kind = 'agent' AND target_id = ?))`,\n [agent.teamId, fullId, fullId],\n )\n db.raw.run('DELETE FROM team_agent_state WHERE agent_id = ?', [fullId])\n agentRepo.remove(db, fullId)\n teamPolicyRepo.bumpRevision(db, agent.teamId)\n })()\n } catch (error) {\n if (hadDir && existsSync(stagedDir)) renameSync(stagedDir, agent.dir)\n throw error\n }\n\n if (hadDir && existsSync(stagedDir)) {\n rmSync(stagedDir, { recursive: true, force: true })\n }\n}\n","import { readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Agent, TeamPolicyPlacement } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\n\nexport function moveAgentCanonical(\n db: BazilionDb,\n paths: Paths,\n agentId: string,\n input: {\n destinationTeamId: string\n sourceExpectedRevision: number\n destinationExpectedRevision: number\n placement: Exclude<TeamPolicyPlacement, 'template_snapshot'>\n },\n): Agent {\n const agent = agentRepo.get(db, agentId)\n if (!agent) throw new Error(`agent not found: ${agentId}`)\n if (!teamRepo.get(db, input.destinationTeamId, paths)) {\n throw new Error(`team not found: ${input.destinationTeamId}`)\n }\n if (agent.teamId === input.destinationTeamId) return agent\n const metadataPath = join(agent.dir, 'agent.json')\n const beforeMetadata = readFileSync(metadataPath, 'utf8')\n try {\n return db.raw.transaction(() => {\n const current = agentRepo.get(db, agent.id)\n if (!current || current.teamId !== agent.teamId) {\n throw new Error(`team_revision_conflict: Agent membership changed for ${agent.id}`)\n }\n const source = teamPolicyRepo.get(db, current.teamId)\n const destination = teamPolicyRepo.get(db, input.destinationTeamId)\n if (!source || !destination) throw new Error('team_policy_missing')\n if (source.revision !== input.sourceExpectedRevision) {\n throw new Error(\n `source_team_revision_conflict: expected ${input.sourceExpectedRevision}, current ${source.revision}`,\n )\n }\n if (destination.revision !== input.destinationExpectedRevision) {\n throw new Error(\n `destination_team_revision_conflict: expected ${input.destinationExpectedRevision}, current ${destination.revision}`,\n )\n }\n db.raw.run(\n `DELETE FROM team_policy_edges WHERE team_id = ? AND\n ((source_kind = 'agent' AND source_id = ?) OR (target_kind = 'agent' AND target_id = ?))`,\n [current.teamId, current.id, current.id],\n )\n db.raw.run('DELETE FROM team_agent_state WHERE agent_id = ?', [current.id])\n pruneBindingAndEmptyCohort(db, current.id, current.teamId)\n agentRepo.setGroup(db, current.id, input.destinationTeamId)\n const metadata = JSON.parse(beforeMetadata) as Record<string, unknown>\n metadata.teamId = input.destinationTeamId\n writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\\n`)\n teamPolicyRepo.insertAgentState(db, input.destinationTeamId, current.id)\n teamPolicyRepo.addPlacementEdges(\n db,\n input.destinationTeamId,\n current.id,\n input.placement,\n current.profileId,\n )\n teamPolicyRepo.bumpRevision(db, current.teamId)\n teamPolicyRepo.bumpRevision(db, input.destinationTeamId)\n const moved = agentRepo.get(db, current.id)\n if (!moved) throw new Error(`agent vanished after move: ${current.id}`)\n return moved\n })()\n } catch (error) {\n writeFileSync(metadataPath, beforeMetadata)\n throw error\n }\n}\n\nexport function pruneBindingAndEmptyCohort(db: BazilionDb, agentId: string, teamId: string): void {\n const binding = db.raw\n .query<{ instantiation_id: string }, [string]>(\n 'SELECT instantiation_id FROM source_slot_bindings WHERE agent_id = ?',\n )\n .get(agentId)\n if (!binding) return\n db.raw.run('DELETE FROM source_slot_bindings WHERE agent_id = ?', [agentId])\n const remaining =\n db.raw\n .query<{ count: number }, [string]>(\n 'SELECT COUNT(*) AS count FROM source_slot_bindings WHERE instantiation_id = ?',\n )\n .get(binding.instantiation_id)?.count ?? 0\n const baseline = teamPolicyRepo.get(db, teamId)?.baselineInstantiationId\n if (remaining === 0 && baseline !== binding.instantiation_id) {\n db.raw.run('DELETE FROM template_instantiations WHERE id = ?', [binding.instantiation_id])\n }\n}\n","import { join } from 'node:path'\nimport type { ResolvedAgent } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport { loadIdentityFromFile } from '../profile/identity.ts'\nimport * as agentLessonProposalRepo from '../repos/agentLessonProposals.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport * as teamRepo from '../repos/teams.ts'\n\nexport function resolveAgent(db: BazilionDb, paths: Paths, agentId: string): ResolvedAgent {\n // `agentRepo.get` accepts either a full UUID or an unambiguous prefix.\n const agent = agentRepo.get(db, agentId)\n if (!agent) throw new Error(`agent not found: ${agentId}`)\n\n // Parse the agent's OWN IDENTITY.md (not the profile's) so the detail page\n // can show avatar/creature. `null` when the file is missing or still holds\n // only placeholder values.\n agent.identity = loadIdentityFromFile(join(agent.dir, 'IDENTITY.md'))\n\n const profile = profileRepo.get(db, agent.profileId)\n if (!profile) {\n throw new Error(`profile not found for agent ${agentId}: ${agent.profileId}`)\n }\n\n const team = teamRepo.get(db, agent.teamId, paths)\n if (!team) {\n throw new Error(`team not found for agent ${agentId}: ${agent.teamId}`)\n }\n\n return {\n agent,\n profile,\n model: agent.modelOverride ?? profile.defaultModel,\n reasoningLevel: agent.reasoningLevel,\n team,\n skills: agentRepo.listAttachedSkills(db, agentId),\n privateLessons: agentLessonProposalRepo\n .listForAgent(db, agent.id, { status: 'approved', limit: 100 })\n .filter((proposal) => proposal.scope === 'private')\n .map((proposal) => proposal.text),\n }\n}\n","import { readFileSync } from 'node:fs'\nimport type { AgentIdentityFile } from '@bazilion/api-types'\n\nconst IDENTITY_PLACEHOLDER_VALUES = new Set([\n 'pick something you like',\n 'ai? robot? familiar? ghost in the machine? something weirder?',\n 'how do you come across? sharp? warm? chaotic? calm?',\n 'your signature - pick one that feels right',\n 'workspace-relative path, http(s) url, or data uri',\n 'an http(s) url or data uri - optional',\n])\n\nfunction normalizeIdentityValue(value: string): string {\n let normalized = value.trim()\n normalized = normalized.replace(/^[*_]+|[*_]+$/g, '').trim()\n if (normalized.startsWith('(') && normalized.endsWith(')')) {\n normalized = normalized.slice(1, -1).trim()\n }\n normalized = normalized.replace(/[\\u2013\\u2014]/g, '-')\n normalized = normalized.replace(/\\s+/g, ' ').toLowerCase()\n return normalized\n}\n\nfunction isIdentityPlaceholder(value: string): boolean {\n return IDENTITY_PLACEHOLDER_VALUES.has(normalizeIdentityValue(value))\n}\n\nexport function parseIdentityMarkdown(content: string): AgentIdentityFile {\n const identity: AgentIdentityFile = {}\n for (const line of content.split(/\\r?\\n/)) {\n const cleaned = line.trim().replace(/^\\s*-\\s*/, '')\n const colonIndex = cleaned.indexOf(':')\n if (colonIndex === -1) continue\n const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, '').trim().toLowerCase()\n const value = cleaned\n .slice(colonIndex + 1)\n .replace(/^[*_]+|[*_]+$/g, '')\n .trim()\n if (!value) continue\n if (isIdentityPlaceholder(value)) continue\n if (label === 'name') identity.name = value\n else if (label === 'emoji') identity.emoji = value\n else if (label === 'creature') identity.creature = value\n else if (label === 'vibe') identity.vibe = value\n else if (label === 'theme') identity.theme = value\n else if (label === 'avatar') identity.avatar = value\n }\n return identity\n}\n\nexport function identityHasValues(identity: AgentIdentityFile): boolean {\n return Boolean(\n identity.name ||\n identity.emoji ||\n identity.theme ||\n identity.creature ||\n identity.vibe ||\n identity.avatar,\n )\n}\n\nexport function loadIdentityFromFile(path: string): AgentIdentityFile | null {\n let content: string\n try {\n content = readFileSync(path, 'utf8')\n } catch {\n return null\n }\n const parsed = parseIdentityMarkdown(content)\n return identityHasValues(parsed) ? parsed : null\n}\n","import { randomUUID } from 'node:crypto'\nimport type {\n AgentLessonEvidence,\n AgentLessonProposal,\n AgentLessonScope,\n AgentLessonStatus,\n} from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawProposal {\n id: string\n review_id: string\n agent_id: string\n scope: string\n text: string\n evidence_json: string\n status: string\n version: number\n decided_at: number | null\n revoked_at: number | null\n applied_key: string | null\n created_at: number\n updated_at: number\n}\n\nfunction toProposal(row: RawProposal): AgentLessonProposal {\n return {\n id: row.id,\n reviewId: row.review_id,\n agentId: row.agent_id,\n scope: row.scope as AgentLessonScope,\n text: row.text,\n evidence: JSON.parse(row.evidence_json) as AgentLessonEvidence[],\n status: row.status as AgentLessonStatus,\n version: row.version,\n decidedAt: row.decided_at,\n revokedAt: row.revoked_at,\n appliedKey: row.applied_key,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nexport function insert(\n db: BazilionDb,\n input: {\n reviewId: string\n agentId: string\n scope: AgentLessonScope\n text: string\n evidence: AgentLessonEvidence[]\n now?: number\n },\n): AgentLessonProposal {\n const now = input.now ?? Date.now()\n const id = randomUUID()\n db.raw.run(\n `INSERT INTO agent_lesson_proposals\n (id, review_id, agent_id, scope, text, evidence_json, status, version, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, 'pending', 1, ?, ?)`,\n [\n id,\n input.reviewId,\n input.agentId,\n input.scope,\n input.text.trim(),\n JSON.stringify(input.evidence),\n now,\n now,\n ],\n )\n const proposal = get(db, id)\n if (!proposal) throw new Error('lesson proposal insert failed')\n return proposal\n}\n\nexport function get(db: BazilionDb, id: string): AgentLessonProposal | null {\n const row = db.raw\n .query<RawProposal, [string]>('SELECT * FROM agent_lesson_proposals WHERE id = ?')\n .get(id)\n return row ? toProposal(row) : null\n}\n\nexport function listForReview(db: BazilionDb, reviewId: string): AgentLessonProposal[] {\n return db.raw\n .query<RawProposal, [string]>(\n 'SELECT * FROM agent_lesson_proposals WHERE review_id = ? ORDER BY created_at ASC',\n )\n .all(reviewId)\n .map(toProposal)\n}\n\nexport function listForAgent(\n db: BazilionDb,\n agentId: string,\n options: { status?: AgentLessonStatus; limit?: number } = {},\n): AgentLessonProposal[] {\n const limit = options.limit ?? 100\n const rows = options.status\n ? db.raw\n .query<RawProposal, [string, AgentLessonStatus, number]>(\n `SELECT * FROM agent_lesson_proposals WHERE agent_id = ? AND status = ?\n ORDER BY created_at DESC LIMIT ?`,\n )\n .all(agentId, options.status, limit)\n : db.raw\n .query<RawProposal, [string, number]>(\n `SELECT * FROM agent_lesson_proposals WHERE agent_id = ?\n ORDER BY created_at DESC LIMIT ?`,\n )\n .all(agentId, limit)\n return rows.map(toProposal)\n}\n\nfunction changedProposal(db: BazilionDb, id: string, changes: number): AgentLessonProposal | null {\n return changes === 1 ? get(db, id) : null\n}\n\nexport function updatePending(\n db: BazilionDb,\n id: string,\n version: number,\n input: { text: string; scope: AgentLessonScope },\n now = Date.now(),\n): AgentLessonProposal | null {\n const result = db.raw.run(\n `UPDATE agent_lesson_proposals SET text = ?, scope = ?, version = version + 1, updated_at = ?\n WHERE id = ? AND version = ? AND status = 'pending'`,\n [input.text.trim(), input.scope, now, id, version],\n )\n return changedProposal(db, id, Number(result.changes))\n}\n\nexport function approve(\n db: BazilionDb,\n id: string,\n version: number,\n appliedKey: string | null,\n now = Date.now(),\n): AgentLessonProposal | null {\n const result = db.raw.run(\n `UPDATE agent_lesson_proposals SET status = 'approved', applied_key = ?, decided_at = ?,\n version = version + 1, updated_at = ?\n WHERE id = ? AND version = ? AND status = 'pending'`,\n [appliedKey, now, now, id, version],\n )\n return changedProposal(db, id, Number(result.changes))\n}\n\nexport function reject(\n db: BazilionDb,\n id: string,\n version: number,\n now = Date.now(),\n): AgentLessonProposal | null {\n const result = db.raw.run(\n `UPDATE agent_lesson_proposals SET status = 'rejected', decided_at = ?,\n version = version + 1, updated_at = ?\n WHERE id = ? AND version = ? AND status = 'pending'`,\n [now, now, id, version],\n )\n return changedProposal(db, id, Number(result.changes))\n}\n\nexport function revoke(\n db: BazilionDb,\n id: string,\n version: number,\n now = Date.now(),\n): AgentLessonProposal | null {\n const result = db.raw.run(\n `UPDATE agent_lesson_proposals SET status = 'revoked', revoked_at = ?,\n version = version + 1, updated_at = ?\n WHERE id = ? AND version = ? AND status = 'approved'`,\n [now, now, id, version],\n )\n return changedProposal(db, id, Number(result.changes))\n}\n","import { randomUUID } from 'node:crypto'\nimport { mkdirSync, rmSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Agent, ReasoningLevel, TeamPolicyPlacement } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport { loadProfile } from '../profile/load.ts'\nimport { DEFAULT_TEAM_ID } from '../profile/seed.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\nimport { discoverSkills } from '../skills/discover.ts'\n\nexport interface SpawnAgentInput {\n profileId: string\n name?: string\n modelOverride?: string | null\n reasoningLevel?: ReasoningLevel\n /**\n * Team the new agent joins. One agent belongs to exactly one team. When\n * omitted, falls back to the seeded 'default' team — the same fallback\n * used everywhere we need a sensible cwd. Must refer to an existing team.\n */\n teamId?: string\n /** Internal batch-spawn switch; the caller regenerates/bump the aggregate once. */\n deferTeamPolicyUpdate?: boolean\n teamExpectedRevision?: number\n placement?: Exclude<TeamPolicyPlacement, 'template_snapshot'>\n}\n\nexport function spawnAgent(db: BazilionDb, paths: Paths, input: SpawnAgentInput): Agent {\n const loaded = loadProfile(db, input.profileId)\n const id = randomUUID()\n const dir = paths.agentDir(id)\n try {\n return db.raw.transaction(() => {\n // Agent's private home: identity files + sessions. Memory now lives at\n // the team level (`teams/<slug>/memory/`), shared by all member agents,\n // so we no longer create per-agent memory dirs here.\n mkdirSync(dir, { recursive: true })\n mkdirSync(join(dir, 'sessions'), { recursive: true })\n\n // Copy profile templates verbatim so the agent can diverge per-instance.\n // BOOTSTRAP.md is intentionally NOT personalized with the spawn slug —\n // the slug is a routing label, not necessarily the persona name. Bootstrap\n // is a pure conversation: the agent asks, the human answers, IDENTITY.md\n // is populated from that exchange.\n writeFileSync(join(dir, 'SOUL.md'), loaded.files.soul)\n writeFileSync(join(dir, 'IDENTITY.md'), loaded.files.identity)\n if (loaded.files.bootstrap !== null) {\n writeFileSync(join(dir, 'BOOTSTRAP.md'), loaded.files.bootstrap)\n }\n if (loaded.files.agents !== null) {\n writeFileSync(join(dir, 'AGENTS.md'), loaded.files.agents)\n }\n if (loaded.files.tools !== null) {\n writeFileSync(join(dir, 'TOOLS.md'), loaded.files.tools)\n }\n\n const reasoningLevel: ReasoningLevel = input.reasoningLevel ?? 'medium'\n\n // Resolve team: explicit input wins; otherwise fall back to the seeded\n // 'default'. If neither exists, error out — an agent can't live without a\n // team (the FK `agents.team_id REFERENCES teams(id)` enforces it too).\n const teamId = input.teamId ?? DEFAULT_TEAM_ID\n const team = teamRepo.get(db, teamId, paths)\n if (!team) {\n throw new Error(\n `spawnAgent: team \"${teamId}\" does not exist. Pass an explicit --team or complete first-run setup first.`,\n )\n }\n let placement = input.placement\n if (!input.deferTeamPolicyUpdate) {\n const teamPolicy = teamPolicyRepo.get(db, team.id)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${team.id}`)\n const expectedRevision = input.teamExpectedRevision ?? teamPolicy.revision\n placement ??= 'profile_defaults'\n if (teamPolicy.revision !== expectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${expectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n }\n\n const agentJson = {\n profileId: input.profileId,\n name: input.name ?? loaded.profile.name,\n modelOverride: input.modelOverride ?? null,\n reasoningLevel,\n teamId: team.id,\n }\n writeFileSync(join(dir, 'agent.json'), `${JSON.stringify(agentJson, null, 2)}\\n`)\n\n const agent = agentRepo.insert(db, {\n id,\n profileId: input.profileId,\n name: agentJson.name,\n modelOverride: agentJson.modelOverride,\n reasoningLevel,\n status: 'idle',\n dir,\n teamId: team.id,\n })\n\n // Skills come from the profile only — `skills_mode='all'` attaches every\n // installed skill, `'selected'` attaches the profile's default list.\n // Per-agent skill changes happen post-spawn via `agent skill add/rm`.\n const skills =\n loaded.profile.skillsMode === 'all'\n ? discoverSkills(paths).map((s) => s.name)\n : loaded.defaultSkills\n for (const s of skills) agentRepo.attachSkill(db, id, s)\n\n if (!input.deferTeamPolicyUpdate) {\n teamPolicyRepo.insertAgentState(db, team.id, agent.id)\n teamPolicyRepo.addPlacementEdges(\n db,\n team.id,\n agent.id,\n placement as Exclude<TeamPolicyPlacement, 'template_snapshot'>,\n input.profileId,\n )\n teamPolicyRepo.bumpRevision(db, team.id)\n }\n\n return agent\n })()\n } catch (error) {\n rmSync(dir, { recursive: true, force: true })\n throw error\n }\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { LoadedProfile } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport * as profileCommunicationDefaultsRepo from '../repos/profileCommunicationDefaults.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport { parseIdentityMarkdown } from './identity.ts'\n\nfunction readOptional(path: string): string | null {\n return existsSync(path) ? readFileSync(path, 'utf8') : null\n}\n\nexport function loadProfile(db: BazilionDb, id: string): LoadedProfile {\n const profile = profileRepo.get(db, id)\n if (!profile) throw new Error(`profile not found: ${id}`)\n\n const soul = readFileSync(join(profile.dir, 'SOUL.md'), 'utf8')\n const identityRaw = readFileSync(join(profile.dir, 'IDENTITY.md'), 'utf8')\n const bootstrap = readOptional(join(profile.dir, 'BOOTSTRAP.md'))\n const agents = readOptional(join(profile.dir, 'AGENTS.md'))\n const tools = readOptional(join(profile.dir, 'TOOLS.md'))\n\n const parsedIdentity = parseIdentityMarkdown(identityRaw)\n const anyIdentity =\n parsedIdentity.name ||\n parsedIdentity.emoji ||\n parsedIdentity.theme ||\n parsedIdentity.creature ||\n parsedIdentity.vibe ||\n parsedIdentity.avatar\n\n return {\n profile,\n defaultSkills: profileRepo.getDefaultSkills(db, id),\n files: {\n soul,\n identity: identityRaw,\n bootstrap,\n agents,\n tools,\n },\n identity: anyIdentity ? parsedIdentity : null,\n communicationDefaults: profileCommunicationDefaultsRepo.get(db, id),\n }\n}\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Profile, Team } from '@bazilion/api-types'\nimport { isSetupComplete, listAvailableModels } from '../availableModels.ts'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport * as teamRepo from '../repos/teams.ts'\nimport { registerTeam } from '../team/register.ts'\nimport { createProfile } from './create.ts'\nimport {\n DEFAULT_AGENTS,\n DEFAULT_BOOTSTRAP,\n DEFAULT_IDENTITY,\n DEFAULT_SOUL,\n DEFAULT_TOOLS,\n} from './templates.ts'\n\nexport const DEFAULT_PROFILE_ID = 'default'\nexport const DEFAULT_TEAM_ID = 'default'\n\nconst DEFAULT_PROFILE_FILES: [string, string][] = [\n ['SOUL.md', DEFAULT_SOUL],\n ['IDENTITY.md', DEFAULT_IDENTITY],\n ['BOOTSTRAP.md', DEFAULT_BOOTSTRAP],\n ['AGENTS.md', DEFAULT_AGENTS],\n ['TOOLS.md', DEFAULT_TOOLS],\n]\n\n/**\n * Bring the on-disk `default` profile's template files up to the current\n * defaults. The `default` profile is bazilion-managed — it always reflects\n * the shipped templates, so operators who want a customized template make\n * their own profile (which is never touched here). Pre-1.0, single-user:\n * we just overwrite rather than diffing against historical defaults.\n *\n * Write-if-differs so a steady-state boot is a no-op (and the log stays\n * quiet). No-op when the default profile isn't on disk yet — a fresh install\n * gets the current templates via seedDefaults → createProfile. Returns the\n * names of files that were (re)written.\n */\nexport function refreshDefaultProfileTemplates(paths: Paths): string[] {\n const dir = paths.profileDir(DEFAULT_PROFILE_ID)\n if (!existsSync(dir)) return []\n const written: string[] = []\n for (const [name, content] of DEFAULT_PROFILE_FILES) {\n const path = join(dir, name)\n if (!existsSync(path) || readFileSync(path, 'utf8') !== content) {\n writeFileSync(path, content)\n written.push(name)\n }\n }\n return written\n}\n\nexport interface SeedDefaultsInput {\n /** `provider:model` used as the profile's defaultModel. */\n model: string\n}\n\nexport interface SeedResult {\n profile: Profile\n team: Team\n /** true if the helper created the profile this call (vs. returned an existing one). */\n profileCreated: boolean\n /** true if the helper created the team this call. */\n teamCreated: boolean\n}\n\n/**\n * Seed the on-disk + DB defaults users land on after finishing first-run setup:\n * a `default` team at `~/.bazilion/teams/default/` and a `default` profile\n * wired to the just-enabled model. Fresh agents spawned from the default\n * profile land in the default team unless another is specified.\n *\n * Idempotent: re-seeding reuses whichever pieces already exist, so it's safe\n * to call whenever the setup state changes.\n */\nexport function seedDefaults(db: BazilionDb, paths: Paths, input: SeedDefaultsInput): SeedResult {\n let team = teamRepo.get(db, DEFAULT_TEAM_ID, paths)\n let teamCreated = false\n if (!team) {\n team = registerTeam(db, { id: DEFAULT_TEAM_ID, name: 'Default' }, paths)\n teamCreated = true\n }\n\n let profile = profileRepo.get(db, DEFAULT_PROFILE_ID)\n let profileCreated = false\n if (!profile) {\n // skillsMode='all' so freshly-spawned default agents inherit every\n // installed skill — the friendlier first-run posture. Custom profiles\n // still default to 'selected' (createProfile's own default).\n profile = createProfile(db, paths, {\n id: DEFAULT_PROFILE_ID,\n name: 'Default',\n defaultModel: input.model,\n skillsMode: 'all',\n communicationDefaults: {\n userInput: true,\n userOutput: true,\n outsideTeamInput: false,\n outsideTeamOutput: false,\n peerDefault: 'allow_all',\n },\n })\n profileCreated = true\n }\n\n return { profile, team, profileCreated, teamCreated }\n}\n\n/**\n * Safe to call from any endpoint that can change setup state (toggling a\n * provider, editing model lists). No-op when either setup isn't complete\n * (nothing to seed against yet) or the default profile already exists.\n * Returns the seed result only on the cold-start transition.\n */\nexport function ensureSetupSeeded(db: BazilionDb, paths: Paths): SeedResult | null {\n if (!isSetupComplete(db)) return null\n if (profileRepo.get(db, DEFAULT_PROFILE_ID)) return null\n const first = listAvailableModels(db)[0]\n if (!first) return null\n return seedDefaults(db, paths, { model: first.value })\n}\n","// Register a team: pick a slug, materialize `~/.bazilion/teams/<slug>/`\n// (real dir or symlink), insert the row.\n//\n// Two modes:\n// - default: creates a real directory at `paths.teamDir(slug)`.\n// - `--link <target>`: creates a symlink at `paths.teamDir(slug)` →\n// `<target>`. The link target must exist and be a directory — that's\n// the \"I want my agents working on my existing project tree\" path.\n//\n// Slug = the row's id. Names are humanized labels separate from the slug;\n// callers may pass `name` explicitly or let it default to the slug.\n\nimport { existsSync, mkdirSync, rmSync, statSync, symlinkSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { Team } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport { validateSlug } from '../profile/validate.ts'\nimport * as teamRepo from '../repos/teams.ts'\n\nexport interface RegisterTeamInput {\n /** Slug. Becomes the row id AND the directory name under `teams/`. */\n id: string\n /** Human-readable label. Defaults to `id`. */\n name?: string\n /**\n * If set, materialize `paths.teamDir(id)` as a symlink to this absolute\n * path instead of as a real directory. Target must exist and be a dir.\n */\n link?: string\n}\n\nexport function registerTeam(db: BazilionDb, input: RegisterTeamInput, paths: Paths): Team {\n validateSlug(input.id)\n\n if (teamRepo.get(db, input.id, paths)) {\n throw new Error(`team already registered: ${input.id}`)\n }\n\n const slot = paths.teamDir(input.id)\n if (existsSync(slot)) {\n throw new Error(`team slot already on disk at ${slot} (move or remove it first)`)\n }\n\n // Make sure the parent `teams/` dir exists — the daemon's bootstrap creates\n // it but tests sometimes resolve a custom $BAZILION_HOME without going\n // through bootstrap.\n mkdirSync(paths.teamsDir, { recursive: true })\n\n if (input.link) {\n const target = resolve(input.link)\n if (!existsSync(target)) {\n throw new Error(`--link target does not exist: ${target}`)\n }\n if (!statSync(target).isDirectory()) {\n throw new Error(`--link target is not a directory: ${target}`)\n }\n symlinkSync(target, slot, 'dir')\n } else {\n mkdirSync(slot, { recursive: true })\n }\n\n // Memory subdir is the qmd index root for this team — created here so\n // the first tool call doesn't have to ensure it.\n mkdirSync(resolve(slot, 'memory'), { recursive: true })\n\n try {\n return db.raw.transaction(() => {\n return teamRepo.insert(db, { id: input.id, name: input.name ?? input.id }, paths)\n })()\n } catch (error) {\n // Remove only the canonical slot. For linked Teams rmSync removes the\n // symlink itself and never touches its target.\n rmSync(slot, { recursive: true, force: true })\n throw error\n }\n}\n","import { mkdirSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Profile, ProfileCommunicationDefaults, SkillsMode } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as profileCommunicationDefaultsRepo from '../repos/profileCommunicationDefaults.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport {\n DEFAULT_AGENTS,\n DEFAULT_BOOTSTRAP,\n DEFAULT_IDENTITY,\n DEFAULT_SOUL,\n DEFAULT_TOOLS,\n} from './templates.ts'\nimport { validateSlug } from './validate.ts'\n\nexport interface CreateProfileInput {\n id: string\n name?: string\n defaultModel: string\n skillsMode?: SkillsMode\n defaultSkills?: string[]\n communicationDefaults?: ProfileCommunicationDefaults\n templates?: {\n soul?: string\n identity?: string\n /** undefined = default bootstrap, null = skip bootstrap, string = override */\n bootstrap?: string | null\n /** undefined = default template, null = skip, string = override */\n agents?: string | null\n /** undefined = default template, null = skip, string = override */\n tools?: string | null\n /** opt-in (off by default): null/undefined = skip, string = seed */\n }\n}\n\nexport function createProfile(db: BazilionDb, paths: Paths, input: CreateProfileInput): Profile {\n validateSlug(input.id)\n\n const dir = paths.profileDir(input.id)\n mkdirSync(dir, { recursive: true })\n\n const soul = input.templates?.soul ?? DEFAULT_SOUL\n const identity = input.templates?.identity ?? DEFAULT_IDENTITY\n // SOUL + IDENTITY are always written. BOOTSTRAP/AGENTS/TOOLS are default-on\n // (undefined → default template, null → skip, string → override).\n const bootstrap =\n input.templates?.bootstrap === null ? null : (input.templates?.bootstrap ?? DEFAULT_BOOTSTRAP)\n const agents =\n input.templates?.agents === null ? null : (input.templates?.agents ?? DEFAULT_AGENTS)\n const tools = input.templates?.tools === null ? null : (input.templates?.tools ?? DEFAULT_TOOLS)\n\n writeFileSync(join(dir, 'SOUL.md'), soul)\n writeFileSync(join(dir, 'IDENTITY.md'), identity)\n if (bootstrap !== null) {\n writeFileSync(join(dir, 'BOOTSTRAP.md'), bootstrap)\n }\n if (agents !== null) {\n writeFileSync(join(dir, 'AGENTS.md'), agents)\n }\n if (tools !== null) {\n writeFileSync(join(dir, 'TOOLS.md'), tools)\n }\n\n const skillsMode: SkillsMode = input.skillsMode ?? 'selected'\n const profileJson = {\n name: input.name ?? input.id,\n defaultModel: input.defaultModel,\n skillsMode,\n defaultSkills: input.defaultSkills ?? [],\n }\n writeFileSync(join(dir, 'profile.json'), `${JSON.stringify(profileJson, null, 2)}\\n`)\n\n const profile = profileRepo.insert(db, {\n id: input.id,\n name: profileJson.name,\n dir,\n defaultModel: input.defaultModel,\n skillsMode,\n })\n\n if (skillsMode === 'selected' && input.defaultSkills && input.defaultSkills.length > 0) {\n profileRepo.setDefaultSkills(db, input.id, input.defaultSkills)\n }\n if (input.communicationDefaults) {\n profileCommunicationDefaultsRepo.set(db, input.id, input.communicationDefaults)\n }\n\n return input.communicationDefaults\n ? { ...profile, communicationDefaults: input.communicationDefaults }\n : profile\n}\n","import { existsSync, readdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Paths } from '../paths.ts'\n\nexport interface DiscoveredSkill {\n name: string\n dir: string\n skillFile: string\n}\n\n/**\n * Walk the bazilion skill library and return every directory that contains a\n * SKILL.md file. Pure filesystem read — does not parse the markdown.\n */\nexport function discoverSkills(paths: Paths): DiscoveredSkill[] {\n if (!existsSync(paths.skillsDir)) return []\n\n const entries = readdirSync(paths.skillsDir, { withFileTypes: true })\n const skills: DiscoveredSkill[] = []\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const dir = join(paths.skillsDir, entry.name)\n const skillFile = join(dir, 'SKILL.md')\n if (!existsSync(skillFile)) continue\n skills.push({ name: entry.name, dir, skillFile })\n }\n return skills.sort((a, b) => a.name.localeCompare(b.name))\n}\n","import { backup, DatabaseSync, type SQLInputValue } from 'node:sqlite'\n\nexport interface QueryStmt<RowType, ParamsType extends unknown[]> {\n get(...params: ParamsType): RowType | null\n all(...params: ParamsType): RowType[]\n run(...params: ParamsType): { changes: number; lastInsertRowid: number | bigint }\n}\n\nexport interface QueryableDatabase {\n query<RowType, ParamsType extends unknown[]>(sql: string): QueryStmt<RowType, ParamsType>\n run(sql: string, params?: unknown[]): { changes: number; lastInsertRowid: number | bigint }\n exec(sql: string): void\n transaction<T>(fn: () => T): () => T\n}\n\nexport interface BazilionDb {\n raw: QueryableDatabase\n /**\n * Copy a transactionally-consistent image of the live database to `path`.\n * SQLite's online-backup API includes committed WAL contents without\n * requiring the daemon to close its connection or checkpoint the WAL.\n */\n backupTo(path: string): Promise<number>\n close(): void\n}\n\nfunction wrap(rawDb: DatabaseSync): QueryableDatabase {\n const cache = new Map<string, ReturnType<DatabaseSync['prepare']>>()\n let transactionDepth = 0\n let savepointSequence = 0\n function getStmt(sql: string) {\n let s = cache.get(sql)\n if (!s) {\n s = rawDb.prepare(sql)\n cache.set(sql, s)\n }\n return s\n }\n\n return {\n query<R, P extends unknown[]>(sql: string): QueryStmt<R, P> {\n const stmt = getStmt(sql)\n return {\n get(...params: P): R | null {\n const result = stmt.get(...(params as SQLInputValue[]))\n return (result as R | undefined) ?? null\n },\n all(...params: P): R[] {\n return stmt.all(...(params as SQLInputValue[])) as R[]\n },\n run(...params: P) {\n return stmt.run(...(params as SQLInputValue[])) as {\n changes: number\n lastInsertRowid: number | bigint\n }\n },\n }\n },\n run(sql, params) {\n const stmt = getStmt(sql)\n return stmt.run(...((params ?? []) as SQLInputValue[])) as {\n changes: number\n lastInsertRowid: number | bigint\n }\n },\n exec(sql) {\n rawDb.exec(sql)\n },\n // node:sqlite has no callable `transaction` wrapper; use manual BEGIN/COMMIT/ROLLBACK.\n transaction<T>(fn: () => T): () => T {\n return () => {\n const outermost = transactionDepth === 0\n const savepoint = outermost ? null : `bazilion_nested_${++savepointSequence}`\n if (outermost) rawDb.exec('BEGIN')\n else rawDb.exec(`SAVEPOINT ${savepoint}`)\n transactionDepth++\n try {\n const result = fn()\n if (outermost) rawDb.exec('COMMIT')\n else rawDb.exec(`RELEASE SAVEPOINT ${savepoint}`)\n return result\n } catch (err) {\n if (outermost) rawDb.exec('ROLLBACK')\n else {\n rawDb.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`)\n rawDb.exec(`RELEASE SAVEPOINT ${savepoint}`)\n }\n throw err\n } finally {\n transactionDepth--\n }\n }\n },\n }\n}\n\nfunction applyPragmas(rawDb: DatabaseSync, includeWal: boolean): void {\n if (includeWal) {\n try {\n rawDb.exec('PRAGMA journal_mode = WAL')\n } catch {\n // some sqlite builds reject WAL on :memory: — ignore\n }\n }\n rawDb.exec('PRAGMA foreign_keys = ON')\n}\n\nexport function openDb(path: string): BazilionDb {\n // Bazilion is the normal sole writer, but online backups and operator-side\n // diagnostics can briefly overlap another WAL connection. Wait for those\n // transient locks instead of turning unrelated reads/writes (including\n // token usage telemetry) into immediate SQLITE_BUSY failures.\n const raw = new DatabaseSync(path, { timeout: 5_000 })\n applyPragmas(raw, true)\n return {\n raw: wrap(raw),\n backupTo(path) {\n return backup(raw, path)\n },\n close() {\n raw.close()\n },\n }\n}\n\nexport function openInMemoryDb(): BazilionDb {\n const raw = new DatabaseSync(':memory:')\n applyPragmas(raw, false)\n return {\n raw: wrap(raw),\n backupTo(path) {\n return backup(raw, path)\n },\n close() {\n raw.close()\n },\n }\n}\n\nexport function inTx<T>(db: BazilionDb, fn: () => T): T {\n return db.raw.transaction(fn)()\n}\n","import { readdirSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { type BazilionDb, openInMemoryDb } from './client.ts'\n\nconst migrationsDir = join(dirname(fileURLToPath(import.meta.url)), 'migrations')\nconst schemaMigrationsSql = `\n CREATE TABLE IF NOT EXISTS schema_migrations (\n version TEXT PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )\n`\n\nexport const INCOMPATIBLE_DATABASE_MESSAGE =\n 'Bazilion cannot start because this home uses an incompatible alpha database schema. ' +\n 'Preserve the entire Bazilion home directory as a filesystem backup first, keeping ' +\n 'bazilion.db and auth.json together. Then perform the reset with ' +\n '`bazilion uninstall --yes` (from the repository root of a source checkout: ' +\n '`pnpm tsx apps/cli/src/index.ts uninstall --yes`) and start Bazilion again. ' +\n 'Use `--all` only if you also want to remove logs and installed skills.'\n\nexport class IncompatibleDatabaseError extends Error {\n constructor() {\n super(INCOMPATIBLE_DATABASE_MESSAGE)\n this.name = 'IncompatibleDatabaseError'\n }\n}\n\ninterface MigrationFile {\n version: string\n sql: string\n}\n\ninterface SchemaObject {\n type: string\n name: string\n tbl_name: string\n sql: string\n}\n\nlet canonicalSchemaObjects: SchemaObject[] | null = null\n\nfunction migrationFiles(): MigrationFile[] {\n return readdirSync(migrationsDir)\n .filter((file) => file.endsWith('.sql'))\n .sort()\n .map((file) => ({\n version: file.replace(/\\.sql$/, ''),\n sql: readFileSync(join(migrationsDir, file), 'utf8'),\n }))\n}\n\nfunction normalizeSql(sql: string): string {\n return sql.replace(/\\s+/g, ' ').trim()\n}\n\nfunction schemaObjects(db: BazilionDb): SchemaObject[] {\n return db.raw\n .query<SchemaObject, []>(\n `SELECT type, name, tbl_name, sql\n FROM sqlite_schema\n WHERE sql IS NOT NULL AND substr(name, 1, 7) <> 'sqlite_'\n ORDER BY type, name`,\n )\n .all()\n}\n\nfunction schemaPayload(rows: SchemaObject[]): string {\n return rows\n .map((row) => [row.type, row.name, row.tbl_name, normalizeSql(row.sql)].join('\\0'))\n .join('\\n')\n}\n\nfunction expectedSchema(files: MigrationFile[]): SchemaObject[] {\n if (canonicalSchemaObjects !== null) return canonicalSchemaObjects\n\n const canonical = openInMemoryDb()\n try {\n canonical.raw.exec(schemaMigrationsSql)\n for (const file of files) canonical.raw.exec(file.sql)\n canonicalSchemaObjects = schemaObjects(canonical)\n return canonicalSchemaObjects\n } finally {\n canonical.close()\n }\n}\n\n/**\n * Enforce the alpha clean-install database contract before any runtime work\n * starts. A fresh database may contain only the empty migration ledger; an\n * initialized database must match this release's ledger and complete schema.\n */\nexport function assertMigrationCompatibility(db: BazilionDb): void {\n const files = migrationFiles()\n const expectedVersions = files.map((file) => file.version)\n let appliedVersions: string[]\n try {\n appliedVersions = db.raw\n .query<{ version: string }, []>('SELECT version FROM schema_migrations ORDER BY version')\n .all()\n .map((row) => row.version)\n } catch {\n throw new IncompatibleDatabaseError()\n }\n\n const actualObjects = schemaObjects(db)\n const expectedObjects = expectedSchema(files)\n\n if (appliedVersions.length === 0) {\n const migrationTable = actualObjects.filter(\n (row) => row.type === 'table' && row.name === 'schema_migrations',\n )\n const expectedMigrationTable = expectedObjects.filter(\n (row) => row.type === 'table' && row.name === 'schema_migrations',\n )\n if (\n actualObjects.length !== 1 ||\n migrationTable.length !== 1 ||\n schemaPayload(migrationTable) !== schemaPayload(expectedMigrationTable)\n ) {\n throw new IncompatibleDatabaseError()\n }\n return\n }\n\n if (\n appliedVersions.length !== expectedVersions.length ||\n appliedVersions.some((version, index) => version !== expectedVersions[index]) ||\n schemaPayload(actualObjects) !== schemaPayload(expectedObjects)\n ) {\n throw new IncompatibleDatabaseError()\n }\n}\n\nexport function runMigrations(db: BazilionDb): void {\n const existingObjects = schemaObjects(db)\n if (\n existingObjects.length > 0 &&\n !existingObjects.some((row) => row.type === 'table' && row.name === 'schema_migrations')\n ) {\n // Do not mutate an unknown pre-ledger/corrupt database merely to discover\n // that it is incompatible with the clean-install contract.\n throw new IncompatibleDatabaseError()\n }\n db.raw.exec(schemaMigrationsSql)\n assertMigrationCompatibility(db)\n\n const applied = new Set(\n db.raw\n .query<{ version: string }, []>('SELECT version FROM schema_migrations')\n .all()\n .map((r) => r.version),\n )\n\n for (const file of migrationFiles()) {\n if (applied.has(file.version)) continue\n const tx = db.raw.transaction(() => {\n db.raw.exec(file.sql)\n db.raw.run('INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)', [\n file.version,\n Date.now(),\n ])\n })\n tx()\n }\n\n assertMigrationCompatibility(db)\n}\n","import { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface Paths {\n home: string\n db: string\n /**\n * Bootstrap auth file shared by the daemon and the CLI: `{token, remote?}`.\n * - `token` is the plaintext of the bootstrap web token. The daemon reads\n * it once at startup to derive the encryption key for the `secrets`\n * table (PBKDF2 over it) and to validate that it matches a row in\n * `web_tokens` (so a corrupted file fails loudly). The CLI reads it as\n * its loopback bearer.\n * - `remote` (set via `bazilion login`) is a CLI-only override pointing at\n * a remote daemon. The local daemon ignores this field.\n *\n * One file replaces the previous `config.json` + `secrets.enc` split:\n * encrypted secrets and plaintext config now live as DB rows.\n */\n authFile: string\n profilesDir: string\n agentsDir: string\n skillsDir: string\n teamsDir: string\n logsDir: string\n profileDir(id: string): string\n agentDir(id: string): string\n skillDir(name: string): string\n teamDir(slug: string): string\n}\n\nexport function resolvePaths(home?: string): Paths {\n const root = home ?? process.env.BAZILION_HOME ?? join(homedir(), '.bazilion')\n return {\n home: root,\n db: join(root, 'bazilion.db'),\n authFile: join(root, 'auth.json'),\n profilesDir: join(root, 'profiles'),\n agentsDir: join(root, 'agents'),\n skillsDir: join(root, 'skills'),\n teamsDir: join(root, 'teams'),\n logsDir: join(root, 'logs'),\n profileDir(id) {\n return join(root, 'profiles', id)\n },\n agentDir(id) {\n return join(root, 'agents', id)\n },\n skillDir(name) {\n return join(root, 'skills', name)\n },\n teamDir(slug) {\n return join(root, 'teams', slug)\n },\n }\n}\n","import { existsSync, rmSync } from 'node:fs'\nimport type { BazilionDb } from '../db/client.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport * as teamTemplateRepo from '../repos/teamTemplates.ts'\n\nexport function deleteProfile(db: BazilionDb, id: string): void {\n const profile = profileRepo.get(db, id)\n if (!profile) throw new Error(`profile not found: ${id}`)\n\n // Check for agents still using this profile\n const agents = agentRepo.list(db, { includeArchived: true }).filter((a) => a.profileId === id)\n if (agents.length > 0) {\n const names = agents.map((a) => `${a.name} (${a.id.slice(0, 8)})`).join(', ')\n throw new Error(\n `profile_in_use: cannot delete profile \"${id}\": ${agents.length} agent(s) still reference it: ${names}. Delete or re-profile them first.`,\n )\n }\n\n // Check the canonical Team roster and every retained immutable snapshot.\n // Their Profile foreign keys are RESTRICT, so surface a stable domain error\n // instead of an opaque SQLite constraint failure.\n const refTemplates = teamTemplateRepo.findReferencingProfile(db, id)\n if (refTemplates.length > 0) {\n const names = refTemplates.map((template) => `${template.name} (${template.id})`).join(', ')\n throw new Error(\n `profile_in_use: cannot delete profile \"${id}\": ${refTemplates.length} Team Template(s) still reference it: ${names}. Remove the slot(s) first.`,\n )\n }\n\n // Remove from DB (CASCADE deletes profile_default_skills)\n profileRepo.remove(db, id)\n\n // Remove the profile directory from disk\n if (existsSync(profile.dir)) {\n rmSync(profile.dir, { recursive: true, force: true })\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type {\n ReasoningLevel,\n TeamTemplate,\n TeamTemplateDetail,\n TeamTemplateEdge,\n TeamTemplateRevision,\n TeamTemplateSlot,\n} from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawTemplate {\n id: string\n name: string\n user_md: string | null\n current_revision: number\n deleted_at: number | null\n created_at: number\n updated_at: number\n}\n\ninterface RawSlot {\n template_id: string\n slot_id: string\n position: number\n profile_id: string\n agent_name: string\n model_override: string | null\n reasoning_level: ReasoningLevel | null\n position_x: number | null\n position_y: number | null\n display_json: string | null\n tombstoned_at: number | null\n}\n\ninterface RawEdge {\n template_id: string\n source_kind: TeamTemplateEdge['sourceKind']\n source_id: string\n target_kind: TeamTemplateEdge['targetKind']\n target_id: string\n posture: TeamTemplateEdge['posture']\n}\n\nexport interface CanonicalSlotInput {\n slotId?: string\n /** Request-local reference for a new server-allocated slot. Never persisted. */\n clientKey?: string\n profileId: string\n agentName: string\n modelOverride?: string | null\n reasoningLevel?: ReasoningLevel | null\n layoutPosition?: { x: number; y: number } | null\n display?: Record<string, unknown> | null\n}\n\nexport interface CanonicalEdgeInput {\n sourceKind: TeamTemplateEdge['sourceKind']\n sourceId?: string | null\n targetKind: TeamTemplateEdge['targetKind']\n targetId?: string | null\n posture?: TeamTemplateEdge['posture']\n}\n\nexport interface CanonicalDefinitionInput {\n expectedRevision: number\n slots: CanonicalSlotInput[]\n edges: CanonicalEdgeInput[]\n /** Daemon-only workflows may pass UUIDs they just allocated. HTTP callers never set this. */\n allowAllocatedSlotIds?: boolean\n}\n\nfunction toTemplate(row: RawTemplate): TeamTemplate {\n return {\n id: row.id,\n name: row.name,\n userMd: row.user_md,\n currentRevision: row.current_revision,\n deletedAt: row.deleted_at,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nfunction toSlot(row: RawSlot): TeamTemplateSlot {\n return {\n templateId: row.template_id,\n slotId: row.slot_id,\n position: row.position,\n profileId: row.profile_id,\n agentName: row.agent_name,\n modelOverride: row.model_override,\n reasoningLevel: row.reasoning_level,\n layoutPosition:\n row.position_x === null || row.position_y === null\n ? null\n : { x: row.position_x, y: row.position_y },\n display: row.display_json ? (JSON.parse(row.display_json) as Record<string, unknown>) : null,\n tombstonedAt: row.tombstoned_at,\n }\n}\n\nfunction toEdge(row: RawEdge): TeamTemplateEdge {\n return {\n templateId: row.template_id,\n sourceKind: row.source_kind,\n sourceId: row.source_id || null,\n targetKind: row.target_kind,\n targetId: row.target_id || null,\n posture: row.posture,\n }\n}\n\nexport function get(db: BazilionDb, id: string): TeamTemplate | null {\n const row = db.raw\n .query<RawTemplate, [string]>('SELECT * FROM team_templates WHERE id = ?')\n .get(id)\n return row ? toTemplate(row) : null\n}\n\nexport function list(db: BazilionDb): TeamTemplate[] {\n return db.raw\n .query<RawTemplate, []>('SELECT * FROM team_templates ORDER BY created_at ASC')\n .all()\n .map(toTemplate)\n}\n\nexport function detail(db: BazilionDb, id: string): TeamTemplateDetail | null {\n const template = get(db, id)\n if (!template) return null\n const currentSnapshot = revision(db, id, template.currentRevision)\n if (!currentSnapshot)\n throw new Error(`template_snapshot_missing: ${id}@${template.currentRevision}`)\n return { template, slots: slots(db, id), edges: edges(db, id), currentSnapshot }\n}\n\nexport function insertCanonical(\n db: BazilionDb,\n input: { id: string; name: string; userMd?: string | null },\n): TeamTemplateDetail {\n const now = Date.now()\n db.raw.transaction(() => {\n db.raw.run(\n `INSERT INTO team_templates\n (id, name, user_md, current_revision, deleted_at, created_at, updated_at)\n VALUES (?, ?, ?, 1, NULL, ?, ?)`,\n [input.id, input.name, input.userMd ?? null, now, now],\n )\n snapshotCurrent(db, input.id, 1, now)\n })()\n return requireDetail(db, input.id)\n}\n\nexport function insertCanonicalDefinition(\n db: BazilionDb,\n input: {\n id: string\n name: string\n userMd?: string | null\n slots: CanonicalSlotInput[]\n edges: CanonicalEdgeInput[]\n },\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n validateCanonicalDefinition(input.slots, input.edges)\n if (input.slots.some((slot) => !slot.slotId)) {\n throw new Error('invalid_template_definition: initial slots require allocated ids')\n }\n const now = Date.now()\n db.raw.run(\n `INSERT INTO team_templates\n (id, name, user_md, current_revision, deleted_at, created_at, updated_at)\n VALUES (?, ?, ?, 1, NULL, ?, ?)`,\n [input.id, input.name, input.userMd ?? null, now, now],\n )\n for (let position = 0; position < input.slots.length; position++) {\n const slot = input.slots[position]\n if (!slot?.slotId) continue\n db.raw.run(\n `INSERT INTO team_template_slots\n (template_id, slot_id, position, profile_id, agent_name, model_override,\n reasoning_level, position_x, position_y, display_json, tombstoned_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n [\n input.id,\n slot.slotId,\n position,\n slot.profileId,\n slot.agentName,\n slot.modelOverride ?? null,\n slot.reasoningLevel ?? null,\n slot.layoutPosition?.x ?? null,\n slot.layoutPosition?.y ?? null,\n slot.display ? JSON.stringify(slot.display) : null,\n ],\n )\n }\n for (const edge of input.edges) insertTemplateEdge(db, input.id, edge)\n snapshotCurrent(db, input.id, 1, now)\n return requireDetail(db, input.id)\n })()\n}\n\nexport function updateCanonicalMetadata(\n db: BazilionDb,\n id: string,\n input: { expectedRevision: number; name?: string; userMd?: string | null },\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n const template = requireMutable(db, id)\n requireRevision(template, input.expectedRevision)\n const name = input.name ?? template.name\n const userMd = Object.hasOwn(input, 'userMd') ? (input.userMd ?? null) : template.userMd\n if (name === template.name && userMd === template.userMd) return requireDetail(db, id)\n const now = Date.now()\n const next = template.currentRevision + 1\n db.raw.run(\n `UPDATE team_templates SET name = ?, user_md = ?, current_revision = ?, updated_at = ?\n WHERE id = ?`,\n [name, userMd, next, now, id],\n )\n snapshotCurrent(db, id, next, now)\n return requireDetail(db, id)\n })()\n}\n\nexport function replaceCanonicalDefinition(\n db: BazilionDb,\n templateId: string,\n input: CanonicalDefinitionInput,\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n const template = requireMutable(db, templateId)\n requireRevision(template, input.expectedRevision)\n const current = slots(db, templateId, { includeTombstoned: true })\n const currentById = new Map(current.map((slot) => [slot.slotId, slot]))\n const activeIds = new Set<string>()\n const resolvedRefs = new Map<string, string>()\n for (const value of input.slots) {\n if (value.slotId && !currentById.has(value.slotId) && !input.allowAllocatedSlotIds) {\n throw new Error(\n `invalid_template_definition: caller cannot allocate slot id ${value.slotId}`,\n )\n }\n if (value.slotId && value.clientKey) {\n throw new Error('invalid_template_definition: slotId and clientKey are mutually exclusive')\n }\n const reference = value.slotId ?? value.clientKey\n if (!reference) continue\n if (resolvedRefs.has(reference)) {\n throw new Error(`invalid_template_definition: duplicate slot reference ${reference}`)\n }\n resolvedRefs.set(reference, value.slotId ?? randomUUID())\n }\n const resolvedEdges = input.edges.map((edge) => ({\n ...edge,\n sourceId:\n edge.sourceKind === 'slot'\n ? (resolvedRefs.get(edge.sourceId ?? '') ?? edge.sourceId)\n : null,\n targetId:\n edge.targetKind === 'slot'\n ? (resolvedRefs.get(edge.targetId ?? '') ?? edge.targetId)\n : null,\n }))\n const resolvedSlots = input.slots.map((slot) => ({\n ...slot,\n slotId: slot.slotId ?? (slot.clientKey ? resolvedRefs.get(slot.clientKey) : randomUUID()),\n }))\n validateCanonicalDefinition(resolvedSlots, resolvedEdges)\n const now = Date.now()\n // Free the partial unique position index before applying an arbitrary reorder.\n db.raw.run(\n `UPDATE team_template_slots SET position = position + 1000000\n WHERE template_id = ? AND tombstoned_at IS NULL`,\n [templateId],\n )\n for (let position = 0; position < resolvedSlots.length; position++) {\n const value = resolvedSlots[position]\n if (!value) continue\n const slotId = value.slotId ?? randomUUID()\n if (activeIds.has(slotId))\n throw new Error(`invalid_template_definition: duplicate slot ${slotId}`)\n activeIds.add(slotId)\n const existing = currentById.get(slotId)\n if (existing?.tombstonedAt !== null && existing) {\n throw new Error(\n `invalid_template_definition: tombstoned slot cannot be re-added: ${slotId}`,\n )\n }\n const coordinates = value.layoutPosition ?? null\n const params = [\n position,\n value.profileId,\n value.agentName,\n value.modelOverride ?? null,\n value.reasoningLevel ?? null,\n coordinates?.x ?? null,\n coordinates?.y ?? null,\n value.display ? JSON.stringify(value.display) : null,\n ]\n if (existing) {\n db.raw.run(\n `UPDATE team_template_slots SET position = ?, profile_id = ?, agent_name = ?,\n model_override = ?, reasoning_level = ?, position_x = ?, position_y = ?, display_json = ?\n WHERE template_id = ? AND slot_id = ?`,\n [...params, templateId, slotId],\n )\n } else {\n db.raw.run(\n `INSERT INTO team_template_slots\n (template_id, slot_id, position, profile_id, agent_name, model_override,\n reasoning_level, position_x, position_y, display_json, tombstoned_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n [templateId, slotId, ...params],\n )\n }\n }\n for (const old of current) {\n if (old.tombstonedAt === null && !activeIds.has(old.slotId)) {\n db.raw.run(\n 'UPDATE team_template_slots SET tombstoned_at = ? WHERE template_id = ? AND slot_id = ?',\n [now, templateId, old.slotId],\n )\n }\n }\n db.raw.run('DELETE FROM team_template_edges WHERE template_id = ?', [templateId])\n for (const edge of resolvedEdges) insertTemplateEdge(db, templateId, edge)\n const next = template.currentRevision + 1\n db.raw.run(`UPDATE team_templates SET current_revision = ?, updated_at = ? WHERE id = ?`, [\n next,\n now,\n templateId,\n ])\n snapshotCurrent(db, templateId, next, now)\n return requireDetail(db, templateId)\n })()\n}\n\nexport function cloneCanonical(\n db: BazilionDb,\n sourceId: string,\n input: { expectedRevision: number; id: string; name?: string },\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n const source = requireMutable(db, sourceId)\n requireRevision(source, input.expectedRevision)\n const snapshot = revision(db, sourceId, input.expectedRevision)\n if (!snapshot)\n throw new Error(`template_snapshot_missing: ${sourceId}@${input.expectedRevision}`)\n const translated = new Map(snapshot.slots.map((slot) => [slot.slotId, randomUUID()]))\n return insertCanonicalDefinition(db, {\n id: input.id,\n name: input.name ?? `${source.name} copy`,\n userMd: snapshot.userMd,\n slots: snapshot.slots.map((slot) => ({\n slotId: translated.get(slot.slotId),\n profileId: slot.profileId,\n agentName: slot.agentName,\n modelOverride: slot.modelOverride,\n reasoningLevel: slot.reasoningLevel,\n layoutPosition: slot.layoutPosition,\n display: slot.display,\n })),\n edges: snapshot.edges.map((edge) => ({\n sourceKind: edge.sourceKind,\n sourceId: edge.sourceKind === 'slot' ? translated.get(edge.sourceId ?? '') : null,\n targetKind: edge.targetKind,\n targetId: edge.targetKind === 'slot' ? translated.get(edge.targetId ?? '') : null,\n })),\n })\n })()\n}\n\nexport function removeCanonical(\n db: BazilionDb,\n id: string,\n expectedRevision: number,\n): 'deleted' | 'tombstoned' {\n return db.raw.transaction(() => {\n const template = requireMutable(db, id)\n requireRevision(template, expectedRevision)\n const lineage = db.raw\n .query<{ count: number }, [string]>(\n 'SELECT COUNT(*) AS count FROM template_instantiations WHERE template_id = ?',\n )\n .get(id)?.count\n if ((lineage ?? 0) > 0) {\n const now = Date.now()\n db.raw.run('UPDATE team_templates SET deleted_at = ?, updated_at = ? WHERE id = ?', [\n now,\n now,\n id,\n ])\n return 'tombstoned'\n }\n db.raw.run('DELETE FROM team_templates WHERE id = ?', [id])\n return 'deleted'\n })()\n}\n\nexport function slots(\n db: BazilionDb,\n templateId: string,\n options: { includeTombstoned?: boolean } = {},\n): TeamTemplateSlot[] {\n const where = options.includeTombstoned ? '' : 'AND tombstoned_at IS NULL'\n return db.raw\n .query<RawSlot, [string]>(\n `SELECT * FROM team_template_slots\n WHERE template_id = ? ${where}\n ORDER BY position ASC, slot_id ASC`,\n )\n .all(templateId)\n .map(toSlot)\n}\n\nexport function edges(db: BazilionDb, templateId: string): TeamTemplateEdge[] {\n return db.raw\n .query<RawEdge, [string]>(\n `SELECT * FROM team_template_edges WHERE template_id = ?\n ORDER BY source_kind, source_id, target_kind, target_id`,\n )\n .all(templateId)\n .map(toEdge)\n}\n\nexport function revision(\n db: BazilionDb,\n templateId: string,\n revisionNumber: number,\n): TeamTemplateRevision | null {\n const header = db.raw\n .query<{ name: string; user_md: string | null; created_at: number }, [string, number]>(\n `SELECT name, user_md, created_at FROM team_template_revisions\n WHERE template_id = ? AND revision = ?`,\n )\n .get(templateId, revisionNumber)\n if (!header) return null\n const revisionSlots = db.raw\n .query<RawSlot, [string, number]>(\n `SELECT template_id, slot_id, position, profile_id, agent_name, model_override,\n reasoning_level, position_x, position_y, display_json, NULL AS tombstoned_at\n FROM team_template_revision_slots\n WHERE template_id = ? AND revision = ? ORDER BY position ASC`,\n )\n .all(templateId, revisionNumber)\n .map(toSlot)\n const revisionEdges = db.raw\n .query<RawEdge, [string, number]>(\n `SELECT template_id, source_kind, source_id, target_kind, target_id, posture\n FROM team_template_revision_edges\n WHERE template_id = ? AND revision = ?\n ORDER BY source_kind, source_id, target_kind, target_id`,\n )\n .all(templateId, revisionNumber)\n .map(toEdge)\n return {\n templateId,\n revision: revisionNumber,\n name: header.name,\n userMd: header.user_md,\n slots: revisionSlots,\n edges: revisionEdges,\n createdAt: header.created_at,\n }\n}\n\nfunction snapshotCurrent(\n db: BazilionDb,\n templateId: string,\n revisionNumber: number,\n now: number,\n): void {\n const template = get(db, templateId)\n if (!template) throw new Error(`team template not found: ${templateId}`)\n db.raw.run(\n `INSERT INTO team_template_revisions (template_id, revision, name, user_md, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n [templateId, revisionNumber, template.name, template.userMd, now],\n )\n db.raw.run(\n `INSERT INTO team_template_revision_slots\n SELECT template_id, ?, slot_id, position, profile_id, agent_name, model_override,\n reasoning_level, position_x, position_y, display_json\n FROM team_template_slots WHERE template_id = ? AND tombstoned_at IS NULL`,\n [revisionNumber, templateId],\n )\n db.raw.run(\n `INSERT INTO team_template_revision_edges\n SELECT template_id, ?, source_kind, source_id, target_kind, target_id, posture\n FROM team_template_edges WHERE template_id = ?`,\n [revisionNumber, templateId],\n )\n}\n\nexport function pruneRevision(db: BazilionDb, templateId: string, revisionNumber: number): void {\n const template = get(db, templateId)\n if (!template) throw new Error(`team template not found: ${templateId}`)\n if (template.currentRevision === revisionNumber) {\n throw new Error(`template_revision_in_use: revision ${revisionNumber} is current`)\n }\n const retained = db.raw\n .query<{ count: number }, [string, number]>(\n `SELECT COUNT(*) AS count FROM template_instantiations\n WHERE template_id = ? AND template_revision = ?`,\n )\n .get(templateId, revisionNumber)?.count\n if ((retained ?? 0) > 0) {\n throw new Error(`template_revision_in_use: revision ${revisionNumber} has live lineage`)\n }\n db.raw.run('DELETE FROM team_template_revisions WHERE template_id = ? AND revision = ?', [\n templateId,\n revisionNumber,\n ])\n}\n\nexport function findReferencingProfile(\n db: BazilionDb,\n profileId: string,\n): Array<{ id: string; name: string }> {\n return db.raw\n .query<{ id: string; name: string }, [string, string]>(\n `SELECT DISTINCT t.id, t.name FROM team_templates t\n WHERE EXISTS (\n SELECT 1 FROM team_template_slots s\n WHERE s.template_id = t.id AND s.profile_id = ?\n ) OR EXISTS (\n SELECT 1 FROM team_template_revision_slots s\n WHERE s.template_id = t.id AND s.profile_id = ?\n )`,\n )\n .all(profileId, profileId)\n}\n\nfunction requireDetail(db: BazilionDb, id: string): TeamTemplateDetail {\n const found = detail(db, id)\n if (!found) throw new Error(`team template not found: ${id}`)\n return found\n}\n\nfunction requireMutable(db: BazilionDb, id: string): TeamTemplate {\n const template = get(db, id)\n if (!template) throw new Error(`team template not found: ${id}`)\n if (template.deletedAt !== null) throw new Error(`template_deleted: ${id}`)\n return template\n}\n\nfunction requireRevision(template: TeamTemplate, expected: number): void {\n if (!Number.isInteger(expected) || expected < 1 || expected !== template.currentRevision) {\n throw new Error(\n `template_revision_conflict: expected ${expected}, current ${template.currentRevision}`,\n )\n }\n}\n\nfunction validateCanonicalDefinition(\n definitionSlots: CanonicalSlotInput[],\n definitionEdges: CanonicalEdgeInput[],\n): void {\n if (!Array.isArray(definitionSlots) || !Array.isArray(definitionEdges)) {\n throw new Error('invalid_template_definition: slots and edges arrays are required')\n }\n const ids = new Set(definitionSlots.map((slot) => slot.slotId).filter(Boolean) as string[])\n if (ids.size !== definitionSlots.filter((slot) => slot.slotId).length) {\n throw new Error('invalid_template_definition: duplicate slot id')\n }\n const edgeKeys = new Set<string>()\n for (const edge of definitionEdges) {\n if (\n edge.posture !== undefined &&\n edge.posture !== 'allow' &&\n edge.posture !== 'approval_required'\n ) {\n throw new Error(`invalid_template_definition: invalid edge posture ${edge.posture}`)\n }\n for (const endpoint of [\n [edge.sourceKind, edge.sourceId],\n [edge.targetKind, edge.targetId],\n ] as const) {\n const [kind, id] = endpoint\n if (!['user', 'outside_team', 'slot'].includes(kind)) {\n throw new Error(`invalid_template_definition: invalid endpoint kind ${kind}`)\n }\n if ((kind === 'slot') !== (typeof id === 'string' && id.length > 0)) {\n throw new Error('invalid_template_definition: slot endpoints require an id')\n }\n if (kind === 'slot' && !ids.has(id ?? '')) {\n throw new Error(`invalid_template_definition: unknown slot endpoint ${id}`)\n }\n }\n if (edge.sourceKind !== 'slot' && edge.targetKind !== 'slot') {\n throw new Error('invalid_template_definition: boundary-to-boundary edge')\n }\n if (edge.sourceKind === edge.targetKind && edge.sourceId === edge.targetId) {\n throw new Error('invalid_template_definition: self edge')\n }\n const key = `${edge.sourceKind}:${edge.sourceId ?? ''}>${edge.targetKind}:${edge.targetId ?? ''}`\n if (edgeKeys.has(key)) throw new Error(`invalid_template_definition: duplicate edge ${key}`)\n edgeKeys.add(key)\n }\n}\n\nfunction insertTemplateEdge(db: BazilionDb, templateId: string, edge: CanonicalEdgeInput): void {\n db.raw.run(\n `INSERT INTO team_template_edges\n (template_id, source_kind, source_id, target_kind, target_id, posture)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [\n templateId,\n edge.sourceKind,\n edge.sourceId ?? '',\n edge.targetKind,\n edge.targetId ?? '',\n edge.posture ?? 'allow',\n ],\n )\n}\n","import { writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Profile, ProfileCommunicationDefaults, SkillsMode } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as profileCommunicationDefaultsRepo from '../repos/profileCommunicationDefaults.ts'\nimport * as profileRepo from '../repos/profiles.ts'\n\nexport interface UpdateProfileInput {\n name?: string\n defaultModel?: string\n skillsMode?: SkillsMode\n defaultSkills?: string[]\n communicationDefaults?: ProfileCommunicationDefaults | null\n}\n\n/**\n * Update the mutable settings in profile.json + the DB row in lockstep.\n * Profile files (SOUL/IDENTITY/BOOTSTRAP) and the memory backend are NOT\n * touched — those have their own paths.\n */\nexport function updateProfile(\n db: BazilionDb,\n paths: Paths,\n id: string,\n input: UpdateProfileInput,\n): Profile {\n const existing = profileRepo.get(db, id)\n if (!existing) throw new Error(`profile not found: ${id}`)\n\n const nextSkillsMode: SkillsMode = input.skillsMode ?? existing.skillsMode\n\n const next = {\n name: input.name ?? existing.name,\n defaultModel: input.defaultModel ?? existing.defaultModel,\n skillsMode: nextSkillsMode,\n }\n profileRepo.update(db, id, next)\n\n if (input.defaultSkills !== undefined) {\n profileRepo.setDefaultSkills(db, id, input.defaultSkills)\n }\n if (Object.hasOwn(input, 'communicationDefaults')) {\n profileCommunicationDefaultsRepo.set(db, id, input.communicationDefaults ?? null)\n }\n\n const skills = profileRepo.getDefaultSkills(db, id)\n const profileJson = {\n name: next.name,\n defaultModel: next.defaultModel,\n skillsMode: next.skillsMode,\n defaultSkills: skills,\n }\n writeFileSync(\n join(paths.profileDir(id), 'profile.json'),\n `${JSON.stringify(profileJson, null, 2)}\\n`,\n )\n\n const updated = profileRepo.get(db, id)\n if (!updated) throw new Error(`profile vanished after update: ${id}`)\n return {\n ...updated,\n communicationDefaults: profileCommunicationDefaultsRepo.get(db, id),\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { AgentReview, AgentReviewStatus, AgentReviewTrigger } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawReview {\n id: string\n agent_id: string\n status: string\n trigger_kind: string\n source_session_id: string | null\n source_start_ordinal: number | null\n source_end_ordinal: number | null\n input_characters: number\n turns_reviewed: number\n proposal_count: number\n attempt_count: number\n next_attempt_at: number\n lease_expires_at: number | null\n started_at: number | null\n finished_at: number | null\n last_error: string | null\n created_at: number\n updated_at: number\n}\n\nfunction toReview(row: RawReview): AgentReview {\n return {\n id: row.id,\n agentId: row.agent_id,\n status: row.status as AgentReviewStatus,\n trigger: row.trigger_kind as AgentReviewTrigger,\n sourceSessionId: row.source_session_id,\n sourceStartOrdinal: row.source_start_ordinal,\n sourceEndOrdinal: row.source_end_ordinal,\n inputCharacters: row.input_characters,\n turnsReviewed: row.turns_reviewed,\n proposalCount: row.proposal_count,\n attemptCount: row.attempt_count,\n nextAttemptAt: row.next_attempt_at,\n leaseExpiresAt: row.lease_expires_at,\n startedAt: row.started_at,\n finishedAt: row.finished_at,\n lastError: row.last_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nfunction insert(\n db: BazilionDb,\n agentId: string,\n trigger: AgentReviewTrigger,\n now: number,\n): AgentReview {\n const id = randomUUID()\n db.raw.run(\n `INSERT INTO agent_reviews\n (id, agent_id, status, trigger_kind, input_characters, turns_reviewed,\n proposal_count, attempt_count, next_attempt_at, created_at, updated_at)\n VALUES (?, ?, 'pending', ?, 0, 0, 0, 0, ?, ?, ?)`,\n [id, agentId, trigger, now, now, now],\n )\n const review = get(db, id)\n if (!review) throw new Error('agent review enqueue failed')\n return review\n}\n\nexport function get(db: BazilionDb, id: string): AgentReview | null {\n const row = db.raw.query<RawReview, [string]>('SELECT * FROM agent_reviews WHERE id = ?').get(id)\n return row ? toReview(row) : null\n}\n\nexport function getOpenForAgent(db: BazilionDb, agentId: string): AgentReview | null {\n const row = db.raw\n .query<RawReview, [string]>(\n `SELECT * FROM agent_reviews WHERE agent_id = ?\n AND status IN ('pending','running','retrying') LIMIT 1`,\n )\n .get(agentId)\n return row ? toReview(row) : null\n}\n\nexport function enqueueManual(db: BazilionDb, agentId: string, now = Date.now()): AgentReview {\n return db.raw.transaction(\n () => getOpenForAgent(db, agentId) ?? insert(db, agentId, 'manual', now),\n )()\n}\n\n/** Count one successful interactive turn and atomically enqueue when cadence becomes due. */\nexport function recordSuccessfulUserTurn(\n db: BazilionDb,\n agentId: string,\n now = Date.now(),\n): AgentReview | null {\n return db.raw.transaction(() => {\n const updated = db.raw.run(\n `UPDATE agents SET review_turns_since_last = review_turns_since_last + 1\n WHERE id = ? AND review_enabled = 1`,\n [agentId],\n )\n if (Number(updated.changes) !== 1) return null\n const state = db.raw\n .query<{ review_turns_since_last: number; review_every_n_turns: number }, [string]>(\n `SELECT review_turns_since_last, review_every_n_turns FROM agents WHERE id = ?`,\n )\n .get(agentId)\n if (!state || state.review_turns_since_last < state.review_every_n_turns) return null\n const open = getOpenForAgent(db, agentId)\n if (open) return open\n const review = insert(db, agentId, 'cadence', now)\n db.raw.run('UPDATE agents SET review_turns_since_last = 0 WHERE id = ?', [agentId])\n return review\n })()\n}\n\nexport function listClaimable(db: BazilionDb, now = Date.now()): AgentReview[] {\n return db.raw\n .query<RawReview, [number, number]>(\n `SELECT * FROM agent_reviews\n WHERE (status IN ('pending','retrying') AND next_attempt_at <= ?)\n OR (status = 'running' AND lease_expires_at <= ?)\n ORDER BY created_at ASC`,\n )\n .all(now, now)\n .map(toReview)\n}\n\nexport function claim(\n db: BazilionDb,\n id: string,\n options: { now?: number; leaseMs?: number } = {},\n): AgentReview | null {\n const now = options.now ?? Date.now()\n const leaseExpiresAt = now + (options.leaseMs ?? 5 * 60_000)\n return db.raw.transaction(() => {\n const result = db.raw.run(\n `UPDATE agent_reviews SET status = 'running', attempt_count = attempt_count + 1,\n lease_expires_at = ?, started_at = ?, finished_at = NULL, updated_at = ?\n WHERE id = ? AND (\n (status IN ('pending','retrying') AND next_attempt_at <= ?)\n OR (status = 'running' AND lease_expires_at <= ?)\n )`,\n [leaseExpiresAt, now, now, id, now, now],\n )\n return Number(result.changes) === 1 ? get(db, id) : null\n })()\n}\n\nexport function defer(db: BazilionDb, id: string, nextAttemptAt = Date.now() + 1_000): void {\n db.raw.run(\n `UPDATE agent_reviews SET status = 'pending',\n attempt_count = CASE WHEN attempt_count > 0 THEN attempt_count - 1 ELSE 0 END,\n next_attempt_at = ?, lease_expires_at = NULL, started_at = NULL, updated_at = ?\n WHERE id = ? AND status = 'running'`,\n [nextAttemptAt, Date.now(), id],\n )\n}\n\nexport function getLatestCompleted(db: BazilionDb, agentId: string): AgentReview | null {\n const row = db.raw\n .query<RawReview, [string]>(\n `SELECT * FROM agent_reviews WHERE agent_id = ? AND status = 'completed'\n ORDER BY finished_at DESC LIMIT 1`,\n )\n .get(agentId)\n return row ? toReview(row) : null\n}\n\nexport function setSource(\n db: BazilionDb,\n id: string,\n source: {\n sessionId: string\n startOrdinal: number\n endOrdinal: number\n inputCharacters: number\n turnsReviewed: number\n },\n): void {\n db.raw.run(\n `UPDATE agent_reviews SET source_session_id = ?, source_start_ordinal = ?,\n source_end_ordinal = ?, input_characters = ?, turns_reviewed = ?, updated_at = ?\n WHERE id = ? AND status = 'running'`,\n [\n source.sessionId,\n source.startOrdinal,\n source.endOrdinal,\n source.inputCharacters,\n source.turnsReviewed,\n Date.now(),\n id,\n ],\n )\n}\n\nexport function complete(\n db: BazilionDb,\n id: string,\n proposalCount: number,\n now = Date.now(),\n): void {\n db.raw.run(\n `UPDATE agent_reviews SET status = 'completed', proposal_count = ?, lease_expires_at = NULL,\n finished_at = ?, last_error = NULL, updated_at = ? WHERE id = ? AND status = 'running'`,\n [proposalCount, now, now, id],\n )\n}\n\nexport function cancel(db: BazilionDb, id: string, reason: string, now = Date.now()): void {\n db.raw.run(\n `UPDATE agent_reviews SET status = 'cancelled', lease_expires_at = NULL, finished_at = ?,\n last_error = ?, updated_at = ? WHERE id = ? AND status IN ('pending','running','retrying')`,\n [now, reason.slice(0, 2_000), now, id],\n )\n}\n\nexport function fail(\n db: BazilionDb,\n id: string,\n error: string,\n options: { now?: number; maxAttempts?: number; retryDelayMs?: number } = {},\n): void {\n const now = options.now ?? Date.now()\n const current = get(db, id)\n if (current?.status !== 'running') return\n const terminal = current.attemptCount >= (options.maxAttempts ?? 3)\n const retryDelay = (options.retryDelayMs ?? 5_000) * 2 ** Math.max(0, current.attemptCount - 1)\n db.raw.run(\n `UPDATE agent_reviews SET status = ?, next_attempt_at = ?, lease_expires_at = NULL,\n finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,\n [\n terminal ? 'failed' : 'retrying',\n terminal ? now : now + retryDelay,\n terminal ? now : null,\n error.slice(0, 2_000),\n now,\n id,\n ],\n )\n}\n\nexport function listForAgent(db: BazilionDb, agentId: string, limit = 50): AgentReview[] {\n return db.raw\n .query<RawReview, [string, number]>(\n 'SELECT * FROM agent_reviews WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?',\n )\n .all(agentId, limit)\n .map(toReview)\n}\n","import { createHash, randomUUID } from 'node:crypto'\nimport type {\n CommunicationApproval,\n CommunicationApprovalDetail,\n CommunicationApprovalEvent,\n CommunicationApprovalStatus,\n CommunicationAuthorizationResult,\n CommunicationEndpoint,\n} from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { AuthorizationInput } from '../team-policy/authorization.ts'\n\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1_000\n\ninterface RawApproval {\n id: string\n attempt_kind: string\n attempt_id: string\n fingerprint: string\n operation: string\n source_kind: CommunicationEndpoint['kind']\n source_id: string\n target_kind: CommunicationEndpoint['kind']\n target_id: string\n source_team_id: string | null\n target_team_id: string | null\n channel: CommunicationApproval['channel']\n origin: string\n requester: string\n policy_refs_json: string\n required_edge_ids_json: string\n payload_kind: string\n payload_json: string\n status: CommunicationApprovalStatus\n expires_at: number\n decided_at: number | null\n decided_by: string | null\n decision_reason: string | null\n delivery_error: string | null\n created_at: number\n updated_at: number\n}\n\ninterface RawEvent {\n id: string\n approval_id: string\n event: CommunicationApprovalEvent['event']\n actor: string\n detail: string | null\n created_at: number\n}\n\nfunction endpoint(kind: CommunicationEndpoint['kind'], id: string, teamId: string | null) {\n if (kind === 'agent') return { kind, id } as const\n return { kind, teamId: teamId ?? '__missing__' } as const\n}\n\nfunction toApproval(row: RawApproval): CommunicationApproval {\n return {\n id: row.id,\n attemptKind: row.attempt_kind,\n attemptId: row.attempt_id,\n operation: row.operation,\n source: endpoint(row.source_kind, row.source_id, row.source_team_id),\n target: endpoint(row.target_kind, row.target_id, row.target_team_id),\n sourceTeamId: row.source_team_id,\n targetTeamId: row.target_team_id,\n channel: row.channel,\n origin: row.origin,\n requester: row.requester,\n policyRefs: JSON.parse(row.policy_refs_json),\n requiredEdgeIds: JSON.parse(row.required_edge_ids_json),\n payloadKind: row.payload_kind,\n status: row.status,\n expiresAt: row.expires_at,\n decidedAt: row.decided_at,\n decidedBy: row.decided_by,\n decisionReason: row.decision_reason,\n deliveryError: row.delivery_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nfunction event(row: RawEvent): CommunicationApprovalEvent {\n return {\n id: row.id,\n approvalId: row.approval_id,\n event: row.event,\n actor: row.actor,\n detail: row.detail,\n createdAt: row.created_at,\n }\n}\n\nfunction raw(db: BazilionDb, id: string): RawApproval | null {\n return db.raw\n .query<RawApproval, [string]>('SELECT * FROM communication_approvals WHERE id = ?')\n .get(id)\n}\n\nfunction rawByAttempt(db: BazilionDb, kind: string, id: string): RawApproval | null {\n return db.raw\n .query<RawApproval, [string, string]>(\n 'SELECT * FROM communication_approvals WHERE attempt_kind = ? AND attempt_id = ?',\n )\n .get(kind, id)\n}\n\nfunction appendEvent(\n db: BazilionDb,\n approvalId: string,\n name: CommunicationApprovalEvent['event'],\n actor: string,\n detail: string | null = null,\n now = Date.now(),\n): void {\n db.raw.run(\n `INSERT INTO communication_approval_events\n (id, approval_id, event, actor, detail, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [randomUUID(), approvalId, name, actor, detail, now],\n )\n}\n\nfunction teams(db: BazilionDb, input: AuthorizationInput): [string | null, string | null] {\n const team = (value: CommunicationEndpoint): string | null =>\n value.kind === 'agent'\n ? (db.raw\n .query<{ team_id: string }, [string]>('SELECT team_id FROM agents WHERE id = ?')\n .get(value.id)?.team_id ?? null)\n : value.teamId\n const source = team(input.source) ?? team(input.target)\n const target = team(input.target) ?? team(input.source)\n return [source, target]\n}\n\nfunction fingerprint(\n input: AuthorizationInput,\n operation: string,\n payloadKind: string,\n payload: unknown,\n): string {\n return createHash('sha256')\n .update(JSON.stringify({ input, operation, payloadKind, payload }))\n .digest('hex')\n}\n\nexport function request(\n db: BazilionDb,\n input: AuthorizationInput,\n operation: string,\n authorization: CommunicationAuthorizationResult,\n payloadKind: string,\n payload: unknown,\n options: { requester?: string; ttlMs?: number; now?: number } = {},\n): CommunicationApproval {\n if (authorization.decision !== 'approval_required')\n throw new Error('approval_request_invalid: authorization does not require approval')\n const now = options.now ?? Date.now()\n const digest = fingerprint(input, operation, payloadKind, payload)\n try {\n return db.raw.transaction(() => {\n const existing = rawByAttempt(db, input.attemptKind, input.attemptId)\n if (existing) {\n if (existing.fingerprint !== digest)\n throw new Error('approval_attempt_conflict: attempt identity has different semantics')\n expireOne(db, existing, now)\n return toApproval(raw(db, existing.id) as RawApproval)\n }\n const id = randomUUID()\n const [sourceGroup, targetGroup] = teams(db, input)\n const expiresAt = now + (options.ttlMs ?? DEFAULT_TTL_MS)\n db.raw.run(\n `INSERT INTO communication_approvals\n (id, attempt_kind, attempt_id, fingerprint, operation, source_kind, source_id,\n target_kind, target_id, source_team_id, target_team_id, channel, origin,\n requester, policy_refs_json, required_edge_ids_json, payload_kind, payload_json,\n status, expires_at, decided_at, decided_by, decision_reason, delivery_error,\n created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?,\n NULL, NULL, NULL, NULL, ?, ?)`,\n [\n id,\n input.attemptKind,\n input.attemptId,\n digest,\n operation,\n input.source.kind,\n input.source.kind === 'agent' ? input.source.id : '',\n input.target.kind,\n input.target.kind === 'agent' ? input.target.id : '',\n sourceGroup,\n targetGroup,\n authorization.channel,\n input.origin,\n options.requester ?? input.origin,\n JSON.stringify(authorization.policyRefs),\n JSON.stringify(authorization.requiredEdgeIds),\n payloadKind,\n JSON.stringify(payload),\n expiresAt,\n now,\n now,\n ],\n )\n appendEvent(db, id, 'requested', options.requester ?? input.origin, null, now)\n return toApproval(raw(db, id) as RawApproval)\n })()\n } catch (error) {\n const raced = rawByAttempt(db, input.attemptKind, input.attemptId)\n if (!raced) throw error\n if (raced.fingerprint !== digest)\n throw new Error('approval_attempt_conflict: attempt identity has different semantics')\n return toApproval(raced)\n }\n}\n\nfunction expireOne(db: BazilionDb, row: RawApproval, now: number): boolean {\n if (row.status !== 'pending' || row.expires_at > now) return false\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'expired', decided_at = ?, decided_by = 'system',\n decision_reason = 'approval expired', updated_at = ?\n WHERE id = ? AND status = 'pending' AND expires_at <= ?`,\n [now, now, row.id, now],\n ).changes\n if (changed) appendEvent(db, row.id, 'expired', 'system', 'approval expired', now)\n return changed > 0\n}\n\nexport function expirePending(db: BazilionDb, now = Date.now()): number {\n return db.raw.transaction(() => {\n const rows = db.raw\n .query<RawApproval, [number]>(\n \"SELECT * FROM communication_approvals WHERE status = 'pending' AND expires_at <= ?\",\n )\n .all(now)\n for (const row of rows) expireOne(db, row, now)\n return rows.length\n })()\n}\n\nexport function get(\n db: BazilionDb,\n id: string,\n includePayload = false,\n): CommunicationApprovalDetail | CommunicationApproval | null {\n expirePending(db)\n const row = raw(db, id)\n if (!row) return null\n const approval = toApproval(row)\n if (!includePayload) return approval\n return detailFromRow(db, row)\n}\n\nexport function getByAttempt(\n db: BazilionDb,\n attemptKind: string,\n attemptId: string,\n includePayload = false,\n): CommunicationApprovalDetail | CommunicationApproval | null {\n expirePending(db)\n const row = rawByAttempt(db, attemptKind, attemptId)\n if (!row) return null\n return includePayload ? detailFromRow(db, row) : toApproval(row)\n}\n\nfunction detailFromRow(db: BazilionDb, row: RawApproval): CommunicationApprovalDetail {\n const events = db.raw\n .query<RawEvent, [string]>(\n 'SELECT * FROM communication_approval_events WHERE approval_id = ? ORDER BY created_at, rowid',\n )\n .all(row.id)\n .map(event)\n return { ...toApproval(row), payload: JSON.parse(row.payload_json), events }\n}\n\nexport function list(\n db: BazilionDb,\n filters: { status?: CommunicationApprovalStatus; teamId?: string; limit?: number } = {},\n): CommunicationApproval[] {\n expirePending(db)\n const limit = Math.min(Math.max(filters.limit ?? 50, 1), 100)\n return db.raw\n .query<RawApproval, [string, string, string, string, string, number]>(\n `SELECT * FROM communication_approvals\n WHERE (? = '' OR status = ?)\n AND (? = '' OR source_team_id = ? OR target_team_id = ?)\n ORDER BY created_at DESC, id DESC LIMIT ?`,\n )\n .all(\n filters.status ?? '',\n filters.status ?? '',\n filters.teamId ?? '',\n filters.teamId ?? '',\n filters.teamId ?? '',\n limit,\n )\n .map(toApproval)\n}\n\nexport function decide(\n db: BazilionDb,\n id: string,\n decision: 'deny' | 'cancel',\n actor: string,\n reason?: string,\n now = Date.now(),\n): CommunicationApproval {\n expirePending(db, now)\n return db.raw.transaction(() => {\n const row = raw(db, id)\n if (!row) throw new Error(`approval_not_found: ${id}`)\n const current = raw(db, id) as RawApproval\n if (current.status !== 'pending')\n throw new Error(`approval_state_conflict: current ${current.status}`)\n const status = decision === 'deny' ? 'denied' : 'cancelled'\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = ?, decided_at = ?, decided_by = ?, decision_reason = ?, updated_at = ?\n WHERE id = ? AND status = 'pending'`,\n [status, now, actor, reason ?? null, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent decision')\n appendEvent(db, id, decision === 'deny' ? 'denied' : 'cancelled', actor, reason ?? null, now)\n return toApproval(raw(db, id) as RawApproval)\n })()\n}\n\nexport function claimDelivery(\n db: BazilionDb,\n id: string,\n actor: string,\n revalidate: (row: CommunicationApprovalDetail) => CommunicationAuthorizationResult,\n now = Date.now(),\n): CommunicationApprovalDetail {\n expirePending(db, now)\n const outcome = db.raw.transaction(\n (): { detail?: CommunicationApprovalDetail; revalidationFailed?: true } => {\n const rawCurrent = raw(db, id)\n if (!rawCurrent) throw new Error(`approval_not_found: ${id}`)\n const detail = detailFromRow(db, raw(db, id) as RawApproval)\n if (detail.status !== 'pending')\n throw new Error(`approval_state_conflict: current ${detail.status}`)\n const authorization = revalidate(detail)\n const refsMatch =\n JSON.stringify(authorization.policyRefs) === JSON.stringify(detail.policyRefs) &&\n JSON.stringify(authorization.requiredEdgeIds) === JSON.stringify(detail.requiredEdgeIds)\n if (authorization.decision !== 'approval_required' || !refsMatch) {\n db.raw.run(\n `UPDATE communication_approvals\n SET status = 'denied', decided_at = ?, decided_by = ?, decision_reason = ?,\n updated_at = ? WHERE id = ? AND status = 'pending'`,\n [now, actor, 'policy or membership changed', now, id],\n )\n appendEvent(db, id, 'denied', actor, 'policy or membership changed', now)\n return { revalidationFailed: true }\n }\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'delivering', decided_at = ?, decided_by = ?, updated_at = ?\n WHERE id = ? AND status = 'pending'`,\n [now, actor, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent approval')\n appendEvent(db, id, 'approved', actor, null, now)\n appendEvent(db, id, 'delivery_started', actor, null, now)\n return {\n detail: {\n ...detail,\n status: 'delivering' as const,\n decidedAt: now,\n decidedBy: actor,\n },\n }\n },\n )()\n if (outcome.revalidationFailed)\n throw new Error('approval_revalidation_failed: policy or membership changed')\n if (!outcome.detail) throw new Error('approval_state_conflict: delivery claim failed')\n return outcome.detail\n}\n\nexport interface SchedulerApprovalGrantResult {\n approval: CommunicationApproval\n granted: boolean\n failureKind?: 'revalidation' | 'delivery'\n error?: string\n}\n\n/**\n * Convert a scheduler approval into a durable grant without executing the\n * Agent turn in the HTTP request. The scheduler remains the only owner of\n * dispatch leases, retries, restart recovery, and final execution status.\n */\nexport function grantSchedulerTrigger(\n db: BazilionDb,\n id: string,\n actor: string,\n revalidate: (row: CommunicationApprovalDetail) => CommunicationAuthorizationResult,\n validateDelivery: (row: CommunicationApprovalDetail) => string | null,\n now = Date.now(),\n): SchedulerApprovalGrantResult {\n expirePending(db, now)\n return db.raw.transaction(() => {\n const current = raw(db, id)\n if (!current) throw new Error(`approval_not_found: ${id}`)\n const detail = detailFromRow(db, current)\n if (detail.status !== 'pending')\n throw new Error(`approval_state_conflict: current ${detail.status}`)\n if (detail.operation !== 'scheduler_trigger' || detail.payloadKind !== 'scheduler_trigger') {\n throw new Error('approval_payload_conflict: not a scheduler trigger approval')\n }\n\n const authorization = revalidate(detail)\n const refsMatch =\n JSON.stringify(authorization.policyRefs) === JSON.stringify(detail.policyRefs) &&\n JSON.stringify(authorization.requiredEdgeIds) === JSON.stringify(detail.requiredEdgeIds)\n if (authorization.decision !== 'approval_required' || !refsMatch) {\n const error = 'policy or membership changed'\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'denied', decided_at = ?, decided_by = ?, decision_reason = ?,\n updated_at = ? WHERE id = ? AND status = 'pending'`,\n [now, actor, error, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent approval')\n appendEvent(db, id, 'denied', actor, error, now)\n return {\n approval: toApproval(raw(db, id) as RawApproval),\n granted: false,\n failureKind: 'revalidation' as const,\n error,\n }\n }\n\n let deliveryError: string | null = null\n try {\n deliveryError = validateDelivery(detail)\n } catch (error) {\n deliveryError = error instanceof Error ? error.message : String(error)\n }\n if (deliveryError) {\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'delivery_failed', decided_at = ?, decided_by = ?,\n delivery_error = ?, updated_at = ?\n WHERE id = ? AND status = 'pending'`,\n [now, actor, deliveryError, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent approval')\n appendEvent(db, id, 'approved', actor, null, now)\n appendEvent(db, id, 'delivery_started', actor, null, now)\n appendEvent(db, id, 'delivery_failed', actor, deliveryError, now)\n return {\n approval: toApproval(raw(db, id) as RawApproval),\n granted: false,\n failureKind: 'delivery' as const,\n error: deliveryError,\n }\n }\n\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'delivered', decided_at = ?, decided_by = ?, delivery_error = NULL,\n updated_at = ? WHERE id = ? AND status = 'pending'`,\n [now, actor, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent approval')\n appendEvent(db, id, 'approved', actor, null, now)\n appendEvent(db, id, 'delivery_started', actor, null, now)\n appendEvent(db, id, 'delivered', actor, null, now)\n return { approval: toApproval(raw(db, id) as RawApproval), granted: true }\n })()\n}\n\nexport function finishDelivery(\n db: BazilionDb,\n id: string,\n ok: boolean,\n actor: string,\n error?: string,\n now = Date.now(),\n): CommunicationApproval {\n return db.raw.transaction(() => {\n const status = ok ? 'delivered' : 'delivery_failed'\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = ?, delivery_error = ?, updated_at = ?\n WHERE id = ? AND status = 'delivering'`,\n [status, ok ? null : (error ?? 'delivery failed'), now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: delivery is not claimable')\n appendEvent(\n db,\n id,\n ok ? 'delivered' : 'delivery_failed',\n actor,\n ok ? null : (error ?? 'delivery failed'),\n now,\n )\n return toApproval(raw(db, id) as RawApproval)\n })()\n}\n","// Per-service field registry — the shape of the /config page.\n//\n// Each entry describes one \"thing\" the user might configure: an LLM provider\n// (Anthropic, LM Studio, …) or an ancillary service (Brave Search, SearXNG).\n// Fields know which storage backend they live in: `secret` → the encrypted\n// `secrets` table, `config` → the plaintext `config` table. The registry is\n// the single source of truth for the UI layout and for the generic\n// field-write endpoint's dispatch.\n//\n// When adding a new provider or service, append an entry here and the\n// config page + CLI pick it up automatically.\n\nexport type FieldKind = 'secret' | 'config'\n\nexport interface ServiceField {\n /** Env var name — canonical key in both stores. */\n envVar: string\n kind: FieldKind\n label: string\n placeholder?: string\n description?: string\n}\n\nexport type ServiceCategory = 'provider' | 'service' | 'integration'\n\nexport interface ServiceDef {\n /** Matches the provider-registry key for providers (e.g. 'anthropic'). */\n id: string\n displayName: string\n category: ServiceCategory\n /** Display grouping label shown above the card on the /config tabs. */\n team?: string\n /** Sign-up link, docs, or 1-line description shown on the card. */\n hint?: string\n fields: ServiceField[]\n}\n\n/**\n * One-liner: what's shown on each service card.\n * Order here is the display order on the config page.\n */\nexport const SERVICES: ServiceDef[] = [\n // --- LLM providers (configured via API keys / URLs) ---\n // Top 3: openai-codex (ChatGPT OAuth), openai (API key), anthropic.\n // Everything else in rough popularity order; locals last.\n {\n id: 'openai-codex',\n displayName: 'OpenAI ChatGPT (OAuth)',\n category: 'provider',\n hint: 'Use your ChatGPT Plus/Pro/Team account (same login as Codex CLI)',\n // No form fields — credentials come from an OAuth flow. The /config page\n // renders a Connect/Disconnect card using /api/auth/openai instead of the\n // standard field inputs.\n fields: [],\n },\n {\n id: 'openai',\n displayName: 'OpenAI',\n category: 'provider',\n hint: 'GPT models · platform.openai.com',\n fields: [{ envVar: 'OPENAI_API_KEY', kind: 'secret', label: 'API key', placeholder: 'sk-...' }],\n },\n {\n id: 'anthropic',\n displayName: 'Anthropic',\n category: 'provider',\n hint: 'Claude models · console.anthropic.com',\n fields: [\n { envVar: 'ANTHROPIC_API_KEY', kind: 'secret', label: 'API key', placeholder: 'sk-ant-...' },\n {\n envVar: 'ANTHROPIC_OAUTH_TOKEN',\n kind: 'secret',\n label: 'OAuth token (alternative to API key)',\n description: 'Takes precedence over ANTHROPIC_API_KEY when set',\n },\n ],\n },\n {\n id: 'google',\n displayName: 'Google (Gemini)',\n category: 'provider',\n hint: 'Gemini models · ai.google.dev (free tier available)',\n fields: [\n { envVar: 'GEMINI_API_KEY', kind: 'secret', label: 'API key', placeholder: 'AIza...' },\n ],\n },\n {\n id: 'google-vertex',\n displayName: 'Google Vertex AI',\n category: 'provider',\n hint: 'Authenticates via `gcloud auth application-default login`',\n fields: [\n {\n envVar: 'GOOGLE_CLOUD_PROJECT',\n kind: 'config',\n label: 'GCP project ID',\n placeholder: 'my-project-123456',\n },\n {\n envVar: 'GOOGLE_CLOUD_LOCATION',\n kind: 'config',\n label: 'GCP region',\n placeholder: 'us-central1',\n },\n {\n envVar: 'GOOGLE_VERTEX_CREDENTIALS_JSON',\n kind: 'secret',\n label: 'Explicit credentials JSON (protected turns)',\n description:\n 'Service-account or ADC JSON projected only into the selected protected worker; ambient ADC files remain unsupported.',\n },\n ],\n },\n {\n id: 'azure-openai',\n displayName: 'Azure OpenAI',\n category: 'provider',\n fields: [{ envVar: 'AZURE_OPENAI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'bedrock',\n displayName: 'Amazon Bedrock',\n category: 'provider',\n hint: 'Authenticates via AWS SDK env (AWS_PROFILE or AWS_ACCESS_KEY_ID/SECRET)',\n fields: [],\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n category: 'provider',\n hint: 'Use a GitHub Copilot subscription to call Claude/GPT/Gemini via Copilot',\n fields: [\n {\n envVar: 'COPILOT_GITHUB_TOKEN',\n kind: 'secret',\n label: 'GitHub token',\n description:\n 'Generic GH_TOKEN/GITHUB_TOKEN are ignored — set this scoped variable explicitly (or run `bazilion auth copilot login` once available).',\n },\n ],\n },\n {\n id: 'deepseek',\n displayName: 'DeepSeek',\n category: 'provider',\n hint: 'DeepSeek V4 Flash / Pro · platform.deepseek.com',\n fields: [{ envVar: 'DEEPSEEK_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'mistral',\n displayName: 'Mistral',\n category: 'provider',\n hint: 'mistral.ai',\n fields: [{ envVar: 'MISTRAL_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xai',\n displayName: 'xAI',\n category: 'provider',\n hint: 'Grok · x.ai',\n fields: [{ envVar: 'XAI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'groq',\n displayName: 'Groq',\n category: 'provider',\n hint: 'Fast inference · groq.com',\n fields: [{ envVar: 'GROQ_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'cerebras',\n displayName: 'Cerebras',\n category: 'provider',\n fields: [{ envVar: 'CEREBRAS_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'fireworks',\n displayName: 'Fireworks AI',\n category: 'provider',\n hint: 'DeepSeek/GLM/Kimi via fireworks.ai',\n fields: [{ envVar: 'FIREWORKS_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'together',\n displayName: 'Together AI',\n category: 'provider',\n hint: 'Open-weight models · together.ai',\n fields: [{ envVar: 'TOGETHER_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'baseten',\n displayName: 'Baseten',\n category: 'provider',\n hint: 'Hosted open-weight models · baseten.co',\n fields: [{ envVar: 'BASETEN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'moonshotai',\n displayName: 'Moonshot AI',\n category: 'provider',\n hint: 'Kimi K2/K2.5/K2.6/K2.7 · platform.moonshot.ai',\n fields: [{ envVar: 'MOONSHOT_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'moonshotai-cn',\n displayName: 'Moonshot AI (CN)',\n category: 'provider',\n hint: 'Kimi models via the China endpoint · platform.moonshot.cn',\n fields: [{ envVar: 'MOONSHOT_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'kimi-coding',\n displayName: 'Kimi Coding',\n category: 'provider',\n hint: 'Coding-tuned Kimi endpoint · platform.moonshot.cn',\n fields: [{ envVar: 'KIMI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'minimax',\n displayName: 'MiniMax',\n category: 'provider',\n hint: 'MiniMax M2/M3 family · platform.minimaxi.com',\n fields: [{ envVar: 'MINIMAX_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'minimax-cn',\n displayName: 'MiniMax (CN)',\n category: 'provider',\n hint: 'MiniMax M2/M3 family via the China endpoint',\n fields: [{ envVar: 'MINIMAX_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'qwen-token-plan',\n displayName: 'Qwen Token Plan',\n category: 'provider',\n hint: 'Coding-model subscription endpoint in Singapore',\n fields: [{ envVar: 'QWEN_TOKEN_PLAN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'qwen-token-plan-cn',\n displayName: 'Qwen Token Plan (CN)',\n category: 'provider',\n hint: 'Coding-model subscription endpoint in China',\n fields: [{ envVar: 'QWEN_TOKEN_PLAN_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'qwen-token-plan-individual',\n displayName: 'Qwen Token Plan (Individual)',\n category: 'provider',\n hint: 'Individual subscription models via the international endpoint',\n fields: [{ envVar: 'QWEN_TOKEN_PLAN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xiaomi',\n displayName: 'Xiaomi MiMo',\n category: 'provider',\n hint: 'API billing endpoint · platform.xiaomimimo.com',\n fields: [{ envVar: 'XIAOMI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xiaomi-token-plan-ams',\n displayName: 'Xiaomi MiMo Token Plan (AMS)',\n category: 'provider',\n hint: 'MiMo token-plan endpoint in Amsterdam',\n fields: [{ envVar: 'XIAOMI_TOKEN_PLAN_AMS_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xiaomi-token-plan-cn',\n displayName: 'Xiaomi MiMo Token Plan (CN)',\n category: 'provider',\n hint: 'MiMo token-plan endpoint in China',\n fields: [{ envVar: 'XIAOMI_TOKEN_PLAN_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xiaomi-token-plan-sgp',\n displayName: 'Xiaomi MiMo Token Plan (SGP)',\n category: 'provider',\n hint: 'MiMo token-plan endpoint in Singapore',\n fields: [{ envVar: 'XIAOMI_TOKEN_PLAN_SGP_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'ant-ling',\n displayName: 'Ant Ling',\n category: 'provider',\n hint: 'Ling/Ring models · api.ant-ling.com',\n fields: [{ envVar: 'ANT_LING_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'nvidia',\n displayName: 'NVIDIA NIM',\n category: 'provider',\n hint: 'NVIDIA hosted models · build.nvidia.com',\n fields: [{ envVar: 'NVIDIA_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'zai',\n displayName: 'zAI',\n category: 'provider',\n fields: [{ envVar: 'ZAI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'zai-coding-cn',\n displayName: 'zAI Coding (CN)',\n category: 'provider',\n hint: 'GLM coding endpoint · open.bigmodel.cn',\n fields: [{ envVar: 'ZAI_CODING_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'huggingface',\n displayName: 'Hugging Face',\n category: 'provider',\n hint: 'Inference endpoints · huggingface.co',\n fields: [{ envVar: 'HF_TOKEN', kind: 'secret', label: 'Access token', placeholder: 'hf_...' }],\n },\n {\n id: 'cloudflare-ai-gateway',\n displayName: 'Cloudflare AI Gateway',\n category: 'provider',\n hint: 'Per-gateway routing to OpenAI/Anthropic/Workers AI',\n fields: [\n { envVar: 'CLOUDFLARE_API_KEY', kind: 'secret', label: 'API key' },\n { envVar: 'CLOUDFLARE_ACCOUNT_ID', kind: 'config', label: 'Account ID' },\n { envVar: 'CLOUDFLARE_GATEWAY_ID', kind: 'config', label: 'Gateway ID' },\n ],\n },\n {\n id: 'cloudflare-workers-ai',\n displayName: 'Cloudflare Workers AI',\n category: 'provider',\n hint: 'Inference on Cloudflare Workers · ai.cloudflare.com',\n fields: [\n { envVar: 'CLOUDFLARE_API_KEY', kind: 'secret', label: 'API key' },\n { envVar: 'CLOUDFLARE_ACCOUNT_ID', kind: 'config', label: 'Account ID' },\n ],\n },\n {\n id: 'openrouter',\n displayName: 'OpenRouter',\n category: 'provider',\n hint: 'Proxy for 200+ models · openrouter.ai',\n fields: [\n { envVar: 'OPENROUTER_API_KEY', kind: 'secret', label: 'API key', placeholder: 'sk-or-...' },\n ],\n },\n {\n id: 'vercel-ai-gateway',\n displayName: 'Vercel AI Gateway',\n category: 'provider',\n fields: [\n { envVar: 'AI_GATEWAY_API_KEY', kind: 'secret', label: 'API key' },\n {\n envVar: 'AI_GATEWAY_BASE_URL',\n kind: 'config',\n label: 'Base URL (optional)',\n placeholder: 'https://ai-gateway.vercel.sh/v1',\n },\n ],\n },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n category: 'provider',\n hint: 'OpenAI-compatible proxy from the OpenCode CLI',\n fields: [{ envVar: 'OPENCODE_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'opencode-go',\n displayName: 'OpenCode Go',\n category: 'provider',\n hint: 'OpenCode hosted Go endpoint',\n fields: [{ envVar: 'OPENCODE_GO_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'lmstudio',\n displayName: 'LM Studio',\n category: 'provider',\n hint: 'Local inference · lmstudio.ai',\n fields: [\n {\n envVar: 'LMSTUDIO_URL',\n kind: 'config',\n label: 'Endpoint URL',\n placeholder: 'http://127.0.0.1:1234/v1',\n },\n {\n envVar: 'LMSTUDIO_API_KEY',\n kind: 'secret',\n label: 'API key (rarely needed)',\n },\n ],\n },\n {\n id: 'ollama',\n displayName: 'Ollama',\n category: 'provider',\n hint: 'Local inference · ollama.com',\n fields: [\n {\n envVar: 'OLLAMA_URL',\n kind: 'config',\n label: 'Endpoint URL',\n placeholder: 'http://127.0.0.1:11434/v1',\n },\n {\n envVar: 'OLLAMA_API_KEY',\n kind: 'secret',\n label: 'API key (rarely needed)',\n },\n ],\n },\n {\n id: 'llamacpp',\n displayName: 'llama.cpp',\n category: 'provider',\n hint: 'Local inference · llama.cpp llama-server (OpenAI-compat /v1 endpoint)',\n fields: [\n {\n envVar: 'LLAMACPP_URL',\n kind: 'config',\n label: 'Endpoint URL',\n placeholder: 'http://127.0.0.1:8080/v1',\n },\n {\n envVar: 'LLAMACPP_API_KEY',\n kind: 'secret',\n label: 'API key (only if started with --api-key)',\n description:\n 'llama-server runs without auth by default. Set this only if you launched the server with the `--api-key KEY` flag.',\n },\n ],\n },\n\n // --- Ancillary services (web search, etc) ---\n {\n id: 'firecrawl',\n displayName: 'Firecrawl',\n category: 'service',\n team: 'Web Search',\n hint: 'web_fetch fallback for JS-heavy/blocked pages · firecrawl.dev (free tier available)',\n fields: [\n {\n envVar: 'FIRECRAWL_API_KEY',\n kind: 'secret',\n label: 'API key',\n placeholder: 'fc-...',\n description:\n 'When set, web_fetch automatically falls back to Firecrawl if the primary Readability extraction yields too little content.',\n },\n {\n envVar: 'FIRECRAWL_URL',\n kind: 'config',\n label: 'Base URL (optional, for self-hosted)',\n placeholder: 'https://api.firecrawl.dev',\n },\n ],\n },\n {\n id: 'brave-search',\n displayName: 'Brave Search',\n category: 'service',\n team: 'Web Search',\n hint: 'Web search tool · free tier at brave.com/search/api/',\n fields: [{ envVar: 'BRAVE_API_KEY', kind: 'secret', label: 'API key', placeholder: 'BSA...' }],\n },\n {\n id: 'searxng',\n displayName: 'SearXNG',\n category: 'service',\n team: 'Web Search',\n hint: 'Self-hosted meta-search engine · searxng.org',\n fields: [\n {\n envVar: 'SEARXNG_URL',\n kind: 'config',\n label: 'Instance URL',\n placeholder: 'https://searxng.example.com',\n },\n ],\n },\n {\n id: 'shell-security',\n displayName: 'Agent Shell Security',\n category: 'service',\n team: 'Agent Runtime',\n hint: 'Optional dangerous-command approval and Docker isolation. Disabled by default.',\n fields: [\n {\n envVar: 'BAZILION_BASH_APPROVAL',\n kind: 'config',\n label: 'Dangerous-command approval',\n placeholder: 'off',\n description:\n 'Use off (current behavior) or dangerous (classified bash commands require per-call approval before execution).',\n },\n {\n envVar: 'BAZILION_BASH_SANDBOX',\n kind: 'config',\n label: 'Sandbox mode',\n placeholder: 'off',\n description:\n 'Use off (current host tools) or docker (containerized bash only; host-backed coding tools are hidden).',\n },\n {\n envVar: 'BAZILION_BASH_SANDBOX_IMAGE',\n kind: 'config',\n label: 'Docker image',\n placeholder: 'debian:bookworm-slim',\n description:\n 'Local image with /bin/bash and /usr/bin/env and no VOLUME declarations. Bazilion never falls back to host execution if it is unavailable or incompatible.',\n },\n {\n envVar: 'BAZILION_BASH_SANDBOX_ENV_ALLOWLIST',\n kind: 'config',\n label: 'Extra environment allowlist',\n placeholder: 'MY_SAFE_VARIABLE,ANOTHER_SAFE_VARIABLE',\n description:\n 'Optional comma-separated variable names copied into the container. Provider credentials remain excluded unless explicitly named.',\n },\n {\n envVar: 'BAZILION_AGENT_LOOP_MAX_HOPS',\n kind: 'config',\n label: 'Agent-message hop limit',\n placeholder: '8',\n description:\n 'Maximum causal hops in an agent-to-agent message chain before the daemon stops it. Default 8.',\n },\n ],\n },\n {\n id: 'browser',\n displayName: 'Browser Automation',\n category: 'service',\n team: 'Browser',\n hint: 'Playwright-driven browser tools (navigate, snapshot, click, screenshot). Chromium is installed with Bazilion.',\n fields: [\n {\n envVar: 'BROWSER_ENABLED',\n kind: 'config',\n label: 'Enable browser tools',\n placeholder: 'true',\n description: 'Expose the browser_* tools to agents (true/false). Default true.',\n },\n {\n envVar: 'BROWSER_HEADLESS',\n kind: 'config',\n label: 'Headless',\n placeholder: 'true',\n description: 'Run Chromium headless (true/false). Default true.',\n },\n {\n envVar: 'BROWSER_ALLOW_PRIVATE_NETWORK',\n kind: 'config',\n label: 'Allow private network',\n placeholder: 'false',\n description:\n 'Permit the browser to reach loopback/private IPs (SSRF guard off). Default false — only enable for local dev.',\n },\n {\n envVar: 'BROWSER_IDLE_MS',\n kind: 'config',\n label: 'Idle timeout (ms)',\n placeholder: '900000',\n description: 'Close an idle browser session after this many ms. Default 900000 (15 min).',\n },\n {\n envVar: 'BROWSER_MAX_SESSIONS',\n kind: 'config',\n label: 'Max concurrent sessions',\n placeholder: '4',\n description: 'Cap on simultaneously-open browser sessions (LRU-evicted). Default 4.',\n },\n ],\n },\n\n // --- External integrations (chat bridges, etc) ---\n // Each integration has its own dedicated /config/integrations/* page with\n // workflow-specific UI (preflight health, setup wizard, …). The fields\n // here exist so the keys are allowlisted in the config/secrets stores and\n // surfaced through the generic `PUT /api/config/fields/:envVar` endpoint.\n // Daemon-managed internal state keys (watermarks, derived topic ids) live\n // in repos/config.ts:INTERNAL_CONFIG_KEYS instead, since the user never\n // edits them.\n {\n id: 'telegram',\n displayName: 'Telegram',\n category: 'integration',\n hint: 'Forum-supergroup bot for talking to your agents from a phone',\n fields: [\n {\n envVar: 'TELEGRAM_BOT_TOKEN',\n kind: 'secret',\n label: 'Bot token',\n placeholder: '1234567890:ABC...',\n description: 'Get one from @BotFather → /newbot. Disable Privacy Mode in Bot Settings.',\n },\n {\n envVar: 'TELEGRAM_CHAT_ID',\n kind: 'config',\n label: 'Supergroup chat ID',\n placeholder: '-1001234567890',\n description: 'Numeric id of the forum-enabled supergroup the bot is admin in.',\n },\n ],\n },\n]\n\n/**\n * Fast lookup: envVar → the field definition + owning service.\n * Rebuilt once at module init — the list is static.\n */\nconst FIELD_INDEX: Map<string, { service: ServiceDef; field: ServiceField }> = (() => {\n const m = new Map<string, { service: ServiceDef; field: ServiceField }>()\n for (const service of SERVICES) {\n for (const field of service.fields) {\n m.set(field.envVar, { service, field })\n }\n }\n return m\n})()\n\nexport function findFieldByEnvVar(\n envVar: string,\n): { service: ServiceDef; field: ServiceField } | undefined {\n return FIELD_INDEX.get(envVar)\n}\n\nexport function servicesByCategory(category: ServiceCategory): ServiceDef[] {\n return SERVICES.filter((s) => s.category === category)\n}\n","// Plaintext config store, backed by the `config` table.\n//\n// Companion to `secrets.ts` — same key-shaped values, but for the ones that\n// don't need confidentiality (server URLs, region slugs, project IDs). Kept\n// separate so the /config UI can show plaintext values directly without\n// extra masking logic.\n//\n// The CONFIG_KEYS allowlist (derived from the services registry) is enforced\n// here on writes — a typo or accidental misclassification can't put an API\n// key in this table.\n\nimport type { BazilionDb } from '../db/client.ts'\nimport { SERVICES } from '../services.ts'\n\n/**\n * Daemon-managed internal state keys. Not exposed in the services registry\n * because the user never edits them — they're written by background machinery\n * (polling watermarks, derived topic ids, …). Kept here so the allowlist\n * still gates accidental misuse from the generic field write endpoint.\n */\nconst INTERNAL_CONFIG_KEYS: readonly string[] = [\n // Telegram (step 2+): polling watermark + derived topic/message ids.\n 'TELEGRAM_LAST_UPDATE_ID',\n 'TELEGRAM_SERVICE_TOPIC_ID',\n 'TELEGRAM_DIRECTORY_MESSAGE_ID',\n // Phase 5: proposed new chat id after a migrate_to_chat_id event, pending\n // operator confirmation via POST /api/config/telegram/reconnect.\n 'TELEGRAM_MIGRATED_CHAT_ID',\n]\n\n/**\n * Env var names that live in the plaintext config store. Derived from the\n * services registry — any field marked `kind: 'config'` ends up here — and\n * unioned with the internal keys list above.\n */\nexport const CONFIG_KEYS: readonly string[] = [\n ...SERVICES.flatMap((s) => s.fields.filter((f) => f.kind === 'config').map((f) => f.envVar)),\n ...INTERNAL_CONFIG_KEYS,\n]\n\nconst CONFIG_KEY_SET = new Set<string>(CONFIG_KEYS)\n\nexport function isConfigKey(key: string): boolean {\n return CONFIG_KEY_SET.has(key)\n}\n\ninterface RawRow {\n key: string\n value: string\n updated_at: number\n}\n\nexport interface ConfigStore {\n get(key: string): string | undefined\n set(key: string, value: string): void\n remove(key: string): void\n list(): { key: string; value: string }[]\n getAll(): Record<string, string>\n}\n\nexport function openConfig(db: BazilionDb): ConfigStore {\n return {\n get(key) {\n const row = db.raw.query<RawRow, [string]>('SELECT * FROM config WHERE key = ?').get(key)\n return row?.value\n },\n set(key, value) {\n if (!isConfigKey(key)) {\n throw new Error(\n `config.set: \"${key}\" is not a known config key (${CONFIG_KEYS.join(', ')})`,\n )\n }\n db.raw.run(\n `INSERT INTO config (key, value, updated_at) VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n [key, value, Date.now()],\n )\n },\n remove(key) {\n db.raw.run('DELETE FROM config WHERE key = ?', [key])\n },\n list() {\n return db.raw\n .query<RawRow, []>('SELECT * FROM config ORDER BY key ASC')\n .all()\n .map((r) => ({ key: r.key, value: r.value }))\n },\n getAll() {\n const out: Record<string, string> = {}\n for (const r of db.raw.query<RawRow, []>('SELECT * FROM config').all()) {\n out[r.key] = r.value\n }\n return out\n },\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { McpServer, McpTransport } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawRow {\n id: string\n name: string\n transport: string\n command: string | null\n args: string\n url: string | null\n has_auth: number\n enabled: number\n created_at: number\n updated_at: number\n}\n\nfunction toServer(r: RawRow): McpServer {\n let args: string[] = []\n try {\n const parsed = JSON.parse(r.args)\n if (Array.isArray(parsed)) args = parsed.filter((a): a is string => typeof a === 'string')\n } catch {}\n return {\n id: r.id,\n name: r.name,\n transport: r.transport as McpTransport,\n command: r.command,\n args,\n url: r.url,\n hasAuthToken: r.has_auth === 1,\n enabled: r.enabled === 1,\n createdAt: r.created_at,\n updatedAt: r.updated_at,\n }\n}\n\nexport interface InsertMcpServerInput {\n name: string\n transport: McpTransport\n command?: string | null\n args?: string[]\n url?: string | null\n hasAuth?: boolean\n enabled?: boolean\n}\n\nexport function insert(db: BazilionDb, input: InsertMcpServerInput): McpServer {\n const id = randomUUID()\n const now = Date.now()\n db.raw.run(\n `INSERT INTO mcp_servers (id, name, transport, command, args, url, has_auth, enabled, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n id,\n input.name,\n input.transport,\n input.command ?? null,\n JSON.stringify(input.args ?? []),\n input.url ?? null,\n input.hasAuth ? 1 : 0,\n input.enabled === false ? 0 : 1,\n now,\n now,\n ],\n )\n const row = get(db, id)\n if (!row) throw new Error('mcp server insert failed')\n return row\n}\n\nexport function get(db: BazilionDb, id: string): McpServer | null {\n const row = db.raw.query<RawRow, [string]>('SELECT * FROM mcp_servers WHERE id = ?').get(id)\n return row ? toServer(row) : null\n}\n\nexport function getByName(db: BazilionDb, name: string): McpServer | null {\n const row = db.raw.query<RawRow, [string]>('SELECT * FROM mcp_servers WHERE name = ?').get(name)\n return row ? toServer(row) : null\n}\n\nexport function list(db: BazilionDb): McpServer[] {\n return db.raw\n .query<RawRow, []>('SELECT * FROM mcp_servers ORDER BY created_at ASC')\n .all()\n .map(toServer)\n}\n\nexport function listEnabled(db: BazilionDb): McpServer[] {\n return db.raw\n .query<RawRow, []>('SELECT * FROM mcp_servers WHERE enabled = 1 ORDER BY created_at ASC')\n .all()\n .map(toServer)\n}\n\nexport interface UpdateMcpServerInput {\n name?: string\n transport?: McpTransport\n command?: string | null\n args?: string[]\n url?: string | null\n enabled?: boolean\n hasAuth?: boolean\n}\n\nexport function update(db: BazilionDb, id: string, input: UpdateMcpServerInput): McpServer | null {\n const existing = get(db, id)\n if (!existing) return null\n const next = {\n name: input.name ?? existing.name,\n transport: input.transport ?? existing.transport,\n command: input.command !== undefined ? input.command : existing.command,\n args: input.args ?? existing.args,\n url: input.url !== undefined ? input.url : existing.url,\n enabled: input.enabled ?? existing.enabled,\n hasAuth: input.hasAuth ?? existing.hasAuthToken,\n }\n db.raw.run(\n `UPDATE mcp_servers\n SET name = ?, transport = ?, command = ?, args = ?, url = ?, enabled = ?, has_auth = ?, updated_at = ?\n WHERE id = ?`,\n [\n next.name,\n next.transport,\n next.command,\n JSON.stringify(next.args),\n next.url,\n next.enabled ? 1 : 0,\n next.hasAuth ? 1 : 0,\n Date.now(),\n id,\n ],\n )\n return get(db, id)\n}\n\nexport function setEnabled(db: BazilionDb, id: string, enabled: boolean): void {\n db.raw.run('UPDATE mcp_servers SET enabled = ?, updated_at = ? WHERE id = ?', [\n enabled ? 1 : 0,\n Date.now(),\n id,\n ])\n}\n\nexport function remove(db: BazilionDb, id: string): void {\n db.raw.run('DELETE FROM mcp_servers WHERE id = ?', [id])\n}\n","import { randomUUID } from 'node:crypto'\nimport type { Message } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawMessage {\n id: string\n from_agent_id: string\n to_agent_id: string\n reply_to: string | null\n causal_chain_id: string\n causal_hop: number\n payload: string\n created_at: number\n read_at: number | null\n policy_disposition: string\n policy_blocked_at: number | null\n policy_claimed_at: number | null\n policy_delivered_at: number | null\n}\n\nfunction toMessage(r: RawMessage): Message {\n return {\n id: r.id,\n fromAgentId: r.from_agent_id,\n toAgentId: r.to_agent_id,\n replyTo: r.reply_to,\n causalChainId: r.causal_chain_id,\n causalHop: r.causal_hop,\n payload: r.payload,\n createdAt: r.created_at,\n readAt: r.read_at,\n }\n}\n\nexport function send(\n db: BazilionDb,\n input: {\n from: string\n to: string\n payload: string\n replyTo?: string | null\n causalChainId?: string\n causalHop?: number\n },\n): Message {\n const id = randomUUID()\n const now = Date.now()\n const causalChainId = input.causalChainId ?? id\n const causalHop = input.causalHop ?? 0\n db.raw.run(\n `INSERT INTO messages\n (id, from_agent_id, to_agent_id, reply_to, causal_chain_id, causal_hop, payload, created_at, read_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n [id, input.from, input.to, input.replyTo ?? null, causalChainId, causalHop, input.payload, now],\n )\n return {\n id,\n fromAgentId: input.from,\n toAgentId: input.to,\n replyTo: input.replyTo ?? null,\n causalChainId,\n causalHop,\n payload: input.payload,\n createdAt: now,\n readAt: null,\n }\n}\n\nexport function get(db: BazilionDb, id: string): Message | null {\n const row = db.raw.query<RawMessage, [string]>('SELECT * FROM messages WHERE id = ?').get(id)\n return row ? toMessage(row) : null\n}\n\nexport function listInbox(\n db: BazilionDb,\n agentId: string,\n opts?: { unreadOnly?: boolean },\n): Message[] {\n const sql = opts?.unreadOnly\n ? \"SELECT * FROM messages WHERE to_agent_id = ? AND read_at IS NULL AND policy_claimed_at IS NULL AND policy_disposition = 'deliverable' ORDER BY created_at ASC\"\n : \"SELECT * FROM messages WHERE to_agent_id = ? AND policy_disposition = 'deliverable' ORDER BY created_at ASC\"\n return db.raw.query<RawMessage, [string]>(sql).all(agentId).map(toMessage)\n}\n\nexport function listInboxForOperator(\n db: BazilionDb,\n agentId: string,\n opts?: { unreadOnly?: boolean },\n): Message[] {\n const sql = opts?.unreadOnly\n ? 'SELECT * FROM messages WHERE to_agent_id = ? AND read_at IS NULL ORDER BY created_at ASC'\n : 'SELECT * FROM messages WHERE to_agent_id = ? ORDER BY created_at ASC'\n return db.raw.query<RawMessage, [string]>(sql).all(agentId).map(toMessage)\n}\n\nexport function markRead(db: BazilionDb, id: string): void {\n db.raw.run('UPDATE messages SET read_at = ? WHERE id = ? AND read_at IS NULL', [Date.now(), id])\n}\n\nexport function markPolicyBlocked(db: BazilionDb, id: string): void {\n db.raw.run(\n \"UPDATE messages SET policy_disposition = 'policy_blocked', policy_blocked_at = ? WHERE id = ? AND policy_disposition = 'deliverable'\",\n [Date.now(), id],\n )\n}\n\nexport function markPolicyClaimed(db: BazilionDb, id: string, when: number): void {\n db.raw.run(\n \"UPDATE messages SET policy_claimed_at = ? WHERE id = ? AND policy_disposition = 'deliverable' AND read_at IS NULL AND policy_claimed_at IS NULL\",\n [when, id],\n )\n}\n\nexport function markPolicyDelivered(db: BazilionDb, id: string, when: number): void {\n db.raw.run(\n \"UPDATE messages SET policy_delivered_at = ?, read_at = ? WHERE id = ? AND policy_disposition = 'deliverable' AND policy_claimed_at IS NOT NULL AND policy_delivered_at IS NULL\",\n [when, when, id],\n )\n}\n\n/**\n * Find messages that are replies to a given message id, addressed to a specific agent.\n * Used by `wait_for_reply` to poll for incoming responses.\n */\nexport function findReplies(db: BazilionDb, toAgentId: string, inReplyTo: string): Message[] {\n return db.raw\n .query<RawMessage, [string, string]>(\n `SELECT * FROM messages\n WHERE to_agent_id = ? AND reply_to = ?\n ORDER BY created_at ASC`,\n )\n .all(toAgentId, inReplyTo)\n .map(toMessage)\n}\n\n/**\n * Return the distinct `to_agent_id`s that currently have at least one unread\n * message. Archived recipients remain included so the scheduler can convert\n * their unread rows into one terminal policy-blocked disposition instead of\n * leaving an eternal retry candidate.\n * Used by the scheduler's message-wake loop so a tick can fan-out auto-\n * delivery turns without walking every agent in the DB.\n */\nexport function listRecipientsWithUnread(db: BazilionDb): string[] {\n const rows = db.raw\n .query<{ to_agent_id: string }, []>(\n `SELECT DISTINCT m.to_agent_id FROM messages m\n JOIN agents a ON a.id = m.to_agent_id\n WHERE m.read_at IS NULL AND m.policy_claimed_at IS NULL AND m.policy_disposition = 'deliverable'\n ORDER BY m.to_agent_id`,\n )\n .all()\n return rows.map((r) => r.to_agent_id)\n}\n\n/**\n * Atomically fetch + mark-read all unread messages addressed to `agentId`.\n * Runs inside a transaction so two concurrent schedulers / manual deliveries\n * can't double-dispatch the same message. Returns the fetched messages in\n * ascending `created_at` order — callers format them into the recipient's\n * wake-up prompt.\n */\nexport function drainUnreadForAgent(db: BazilionDb, agentId: string): Message[] {\n return db.raw.transaction(() => {\n const rows = db.raw\n .query<RawMessage, [string]>(\n `SELECT * FROM messages\n WHERE to_agent_id = ? AND read_at IS NULL AND policy_disposition = 'deliverable'\n ORDER BY created_at ASC`,\n )\n .all(agentId)\n if (rows.length === 0) return []\n const now = Date.now()\n db.raw.run(\n `UPDATE messages SET read_at = ?\n WHERE to_agent_id = ? AND read_at IS NULL AND policy_disposition = 'deliverable'`,\n [now, agentId],\n )\n return rows.map((r) => toMessage({ ...r, read_at: now }))\n })()\n}\n","// Encrypted secrets store, backed by the `secrets` table.\n//\n// Layout: one row per env-var-shaped key (`ANTHROPIC_API_KEY`,\n// `OPENAI_CODEX_OAUTH`, …). Each value is an AES-256-GCM envelope (salt +\n// iv + tag + data, hex-encoded JSON), with the key derived from the\n// bootstrap token via PBKDF2-SHA256 (100k iterations). The crypto matches\n// the previous `secrets.enc` file format byte-for-byte — only the storage\n// medium changed.\n//\n// Why encrypt at all when the password lives in `~/.bazilion/auth.json`\n// next to the DB? Same reason as before: it's defense against accidental\n// exposure (cat'd dumps, screenshares, naive backups), not against an\n// attacker with filesystem read. Anyone who can read both files wins.\n//\n// Caching: `deriveKey` runs PBKDF2 once per (password, salt) pair and the\n// salt is per-row, so a busy daemon does ~one PBKDF2 per secret read. The\n// `secretCache` keeps decrypted values in-memory keyed by row id so repeated\n// reads inside one process don't repeat the work; mutations clear the cache\n// row.\n\nimport { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from 'node:crypto'\nimport type { BazilionDb } from '../db/client.ts'\n\nconst ALGORITHM = 'aes-256-gcm'\nconst KEY_LEN = 32\nconst ITERATIONS = 100_000\nconst DIGEST = 'sha256'\n\ninterface EncryptedEnvelope {\n salt: string\n iv: string\n tag: string\n data: string\n}\n\nfunction deriveKey(password: string, salt: Buffer): Buffer {\n return pbkdf2Sync(password, salt, ITERATIONS, KEY_LEN, DIGEST)\n}\n\nfunction encrypt(plaintext: string, password: string): EncryptedEnvelope {\n const salt = randomBytes(16)\n const key = deriveKey(password, salt)\n const iv = randomBytes(12)\n const cipher = createCipheriv(ALGORITHM, key, iv)\n let data = cipher.update(plaintext, 'utf8', 'hex')\n data += cipher.final('hex')\n const tag = cipher.getAuthTag()\n return {\n salt: salt.toString('hex'),\n iv: iv.toString('hex'),\n tag: tag.toString('hex'),\n data,\n }\n}\n\nfunction decrypt(envelope: EncryptedEnvelope, password: string): string {\n const salt = Buffer.from(envelope.salt, 'hex')\n const key = deriveKey(password, salt)\n const iv = Buffer.from(envelope.iv, 'hex')\n const tag = Buffer.from(envelope.tag, 'hex')\n const decipher = createDecipheriv(ALGORITHM, key, iv)\n decipher.setAuthTag(tag)\n let plaintext = decipher.update(envelope.data, 'hex', 'utf8')\n plaintext += decipher.final('utf8')\n return plaintext\n}\n\ninterface RawRow {\n key: string\n envelope: string\n updated_at: number\n}\n\nexport interface SecretsStore {\n get(key: string): string | undefined\n set(key: string, value: string): void\n remove(key: string): void\n has(key: string): boolean\n list(): { key: string; preview: string }[]\n getAll(): Record<string, string>\n}\n\n/**\n * Open the encrypted secrets store. `password` is the bootstrap token\n * (read from `auth.json` by the caller). Throws on individual-row decrypt\n * failures only when actively reading that row — bad rows show as\n * `undefined` from `get`, the caller can `set` to overwrite.\n */\nexport function openSecrets(db: BazilionDb, password: string): SecretsStore {\n function getRow(key: string): RawRow | null {\n return db.raw.query<RawRow, [string]>('SELECT * FROM secrets WHERE key = ?').get(key)\n }\n\n function listAll(): RawRow[] {\n return db.raw.query<RawRow, []>('SELECT * FROM secrets ORDER BY key ASC').all()\n }\n\n function tryDecrypt(row: RawRow): string | undefined {\n try {\n const envelope = JSON.parse(row.envelope) as EncryptedEnvelope\n return decrypt(envelope, password)\n } catch {\n return undefined\n }\n }\n\n return {\n get(key) {\n const row = getRow(key)\n if (!row) return undefined\n return tryDecrypt(row)\n },\n set(key, value) {\n const envelope = JSON.stringify(encrypt(value, password))\n db.raw.run(\n `INSERT INTO secrets (key, envelope, updated_at) VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET envelope = excluded.envelope, updated_at = excluded.updated_at`,\n [key, envelope, Date.now()],\n )\n },\n remove(key) {\n db.raw.run('DELETE FROM secrets WHERE key = ?', [key])\n },\n has(key) {\n return getRow(key) !== null\n },\n list() {\n return listAll().map((r) => {\n const value = tryDecrypt(r)\n return {\n key: r.key,\n preview: value ? (value.length > 8 ? `${value.slice(0, 6)}…` : '***') : '(unreadable)',\n }\n })\n },\n getAll() {\n const out: Record<string, string> = {}\n for (const r of listAll()) {\n const v = tryDecrypt(r)\n if (v !== undefined) out[r.key] = v\n }\n return out\n },\n }\n}\n","import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'\nimport type { BazilionDb } from '../db/client.ts'\nimport { add } from './telegram-acl.ts'\n\nconst TTL_MS = 10 * 60_000\n\ninterface PairingRow {\n digest: string\n expires_at: number\n created_at: number\n}\n\nexport interface TelegramPairingStatus {\n paired: boolean\n challengeActive: boolean\n challengeExpiresAt: number | null\n ownerUserId: number | null\n}\n\nfunction digest(code: string): Buffer {\n return createHash('sha256').update(code, 'utf8').digest()\n}\n\nexport function create(db: BazilionDb): { code: string; expiresAt: number } {\n if (status(db).paired) throw new Error('Telegram already has a paired owner')\n const code = randomBytes(16).toString('base64url')\n const now = Date.now()\n const expiresAt = now + TTL_MS\n db.raw.run(\n `INSERT INTO telegram_pairing_challenge (singleton, digest, expires_at, created_at)\n VALUES (1, ?, ?, ?)\n ON CONFLICT(singleton) DO UPDATE SET\n digest = excluded.digest, expires_at = excluded.expires_at, created_at = excluded.created_at`,\n [digest(code).toString('hex'), expiresAt, now],\n )\n return { code, expiresAt }\n}\n\nexport function cancel(db: BazilionDb): void {\n db.raw.run('DELETE FROM telegram_pairing_challenge WHERE singleton = 1')\n}\n\nexport function reset(db: BazilionDb): void {\n db.raw.transaction(() => {\n cancel(db)\n db.raw.run('DELETE FROM telegram_allowed_users')\n })()\n}\n\nexport function status(db: BazilionDb): TelegramPairingStatus {\n const owner = db.raw\n .query<{ user_id: number }, []>(\n \"SELECT user_id FROM telegram_allowed_users WHERE role = 'owner' ORDER BY added_at LIMIT 1\",\n )\n .get()\n const row = db.raw\n .query<PairingRow, []>(\n 'SELECT digest, expires_at, created_at FROM telegram_pairing_challenge WHERE singleton = 1',\n )\n .get()\n const active = row !== null && row.expires_at > Date.now()\n if (row && !active) cancel(db)\n return {\n paired: owner !== null,\n challengeActive: active,\n challengeExpiresAt: active ? (row?.expires_at ?? null) : null,\n ownerUserId: owner?.user_id ?? null,\n }\n}\n\nexport function consume(\n db: BazilionDb,\n input: { code: string; userId: number; username?: string | null; label?: string | null },\n): boolean {\n return db.raw.transaction(() => {\n if (status(db).paired) return false\n const row = db.raw\n .query<PairingRow, []>(\n 'SELECT digest, expires_at, created_at FROM telegram_pairing_challenge WHERE singleton = 1',\n )\n .get()\n if (!row || row.expires_at <= Date.now()) {\n cancel(db)\n return false\n }\n const supplied = digest(input.code)\n const expected = Buffer.from(row.digest, 'hex')\n if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) return false\n add(db, { ...input, role: 'owner' })\n cancel(db)\n return true\n })()\n}\n","import { randomUUID } from 'node:crypto'\nimport type { TriggerDispatch, TriggerDispatchStatus } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawDispatch {\n id: string\n trigger_id: string\n agent_id: string\n scheduled_at: number\n status: string\n attempt_count: number\n next_attempt_at: number\n lease_expires_at: number | null\n started_at: number | null\n finished_at: number | null\n last_error: string | null\n created_at: number\n updated_at: number\n}\n\nfunction toDispatch(row: RawDispatch): TriggerDispatch {\n return {\n id: row.id,\n triggerId: row.trigger_id,\n agentId: row.agent_id,\n scheduledAt: row.scheduled_at,\n status: row.status as TriggerDispatchStatus,\n attemptCount: row.attempt_count,\n nextAttemptAt: row.next_attempt_at,\n leaseExpiresAt: row.lease_expires_at,\n startedAt: row.started_at,\n finishedAt: row.finished_at,\n lastError: row.last_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nexport function materialize(\n db: BazilionDb,\n input: { triggerId: string; agentId: string; scheduledAt: number; now?: number },\n): TriggerDispatch {\n const now = input.now ?? Date.now()\n db.raw.run(\n `INSERT INTO trigger_dispatches\n (id, trigger_id, agent_id, scheduled_at, status, attempt_count, next_attempt_at,\n lease_expires_at, started_at, finished_at, last_error, created_at, updated_at)\n VALUES (?, ?, ?, ?, 'pending', 0, ?, NULL, NULL, NULL, NULL, ?, ?)\n ON CONFLICT(trigger_id, scheduled_at) DO NOTHING`,\n [randomUUID(), input.triggerId, input.agentId, input.scheduledAt, now, now, now],\n )\n const row = db.raw\n .query<RawDispatch, [string, number]>(\n 'SELECT * FROM trigger_dispatches WHERE trigger_id = ? AND scheduled_at = ?',\n )\n .get(input.triggerId, input.scheduledAt)\n if (!row) throw new Error('trigger dispatch materialization failed')\n return toDispatch(row)\n}\n\nexport function get(db: BazilionDb, id: string): TriggerDispatch | null {\n const row = db.raw\n .query<RawDispatch, [string]>('SELECT * FROM trigger_dispatches WHERE id = ?')\n .get(id)\n return row ? toDispatch(row) : null\n}\n\nexport function listClaimable(db: BazilionDb, now = Date.now()): TriggerDispatch[] {\n return db.raw\n .query<RawDispatch, [number, number]>(\n `SELECT * FROM trigger_dispatches\n WHERE (status IN ('pending','retrying') AND next_attempt_at <= ?)\n OR (status = 'running' AND lease_expires_at <= ?)\n ORDER BY scheduled_at ASC, created_at ASC`,\n )\n .all(now, now)\n .map(toDispatch)\n}\n\nexport function hasOpenForTrigger(db: BazilionDb, triggerId: string): boolean {\n return (\n db.raw\n .query<{ found: number }, [string]>(\n `SELECT 1 found FROM trigger_dispatches\n WHERE trigger_id = ? AND status IN ('pending','running','retrying') LIMIT 1`,\n )\n .get(triggerId) !== null\n )\n}\n\nexport function claim(\n db: BazilionDb,\n id: string,\n options: { now?: number; leaseMs?: number } = {},\n): TriggerDispatch | null {\n const now = options.now ?? Date.now()\n const leaseExpiresAt = now + (options.leaseMs ?? 5 * 60_000)\n return db.raw.transaction(() => {\n const result = db.raw.run(\n `UPDATE trigger_dispatches\n SET status = 'running', attempt_count = attempt_count + 1,\n lease_expires_at = ?, started_at = ?, finished_at = NULL, updated_at = ?\n WHERE id = ? AND (\n (status IN ('pending','retrying') AND next_attempt_at <= ?)\n OR (status = 'running' AND lease_expires_at <= ?)\n )`,\n [leaseExpiresAt, now, now, id, now, now],\n )\n if (Number(result.changes) !== 1) return null\n const row = db.raw\n .query<RawDispatch, [string]>('SELECT * FROM trigger_dispatches WHERE id = ?')\n .get(id)\n return row ? toDispatch(row) : null\n })()\n}\n\nexport function succeed(db: BazilionDb, id: string, now = Date.now()): void {\n db.raw.run(\n `UPDATE trigger_dispatches SET status = 'succeeded', lease_expires_at = NULL,\n finished_at = ?, last_error = NULL, updated_at = ? WHERE id = ? AND status = 'running'`,\n [now, now, id],\n )\n}\n\nexport function defer(db: BazilionDb, id: string, nextAttemptAt = Date.now() + 1_000): void {\n db.raw.run(\n `UPDATE trigger_dispatches SET status = 'pending',\n attempt_count = CASE WHEN attempt_count > 0 THEN attempt_count - 1 ELSE 0 END,\n next_attempt_at = ?, lease_expires_at = NULL, started_at = NULL, updated_at = ?\n WHERE id = ? AND status = 'running'`,\n [nextAttemptAt, Date.now(), id],\n )\n}\n\nexport function cancelRunning(db: BazilionDb, id: string, reason: string, now = Date.now()): void {\n db.raw.run(\n `UPDATE trigger_dispatches SET status = 'cancelled', lease_expires_at = NULL,\n finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,\n [now, reason.slice(0, 2_000), now, id],\n )\n}\n\nexport function fail(\n db: BazilionDb,\n id: string,\n error: string,\n options: { now?: number; maxAttempts?: number; retryDelayMs?: number } = {},\n): void {\n const now = options.now ?? Date.now()\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 5_000\n const current = db.raw\n .query<{ attempt_count: number }, [string]>(\n 'SELECT attempt_count FROM trigger_dispatches WHERE id = ?',\n )\n .get(id)\n if (!current) return\n const terminal = current.attempt_count >= maxAttempts\n db.raw.run(\n `UPDATE trigger_dispatches SET status = ?, next_attempt_at = ?, lease_expires_at = NULL,\n finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,\n [\n terminal ? 'failed' : 'retrying',\n terminal ? now : now + retryDelayMs * 2 ** Math.max(0, current.attempt_count - 1),\n terminal ? now : null,\n error.slice(0, 2_000),\n now,\n id,\n ],\n )\n}\n\nexport function cancelPendingForTrigger(db: BazilionDb, triggerId: string, now = Date.now()): void {\n db.raw.run(\n `UPDATE trigger_dispatches SET status = 'cancelled', finished_at = ?, updated_at = ?\n WHERE trigger_id = ? AND status IN ('pending','retrying')`,\n [now, now, triggerId],\n )\n}\n\nexport function listForTrigger(db: BazilionDb, triggerId: string, limit = 20): TriggerDispatch[] {\n return db.raw\n .query<RawDispatch, [string, number]>(\n 'SELECT * FROM trigger_dispatches WHERE trigger_id = ? ORDER BY scheduled_at DESC LIMIT ?',\n )\n .all(triggerId, limit)\n .map(toDispatch)\n}\n","import { randomUUID } from 'node:crypto'\nimport type { AgentTrigger, TriggerKind } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport { cancelPendingForTrigger } from './triggerDispatches.ts'\n\ninterface RawTrigger {\n id: string\n agent_id: string\n kind: string\n interval_sec: number | null\n cron_expr: string | null\n message: string\n enabled: number\n last_fired_at: number | null\n created_at: number\n}\n\nfunction toTrigger(r: RawTrigger): AgentTrigger {\n return {\n id: r.id,\n agentId: r.agent_id,\n kind: r.kind as TriggerKind,\n intervalSec: r.interval_sec,\n cronExpr: r.cron_expr,\n message: r.message,\n enabled: r.enabled === 1,\n lastFiredAt: r.last_fired_at,\n createdAt: r.created_at,\n }\n}\n\nexport interface InsertTriggerInput {\n agentId: string\n kind: TriggerKind\n intervalSec: number | null\n cronExpr: string | null\n message: string\n enabled?: boolean\n}\n\nexport function insert(db: BazilionDb, input: InsertTriggerInput): AgentTrigger {\n const id = randomUUID()\n const now = Date.now()\n const enabled = input.enabled === false ? 0 : 1\n db.raw.run(\n `INSERT INTO agent_triggers\n (id, agent_id, kind, interval_sec, cron_expr, message, enabled, last_fired_at, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?)`,\n [id, input.agentId, input.kind, input.intervalSec, input.cronExpr, input.message, enabled, now],\n )\n return {\n id,\n agentId: input.agentId,\n kind: input.kind,\n intervalSec: input.intervalSec,\n cronExpr: input.cronExpr,\n message: input.message,\n enabled: enabled === 1,\n lastFiredAt: null,\n createdAt: now,\n }\n}\n\nexport function get(db: BazilionDb, id: string): AgentTrigger | null {\n const row = db.raw\n .query<RawTrigger, [string]>('SELECT * FROM agent_triggers WHERE id = ?')\n .get(id)\n return row ? toTrigger(row) : null\n}\n\nexport function listForAgent(db: BazilionDb, agentId: string): AgentTrigger[] {\n return db.raw\n .query<RawTrigger, [string]>(\n 'SELECT * FROM agent_triggers WHERE agent_id = ? ORDER BY created_at ASC',\n )\n .all(agentId)\n .map(toTrigger)\n}\n\nexport function listEnabled(db: BazilionDb): AgentTrigger[] {\n return db.raw\n .query<RawTrigger, []>(\n `SELECT t.* FROM agent_triggers t\n JOIN agents a ON a.id = t.agent_id\n WHERE t.enabled = 1\n ORDER BY t.created_at ASC`,\n )\n .all()\n .map(toTrigger)\n}\n\nexport function setEnabled(db: BazilionDb, id: string, enabled: boolean): void {\n db.raw.transaction(() => {\n db.raw.run('UPDATE agent_triggers SET enabled = ? WHERE id = ?', [enabled ? 1 : 0, id])\n if (!enabled) cancelPendingForTrigger(db, id)\n })()\n}\n\nexport function markFired(db: BazilionDb, id: string, when: number = Date.now()): void {\n db.raw.run('UPDATE agent_triggers SET last_fired_at = ? WHERE id = ?', [when, id])\n}\n\nexport function remove(db: BazilionDb, id: string): void {\n db.raw.run('DELETE FROM agent_triggers WHERE id = ?', [id])\n}\n","import { createHash, randomBytes, randomUUID } from 'node:crypto'\nimport type { WebSession } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport { hashToken } from './webTokens.ts'\n\nexport const SESSION_IDLE_MS = 12 * 60 * 60 * 1000\nexport const SESSION_ABSOLUTE_MS = 7 * 24 * 60 * 60 * 1000\n\ninterface RawSession {\n id: string\n secret_hash: string\n csrf_hash: string\n device_token_id: string\n device_label: string\n created_at: number\n last_seen_at: number\n idle_expires_at: number\n absolute_expires_at: number\n revoked_at: number | null\n}\n\nexport interface CreatedSession {\n session: WebSession\n cookieValue: string\n csrfToken: string\n}\n\nexport interface AuthenticatedSession {\n id: string\n deviceTokenId: string\n deviceLabel: string\n csrfHash: string\n}\n\nfunction hash(value: string): string {\n return createHash('sha256').update(value).digest('hex')\n}\n\nfunction toSession(row: RawSession, currentId?: string): WebSession {\n return {\n id: row.id,\n deviceTokenId: row.device_token_id,\n deviceLabel: row.device_label,\n createdAt: row.created_at,\n lastSeenAt: row.last_seen_at,\n idleExpiresAt: row.idle_expires_at,\n absoluteExpiresAt: row.absolute_expires_at,\n revokedAt: row.revoked_at,\n current: row.id === currentId,\n }\n}\n\nexport function create(db: BazilionDb, deviceTokenId: string, now = Date.now()): CreatedSession {\n const token = db.raw\n .query<{ id: string; label: string }, [string, number]>(\n `SELECT id, label FROM web_tokens\n WHERE id = ? AND kind = 'device' AND revoked_at IS NULL AND expires_at > ?`,\n )\n .get(deviceTokenId, now)\n if (!token) throw new Error('active device credential required')\n const id = randomUUID()\n const secret = randomBytes(32).toString('base64url')\n const csrfToken = randomBytes(32).toString('base64url')\n const idleExpiresAt = now + SESSION_IDLE_MS\n const absoluteExpiresAt = now + SESSION_ABSOLUTE_MS\n db.raw.run(\n `INSERT INTO web_sessions\n (id, secret_hash, csrf_hash, device_token_id, created_at, last_seen_at,\n idle_expires_at, absolute_expires_at, revoked_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n [id, hash(secret), hash(csrfToken), deviceTokenId, now, now, idleExpiresAt, absoluteExpiresAt],\n )\n return {\n cookieValue: `${id}.${secret}`,\n csrfToken,\n session: {\n id,\n deviceTokenId,\n deviceLabel: token.label,\n createdAt: now,\n lastSeenAt: now,\n idleExpiresAt,\n absoluteExpiresAt,\n revokedAt: null,\n current: true,\n },\n }\n}\n\nexport function authenticate(\n db: BazilionDb,\n cookieValue: string,\n now = Date.now(),\n): AuthenticatedSession | null {\n const separator = cookieValue.indexOf('.')\n if (separator <= 0) return null\n const id = cookieValue.slice(0, separator)\n const secret = cookieValue.slice(separator + 1)\n if (!secret) return null\n const row = db.raw\n .query<RawSession, [string, string, number, number, number]>(\n `SELECT s.*, t.label AS device_label\n FROM web_sessions s JOIN web_tokens t ON t.id = s.device_token_id\n WHERE s.id = ? AND s.secret_hash = ? AND s.revoked_at IS NULL\n AND s.idle_expires_at > ? AND s.absolute_expires_at > ?\n AND t.kind = 'device' AND t.revoked_at IS NULL AND t.expires_at > ?`,\n )\n .get(id, hash(secret), now, now, now)\n if (!row) return null\n const idleExpiresAt = Math.min(now + SESSION_IDLE_MS, row.absolute_expires_at)\n db.raw.run('UPDATE web_sessions SET last_seen_at = ?, idle_expires_at = ? WHERE id = ?', [\n now,\n idleExpiresAt,\n id,\n ])\n return {\n id,\n deviceTokenId: row.device_token_id,\n deviceLabel: row.device_label,\n csrfHash: row.csrf_hash,\n }\n}\n\nexport function csrfMatches(session: AuthenticatedSession, csrf: string): boolean {\n return hashToken(csrf) === session.csrfHash\n}\n\nexport function list(\n db: BazilionDb,\n opts: { includeRevoked?: boolean; currentId?: string } = {},\n): WebSession[] {\n const where = opts.includeRevoked ? '' : 'WHERE s.revoked_at IS NULL'\n return db.raw\n .query<RawSession, []>(\n `SELECT s.*, t.label AS device_label FROM web_sessions s\n JOIN web_tokens t ON t.id = s.device_token_id\n ${where} ORDER BY s.created_at DESC`,\n )\n .all()\n .map((row) => toSession(row, opts.currentId))\n}\n\nexport function revoke(db: BazilionDb, id: string, now = Date.now()): boolean {\n return (\n db.raw.run('UPDATE web_sessions SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL', [\n now,\n id,\n ]).changes > 0\n )\n}\n\nexport function revokeForDevice(db: BazilionDb, tokenId: string, now = Date.now()): number {\n return db.raw.run(\n 'UPDATE web_sessions SET revoked_at = ? WHERE device_token_id = ? AND revoked_at IS NULL',\n [now, tokenId],\n ).changes\n}\n\nexport function revokeAll(db: BazilionDb, now = Date.now()): number {\n return db.raw.run('UPDATE web_sessions SET revoked_at = ? WHERE revoked_at IS NULL', [now])\n .changes\n}\n","import { createHash, randomBytes, randomUUID } from 'node:crypto'\nimport type { WebToken } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawToken {\n id: string\n label: string\n kind: 'bootstrap' | 'device'\n token_hash: string\n created_at: number\n last_used_at: number | null\n expires_at: number | null\n revoked_at: number | null\n}\n\nfunction toToken(r: RawToken): WebToken {\n return {\n id: r.id,\n label: r.label,\n kind: r.kind,\n createdAt: r.created_at,\n lastUsedAt: r.last_used_at,\n expiresAt: r.expires_at,\n revokedAt: r.revoked_at,\n }\n}\n\nexport function hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex')\n}\n\nexport interface CreatedToken {\n meta: WebToken\n /** Plaintext token — shown exactly once, never re-queryable. */\n token: string\n}\n\nexport function create(\n db: BazilionDb,\n label: string,\n opts: { kind?: 'bootstrap' | 'device'; expiresAt?: number | null } = {},\n): CreatedToken {\n const id = randomUUID()\n const token = randomBytes(24).toString('hex')\n const tokenHash = hashToken(token)\n const now = Date.now()\n const kind = opts.kind ?? 'device'\n const expiresAt = kind === 'bootstrap' ? null : (opts.expiresAt ?? now + 90 * 86_400_000)\n db.raw.run(\n `INSERT INTO web_tokens\n (id, label, kind, token_hash, created_at, last_used_at, expires_at, revoked_at)\n VALUES (?, ?, ?, ?, ?, NULL, ?, NULL)`,\n [id, label, kind, tokenHash, now, expiresAt],\n )\n return {\n token,\n meta: {\n id,\n label,\n kind,\n createdAt: now,\n lastUsedAt: null,\n expiresAt,\n revokedAt: null,\n },\n }\n}\n\nexport function list(db: BazilionDb, opts?: { includeRevoked?: boolean }): WebToken[] {\n const sql = opts?.includeRevoked\n ? 'SELECT * FROM web_tokens ORDER BY created_at ASC'\n : 'SELECT * FROM web_tokens WHERE revoked_at IS NULL ORDER BY created_at ASC'\n return db.raw.query<RawToken, []>(sql).all().map(toToken)\n}\n\nexport function get(db: BazilionDb, id: string): WebToken | null {\n const row = db.raw.query<RawToken, [string]>('SELECT * FROM web_tokens WHERE id = ?').get(id)\n return row ? toToken(row) : null\n}\n\n/**\n * Returns the active token row matching the given plaintext, or null.\n * Does NOT bump last_used_at — call markUsed separately once the caller\n * has decided the request is authorized.\n */\nexport function findActiveByToken(db: BazilionDb, token: string): WebToken | null {\n const tokenHash = hashToken(token)\n const row = db.raw\n .query<RawToken, [string, number]>(\n `SELECT * FROM web_tokens\n WHERE token_hash = ? AND revoked_at IS NULL\n AND (expires_at IS NULL OR expires_at > ?)`,\n )\n .get(tokenHash, Date.now())\n return row ? toToken(row) : null\n}\n\nexport function rejectionReason(\n db: BazilionDb,\n token: string,\n now = Date.now(),\n): 'invalid' | 'expired' | 'revoked' {\n const row = db.raw\n .query<Pick<RawToken, 'expires_at' | 'revoked_at'>, [string]>(\n 'SELECT expires_at, revoked_at FROM web_tokens WHERE token_hash = ?',\n )\n .get(hashToken(token))\n if (!row) return 'invalid'\n if (row.revoked_at !== null) return 'revoked'\n if (row.expires_at !== null && row.expires_at <= now) return 'expired'\n return 'invalid'\n}\n\nexport function markUsed(db: BazilionDb, id: string, when: number = Date.now()): void {\n db.raw.run('UPDATE web_tokens SET last_used_at = ? WHERE id = ?', [when, id])\n}\n\nexport function revoke(db: BazilionDb, id: string, when: number = Date.now()): boolean {\n const res = db.raw.run(\n 'UPDATE web_tokens SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL',\n [when, id],\n )\n return res.changes > 0\n}\n","// Auth / secrets entry points keyed off the bootstrap `auth.json` file.\n//\n// `auth.json` carries one mandatory field — `token` — written by the daemon's\n// first-run bootstrap. The daemon uses it as the PBKDF2 seed for the `secrets`\n// table; the CLI uses it as the bearer for loopback HTTP. CLI-side `remote`\n// overrides (set by `bazilion login`) coexist in the same file.\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport type { BazilionDb } from './db/client.ts'\nimport { openConfig } from './repos/config.ts'\nimport { openSecrets } from './repos/secrets.ts'\n\nexport interface AuthFile {\n token: string\n remote?: { server: string; token: string } | null\n}\n\n/**\n * Read `auth.json` and return the parsed contents. Throws when missing or\n * malformed — callers should treat that as \"bazilion not initialized.\"\n */\nexport function readAuthFile(authFile: string): AuthFile {\n if (!existsSync(authFile)) {\n throw new Error(\n `${authFile} not found. Start the daemon (\\`bazilion serve\\`) — it auto-bootstraps on first run.`,\n )\n }\n const raw = readFileSync(authFile, 'utf8')\n const parsed = JSON.parse(raw) as Partial<AuthFile>\n if (typeof parsed.token !== 'string' || !parsed.token) {\n throw new Error(`${authFile} is missing the \"token\" field`)\n }\n return {\n token: parsed.token,\n remote: parsed.remote ?? null,\n }\n}\n\n/**\n * Merge plaintext config + decrypted secrets + process env into a single\n * env-shaped record. Precedence (low → high): config → secrets → env. The\n * caller supplies the bootstrap `password` (typically `readAuthFile().token`)\n * because the secrets table is encrypted with it.\n *\n * Either layer may fail to read (corrupt row, wrong key, table absent at\n * fixture-bootstrap time); failures are swallowed per-layer so a single bad\n * value never blocks the merge.\n */\nexport function mergeSecretsIntoEnv(\n db: BazilionDb,\n password: string,\n env: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n let configValues: Record<string, string> = {}\n let secretValues: Record<string, string> = {}\n try {\n configValues = openConfig(db).getAll()\n } catch {\n // table missing or unreadable — continue without the layer\n }\n try {\n secretValues = openSecrets(db, password).getAll()\n } catch {\n // table missing or wrong password — continue without the layer\n }\n return { ...configValues, ...secretValues, ...env }\n}\n","import { cpSync, existsSync, mkdtempSync, readdirSync, rmSync, statSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, join, resolve, sep } from 'node:path'\nimport type { SkillScanFinding } from '@bazilion/api-types'\nimport AdmZip from 'adm-zip'\nimport type { Paths } from '../paths.ts'\nimport { parseSkillFile } from './parse.ts'\nimport { scanSkillContent } from './scan.ts'\n\nexport interface ImportSkillsInput {\n /**\n * Absolute path to either:\n * - a single skill folder (contains SKILL.md),\n * - a parent directory containing multiple skill folders,\n * - a `.zip` file whose top level holds one-folder-per-skill (or a single\n * wrapping folder that holds them).\n */\n source: string\n /** overwrite existing target skills */\n force?: boolean\n}\n\nexport interface ImportResult {\n imported: string[]\n skipped: { name: string; reason: string; findings?: SkillScanFinding[] }[]\n findings?: Record<string, SkillScanFinding[]>\n}\n\nexport class SkillScanBlockedError extends Error {\n findings: Record<string, SkillScanFinding[]>\n\n constructor(findings: Record<string, SkillScanFinding[]>) {\n const names = Object.keys(findings).sort()\n super(\n `skill scan blocked import: ${names.join(', ')} ${names.length === 1 ? 'has' : 'have'} findings (rerun with --force to confirm)`,\n )\n this.name = 'SkillScanBlockedError'\n this.findings = findings\n }\n}\n\n/**\n * Extract a zip archive into a fresh temp dir with a zip-slip guard — every\n * entry's resolved path must stay under the extraction root. Returns both the\n * mkdtemp root (for cleanup) and the effective source path to hand to the\n * importer. When the archive is wrapped in a single top-level directory\n * (common with GitHub-style archives), we descend into it so `source` lines\n * up with the \"parent-of-skill-dirs\" shape the importer already understands.\n */\nfunction extractZipSafely(zipPath: string): { root: string; effectiveSource: string } {\n const root = mkdtempSync(join(tmpdir(), 'bazilion-skill-zip-'))\n try {\n const zip = new AdmZip(zipPath)\n for (const entry of zip.getEntries()) {\n const rawName = entry.entryName\n if (rawName.startsWith('/') || rawName.startsWith('\\\\')) {\n throw new Error(`zip entry has absolute path: ${rawName}`)\n }\n const resolved = resolve(root, rawName)\n if (resolved !== root && !resolved.startsWith(root + sep)) {\n throw new Error(`zip entry escapes extraction root: ${rawName}`)\n }\n }\n zip.extractAllTo(root, true)\n } catch (err) {\n rmSync(root, { recursive: true, force: true })\n throw err\n }\n\n // Unwrap a single top-level folder if present — the importer's\n // \"source-is-a-directory\" cases (single-skill dir vs. parent-of-skills dir)\n // both apply equally well to the unwrapped path.\n let effectiveSource = root\n const topEntries = readdirSync(root, { withFileTypes: true })\n if (topEntries.length === 1 && topEntries[0]?.isDirectory()) {\n effectiveSource = join(root, topEntries[0].name)\n }\n return { root, effectiveSource }\n}\n\nexport function importSkills(paths: Paths, input: ImportSkillsInput): ImportResult {\n const rawSource = resolve(input.source)\n if (!existsSync(rawSource)) {\n throw new Error(`source does not exist: ${rawSource}`)\n }\n\n let source = rawSource\n let tempRoot: string | null = null\n const sourceStat = statSync(rawSource)\n if (sourceStat.isFile()) {\n if (!rawSource.toLowerCase().endsWith('.zip')) {\n throw new Error(`source file must be a .zip archive: ${rawSource}`)\n }\n const { root, effectiveSource } = extractZipSafely(rawSource)\n tempRoot = root\n source = effectiveSource\n } else if (!sourceStat.isDirectory()) {\n throw new Error(`source is not a directory: ${rawSource}`)\n }\n\n try {\n return importSkillsFromDir(paths, source, input)\n } finally {\n if (tempRoot) rmSync(tempRoot, { recursive: true, force: true })\n }\n}\n\nfunction importSkillsFromDir(paths: Paths, source: string, input: ImportSkillsInput): ImportResult {\n const candidates: { name: string; dir: string }[] = []\n\n // Two shapes are accepted:\n // 1. source is itself a single skill dir (contains SKILL.md)\n // 2. source is a parent containing multiple skill dirs\n if (existsSync(join(source, 'SKILL.md'))) {\n candidates.push({ name: basename(source), dir: source })\n } else {\n const entries = readdirSync(source, { withFileTypes: true })\n for (const e of entries) {\n if (!e.isDirectory()) continue\n const skillDir = join(source, e.name)\n if (!existsSync(join(skillDir, 'SKILL.md'))) continue\n candidates.push({ name: e.name, dir: skillDir })\n }\n }\n\n if (candidates.length === 0) {\n throw new Error(`no skills found in ${source}`)\n }\n\n // Validate and scan every SKILL.md before touching the target dir.\n const findingsBySkill: Record<string, SkillScanFinding[]> = {}\n for (const c of candidates) {\n const parsed = parseSkillFile(join(c.dir, 'SKILL.md'))\n const findings = scanSkillContent(parsed.raw)\n if (findings.length > 0) findingsBySkill[c.name] = findings\n }\n if (Object.keys(findingsBySkill).length > 0 && !input.force) {\n throw new SkillScanBlockedError(findingsBySkill)\n }\n\n const imported: string[] = []\n const skipped: { name: string; reason: string; findings?: SkillScanFinding[] }[] = []\n\n for (const c of candidates) {\n const target = join(paths.skillsDir, c.name)\n if (existsSync(target) && !input.force) {\n skipped.push({\n name: c.name,\n reason: 'already exists (use --force to overwrite)',\n findings: findingsBySkill[c.name],\n })\n continue\n }\n cpSync(c.dir, target, { recursive: true, force: !!input.force })\n imported.push(c.name)\n }\n\n return {\n imported,\n skipped,\n findings: Object.keys(findingsBySkill).length > 0 ? findingsBySkill : undefined,\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { parse as parseYaml } from 'yaml'\n\n/**\n * Standard agent-skill frontmatter. Required fields are typed; the rest is open\n * (skill formats may add fields like `allowed-tools`, `homepage`, etc.).\n */\nexport interface SkillFrontmatter {\n name: string\n description: string\n [key: string]: unknown\n}\n\nexport interface ParsedSkill {\n frontmatter: SkillFrontmatter\n body: string\n raw: string\n}\n\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/\n\nexport function parseSkillContent(raw: string): ParsedSkill {\n const m = raw.match(FRONTMATTER_RE)\n if (!m) {\n throw new Error('SKILL.md missing YAML frontmatter (expected leading \"---\")')\n }\n const yamlBlock = m[1] ?? ''\n const body = m[2] ?? ''\n\n let fm: unknown\n try {\n fm = parseYaml(yamlBlock)\n } catch (err) {\n throw new Error(`SKILL.md frontmatter is not valid YAML: ${(err as Error).message}`)\n }\n if (!fm || typeof fm !== 'object' || Array.isArray(fm)) {\n throw new Error('SKILL.md frontmatter must be a YAML object')\n }\n const fmObj = fm as Record<string, unknown>\n if (typeof fmObj.name !== 'string' || fmObj.name.length === 0) {\n throw new Error('SKILL.md frontmatter missing required \"name\"')\n }\n if (typeof fmObj.description !== 'string' || fmObj.description.length === 0) {\n throw new Error('SKILL.md frontmatter missing required \"description\"')\n }\n return { frontmatter: fmObj as SkillFrontmatter, body, raw }\n}\n\nexport function parseSkillFile(path: string): ParsedSkill {\n const raw = readFileSync(path, 'utf8')\n return parseSkillContent(raw)\n}\n","import type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\n\nexport function deleteTeam(\n db: BazilionDb,\n paths: Paths,\n id: string,\n expectedTeamPolicyRevision: number,\n): void {\n const g = teamRepo.get(db, id, paths)\n if (!g) throw new Error(`team not found: ${id}`)\n const teamPolicy = teamPolicyRepo.get(db, id)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${id}`)\n if (teamPolicy.revision !== expectedTeamPolicyRevision) {\n throw new Error(\n `team_revision_conflict: expected ${expectedTeamPolicyRevision}, current ${teamPolicy.revision}`,\n )\n }\n\n // ON DELETE RESTRICT on agents.team_id enforces this at the SQL layer,\n // but we surface a friendlier error listing the blocking members.\n const members = agentRepo.list(db, { includeArchived: true }).filter((a) => a.teamId === id)\n if (members.length > 0) {\n const names = members.map((a) => `${a.name} (${a.id.slice(0, 8)})`).join(', ')\n throw new Error(\n `cannot delete team \"${id}\": ${members.length} agent(s) still belong to it: ${names}. Move or delete them first.`,\n )\n }\n const stagedPath = `${g.path}.deleting-${randomUUID()}`\n const hadSlot = existsSync(g.path)\n if (hadSlot) renameSync(g.path, stagedPath)\n try {\n db.raw.transaction(() => {\n const current = teamPolicyRepo.get(db, id)\n if (!current) throw new Error(`team_policy_missing: ${id}`)\n if (current.revision !== expectedTeamPolicyRevision) {\n throw new Error(\n `team_revision_conflict: expected ${expectedTeamPolicyRevision}, current ${current.revision}`,\n )\n }\n teamRepo.remove(db, id)\n })()\n } catch (error) {\n if (hadSlot && existsSync(stagedPath)) renameSync(stagedPath, g.path)\n throw error\n }\n if (hadSlot && existsSync(stagedPath)) {\n // For linked Teams this removes the renamed symlink only, never its target.\n rmSync(stagedPath, { recursive: true, force: true })\n }\n}\n\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, renameSync, rmSync } from 'node:fs'\n","import { randomUUID } from 'node:crypto'\nimport type { ResolvedTeamPolicy, TeamPolicyEdge, TeamPolicyPlacement } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamTemplateRepo from '../repos/teamTemplates.ts'\n\ntype RemainingPlacement = Exclude<TeamPolicyPlacement, 'template_snapshot'>\n\nexport interface AdoptTeamTemplateInput {\n teamExpectedRevision: number\n templateId: string\n templateExpectedRevision: number\n slotMappings: Array<{ slotId: string; agentId: string }>\n remainingPlacements: Array<{ agentId: string; placement: RemainingPlacement }>\n previewEdges: Omit<TeamPolicyEdge, 'teamId'>[]\n}\n\nexport function previewTeamPolicyAdoption(\n db: BazilionDb,\n teamId: string,\n input: Omit<AdoptTeamTemplateInput, 'previewEdges'>,\n): Omit<TeamPolicyEdge, 'teamId'>[] {\n return resolveAdoptionPlan(db, teamId, { ...input, previewEdges: [] }).resolved\n}\n\nfunction resolveAdoptionPlan(db: BazilionDb, teamId: string, input: AdoptTeamTemplateInput) {\n const teamPolicy = teamPolicyRepo.get(db, teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${teamId}`)\n if (teamPolicy.revision !== input.teamExpectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n const template = teamTemplateRepo.get(db, input.templateId)\n if (!template) throw new Error(`team template not found: ${input.templateId}`)\n if (template.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`)\n if (template.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${template.currentRevision}`,\n )\n }\n const snapshot = teamTemplateRepo.revision(db, template.id, input.templateExpectedRevision)\n if (!snapshot) throw new Error('template_snapshot_missing')\n const members = agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === teamId)\n const memberIds = new Set(members.map((agent) => agent.id))\n const mapping = new Map(input.slotMappings.map((item) => [item.slotId, item.agentId]))\n if (mapping.size !== input.slotMappings.length || mapping.size !== snapshot.slots.length) {\n throw new Error('adoption_mapping_invalid: every active source slot must be mapped once')\n }\n const mappedAgents = new Set(mapping.values())\n if (mappedAgents.size !== mapping.size) {\n throw new Error('adoption_mapping_invalid: mappings must be injective')\n }\n for (const slot of snapshot.slots) {\n const agentId = mapping.get(slot.slotId)\n if (!agentId || !memberIds.has(agentId)) {\n throw new Error(`adoption_mapping_invalid: slot ${slot.slotId} is not mapped to a member`)\n }\n }\n const remainingIds = new Set([...memberIds].filter((id) => !mappedAgents.has(id)))\n if (\n input.remainingPlacements.some(\n (item) => item.placement !== 'isolated' && item.placement !== 'profile_defaults',\n )\n ) {\n throw new Error('adoption_mapping_invalid: unknown placement')\n }\n const placements = new Map(\n input.remainingPlacements.map((item) => [item.agentId, item.placement]),\n )\n if (\n placements.size !== input.remainingPlacements.length ||\n placements.size !== remainingIds.size ||\n [...remainingIds].some((id) => !placements.has(id)) ||\n [...placements].some(([id]) => !remainingIds.has(id))\n ) {\n throw new Error('adoption_mapping_invalid: every remaining Agent needs one placement')\n }\n return {\n template,\n snapshot,\n mapping,\n resolved: resolveAdoptionEdges(db, snapshot.edges, mapping, placements, members),\n }\n}\n\nexport function adoptTeamTemplate(\n db: BazilionDb,\n teamId: string,\n input: AdoptTeamTemplateInput,\n): ResolvedTeamPolicy {\n return db.raw.transaction(() => {\n const { template, snapshot, mapping, resolved } = resolveAdoptionPlan(db, teamId, input)\n if (!sameEdges(resolved, input.previewEdges)) {\n throw new Error('adoption_preview_mismatch: reviewed preview does not match resolved policy')\n }\n\n db.raw.run('UPDATE team_policies SET baseline_instantiation_id = NULL WHERE team_id = ?', [\n teamId,\n ])\n db.raw.run(\n `DELETE FROM source_slot_bindings WHERE instantiation_id IN\n (SELECT id FROM template_instantiations WHERE team_id = ?)`,\n [teamId],\n )\n db.raw.run('DELETE FROM template_instantiations WHERE team_id = ?', [teamId])\n db.raw.run('DELETE FROM team_policy_edges WHERE team_id = ?', [teamId])\n const instantiationId = randomUUID()\n db.raw.run(\n `INSERT INTO template_instantiations\n (id, team_id, template_id, template_revision, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n [instantiationId, teamId, template.id, snapshot.revision, Date.now()],\n )\n for (const [slotId, agentId] of mapping) {\n db.raw.run(\n `INSERT INTO source_slot_bindings (agent_id, instantiation_id, source_slot_id)\n VALUES (?, ?, ?)`,\n [agentId, instantiationId, slotId],\n )\n }\n for (const edge of resolved) {\n db.raw.run(\n `INSERT INTO team_policy_edges\n (team_id, source_kind, source_id, target_kind, target_id, posture)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [\n teamId,\n edge.sourceKind,\n edge.sourceId ?? '',\n edge.targetKind,\n edge.targetId ?? '',\n edge.posture,\n ],\n )\n }\n db.raw.run('UPDATE team_policies SET baseline_instantiation_id = ? WHERE team_id = ?', [\n instantiationId,\n teamId,\n ])\n teamPolicyRepo.bumpRevision(db, teamId)\n const result = teamPolicyRepo.detail(db, teamId)\n if (!result) throw new Error(`team_policy_missing: ${teamId}`)\n return result\n })()\n}\n\nfunction resolveAdoptionEdges(\n db: BazilionDb,\n templateEdges: Array<{\n sourceKind: 'user' | 'outside_team' | 'slot'\n sourceId: string | null\n targetKind: 'user' | 'outside_team' | 'slot'\n targetId: string | null\n posture: 'allow' | 'approval_required'\n }>,\n mapping: ReadonlyMap<string, string>,\n placements: ReadonlyMap<string, RemainingPlacement>,\n members: ReturnType<typeof agentRepo.list>,\n): Omit<TeamPolicyEdge, 'teamId'>[] {\n const edgeMap = new Map<string, Omit<TeamPolicyEdge, 'teamId'>>()\n const add = (\n sourceKind: TeamPolicyEdge['sourceKind'],\n sourceId: string | null,\n targetKind: TeamPolicyEdge['targetKind'],\n targetId: string | null,\n posture: TeamPolicyEdge['posture'] = 'allow',\n ) => {\n const edge = { sourceKind, sourceId, targetKind, targetId, posture }\n edgeMap.set(edgeKey(edge), edge)\n }\n for (const edge of templateEdges) {\n add(\n edge.sourceKind === 'slot' ? 'agent' : edge.sourceKind,\n edge.sourceKind === 'slot' ? (mapping.get(edge.sourceId ?? '') ?? null) : null,\n edge.targetKind === 'slot' ? 'agent' : edge.targetKind,\n edge.targetKind === 'slot' ? (mapping.get(edge.targetId ?? '') ?? null) : null,\n edge.posture,\n )\n }\n const mappedIds = new Set(mapping.values())\n const memberById = new Map(members.map((agent) => [agent.id, agent]))\n const requestsPeers = new Set<string>()\n for (const [agentId, placement] of placements) {\n const agent = memberById.get(agentId)\n if (!agent) continue\n if (placement === 'profile_defaults') {\n const defaults = db.raw\n .query<\n {\n user_input: number\n user_output: number\n outside_team_input: number\n outside_team_output: number\n peer_default: string\n },\n [string]\n >('SELECT * FROM profile_communication_defaults WHERE profile_id = ?')\n .get(agent.profileId)\n if (defaults?.user_input) add('user', null, 'agent', agentId)\n if (defaults?.user_output) add('agent', agentId, 'user', null)\n if (defaults?.outside_team_input) add('outside_team', null, 'agent', agentId)\n if (defaults?.outside_team_output) add('agent', agentId, 'outside_team', null)\n if (defaults?.peer_default === 'allow_all') requestsPeers.add(agentId)\n }\n }\n for (const agentId of requestsPeers) {\n for (const mappedId of mappedIds) {\n add('agent', agentId, 'agent', mappedId)\n add('agent', mappedId, 'agent', agentId)\n }\n }\n const liveOnly = [...placements.keys()]\n for (let left = 0; left < liveOnly.length; left++) {\n for (let right = left + 1; right < liveOnly.length; right++) {\n const a = liveOnly[left]\n const b = liveOnly[right]\n if (a && b && requestsPeers.has(a) && requestsPeers.has(b)) {\n add('agent', a, 'agent', b)\n add('agent', b, 'agent', a)\n }\n }\n }\n return [...edgeMap.values()].sort((a, b) => edgeKey(a).localeCompare(edgeKey(b)))\n}\n\nfunction sameEdges(\n actual: Omit<TeamPolicyEdge, 'teamId'>[],\n preview: Omit<TeamPolicyEdge, 'teamId'>[],\n): boolean {\n const a = actual.map(edgeKey).sort()\n const b = preview.map(edgeKey).sort()\n return a.length === b.length && a.every((value, index) => value === b[index])\n}\n\nfunction edgeKey(edge: Omit<TeamPolicyEdge, 'teamId'>): string {\n return `${edge.sourceKind}:${edge.sourceId ?? ''}>${edge.targetKind}:${edge.targetId ?? ''}[${edge.posture ?? 'allow'}]`\n}\n","import { createHash, randomUUID } from 'node:crypto'\nimport type {\n CommunicationAuthorizationResult,\n CommunicationChannel,\n CommunicationComponentOutcome,\n CommunicationDecision,\n CommunicationEndpoint,\n CommunicationPolicyRef,\n} from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\nexport type { CommunicationChannel, CommunicationDecision, CommunicationEndpoint }\n\nexport interface AuthorizationInput {\n source: CommunicationEndpoint\n target: CommunicationEndpoint\n origin: string\n attemptKind: string\n attemptId: string\n // Boundary-only metadata ignored by policy evaluation.\n approvalPayloadKind?: string\n approvalPayload?: unknown\n requester?: string\n}\n\nexport type PolicyRef = CommunicationPolicyRef\nexport type ComponentOutcome = CommunicationComponentOutcome\nexport type AuthorizationResult = CommunicationAuthorizationResult\n\ninterface AgentRow {\n id: string\n team_id: string\n status: string\n}\ninterface TeamPolicyRow {\n team_id: string\n revision: number\n}\ninterface EdgeRow {\n source_kind: string\n source_id: string\n target_kind: string\n target_id: string\n posture: 'allow' | 'approval_required'\n}\n\nfunction denied(channel: CommunicationChannel, code: string, reason: string): AuthorizationResult {\n return {\n decision: 'deny',\n channel,\n reasonCode: code,\n reason,\n policyRefs: [],\n componentOutcomes: [],\n matchedEdgeIds: [],\n requiredEdgeIds: [],\n }\n}\n\nfunction edgeId(teamId: string, sk: string, sid: string, tk: string, tid: string): string {\n return `${teamId}:${sk}:${sid || '-'}->${tk}:${tid || '-'}`\n}\n\nexport function authorizeCommunication(\n db: BazilionDb,\n input: AuthorizationInput,\n): AuthorizationResult {\n return db.raw.transaction(() => authorizeInSnapshot(db, input))()\n}\n\nexport function authorizeInSnapshot(\n db: BazilionDb,\n input: AuthorizationInput,\n): AuthorizationResult {\n const channel: CommunicationChannel =\n input.source.kind === 'user' || input.target.kind === 'user'\n ? 'user'\n : input.source.kind === 'outside_team' || input.target.kind === 'outside_team'\n ? 'outside_team'\n : 'same_team'\n if (!input.origin || !input.attemptKind || !input.attemptId)\n return denied(\n channel,\n 'invalid_communication_path',\n 'origin and typed attempt identity are required',\n )\n if (input.source.kind !== 'agent' && input.target.kind !== 'agent')\n return denied(\n channel,\n 'invalid_communication_path',\n 'a communication path must include an Agent',\n )\n if (\n input.source.kind === 'agent' &&\n input.target.kind === 'agent' &&\n input.source.id === input.target.id\n )\n return denied(\n channel,\n 'invalid_communication_path',\n 'self communication is not a valid policy path',\n )\n\n const resolveAgent = (endpoint: CommunicationEndpoint): AgentRow | null =>\n endpoint.kind === 'agent'\n ? db.raw\n .query<AgentRow, [string]>('SELECT id, team_id, status FROM agents WHERE id = ?')\n .get(endpoint.id)\n : null\n const sourceAgent = resolveAgent(input.source)\n const targetAgent = resolveAgent(input.target)\n if (input.source.kind === 'agent' && !sourceAgent)\n return denied(channel, 'agent_not_found', 'source Agent was not found')\n if (input.target.kind === 'agent' && !targetAgent)\n return denied(channel, 'agent_not_found', 'target Agent was not found')\n if (sourceAgent?.status === 'archived' || targetAgent?.status === 'archived')\n return denied(channel, 'agent_archived', 'archived Agents cannot communicate')\n\n const sourceGroup =\n sourceAgent?.team_id ??\n targetAgent?.team_id ??\n ('teamId' in input.source ? input.source.teamId : '')\n const targetGroup =\n targetAgent?.team_id ??\n sourceAgent?.team_id ??\n ('teamId' in input.target ? input.target.teamId : '')\n const actualChannel: CommunicationChannel =\n sourceGroup && targetGroup && sourceGroup !== targetGroup ? 'cross_team' : channel\n if (!sourceGroup || !targetGroup)\n return denied(actualChannel, 'member_not_in_group', 'endpoint is not a Team member')\n\n const teams = [...new Set([sourceGroup, targetGroup])]\n const teamPolicyes = new Map<string, TeamPolicyRow>()\n for (const team of teams) {\n const teamPolicy = db.raw\n .query<TeamPolicyRow, [string]>(\n 'SELECT team_id, revision FROM team_policies WHERE team_id = ?',\n )\n .get(team)\n if (!teamPolicy)\n return denied(actualChannel, 'team_policy_missing', `Team policy is missing for ${team}`)\n if (!Number.isInteger(teamPolicy.revision) || teamPolicy.revision < 1)\n return denied(actualChannel, 'team_policy_invalid', `Team policy is invalid for ${team}`)\n teamPolicyes.set(team, teamPolicy)\n }\n\n const requirements: Array<{\n teamId: string\n sk: string\n sid: string\n tk: string\n tid: string\n failure: string\n }> = []\n if (\n input.source.kind === 'agent' &&\n input.target.kind === 'agent' &&\n sourceGroup !== targetGroup\n ) {\n requirements.push(\n {\n teamId: sourceGroup,\n sk: 'agent',\n sid: input.source.id,\n tk: 'outside_team',\n tid: '',\n failure: 'source_outside_output_denied',\n },\n {\n teamId: targetGroup,\n sk: 'outside_team',\n sid: '',\n tk: 'agent',\n tid: input.target.id,\n failure: 'target_outside_input_denied',\n },\n )\n } else {\n const teamId = sourceGroup\n const mapEndpoint = (endpoint: CommunicationEndpoint): [string, string] =>\n endpoint.kind === 'agent' ? ['agent', endpoint.id] : [endpoint.kind, '']\n const [sk, sid] = mapEndpoint(input.source)\n const [tk, tid] = mapEndpoint(input.target)\n requirements.push({ teamId, sk, sid, tk, tid, failure: 'no_allow_edge' })\n }\n\n const outcomes = requirements.map((requirement) => {\n const edge = db.raw\n .query<EdgeRow, [string, string, string, string, string]>(\n 'SELECT source_kind, source_id, target_kind, target_id, posture FROM team_policy_edges WHERE team_id = ? AND source_kind = ? AND source_id = ? AND target_kind = ? AND target_id = ?',\n )\n .get(requirement.teamId, requirement.sk, requirement.sid, requirement.tk, requirement.tid)\n return {\n teamId: requirement.teamId,\n edge: edgeId(\n requirement.teamId,\n requirement.sk,\n requirement.sid,\n requirement.tk,\n requirement.tid,\n ),\n matched: edge !== null,\n posture: edge?.posture ?? null,\n }\n })\n const policyRefs = teams.map((teamId) => ({\n teamId,\n revision: teamPolicyes.get(teamId)?.revision ?? 0,\n }))\n const requiredEdgeIds = outcomes.map((outcome) => outcome.edge)\n const matchedEdgeIds = outcomes\n .filter((outcome) => outcome.matched)\n .map((outcome) => outcome.edge)\n const failed = requirements.find((_, index) => !outcomes[index]?.matched)\n if (failed) {\n return {\n decision: 'deny',\n channel: actualChannel,\n reasonCode: failed.failure,\n reason: `required allow edge is absent: ${outcomes[requirements.indexOf(failed)]?.edge}`,\n policyRefs,\n componentOutcomes: outcomes,\n matchedEdgeIds,\n requiredEdgeIds,\n }\n }\n const approvalEdges = outcomes.filter((outcome) => outcome.posture === 'approval_required')\n return approvalEdges.length\n ? {\n decision: 'approval_required',\n channel: actualChannel,\n reasonCode: 'approval_required',\n reason: `approval is required by ${approvalEdges.length} policy edge${approvalEdges.length === 1 ? '' : 's'}`,\n policyRefs,\n componentOutcomes: outcomes,\n matchedEdgeIds,\n requiredEdgeIds,\n }\n : {\n decision: 'allow',\n channel: actualChannel,\n reasonCode: 'allowed',\n reason: 'all required policy edges allow communication',\n policyRefs,\n componentOutcomes: outcomes,\n matchedEdgeIds,\n requiredEdgeIds,\n }\n}\n\nexport function authorizationFingerprint(input: AuthorizationInput, operation: string): string {\n return createHash('sha256')\n .update(JSON.stringify({ operation, source: input.source, target: input.target }))\n .digest('hex')\n}\n\nexport function recordDenial(\n db: BazilionDb,\n input: AuthorizationInput,\n operation: string,\n result: AuthorizationResult,\n): AuthorizationResult {\n const fingerprint = authorizationFingerprint(input, operation)\n const loadExisting = () =>\n db.raw\n .query<\n {\n fingerprint: string\n reason_code: string\n reason: string\n channel: CommunicationChannel\n policy_refs_json: string\n component_outcomes_json: string\n matched_edge_ids_json: string\n required_edge_ids_json: string\n },\n [string, string]\n >(\n 'SELECT fingerprint, reason_code, reason, channel, policy_refs_json, component_outcomes_json, matched_edge_ids_json, required_edge_ids_json FROM team_policy_block_events WHERE attempt_kind = ? AND attempt_id = ?',\n )\n .get(input.attemptKind, input.attemptId)\n const existing = loadExisting()\n if (existing) {\n if (existing.fingerprint !== fingerprint)\n return denied(\n result.channel,\n 'attempt_key_conflict',\n 'typed attempt identity was already used for different semantics',\n )\n return {\n decision: 'deny',\n channel: existing.channel,\n reasonCode: existing.reason_code,\n reason: existing.reason,\n policyRefs: JSON.parse(existing.policy_refs_json),\n componentOutcomes: JSON.parse(existing.component_outcomes_json),\n matchedEdgeIds: JSON.parse(existing.matched_edge_ids_json),\n requiredEdgeIds: JSON.parse(existing.required_edge_ids_json),\n }\n }\n const agentGroup = (endpoint: CommunicationEndpoint) =>\n endpoint.kind === 'agent'\n ? (db.raw\n .query<{ team_id: string }, [string]>('SELECT team_id FROM agents WHERE id = ?')\n .get(endpoint.id)?.team_id ?? null)\n : null\n const sourceGroup =\n agentGroup(input.source) ??\n agentGroup(input.target) ??\n ('teamId' in input.source ? input.source.teamId : null)\n const targetGroup =\n agentGroup(input.target) ??\n agentGroup(input.source) ??\n ('teamId' in input.target ? input.target.teamId : null)\n const values = [\n randomUUID(),\n input.attemptKind,\n input.attemptId,\n operation,\n input.source.kind,\n input.source.kind === 'agent' ? input.source.id : '',\n input.target.kind,\n input.target.kind === 'agent' ? input.target.id : '',\n sourceGroup,\n targetGroup,\n result.channel,\n input.origin,\n result.reasonCode,\n result.reason,\n JSON.stringify(result.policyRefs),\n JSON.stringify(result.componentOutcomes),\n JSON.stringify(result.matchedEdgeIds),\n JSON.stringify(result.requiredEdgeIds),\n fingerprint,\n Date.now(),\n ]\n try {\n db.raw.run(\n `INSERT INTO team_policy_block_events (id, attempt_kind, attempt_id, operation, source_kind, source_id, target_kind, target_id, source_team_id, target_team_id, channel, origin, reason_code, reason, policy_refs_json, component_outcomes_json, matched_edge_ids_json, required_edge_ids_json, fingerprint, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n values,\n )\n } catch (error) {\n // Another connection may have won the typed-attempt unique race after our lookup.\n // Reloading applies the same immutable fingerprint contract; unrelated DB failures remain errors.\n const raced = loadExisting()\n if (!raced) throw error\n if (raced.fingerprint !== fingerprint)\n return denied(\n result.channel,\n 'attempt_key_conflict',\n 'typed attempt identity was already used for different semantics',\n )\n return {\n decision: 'deny',\n channel: raced.channel,\n reasonCode: raced.reason_code,\n reason: raced.reason,\n policyRefs: JSON.parse(raced.policy_refs_json),\n componentOutcomes: JSON.parse(raced.component_outcomes_json),\n matchedEdgeIds: JSON.parse(raced.matched_edge_ids_json),\n requiredEdgeIds: JSON.parse(raced.required_edge_ids_json),\n }\n }\n return result\n}\n","import { randomUUID } from 'node:crypto'\nimport type { TeamPolicyEdge, TeamTemplateDetail } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\nimport * as teamTemplateRepo from '../repos/teamTemplates.ts'\n\nexport interface TeamPolicyDiff {\n teamId: string\n liveRevision: number\n baseline: {\n instantiationId: string\n templateId: string\n templateRevision: number\n snapshot: ReturnType<typeof teamTemplateRepo.revision>\n } | null\n currentSource: TeamTemplateDetail | null\n liveEdges: TeamPolicyEdge[]\n sourceDiverged: boolean\n comparison: {\n baselineEdges: TeamPolicyEdge[]\n addedSinceBaseline: TeamPolicyEdge[]\n removedSinceBaseline: TeamPolicyEdge[]\n currentSourceAddedSlotIds: string[]\n currentSourceRemovedSlotIds: string[]\n }\n}\n\nexport function diffTeamPolicy(db: BazilionDb, teamId: string): TeamPolicyDiff {\n const detail = teamPolicyRepo.detail(db, teamId)\n if (!detail) throw new Error(`team_policy_missing: ${teamId}`)\n const baseline = detail.baseline\n const source = baseline ? teamTemplateRepo.detail(db, baseline.templateId) : null\n const baselineSnapshot = baseline\n ? teamTemplateRepo.revision(db, baseline.templateId, baseline.templateRevision)\n : null\n const bindingBySlot = new Map(\n detail.bindings\n .filter((binding) => binding.instantiationId === baseline?.id)\n .map((binding) => [binding.sourceSlotId, binding.agentId]),\n )\n const baselineEdges: TeamPolicyEdge[] = (baselineSnapshot?.edges ?? []).flatMap((edge) => {\n const sourceId = edge.sourceKind === 'slot' ? bindingBySlot.get(edge.sourceId ?? '') : null\n const targetId = edge.targetKind === 'slot' ? bindingBySlot.get(edge.targetId ?? '') : null\n if ((edge.sourceKind === 'slot' && !sourceId) || (edge.targetKind === 'slot' && !targetId)) {\n return []\n }\n return [\n {\n teamId,\n sourceKind: edge.sourceKind === 'slot' ? 'agent' : edge.sourceKind,\n sourceId: sourceId ?? null,\n targetKind: edge.targetKind === 'slot' ? 'agent' : edge.targetKind,\n targetId: targetId ?? null,\n posture: edge.posture,\n },\n ]\n })\n const liveEdges = detail.edges\n const baselineKeys = new Set(baselineEdges.map(liveEdgeKey))\n const liveKeys = new Set(liveEdges.map(liveEdgeKey))\n const baselineSlotIds = new Set(baselineSnapshot?.slots.map((slot) => slot.slotId) ?? [])\n const currentSlotIds = new Set(source?.slots.map((slot) => slot.slotId) ?? [])\n return {\n teamId,\n liveRevision: detail.teamPolicy.revision,\n baseline: baseline\n ? {\n instantiationId: baseline.id,\n templateId: baseline.templateId,\n templateRevision: baseline.templateRevision,\n snapshot: baselineSnapshot,\n }\n : null,\n currentSource: source,\n liveEdges,\n sourceDiverged: !!baseline && source?.template.currentRevision !== baseline.templateRevision,\n comparison: {\n baselineEdges,\n addedSinceBaseline: liveEdges.filter((edge) => !baselineKeys.has(liveEdgeKey(edge))),\n removedSinceBaseline: baselineEdges.filter((edge) => !liveKeys.has(liveEdgeKey(edge))),\n currentSourceAddedSlotIds: [...currentSlotIds].filter((id) => !baselineSlotIds.has(id)),\n currentSourceRemovedSlotIds: [...baselineSlotIds].filter((id) => !currentSlotIds.has(id)),\n },\n }\n}\n\nexport function saveTeamPolicyAsTemplate(\n db: BazilionDb,\n paths: Paths,\n teamId: string,\n input: { expectedRevision: number; id: string; name: string; userMd?: string | null },\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n const teamPolicy = teamPolicyRepo.get(db, teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${teamId}`)\n if (teamPolicy.revision !== input.expectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.expectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n if (teamTemplateRepo.get(db, input.id)) {\n throw new Error(`team template already exists: ${input.id}`)\n }\n const team = teamRepo.get(db, teamId, paths)\n if (!team) throw new Error(`team not found: ${teamId}`)\n const members = agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === teamId)\n const slotByAgent = new Map(members.map((agent) => [agent.id, randomUUID()]))\n const stateByAgent = new Map(\n teamPolicyRepo.agentState(db, teamId).map((state) => [state.agentId, state]),\n )\n return teamTemplateRepo.insertCanonicalDefinition(db, {\n id: input.id,\n name: input.name,\n userMd: Object.hasOwn(input, 'userMd') ? input.userMd : team.userMd,\n slots: members.map((agent) => ({\n slotId: slotByAgent.get(agent.id),\n profileId: agent.profileId,\n agentName: agent.name,\n modelOverride: agent.modelOverride,\n reasoningLevel: agent.reasoningLevel,\n layoutPosition: stateByAgent.get(agent.id)?.position ?? null,\n display: stateByAgent.get(agent.id)?.display ?? null,\n })),\n edges: teamPolicyRepo.edges(db, teamId).map((edge) => ({\n sourceKind: edge.sourceKind === 'agent' ? 'slot' : edge.sourceKind,\n sourceId: edge.sourceKind === 'agent' ? slotByAgent.get(edge.sourceId ?? '') : null,\n targetKind: edge.targetKind === 'agent' ? 'slot' : edge.targetKind,\n targetId: edge.targetKind === 'agent' ? slotByAgent.get(edge.targetId ?? '') : null,\n posture: edge.posture,\n })),\n })\n })()\n}\n\nfunction liveEdgeKey(edge: TeamPolicyEdge): string {\n return `${edge.sourceKind}:${edge.sourceId ?? ''}>${edge.targetKind}:${edge.targetId ?? ''}[${edge.posture}]`\n}\n\nexport function updateTeamPolicySource(\n db: BazilionDb,\n teamId: string,\n input: {\n teamExpectedRevision: number\n templateExpectedRevision: number\n includeAgentIds: string[]\n },\n): {\n template: TeamTemplateDetail\n team: NonNullable<ReturnType<typeof teamPolicyRepo.detail>>\n} {\n return db.raw.transaction(() => {\n const teamPolicy = teamPolicyRepo.get(db, teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${teamId}`)\n if (teamPolicy.revision !== input.teamExpectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n if (!teamPolicy.baselineInstantiationId) throw new Error('baseline_missing: Team has no source')\n const baseline = teamPolicyRepo\n .instantiations(db, teamId)\n .find((item) => item.id === teamPolicy.baselineInstantiationId)\n if (!baseline) throw new Error('baseline_missing: retained baseline does not exist')\n const source = teamTemplateRepo.get(db, baseline.templateId)\n if (!source) throw new Error(`team template not found: ${baseline.templateId}`)\n if (source.deletedAt !== null) throw new Error(`template_deleted: ${source.id}`)\n if (source.currentRevision !== baseline.templateRevision) {\n throw new Error(\n `source_diverged: retained ${baseline.templateRevision}, current ${source.currentRevision}`,\n )\n }\n if (source.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${source.currentRevision}`,\n )\n }\n const include = new Set(input.includeAgentIds)\n if (include.size !== input.includeAgentIds.length) {\n throw new Error('source_update_invalid: duplicate included Agent')\n }\n const members = agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === teamId)\n const memberById = new Map(members.map((agent) => [agent.id, agent]))\n for (const id of include) {\n if (!memberById.has(id)) throw new Error(`member_not_in_group: ${id}`)\n }\n const bindings = teamPolicyRepo.bindings(db, teamId)\n const previous = teamTemplateRepo.revision(db, source.id, source.currentRevision)\n if (!previous) throw new Error('template_snapshot_missing')\n const slotPosition = new Map(previous.slots.map((slot) => [slot.slotId, slot.position]))\n const baselineBindings = bindings\n .filter((binding) => binding.instantiationId === baseline.id)\n .sort(\n (a, b) =>\n (slotPosition.get(a.sourceSlotId) ?? Number.MAX_SAFE_INTEGER) -\n (slotPosition.get(b.sourceSlotId) ?? Number.MAX_SAFE_INTEGER),\n )\n const selectedIds = new Set([...baselineBindings.map((binding) => binding.agentId), ...include])\n const slotByAgent = new Map(\n baselineBindings.map((binding) => [binding.agentId, binding.sourceSlotId]),\n )\n for (const id of selectedIds) {\n if (!slotByAgent.has(id)) slotByAgent.set(id, randomUUID())\n }\n const previousSlotById = new Map(previous.slots.map((slot) => [slot.slotId, slot]))\n const selectedMembers = [...selectedIds].map((id) => {\n const agent = memberById.get(id)\n if (!agent) throw new Error(`member_not_in_group: ${id}`)\n return agent\n })\n const template = teamTemplateRepo.replaceCanonicalDefinition(db, source.id, {\n expectedRevision: input.templateExpectedRevision,\n allowAllocatedSlotIds: true,\n slots: selectedMembers.map((agent) => {\n const slotId = slotByAgent.get(agent.id)\n const old = slotId ? previousSlotById.get(slotId) : undefined\n return {\n slotId,\n profileId: agent.profileId,\n agentName: agent.name,\n modelOverride: agent.modelOverride,\n reasoningLevel: agent.reasoningLevel,\n layoutPosition: old?.layoutPosition ?? null,\n display: old?.display ?? null,\n }\n }),\n edges: teamPolicyRepo\n .edges(db, teamId)\n .filter(\n (edge) =>\n (edge.sourceKind !== 'agent' || selectedIds.has(edge.sourceId ?? '')) &&\n (edge.targetKind !== 'agent' || selectedIds.has(edge.targetId ?? '')),\n )\n .map((edge) => ({\n sourceKind: edge.sourceKind === 'agent' ? 'slot' : edge.sourceKind,\n sourceId: edge.sourceKind === 'agent' ? slotByAgent.get(edge.sourceId ?? '') : null,\n targetKind: edge.targetKind === 'agent' ? 'slot' : edge.targetKind,\n targetId: edge.targetKind === 'agent' ? slotByAgent.get(edge.targetId ?? '') : null,\n })),\n })\n db.raw.run('UPDATE template_instantiations SET template_revision = ? WHERE id = ?', [\n template.template.currentRevision,\n baseline.id,\n ])\n for (const id of include) {\n const oldBinding = bindings.find((binding) => binding.agentId === id)\n if (oldBinding?.instantiationId === baseline.id) continue\n if (oldBinding) {\n db.raw.run('DELETE FROM source_slot_bindings WHERE agent_id = ?', [id])\n }\n db.raw.run(\n `INSERT INTO source_slot_bindings (agent_id, instantiation_id, source_slot_id)\n VALUES (?, ?, ?)`,\n [id, baseline.id, slotByAgent.get(id)],\n )\n if (oldBinding)\n pruneEmptyNonbaselineInstantiation(db, oldBinding.instantiationId, baseline.id)\n }\n teamPolicyRepo.bumpRevision(db, teamId)\n const team = teamPolicyRepo.detail(db, teamId)\n if (!team) throw new Error(`team_policy_missing: ${teamId}`)\n return { template, team }\n })()\n}\n\nfunction pruneEmptyNonbaselineInstantiation(\n db: BazilionDb,\n instantiationId: string,\n baselineId: string,\n): void {\n if (instantiationId === baselineId) return\n const remaining =\n db.raw\n .query<{ count: number }, [string]>(\n 'SELECT COUNT(*) AS count FROM source_slot_bindings WHERE instantiation_id = ?',\n )\n .get(instantiationId)?.count ?? 0\n if (remaining === 0) {\n db.raw.run('DELETE FROM template_instantiations WHERE id = ?', [instantiationId])\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport { readdirSync, rmSync } from 'node:fs'\nimport type { Agent, ResolvedTeamPolicy } from '@bazilion/api-types'\nimport { spawnAgent } from '../agent/spawn.ts'\nimport type { BazilionDb } from '../db/client.ts'\nimport { inTx } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\nimport * as teamTemplateRepo from '../repos/teamTemplates.ts'\nimport { registerTeam } from '../team/register.ts'\nimport { rmWithRetry } from '../team-policy/rm-with-retry.ts'\n\nexport interface SpawnTeamTemplateInput {\n templateId: string\n templateExpectedRevision: number\n teamId: string\n teamExpectedRevision?: number\n mode: 'initialize' | 'append'\n userMd?: string\n}\n\nexport interface SpawnTeamTemplateResult {\n agents: Agent[]\n team: ResolvedTeamPolicy\n}\n\nexport interface SpawnTeamTemplatePreview {\n mode: 'initialize' | 'append'\n teamId: string\n currentRevision: number | null\n resultingRevision: number\n newMembers: Array<{ slotId: string; agentName: string; profileId: string }>\n edges: Array<{\n sourceKind: 'user' | 'outside_team' | 'agent'\n sourceId: string | null\n targetKind: 'user' | 'outside_team' | 'agent'\n targetId: string | null\n posture: 'allow' | 'approval_required'\n }>\n}\n\nexport function previewTeamTemplateSpawn(\n db: BazilionDb,\n paths: Paths,\n input: SpawnTeamTemplateInput,\n): SpawnTeamTemplatePreview {\n const source = teamTemplateRepo.get(db, input.templateId)\n if (!source) throw new Error(`team template not found: ${input.templateId}`)\n if (source.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`)\n if (source.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${source.currentRevision}`,\n )\n }\n const snapshot = teamTemplateRepo.revision(db, source.id, input.templateExpectedRevision)\n if (!snapshot)\n throw new Error(`template_snapshot_missing: ${source.id}@${source.currentRevision}`)\n const existingGroup = teamRepo.get(db, input.teamId, paths)\n const teamPolicy = existingGroup ? teamPolicyRepo.get(db, input.teamId) : null\n if (existingGroup && !input.teamExpectedRevision) {\n throw new Error('team_revision_required: teamExpectedRevision is required')\n }\n if (teamPolicy && teamPolicy.revision !== input.teamExpectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n const members = existingGroup\n ? agentRepo.list(db, { includeArchived: true }).filter((agent) => agent.teamId === input.teamId)\n : []\n if (!existingGroup && input.mode !== 'initialize') {\n throw new Error('initialize_required: a new Team must establish a baseline')\n }\n if (\n existingGroup &&\n input.mode === 'append' &&\n members.length === 0 &&\n !teamPolicy?.baselineInstantiationId\n ) {\n throw new Error('initialize_required: empty uninitialized Team must establish a baseline')\n }\n if (input.mode === 'initialize') {\n if (members.length > 0) throw new Error('team_not_empty: initialize requires an empty Team')\n if (teamPolicy?.baselineInstantiationId) {\n throw new Error('baseline_replacement_required: empty Team retains a baseline')\n }\n }\n const slotEndpoint = (slotId: string | null) => (slotId ? `new:${slotId}` : null)\n const newEdges = snapshot.edges.map((edge) => ({\n sourceKind: edge.sourceKind === 'slot' ? ('agent' as const) : edge.sourceKind,\n sourceId: edge.sourceKind === 'slot' ? slotEndpoint(edge.sourceId) : null,\n targetKind: edge.targetKind === 'slot' ? ('agent' as const) : edge.targetKind,\n targetId: edge.targetKind === 'slot' ? slotEndpoint(edge.targetId) : null,\n posture: edge.posture,\n }))\n return {\n mode: input.mode,\n teamId: input.teamId,\n currentRevision: teamPolicy?.revision ?? null,\n resultingRevision: teamPolicy ? teamPolicy.revision + 1 : 1,\n newMembers: snapshot.slots.map((slot) => ({\n slotId: slot.slotId,\n agentName: slot.agentName,\n profileId: slot.profileId,\n })),\n edges: [...(existingGroup ? teamPolicyRepo.edges(db, input.teamId) : []), ...newEdges],\n }\n}\n\nexport class SpawnTeamTemplateError extends Error {\n override name = 'SpawnTeamTemplateError'\n readonly orphanAgentIds: string[]\n override readonly cause: unknown\n constructor(message: string, orphanAgentIds: string[], cause: unknown) {\n super(message)\n this.orphanAgentIds = orphanAgentIds\n this.cause = cause\n }\n}\n\nexport async function spawnTeamTemplate(\n db: BazilionDb,\n paths: Paths,\n input: SpawnTeamTemplateInput,\n): Promise<SpawnTeamTemplateResult> {\n const source = teamTemplateRepo.get(db, input.templateId)\n if (!source) throw new Error(`team template not found: ${input.templateId}`)\n if (source.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`)\n if (source.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${source.currentRevision}`,\n )\n }\n const snapshot = teamTemplateRepo.revision(db, source.id, input.templateExpectedRevision)\n if (!snapshot)\n throw new Error(`template_snapshot_missing: ${source.id}@${source.currentRevision}`)\n for (const slot of snapshot.slots) {\n if (!profileRepo.get(db, slot.profileId)) {\n throw new Error(`profile not found: ${slot.profileId}`)\n }\n }\n const existingGroup = teamRepo.get(db, input.teamId, paths)\n if (existingGroup && !input.teamExpectedRevision) {\n throw new Error('team_revision_required: teamExpectedRevision is required')\n }\n const beforeAgentDirs = new Set(safeReaddir(paths.agentsDir))\n const beforeGroupDirs = new Set(safeReaddir(paths.teamsDir))\n const created: Agent[] = []\n try {\n inTx(db, () => {\n const transactionalSource = teamTemplateRepo.get(db, input.templateId)\n if (!transactionalSource || transactionalSource.deletedAt !== null) {\n throw new Error(`template_deleted: ${input.templateId}`)\n }\n if (transactionalSource.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${transactionalSource.currentRevision}`,\n )\n }\n const teamCreated = !existingGroup\n if (teamCreated) registerTeam(db, { id: input.teamId, name: input.teamId }, paths)\n const teamPolicy = teamPolicyRepo.get(db, input.teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${input.teamId}`)\n if (!teamCreated && teamPolicy.revision !== input.teamExpectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n const memberCount = agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === input.teamId).length\n if (teamCreated && input.mode !== 'initialize') {\n throw new Error('initialize_required: a new Team must establish a baseline')\n }\n if (\n !teamCreated &&\n input.mode === 'append' &&\n memberCount === 0 &&\n teamPolicy.baselineInstantiationId === null\n ) {\n throw new Error('initialize_required: empty uninitialized Team must establish a baseline')\n }\n if (input.mode === 'initialize') {\n if (memberCount > 0) throw new Error('team_not_empty: initialize requires an empty Team')\n if (teamPolicy.baselineInstantiationId !== null) {\n throw new Error('baseline_replacement_required: empty Team retains a baseline')\n }\n }\n if (teamCreated && (input.userMd ?? snapshot.userMd)) {\n teamRepo.setUserMd(db, input.teamId, input.userMd ?? snapshot.userMd ?? '')\n }\n const existingNames = new Set(\n agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === input.teamId)\n .map((agent) => agent.name),\n )\n for (const slot of snapshot.slots) {\n const name = uniqueName(slot.agentName, existingNames)\n existingNames.add(name)\n created.push(\n spawnAgent(db, paths, {\n profileId: slot.profileId,\n name,\n modelOverride: slot.modelOverride,\n reasoningLevel: slot.reasoningLevel ?? 'medium',\n teamId: input.teamId,\n deferTeamPolicyUpdate: true,\n }),\n )\n }\n const instantiationId = randomUUID()\n db.raw.run(\n `INSERT INTO template_instantiations\n (id, team_id, template_id, template_revision, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n [instantiationId, input.teamId, source.id, snapshot.revision, Date.now()],\n )\n const agentBySlot = new Map<string, Agent>()\n for (let index = 0; index < snapshot.slots.length; index++) {\n const slot = snapshot.slots[index]\n const agent = created[index]\n if (!slot || !agent) throw new Error('template_snapshot_invalid: slot mapping missing')\n agentBySlot.set(slot.slotId, agent)\n db.raw.run(\n `INSERT INTO source_slot_bindings (agent_id, instantiation_id, source_slot_id)\n VALUES (?, ?, ?)`,\n [agent.id, instantiationId, slot.slotId],\n )\n teamPolicyRepo.insertAgentState(db, input.teamId, agent.id)\n }\n for (const edge of snapshot.edges) {\n const sourceId =\n edge.sourceKind === 'slot' ? agentBySlot.get(edge.sourceId ?? '')?.id : null\n const targetId =\n edge.targetKind === 'slot' ? agentBySlot.get(edge.targetId ?? '')?.id : null\n if (\n (edge.sourceKind === 'slot' && !sourceId) ||\n (edge.targetKind === 'slot' && !targetId)\n ) {\n throw new Error('template_snapshot_invalid: edge slot mapping missing')\n }\n db.raw.run(\n `INSERT INTO team_policy_edges\n (team_id, source_kind, source_id, target_kind, target_id, posture)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [\n input.teamId,\n edge.sourceKind === 'slot' ? 'agent' : edge.sourceKind,\n sourceId ?? '',\n edge.targetKind === 'slot' ? 'agent' : edge.targetKind,\n targetId ?? '',\n edge.posture,\n ],\n )\n }\n if (input.mode === 'initialize') {\n db.raw.run('UPDATE team_policies SET baseline_instantiation_id = ? WHERE team_id = ?', [\n instantiationId,\n input.teamId,\n ])\n }\n if (teamCreated) {\n db.raw.run('UPDATE team_policies SET updated_at = ? WHERE team_id = ?', [\n Date.now(),\n input.teamId,\n ])\n } else {\n teamPolicyRepo.bumpRevision(db, input.teamId)\n }\n })\n const team = teamPolicyRepo.detail(db, input.teamId)\n if (!team) throw new Error(`team_policy_missing: ${input.teamId}`)\n return { agents: created, team }\n } catch (error) {\n const orphans: string[] = []\n for (const dir of safeReaddir(paths.agentsDir).filter((name) => !beforeAgentDirs.has(name))) {\n if (!(await rmWithRetry(paths.agentDir(dir)))) orphans.push(dir)\n }\n for (const slug of safeReaddir(paths.teamsDir).filter((name) => !beforeGroupDirs.has(name))) {\n try {\n rmSync(paths.teamDir(slug), { recursive: true, force: true })\n } catch {}\n }\n throw new SpawnTeamTemplateError((error as Error).message, orphans, error)\n }\n}\n\nfunction uniqueName(base: string, existing: ReadonlySet<string>): string {\n if (!existing.has(base)) return base\n let suffix = 2\n while (existing.has(`${base}-${suffix}`)) suffix++\n return `${base}-${suffix}`\n}\n\nfunction safeReaddir(path: string): string[] {\n try {\n return readdirSync(path)\n } catch {\n return []\n }\n}\n","import { rmSync } from 'node:fs'\n\nexport const DEFAULT_RM_RETRY_DELAYS_MS = [100, 500, 2000] as const\n\nexport interface RmRetryOptions {\n /** Injectable rm fn for tests. Defaults to `rmSync(target, { recursive, force })`. */\n rm?: (target: string) => void\n /** Backoff between attempts. Defaults to [100, 500, 2000]. */\n delays?: readonly number[]\n /** Injectable sleep for tests. Defaults to `setTimeout`-backed promise. */\n sleep?: (ms: number) => Promise<void>\n}\n\n/**\n * Retry filesystem removal with backoff. Returns true if the target is\n * gone after some attempt, false if every attempt failed. Used during\n * profile-team spawn rollback to clean up agent dirs created by\n * successful `spawnAgent` calls before the failing slot.\n *\n * The default rm is `rmSync(target, { recursive: true, force: true })` —\n * `force: true` swallows ENOENT, so a missing target counts as success.\n */\nexport async function rmWithRetry(target: string, opts: RmRetryOptions = {}): Promise<boolean> {\n const rm = opts.rm ?? ((p: string) => rmSync(p, { recursive: true, force: true }))\n const delays = opts.delays ?? DEFAULT_RM_RETRY_DELAYS_MS\n const sleep = opts.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)))\n for (let attempt = 0; attempt <= delays.length; attempt++) {\n try {\n rm(target)\n return true\n } catch {\n if (attempt === delays.length) return false\n const delay = delays[attempt] ?? 0\n await sleep(delay)\n }\n }\n return false\n}\n","import type { Stats } from 'node:fs'\nimport {\n closeSync,\n constants,\n fstatSync,\n lstatSync,\n mkdirSync,\n openSync,\n readdirSync,\n readFileSync,\n realpathSync,\n rmSync,\n writeFileSync,\n} from 'node:fs'\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport type { MemoryEntry, MemoryHit } from '@bazilion/api-types'\nimport { createStore, extractSnippet, type QMDStore } from '@tobilu/qmd'\nimport { withNativeModuleErrorBoundary } from '../../lib/native-module-error.ts'\nimport type { MemoryBackend } from './types.ts'\n\nconst INDEX_FILENAME = '.qmd-index.sqlite'\nconst COLLECTION_NAME = 'memory'\nconst PATTERN = '**/*.md'\nconst QMD_NATIVE_ERROR_OPTIONS = { subject: 'Bazilion memory' } as const\n\nexport interface QmdBackendOptions {\n /** Test seam for exercising qmd initialization failures without replacing a native binary. */\n createStore?: typeof createStore\n}\n\n// One store per memory directory per process. createStore opens a SQLite\n// handle; we dedupe so concurrent chat requests for the same agent reuse it.\nconst storeCache = new Map<string, Promise<QMDStore>>()\n\nfunction getStore(dir: string, storeFactory: typeof createStore): Promise<QMDStore> {\n let p = storeCache.get(dir)\n if (!p) {\n p = withNativeModuleErrorBoundary(\n () =>\n storeFactory({\n dbPath: join(dir, INDEX_FILENAME),\n config: {\n collections: {\n [COLLECTION_NAME]: { path: dir, pattern: PATTERN },\n },\n },\n }),\n QMD_NATIVE_ERROR_OPTIONS,\n ).catch((error) => {\n // A failed initialization must be retryable after the operator rebuilds\n // native dependencies without restarting the daemon.\n storeCache.delete(dir)\n throw error\n })\n storeCache.set(dir, p)\n }\n return p\n}\n\nfunction unsafeMemoryPath(path: string, reason: string): Error {\n return new Error(`unsafe memory path \"${path}\": ${reason}`)\n}\n\nfunction lstatIfPresent(path: string): Stats | undefined {\n try {\n return lstatSync(path)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined\n throw error\n }\n}\n\nfunction isContained(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (!isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`))\n}\n\nfunction assertContained(root: string, candidate: string): void {\n if (!isContained(root, candidate)) {\n throw unsafeMemoryPath(candidate, `resolved outside memory root \"${root}\"`)\n }\n}\n\n/**\n * Create and canonicalize the memory root without accepting a final symlink.\n * A Team itself may intentionally be registered through a symlink, so only\n * the memory root and descendants are treated as the capability boundary.\n */\nfunction initializeSafeRoot(root: string): string {\n mkdirSync(root, { recursive: true })\n const info = lstatSync(root)\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(root, 'memory root must not be a symbolic link')\n }\n if (!info.isDirectory()) {\n throw unsafeMemoryPath(root, 'memory root is not a directory')\n }\n return realpathSync(root)\n}\n\nfunction assertRootUnchanged(root: string, canonicalRoot: string): void {\n const info = lstatIfPresent(root)\n if (!info) throw unsafeMemoryPath(root, 'memory root disappeared')\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(root, 'memory root must not be a symbolic link')\n }\n if (!info.isDirectory()) {\n throw unsafeMemoryPath(root, 'memory root is not a directory')\n }\n const current = realpathSync(root)\n if (relative(canonicalRoot, current) !== '' || relative(current, canonicalRoot) !== '') {\n throw unsafeMemoryPath(root, 'memory root changed after initialization')\n }\n}\n\nfunction safeKey(root: string, key: string): string {\n const segments = key.split(/[\\\\/]/)\n if (\n key.length === 0 ||\n key.includes('\\0') ||\n isAbsolute(key) ||\n /^[A-Za-z]:[\\\\/]/.test(key) ||\n key.startsWith('\\\\\\\\') ||\n segments.includes('..')\n ) {\n throw new Error(`unsafe memory key: ${key}`)\n }\n\n const candidate = resolve(root, key)\n if (candidate === root) throw new Error(`unsafe memory key: ${key}`)\n assertContained(root, candidate)\n return candidate\n}\n\n/**\n * Inspect every existing component of a key without following symlinks.\n * Returns the final lstat, or undefined when the final path does not exist.\n */\nfunction inspectSafePath(root: string, path: string): Stats | undefined {\n assertContained(root, path)\n const rel = relative(root, path)\n let current = root\n const components = rel.split(sep).filter(Boolean)\n\n for (let index = 0; index < components.length; index += 1) {\n current = join(current, components[index] as string)\n const info = lstatIfPresent(current)\n if (!info) return undefined\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(current, 'symbolic links are not allowed')\n }\n if (index < components.length - 1 && !info.isDirectory()) {\n throw unsafeMemoryPath(current, 'path ancestor is not a directory')\n }\n assertContained(root, realpathSync(current))\n }\n\n return lstatIfPresent(path)\n}\n\nfunction ensureSafeParentDirectories(root: string, path: string): void {\n const parent = dirname(path)\n assertContained(root, parent)\n const rel = relative(root, parent)\n let current = root\n\n for (const component of rel.split(sep).filter(Boolean)) {\n current = join(current, component)\n let info = lstatIfPresent(current)\n if (!info) {\n mkdirSync(current)\n info = lstatSync(current)\n }\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(current, 'symbolic-link ancestors are not allowed')\n }\n if (!info.isDirectory()) {\n throw unsafeMemoryPath(current, 'path ancestor is not a directory')\n }\n assertContained(root, realpathSync(current))\n }\n}\n\nfunction readSafeFile(root: string, path: string): { content: string; stats: Stats } {\n const info = inspectSafePath(root, path)\n if (!info) throw new Error(`memory entry not found: ${relative(root, path)}`)\n if (!info.isFile()) throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n\n let fd: number | undefined\n try {\n fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW)\n const stats = fstatSync(fd)\n if (!stats.isFile()) throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n return { content: readFileSync(fd, 'utf8'), stats }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ELOOP') {\n throw unsafeMemoryPath(path, 'symbolic links are not allowed')\n }\n throw error\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n}\n\nfunction writeSafeFile(root: string, path: string, content: string): Stats {\n ensureSafeParentDirectories(root, path)\n const existing = inspectSafePath(root, path)\n if (existing?.isSymbolicLink()) {\n throw unsafeMemoryPath(path, 'symbolic links are not allowed')\n }\n if (existing && !existing.isFile()) {\n throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n }\n\n let fd: number | undefined\n try {\n fd = openSync(\n path,\n constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,\n 0o666,\n )\n const stats = fstatSync(fd)\n if (!stats.isFile()) throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n writeFileSync(fd, content)\n return fstatSync(fd)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ELOOP') {\n throw unsafeMemoryPath(path, 'symbolic links are not allowed')\n }\n throw error\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n}\n\nfunction assertSafeTree(root: string, dir = root): void {\n assertContained(root, realpathSync(dir))\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name)\n const info = lstatSync(full)\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(full, 'symbolic links are not allowed in memory storage')\n }\n if (info.isDirectory()) {\n assertSafeTree(root, full)\n } else if (!info.isFile()) {\n throw unsafeMemoryPath(full, 'special files are not allowed in memory storage')\n }\n }\n}\n\nfunction walkMd(root: string, dir: string, prefix: string, out: MemoryEntry[]): void {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name)\n const info = lstatSync(full)\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(full, 'symbolic links are not allowed in memory storage')\n }\n if (entry.name.startsWith('.')) continue // skip .qmd-index.sqlite and friends\n\n const key = prefix ? `${prefix}/${entry.name}` : entry.name\n if (info.isDirectory()) {\n walkMd(root, full, key, out)\n } else if (info.isFile() && entry.name.endsWith('.md')) {\n const { content, stats } = readSafeFile(root, full)\n out.push({ key, content, updatedAt: stats.mtimeMs })\n } else if (!info.isFile()) {\n throw unsafeMemoryPath(full, 'special files are not allowed in memory storage')\n }\n }\n}\n\n/**\n * Memory backend backed by @tobilu/qmd — BM25 keyword search over markdown\n * files under `root`. Writes markdown to disk, then asks qmd to reindex.\n *\n * Uses `searchLex` only; no embeddings, no LLM rerank, no model download.\n * The hybrid `search()` / `searchVector()` paths exist in the qmd SDK and\n * can be wired in later if we want semantic search — they'd add a dependency\n * on `node-llama-cpp` and several GB of GGUF models.\n */\nexport function qmdBackend(root: string, options: QmdBackendOptions = {}): MemoryBackend {\n let canonicalRoot: string | undefined\n const storeFactory = options.createStore ?? createStore\n\n const safeRoot = (): string => {\n if (!canonicalRoot) canonicalRoot = initializeSafeRoot(root)\n else assertRootUnchanged(root, canonicalRoot)\n return canonicalRoot\n }\n\n return {\n async init() {\n const safe = safeRoot()\n assertSafeTree(safe)\n // Opening the store + initial scan. update() is idempotent.\n const store = await getStore(safe, storeFactory)\n await withNativeModuleErrorBoundary(() => store.update(), QMD_NATIVE_ERROR_OPTIONS)\n },\n\n async read(key) {\n const safe = safeRoot()\n const path = safeKey(safe, key)\n const { content, stats } = readSafeFile(safe, path)\n return {\n key,\n content,\n updatedAt: stats.mtimeMs,\n }\n },\n\n async write(key, content) {\n const safe = safeRoot()\n assertSafeTree(safe)\n const path = safeKey(safe, key)\n const stats = writeSafeFile(safe, path, content)\n // Reindex so the newly-written file is searchable on the next search().\n // update() re-scans the collection; for typical memory sizes (tens of\n // files) this is sub-millisecond.\n assertSafeTree(safe)\n const store = await getStore(safe, storeFactory)\n await withNativeModuleErrorBoundary(() => store.update(), QMD_NATIVE_ERROR_OPTIONS)\n return { key, content, updatedAt: stats.mtimeMs }\n },\n\n async search(query, opts) {\n const limit = opts?.limit ?? 10\n const safe = safeRoot()\n assertSafeTree(safe)\n const store = await getStore(safe, storeFactory)\n const results = await withNativeModuleErrorBoundary(\n () =>\n store.searchLex(query, {\n limit,\n collection: COLLECTION_NAME,\n }),\n QMD_NATIVE_ERROR_OPTIONS,\n )\n const hits: MemoryHit[] = []\n for (const r of results) {\n // qmd's filepath is a synthetic URI (`qmd://<collection>/<path>`);\n // displayPath is collection-prefixed (`<collection>/<path>`). Strip\n // the leading `<collection>/` to get the key the caller wrote.\n const prefix = `${COLLECTION_NAME}/`\n const key = r.displayPath.startsWith(prefix)\n ? r.displayPath.slice(prefix.length)\n : r.displayPath\n const path = safeKey(safe, key)\n const info = inspectSafePath(safe, path)\n if (!info) continue\n if (!info.isFile()) throw unsafeMemoryPath(path, 'search result is not a regular file')\n let content = r.body ?? ''\n if (!content) {\n content = readSafeFile(safe, path).content\n }\n const snippet = extractSnippet(content, query).snippet\n hits.push({ key, snippet, score: r.score })\n }\n return hits\n },\n\n async list() {\n const safe = safeRoot()\n assertSafeTree(safe)\n const out: MemoryEntry[] = []\n walkMd(safe, safe, '', out)\n return out.sort((a, b) => a.key.localeCompare(b.key))\n },\n\n async remove(key) {\n const safe = safeRoot()\n assertSafeTree(safe)\n const path = safeKey(safe, key)\n const info = inspectSafePath(safe, path)\n if (info) {\n if (!info.isFile()) throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n rmSync(path)\n }\n assertSafeTree(safe)\n const store = await getStore(safe, storeFactory)\n await withNativeModuleErrorBoundary(() => store.update(), QMD_NATIVE_ERROR_OPTIONS)\n },\n }\n}\n","const NODE_MODULE_ABI_MISMATCH =\n /was compiled against a different Node\\.js version using\\s+NODE_MODULE_VERSION\\s+(\\d+)[\\s\\S]*?requires\\s+NODE_MODULE_VERSION\\s+(\\d+)/i\n\nexport interface NativeModuleErrorOptions {\n subject?: string\n}\n\n/**\n * Return a stable, user-safe recovery message for Node native-addon ABI\n * mismatches. The original diagnostic can contain absolute checkout paths, so\n * callers must only retain it in an explicitly internal diagnostic sink.\n */\nexport function formatNativeModuleAbiMismatch(\n diagnostic: string,\n options: NativeModuleErrorOptions = {},\n): string | undefined {\n const mismatch = NODE_MODULE_ABI_MISMATCH.exec(diagnostic)\n if (!mismatch) return undefined\n\n const builtAbi = mismatch[1]\n const requiredAbi = mismatch[2]\n const rebuildCommand = /better_sqlite3\\.node|better-sqlite3/i.test(diagnostic)\n ? '`pnpm rebuild better-sqlite3`'\n : '`pnpm rebuild`'\n const subject = options.subject ?? 'Bazilion'\n return (\n `${subject} could not load a native dependency built for Node module ABI ${builtAbi}; ` +\n `the current ${process.version} runtime requires ABI ${requiredAbi}. ` +\n 'Reinstall Bazilion with this same Node.js version, then restart it. ' +\n `From the repository root of a source checkout, run ${rebuildCommand} ` +\n '(or `pnpm install --force`).'\n )\n}\n\nexport class NativeModuleAbiMismatchError extends Error {\n readonly code = 'native_module_abi_mismatch'\n\n constructor(message: string) {\n super(message)\n this.name = 'NativeModuleAbiMismatchError'\n }\n}\n\n/**\n * Replace an ABI-mismatch error with a safe public error. Raw diagnostics are\n * intentionally not attached as a cause: callers such as the worker launcher\n * may send them to an explicit internal sink before using this helper, but an\n * ordinary logged error must not reveal a checkout path. Non-ABI errors\n * preserve their original identity and message.\n */\nexport function sanitizeNativeModuleError(\n error: unknown,\n options: NativeModuleErrorOptions = {},\n): unknown {\n const diagnostic = error instanceof Error ? error.message : String(error)\n const formatted = formatNativeModuleAbiMismatch(diagnostic, options)\n if (!formatted) return error\n return new NativeModuleAbiMismatchError(formatted)\n}\n\nexport async function withNativeModuleErrorBoundary<T>(\n operation: () => T | Promise<T>,\n options: NativeModuleErrorOptions = {},\n): Promise<T> {\n try {\n return await operation()\n } catch (error) {\n throw sanitizeNativeModuleError(error, options)\n }\n}\n","// Translator: pi `AgentSessionEvent` → Bazilion `SessionEvent[]`.\n//\n// The worker's NDJSON wire format (`ChatFrame`) is still the same discrete\n// event stream CLI / browser clients know how to render. This module is the\n// adapter at the single choke-point — pi event in, zero-or-more Bazilion\n// events out.\n//\n// Coverage:\n// - user message_start → one `user_message`\n// - assistant text_delta → one `assistant_delta` per chunk\n// - assistant message_end:\n// - if the message carries text → one `assistant_message`\n// - for every tool-call content → one `tool_call`\n// - tool_execution_end → `tool_result` or `tool_error` based on isError\n// - message_end with aborted/error stopReason → one `error`\n//\n// Not surfaced (intentional):\n// - agent_start / agent_end — run-row lifecycle, not chat events\n// - turn_start / turn_end — too chatty, nothing to render\n// - message_start (assistant) — text will come via updates + end\n// - message_update (non-text) — thinking deltas etc; left for a later\n// pass when UIs render thinking blocks\n// - queue_update / compaction_* — session meta, not assistant output\n// - auto_retry_* — silently retried, user sees only the\n// eventual success or failure\n\nimport type { ProviderMessage, SessionEvent, ToolCall, ToolResultImage } from '@bazilion/api-types'\nimport type { AgentMessage, AgentToolResult } from '@earendil-works/pi-agent-core'\nimport type { AssistantMessage } from '@earendil-works/pi-ai'\nimport type { AgentSessionEvent } from '@earendil-works/pi-coding-agent'\n\nexport function translatePiEvent(e: AgentSessionEvent): SessionEvent[] {\n switch (e.type) {\n case 'message_start': {\n if (e.message.role === 'user') {\n return [{ type: 'user_message', text: stringifyContent(e.message.content) }]\n }\n return []\n }\n\n case 'message_update': {\n const inner = e.assistantMessageEvent\n if (inner.type === 'text_delta') {\n return [{ type: 'assistant_delta', delta: inner.delta }]\n }\n return []\n }\n\n case 'message_end': {\n const m = e.message\n if (m.role !== 'assistant') return []\n const out: SessionEvent[] = []\n const text = extractAssistantText(m as AssistantMessage)\n if (text) out.push({ type: 'assistant_message', text })\n for (const block of (m as AssistantMessage).content ?? []) {\n if (block.type === 'toolCall') {\n out.push({\n type: 'tool_call',\n id: block.id,\n name: block.name,\n arguments: JSON.stringify(block.arguments ?? {}),\n })\n }\n }\n const stopReason = (m as AssistantMessage).stopReason\n if (stopReason === 'aborted' || stopReason === 'error') {\n const errText = (m as AssistantMessage).errorMessage ?? stopReason\n out.push({ type: 'error', error: errText })\n }\n return out\n }\n\n case 'tool_execution_end': {\n const text = extractToolResultText(e.result)\n if (e.isError) {\n return [{ type: 'tool_error', id: e.toolCallId, name: e.toolName, error: text }]\n }\n const images = extractToolResultImages(e.result)\n return [\n {\n type: 'tool_result',\n id: e.toolCallId,\n name: e.toolName,\n result: text,\n ...(images.length > 0 ? { images } : {}),\n },\n ]\n }\n\n default:\n return []\n }\n}\n\nexport function extractAssistantText(m: AssistantMessage): string {\n let out = ''\n for (const block of m.content ?? []) {\n if (block.type === 'text') out += block.text\n }\n return out\n}\n\nexport function extractAssistantToolCalls(m: AssistantMessage): ToolCall[] {\n const out: ToolCall[] = []\n for (const block of m.content ?? []) {\n if (block.type === 'toolCall') {\n out.push({ id: block.id, name: block.name, arguments: JSON.stringify(block.arguments ?? {}) })\n }\n }\n return out\n}\n\n/** Flatten `AgentToolResult.content` blocks into a single string — matches the\n * shape Bazilion tools always returned before pi adoption. */\nexport function extractToolResultText(result: unknown): string {\n const r = result as AgentToolResult<unknown> | undefined\n if (!r?.content) return ''\n let out = ''\n for (const block of r.content) {\n if (block.type === 'text') out += block.text\n }\n return out\n}\n\n/** Pull image blocks out of a tool result (browser screenshots, MCP images). */\nexport function extractToolResultImages(result: unknown): ToolResultImage[] {\n const r = result as AgentToolResult<unknown> | undefined\n if (!r?.content) return []\n const out: ToolResultImage[] = []\n for (const block of r.content) {\n if (block.type === 'image') {\n const b = block as { data?: string; mimeType?: string }\n if (typeof b.data === 'string' && typeof b.mimeType === 'string') {\n out.push({ data: b.data, mimeType: b.mimeType })\n }\n }\n }\n return out\n}\n\n/** Stringify a pi user-message content array. */\nfunction stringifyContent(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n let out = ''\n for (const block of content as { type: string; text?: string }[]) {\n if (block.type === 'text' && typeof block.text === 'string') out += block.text\n }\n return out\n}\n\n/**\n * Convert pi's authoritative `session.state.messages` (AgentMessage[]) into\n * Bazilion's ProviderMessage[] shape, so the `done` ChatFrame stays\n * compatible with browser + CLI clients that rehydrate from it.\n *\n * Notes on role mapping: pi uses `\"toolResult\"` for tool-response messages;\n * Bazilion uses `\"tool\"`. AssistantMessage content arrays become either\n * `content` string (text blocks joined) or a `toolCalls` array (toolCall\n * blocks converted).\n */\nexport function piMessagesToProviderView(messages: AgentMessage[]): ProviderMessage[] {\n const out: ProviderMessage[] = []\n for (const m of messages) {\n // Custom Bazilion message types would land in this switch too, but we\n // don't register any via CustomAgentMessages declaration merging yet.\n switch (m.role) {\n case 'user': {\n const content = (m as { content: unknown }).content\n const images = extractToolResultImages({ content })\n out.push({\n role: 'user',\n content: stringifyContent(content),\n ...(images.length > 0 ? { images } : {}),\n })\n break\n }\n case 'assistant': {\n const am = m as AssistantMessage\n const text = extractAssistantText(am)\n const toolCalls = extractAssistantToolCalls(am)\n const msg: ProviderMessage = { role: 'assistant', content: text }\n if (toolCalls.length > 0) msg.toolCalls = toolCalls\n out.push(msg)\n break\n }\n case 'toolResult': {\n const tr = m as { content: unknown; toolCallId?: string; toolName?: string }\n const images = extractToolResultImages({ content: tr.content })\n out.push({\n role: 'tool',\n content: stringifyContent(tr.content),\n toolCallId: tr.toolCallId,\n toolName: tr.toolName,\n ...(images.length > 0 ? { images } : {}),\n })\n break\n }\n default:\n // System and other unknown roles are skipped — the runtime's system\n // prompt is already wired via pi's settingsManager + our buildSystemPrompt.\n break\n }\n }\n return out\n}\n","// Bazilion → pi-coding-agent session bridge.\n//\n// `createBazilionSession` returns a fully-wired `AgentSession` suitable for\n// calling `session.prompt(text)` / `session.compact(instructions)` / etc.\n//\n// What we take ownership of (and hand to pi):\n// - cwd: the agent's team workspace path. With shell security off, Pi's\n// built-in coding tools use it as their working directory. With Docker\n// isolation on, Bazilion exposes only a same-name containerized `bash`\n// replacement and hides every host-backed coding file tool.\n// - agentDir: `<bazilion-home>/pi` — pi writes transient state here\n// (settings overrides, resource caches). We don't share it with the\n// user's global `~/.pi/agent` so a Bazilion install never clobbers an\n// independent pi CLI install.\n// - authStorage: `InMemoryAuthStorageBackend` pre-seeded with the resolved\n// API key for the agent's current provider. We never let pi read/write\n// its own auth file — secrets live in the daemon-owned `secrets` table\n// and reach us via `opts.apiKey` (initial) + `opts.refreshApiKey`\n// (OAuth refresher for long turns).\n// - modelRegistry: in-memory. Native providers (anthropic/openai/google/…)\n// come from pi's bundled catalog. For Bazilion-only providers\n// (`lmstudio`, `ollama`) we call `registerProvider(name, {baseUrl,\n// api: 'openai-completions', authHeader: false})` — matches the\n// openai-completions shim our pi-adapter has been using.\n// - sessionManager: `SessionManager.create(cwd, <agentDir>/sessions)`\n// writing JSONL to `~/.bazilion/agents/<id>/sessions/<sessionId>.jsonl`.\n// Crash-survival, append-only, branching, compaction entries — all owned\n// by pi now. Replaces our `agents.chat_messages` blob.\n// - settingsManager: in-memory. Bazilion controls auto-compaction\n// (disabled — we compact manually on user request via /compact) and\n// retry (enabled with Bazilion-tuned caps).\n//\n// What stays outside pi's purview:\n// - spawning agents / profiles / skills discovery (core/)\n// - workspaces registry & mount tracking (core/)\n// - inter-agent messaging, triggers, scheduler (core + apps/web)\n// - memory backend (we wrap it as a pi customTool via `createBazilionCustomTools`)\n\nimport { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\nimport type { ResolvedAgent } from '@bazilion/api-types'\nimport type { AgentMessage, ThinkingLevel } from '@earendil-works/pi-agent-core'\nimport {\n type AssistantMessage,\n type AssistantMessageEvent,\n createAssistantMessageEventStream,\n} from '@earendil-works/pi-ai'\nimport {\n type AgentSession,\n createAgentSession,\n DefaultResourceLoader,\n SessionManager,\n SettingsManager,\n type ToolDefinition,\n} from '@earendil-works/pi-coding-agent'\nimport type { BazilionDb, Paths } from '../../core/index.ts'\nimport { providerStateRepo } from '../../core/index.ts'\nimport type { MemoryBackend } from '../memory/types.ts'\nimport {\n createBazilionPiRuntime,\n createProtectedPiRuntime,\n piProviderName,\n providerApiKey,\n providerBaseUrl,\n resolvePiModel,\n} from '../providers/pi-runtime.ts'\nimport { createProviderRegistry, loadProviderConfigFromEnv } from '../providers/registry.ts'\nimport { buildSystemPrompt, loadPromptSkills } from '../session/prompt.ts'\nimport type { BashApprovalHost } from '../shell/approval.ts'\nimport type { ProtectedDockerRuntime } from '../shell/docker.ts'\nimport { createProtectedSessionShellTools, createSessionShellTools } from '../shell/tooling.ts'\nimport type {\n BrowserHost,\n InjectedMcpTool,\n McpHost,\n MessagingHost,\n UserMdHost,\n} from '../worker/ipc-protocol.ts'\nimport type {\n MinimalWorkerScratch,\n ProtectedProviderWorkerRuntime,\n ProtectedWorkerPaths,\n} from '../worker/runtime.ts'\nimport { protectedRuntimeSecrets, redactJsonValue } from '../worker/runtime.ts'\nimport { createBazilionCustomTools, createProtectedBazilionCustomTools } from './tools.ts'\n\nexport interface CreateBazilionSessionOptions {\n agent: ResolvedAgent\n paths: Paths\n /** Merged env (process.env + secrets) — produced via `mergeSecretsIntoEnv`. */\n env: NodeJS.ProcessEnv\n memory: MemoryBackend\n /**\n * Names of providers the user has explicitly enabled in /config.\n * Empty set means \"no per-provider gating configured\" — all providers pass.\n * Pre-computed by the daemon and handed in so the session never has to\n * touch the SQLite `provider_state` table itself.\n */\n enabledProviders: Set<string>\n /**\n * Optional host for inter-agent messaging. Wired from the worker's IPC\n * channel back to the daemon — workers no longer hold a SQLite handle of\n * their own. Omit to disable the messaging tools entirely (e.g. unit\n * tests that don't exercise inbox flows).\n */\n messagingHost?: MessagingHost\n /**\n * Optional host for the team-shared USER.md append tool. Like\n * `messagingHost`, this is wired via the worker's IPC channel. Omit to\n * disable the `user_md_append` tool.\n */\n userMdHost?: UserMdHost\n /**\n * Optional browser pool surface for the `browser_*` tools. Wired from the\n * worker's IPC channel back to the daemon (the Playwright session lives in\n * the daemon, persistent across turns). Omit to disable browser automation.\n */\n browserHost?: BrowserHost\n /**\n * Optional MCP connection-pool surface for proxied MCP tools, paired with\n * `mcpTools`. Wired via the worker's IPC channel. Omit when no MCP servers\n * are enabled.\n */\n mcpHost?: McpHost\n /** MCP tools discovered daemon-side, exposed as proxy tools alongside `mcpHost`. */\n mcpTools?: InjectedMcpTool[]\n /** If provided, enables the `deliver_file` tool — the agent's outbound file channel. */\n fileSink?: import('../tools/deliver-file.ts').FileSink\n /** Turn-scoped bridge for dangerous bash commands. Omit to fail closed. */\n bashApprovalHost?: BashApprovalHost\n /**\n * Optional explicit API key for the agent's provider. Wins over any value\n * derived from `env`. Required for OAuth-backed providers (`openai-codex`)\n * since their credentials live in the daemon-owned `secrets` table, not\n * in env vars.\n */\n apiKey?: string\n /**\n * Optional callback for OAuth-backed providers whose access tokens may\n * expire mid-turn. When provided, pi calls it to refresh the JWT during\n * long tool-execution loops. Daemon-side callers (compact/context/truncate)\n * wire this directly against the secrets repo; worker turns call the same\n * daemon-owned refresher through their private, turn-scoped IPC channel.\n */\n refreshApiKey?: (providerName: string) => Promise<string>\n /**\n * Session id to resume. When omitted, pi starts a fresh session file.\n * `/reset` passes `undefined` to rotate; normal chat passes the agent's\n * current session id (persisted on the Bazilion side as `agents.session_id`\n * if we later add that column — today we just restore the most recent\n * session file, which pi's SessionManager locates automatically).\n */\n sessionId?: string\n /** Internal restricted mode used by the learning reviewer. */\n restricted?: {\n systemPrompt: string\n tools: ToolDefinition[]\n sessionDir: string\n reasoningLevel: ResolvedAgent['reasoningLevel']\n }\n}\n\nexport interface BazilionSessionHandle {\n session: AgentSession\n /** Call when done — disposes listeners + closes the pi session. */\n dispose(): void\n}\n\nexport interface CreateProtectedBazilionSessionOptions {\n agent: ResolvedAgent\n runtime: ProtectedProviderWorkerRuntime\n paths: ProtectedWorkerPaths\n scratch: MinimalWorkerScratch\n docker: ProtectedDockerRuntime\n memory: MemoryBackend\n messagingHost: MessagingHost\n userMdHost: UserMdHost\n fileSink: import('../tools/deliver-file.ts').FileSink\n bashApprovalHost: BashApprovalHost\n refreshApiKey: (providerName: string) => Promise<string>\n}\n\nexport interface CreateRestrictedReviewSessionOptions {\n runtime: ProtectedProviderWorkerRuntime\n scratch: MinimalWorkerScratch\n systemPrompt: string\n tools: ToolDefinition[]\n refreshApiKey: (providerName: string) => Promise<string>\n}\n\nconst PROTECTED_MODEL_CWD = '/workspace'\nconst REVIEW_MODEL_CWD = '/review'\nconst PROTECTED_BASE_SYSTEM_PROMPT = `You are an assistant operating inside Bazilion's protected\nAgent runtime. Use only the tools exposed for this turn. Coding commands execute in an ephemeral,\nnetwork-disabled Docker container whose Team workspace is /workspace. Host paths and host tools are\nnot available. Be concise and show container paths clearly when working with files.`\n\n/**\n * Build a pi `AgentSession` using Bazilion's resolved agent + provider state.\n * The returned session is ready for `prompt()` / `compact()` / `reset()`.\n */\nexport async function createBazilionSession(\n opts: CreateBazilionSessionOptions,\n): Promise<BazilionSessionHandle> {\n const {\n agent,\n paths,\n env,\n memory,\n enabledProviders,\n messagingHost,\n userMdHost,\n browserHost,\n mcpHost,\n mcpTools,\n fileSink,\n bashApprovalHost,\n refreshApiKey,\n restricted,\n } = opts\n\n const { providerName, modelId } = splitModelString(agent.model)\n\n // Enabled-set gate — mirrors createProviderRegistry's check. We keep the\n // Bazilion-side enabled/disabled /config toggles authoritative even though\n // pi does its own provider resolution: we simply refuse to build a session\n // for a disabled provider. The set is pre-computed by the daemon (the\n // worker has no SQLite handle of its own).\n if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {\n throw new Error(`${providerName} provider is disabled — enable it on the /config page`)\n }\n\n // One public Pi runtime owns provider aliases, credentials, catalog lookup,\n // local-provider registration and arbitrary-model fallback for both direct\n // Provider.chat calls and full coding-agent sessions.\n const apiKey = opts.apiKey ?? providerApiKey(providerName, env)\n const baseUrl = providerBaseUrl(providerName, env)\n const modelRuntime = await createBazilionPiRuntime({\n providerName,\n env,\n ...(apiKey ? { apiKey } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n modelId,\n })\n const model = resolvePiModel(modelRuntime, providerName, modelId, baseUrl)\n\n // cwd is the agent's team directory. Every agent belongs to exactly one\n // team; the team's filesystem root is where work product lives. In host\n // mode Pi uses this as the coding tools' working directory (not a security\n // boundary). In Docker mode only the containerized bash is exposed and the\n // directory is its sole read/write bind mount. Private identity/soul files\n // live in `agent.dir` and are reached through scoped `home_*` tools.\n const cwd = agent.team.path\n if (!existsSync(cwd)) mkdirSync(cwd, { recursive: true })\n\n const promptSkills = restricted ? [] : loadPromptSkills(paths.skillsDir, agent.skills)\n const uploadsDir = join(agent.agent.dir, 'uploads')\n const shellTools = restricted\n ? null\n : createSessionShellTools(cwd, env, {\n ...(existsSync(uploadsDir) ? { inputsDir: uploadsDir } : {}),\n skillMounts: promptSkills.map((skill) => ({\n source: skill.hostDir,\n target: skill.sandboxDir,\n })),\n approvalHost: bashApprovalHost,\n })\n\n // Session file under the agent's own directory. Keeping it under\n // `agents/<id>/sessions/` makes `bazilion uninstall` (reset tier) already\n // clean them up without changes.\n //\n // Resume-or-create: pi's SessionManager has no built-in \"latest session\"\n // opener. We walk the session dir for the newest `.jsonl` and open it;\n // fall back to `create()` when none exists (fresh agent or post-/reset).\n // This is what makes turn-to-turn continuity work: each worker turn picks\n // up where the last one left off.\n const sessionDir = restricted?.sessionDir ?? join(paths.agentDir(agent.agent.id), 'sessions')\n mkdirSync(sessionDir, { recursive: true })\n const existing = restricted ? null : findMostRecent(sessionDir)\n const sessionManager = existing\n ? SessionManager.open(existing, sessionDir, cwd)\n : SessionManager.create(cwd, sessionDir)\n\n // In-memory settings: auto-compaction off (we trigger compaction manually\n // via /compact), retry on with Bazilion-tuned caps matching what withRetry\n // used to apply before pi-adoption.\n const settingsManager = SettingsManager.inMemory({\n compaction: { enabled: false },\n retry: {\n enabled: true,\n maxRetries: 2,\n baseDelayMs: 500,\n provider: { maxRetryDelayMs: 8_000 },\n },\n })\n\n // Bazilion-authored system prompt becomes an `appendSystemPrompt` entry.\n // Pi keeps its default base (which lists built-in tools + guidelines), our\n // profile content (SOUL.md / IDENTITY.md / workspaces / memory hint) is\n // concatenated after it. This is the same injection hook pi extensions use.\n const bazilionPrompt =\n restricted?.systemPrompt ??\n buildSystemPrompt(agent, {\n skills: promptSkills,\n sandboxMode: shellTools?.config.sandboxMode ?? 'off',\n })\n const resourceLoader = new DefaultResourceLoader({\n cwd,\n agentDir: join(paths.home, 'pi'),\n settingsManager,\n noExtensions: true,\n noSkills: true,\n noPromptTemplates: true,\n noThemes: true,\n noContextFiles: true,\n appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : undefined,\n })\n await resourceLoader.reload()\n\n // Tool allowlist: pi's `tools` option is exclusive when provided — only\n // the listed names are enabled, regardless of what's in `customTools`.\n // So we enumerate the host-backed Pi tools allowed by the shell policy and\n // every Bazilion custom tool we want the LLM to see. Docker mode contributes\n // a custom same-name `bash` and zero host tools; missing its name (or any\n // memory/messaging/web/bootstrap name) would silently drop it.\n const bazilionTools = restricted\n ? restricted.tools\n : createBazilionCustomTools({\n agent,\n memory,\n messagingHost,\n userMdHost,\n browserHost,\n mcpHost,\n mcpTools,\n fileSink,\n env,\n })\n const customTools = shellTools?.customBash\n ? [...bazilionTools, shellTools.customBash]\n : bazilionTools\n const allowedTools = [...(shellTools?.hostToolNames ?? []), ...customTools.map((t) => t.name)]\n\n const { session } = await createAgentSession({\n cwd,\n agentDir: join(paths.home, 'pi'),\n model,\n thinkingLevel: toPiThinkingLevel(restricted?.reasoningLevel ?? agent.reasoningLevel),\n tools: allowedTools,\n customTools,\n sessionManager,\n settingsManager,\n modelRuntime,\n resourceLoader,\n })\n\n // OAuth providers: wire pi's per-request `getApiKey` callback so the JWT\n // gets refreshed *during* a long tool-execution loop, not just at the\n // start of the turn. This is exactly the use case pi-agent-core's doc\n // calls out for this hook (\"short-lived OAuth tokens that may expire\n // during long-running tool execution phases\"). Caller supplies the\n // refresher because only they have access to the secrets table.\n if (refreshApiKey) {\n session.agent.getApiKey = async (requestedProvider) => {\n if (requestedProvider !== piProviderName(providerName)) return undefined\n try {\n return await refreshApiKey(providerName)\n } catch {\n // Stale/removed credentials mid-session → return undefined so pi\n // surfaces a \"no auth\" error cleanly instead of us throwing out of\n // the provider callback (which would drag down the whole turn).\n return undefined\n }\n }\n }\n\n return {\n session,\n dispose() {\n session.dispose()\n },\n }\n}\n\n/** Build the closed Docker-only normal session used by protected invocations. */\nexport async function createProtectedBazilionSession(\n opts: CreateProtectedBazilionSessionOptions,\n): Promise<BazilionSessionHandle> {\n const { providerName, modelId } = splitModelString(opts.agent.model)\n if (providerName !== opts.runtime.providerName || modelId !== opts.runtime.modelId) {\n throw new Error('protected Agent model does not match the prepared provider runtime')\n }\n if (opts.agent.agent.dir !== opts.paths.agentDir || opts.agent.team.path !== opts.paths.teamDir) {\n throw new Error('protected session paths do not match the resolved Agent')\n }\n if (!existsSync(opts.paths.teamDir)) {\n throw new Error('protected Team workspace is unavailable')\n }\n\n const modelRuntime = await createProtectedPiRuntime({\n providerName: opts.runtime.providerName,\n modelId: opts.runtime.modelId,\n apiKey: opts.runtime.apiKey,\n baseUrl: opts.runtime.baseUrl,\n credentialEnv: materializeProtectedCredentialFile(opts.runtime, opts.scratch),\n })\n const model = resolvePiModel(\n modelRuntime,\n opts.runtime.providerName,\n opts.runtime.modelId,\n opts.runtime.baseUrl,\n )\n const shellTools = createProtectedSessionShellTools(\n opts.paths.teamDir,\n opts.docker,\n opts.bashApprovalHost,\n )\n\n mkdirSync(opts.paths.sessionDir, { recursive: true })\n const existing = findMostRecent(opts.paths.sessionDir)\n const sessionManager = existing\n ? SessionManager.open(existing, opts.paths.sessionDir, opts.paths.teamDir)\n : SessionManager.create(opts.paths.teamDir, opts.paths.sessionDir)\n const settingsManager = createInMemorySettingsManager()\n const bazilionPrompt = redactJsonValue(\n buildSystemPrompt(opts.agent, {\n skills: opts.paths.skills,\n sandboxMode: 'docker',\n homeDocuments: opts.paths.homeDocuments,\n }),\n protectedRuntimeSecrets(opts.runtime),\n )\n const resourceLoader = new DefaultResourceLoader({\n cwd: PROTECTED_MODEL_CWD,\n agentDir: opts.scratch.piAgentDir,\n settingsManager,\n noExtensions: true,\n noSkills: true,\n noPromptTemplates: true,\n noThemes: true,\n noContextFiles: true,\n systemPrompt: PROTECTED_BASE_SYSTEM_PROMPT,\n appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : undefined,\n })\n await resourceLoader.reload()\n\n const bazilionTools = createProtectedBazilionCustomTools({\n agent: opts.agent,\n memory: opts.memory,\n messagingHost: opts.messagingHost,\n userMdHost: opts.userMdHost,\n fileSink: opts.fileSink,\n })\n if (!shellTools.customBash) throw new Error('protected Docker bash tool is unavailable')\n const customTools = [...bazilionTools, shellTools.customBash]\n const { session } = await createAgentSession({\n cwd: PROTECTED_MODEL_CWD,\n agentDir: opts.scratch.piAgentDir,\n model,\n thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),\n tools: customTools.map((tool) => tool.name),\n customTools,\n sessionManager,\n settingsManager,\n modelRuntime,\n resourceLoader,\n })\n installProtectedCredentialBoundary(\n session,\n opts.runtime.providerName,\n protectedRuntimeSecrets(opts.runtime),\n opts.refreshApiKey,\n )\n return sessionHandle(session)\n}\n\n/** Build a scratch-only reviewer with exactly the caller-supplied proposal tool. */\nexport async function createRestrictedReviewSession(\n opts: CreateRestrictedReviewSessionOptions,\n): Promise<BazilionSessionHandle> {\n const modelRuntime = await createProtectedPiRuntime({\n providerName: opts.runtime.providerName,\n modelId: opts.runtime.modelId,\n apiKey: opts.runtime.apiKey,\n baseUrl: opts.runtime.baseUrl,\n credentialEnv: materializeProtectedCredentialFile(opts.runtime, opts.scratch),\n })\n const model = resolvePiModel(\n modelRuntime,\n opts.runtime.providerName,\n opts.runtime.modelId,\n opts.runtime.baseUrl,\n )\n const settingsManager = createInMemorySettingsManager()\n const sessionManager = SessionManager.create(\n opts.scratch.reviewCwd,\n opts.scratch.reviewSessionDir,\n )\n const resourceLoader = new DefaultResourceLoader({\n cwd: REVIEW_MODEL_CWD,\n agentDir: opts.scratch.piAgentDir,\n settingsManager,\n noExtensions: true,\n noSkills: true,\n noPromptTemplates: true,\n noThemes: true,\n noContextFiles: true,\n systemPrompt: opts.systemPrompt,\n })\n await resourceLoader.reload()\n\n const { session } = await createAgentSession({\n cwd: REVIEW_MODEL_CWD,\n agentDir: opts.scratch.piAgentDir,\n model,\n thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),\n tools: opts.tools.map((tool) => tool.name),\n customTools: opts.tools,\n sessionManager,\n settingsManager,\n modelRuntime,\n resourceLoader,\n })\n installProtectedCredentialBoundary(\n session,\n opts.runtime.providerName,\n protectedRuntimeSecrets(opts.runtime),\n opts.refreshApiKey,\n )\n return sessionHandle(session)\n}\n\nfunction materializeProtectedCredentialFile(\n runtime: ProtectedProviderWorkerRuntime,\n scratch: MinimalWorkerScratch,\n): Array<{ name: string; value: string }> | undefined {\n const fields = [...(runtime.credentialEnv ?? [])]\n if (runtime.credentialFile) {\n const path = join(scratch.tempDir, 'provider-credential.json')\n writeFileSync(path, runtime.credentialFile.content, {\n encoding: 'utf8',\n mode: 0o600,\n flag: 'wx',\n })\n fields.push({ name: runtime.credentialFile.envName, value: path })\n }\n return fields.length > 0 ? fields : undefined\n}\n\n// --- helpers ---\n\nfunction createInMemorySettingsManager(): SettingsManager {\n return SettingsManager.inMemory({\n compaction: { enabled: false },\n retry: {\n enabled: true,\n maxRetries: 2,\n baseDelayMs: 500,\n provider: { maxRetryDelayMs: 8_000 },\n },\n })\n}\n\nexport function installProtectedCredentialBoundary(\n session: AgentSession,\n providerName: string,\n initialSecrets: string[],\n refreshApiKey: (providerName: string) => Promise<string>,\n): void {\n const activeTokens = [...new Set(initialSecrets.filter(Boolean))]\n session.agent.state.messages = redactJsonValue(session.agent.state.messages, activeTokens)\n const originalStream = session.agent.streamFunction\n session.agent.streamFunction = async (model, context, options) => {\n let upstream: Awaited<ReturnType<typeof originalStream>>\n try {\n upstream = await originalStream(model, context, options)\n } catch (error) {\n throw credentialSafeError(error, activeTokens)\n }\n const sanitized = createAssistantMessageEventStream()\n void (async () => {\n let latestPartial: AssistantMessage | undefined\n try {\n for await (const event of upstream) {\n const safeEvent = redactJsonValue(event, activeTokens) as AssistantMessageEvent\n if ('partial' in safeEvent) latestPartial = safeEvent.partial\n sanitized.push(safeEvent)\n }\n } catch (error) {\n const safeError = credentialSafeError(error, activeTokens)\n sanitized.push({\n type: 'error',\n reason: 'error',\n error: credentialSafeAssistantError(model, latestPartial, safeError, activeTokens),\n })\n } finally {\n sanitized.end()\n }\n })()\n return sanitized\n }\n\n const originalAfterToolCall = session.agent.afterToolCall\n session.agent.afterToolCall = async (context, signal) => {\n let prior: Awaited<ReturnType<NonNullable<typeof originalAfterToolCall>>> | undefined\n try {\n prior = await originalAfterToolCall?.(context, signal)\n } catch (error) {\n throw credentialSafeError(error, activeTokens)\n }\n return {\n ...prior,\n content: redactJsonValue(prior?.content ?? context.result.content, activeTokens),\n details: redactJsonValue(prior?.details ?? context.result.details, activeTokens),\n }\n }\n\n session.agent.getApiKey = async (requestedProvider) => {\n if (requestedProvider !== providerName) return undefined\n try {\n const token = await refreshApiKey(providerName)\n if (token && !activeTokens.includes(token)) activeTokens.push(token)\n return token || undefined\n } catch {\n return undefined\n }\n }\n}\n\nfunction credentialSafeError(error: unknown, activeTokens: readonly string[]): Error {\n const message = error instanceof Error ? error.message : String(error)\n return new Error(redactJsonValue(message, activeTokens))\n}\n\nfunction credentialSafeAssistantError(\n model: {\n api: AssistantMessage['api']\n provider: AssistantMessage['provider']\n id: string\n },\n latestPartial: AssistantMessage | undefined,\n error: Error,\n activeTokens: readonly string[],\n): AssistantMessage {\n const safePartial = latestPartial\n ? redactJsonValue(latestPartial, activeTokens)\n : {\n role: 'assistant' as const,\n content: [],\n api: model.api,\n provider: model.provider,\n model: model.id,\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n timestamp: Date.now(),\n }\n return {\n ...safePartial,\n stopReason: 'error',\n errorMessage: error.message,\n timestamp: Date.now(),\n }\n}\n\nfunction sessionHandle(session: AgentSession): BazilionSessionHandle {\n return {\n session,\n dispose() {\n session.dispose()\n },\n }\n}\n\nfunction splitModelString(s: string): { providerName: string; modelId: string } {\n const idx = s.indexOf(':')\n if (idx === -1) {\n throw new Error(`invalid model string \"${s}\": expected \"provider:model\"`)\n }\n return { providerName: s.slice(0, idx), modelId: s.slice(idx + 1) }\n}\n\nfunction toPiThinkingLevel(level: string): ThinkingLevel {\n switch (level) {\n case 'off':\n case 'minimal':\n case 'low':\n case 'medium':\n case 'high':\n case 'xhigh':\n return level\n default:\n return 'medium'\n }\n}\n\n/**\n * Re-exported for callers that want to check whether a provider is\n * Bazilion-enabled before even trying to spawn a session (e.g. /context\n * endpoint which builds a session just to enumerate tools).\n */\nexport function isProviderEnabled(db: BazilionDb, providerName: string): boolean {\n const enabled = providerStateRepo.listEnabled(db)\n return enabled.size === 0 || enabled.has(providerName)\n}\n\n/**\n * Escape hatch for callers that need the raw provider registry (e.g. the\n * current /api/providers/test endpoint). Keeps that one endpoint on our\n * existing non-pi path until we migrate it in a follow-up.\n */\nexport function loadEnabledRegistry(db: BazilionDb, authToken: string, env: NodeJS.ProcessEnv) {\n return createProviderRegistry(loadProviderConfigFromEnv(env, { db, authToken }), {\n enabledSet: providerStateRepo.listEnabled(db),\n })\n}\n\n/**\n * Read the most recent session file for an agent *without* spawning a full\n * AgentSession, and return the resolved provider-message view. Used for SSR\n * page loads that only need to render the canonical transcript — no need\n * to boot pi just to inspect the transcript.\n *\n * Returns an empty array when the agent has no prior session (fresh spawn,\n * or post-/reset).\n */\nexport function loadInitialMessages(agent: ResolvedAgent, paths: Paths): AgentMessage[] {\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n if (!existsSync(sessionDir)) return []\n const cwd = agent.team.path\n if (!existsSync(cwd)) return []\n const recent = findMostRecent(sessionDir)\n if (!recent) return []\n try {\n const sm = SessionManager.open(recent, sessionDir)\n const ctx = sm.buildSessionContext()\n return ctx.messages\n } catch (err) {\n // Corrupt session file, stale format, or pi version bump — log loud\n // enough that an operator noticing a blank chat can find the cause in\n // server logs. The turn loop itself starts a fresh session on the\n // next message, so this isn't load-bearing for writes, only reads.\n console.error(\n `[session] loadInitialMessages failed for agent ${agent.agent.id} (${recent}):`,\n err instanceof Error ? (err.stack ?? err.message) : err,\n )\n return []\n }\n}\n\n/** Read one named canonical session for evidence display without creating a live session. */\nexport function loadSessionMessages(\n agent: ResolvedAgent,\n paths: Paths,\n sessionId: string,\n): AgentMessage[] {\n if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return []\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n const file = join(sessionDir, `${sessionId}.jsonl`)\n if (!existsSync(file) || !existsSync(agent.team.path)) return []\n try {\n return SessionManager.open(file, sessionDir).buildSessionContext().messages\n } catch {\n return []\n }\n}\n\n/**\n * Cheap \"has the session changed?\" probe for polling clients (the web chat\n * stale-tab banner). Returns the most recent session file's basename plus\n * byte size — append-only JSONL, so either value moving means new activity.\n * Returns `{ file: null, size: 0 }` for agents that have never had a turn.\n */\nexport function loadSessionHead(\n agent: ResolvedAgent,\n paths: Paths,\n): { file: string | null; size: number } {\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n if (!existsSync(sessionDir)) return { file: null, size: 0 }\n const recent = findMostRecent(sessionDir)\n if (!recent) return { file: null, size: 0 }\n try {\n const s = statSync(recent)\n return { file: basename(recent), size: s.size }\n } catch {\n return { file: null, size: 0 }\n }\n}\n\n/**\n * Test helper: seed a pi session file for an agent with `n` synthetic\n * user/assistant message pairs. Writes a real JSONL entry tree via\n * SessionManager so round-tripping through pi's own reader stays honest.\n * Exported from runtime rather than lived in tests because tests in apps/cli\n * can't directly import pi packages (not a direct dep).\n */\nexport function seedSessionForTest(\n agent: ResolvedAgent,\n paths: Paths,\n messages: Array<{ role: 'user' | 'assistant'; text: string }>,\n): void {\n const cwd = agent.team.path\n if (!existsSync(cwd)) mkdirSync(cwd, { recursive: true })\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n mkdirSync(sessionDir, { recursive: true })\n const sm = SessionManager.create(cwd, sessionDir)\n const now = Date.now()\n messages.forEach((m, i) => {\n if (m.role === 'user') {\n sm.appendMessage({\n role: 'user',\n content: [{ type: 'text', text: m.text }],\n timestamp: now + i,\n })\n } else {\n sm.appendMessage({\n role: 'assistant',\n content: [{ type: 'text', text: m.text }],\n api: 'openai-completions',\n provider: 'lmstudio',\n model: 'test-model',\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason: 'stop',\n timestamp: now + i,\n })\n }\n })\n}\n\n/**\n * Test helper: count message entries on the current leaf's branch of the\n * agent's most-recent session file. Returns 0 when no session file exists.\n */\nexport function countSessionMessagesForTest(agent: ResolvedAgent, paths: Paths): number {\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n const recent = findMostRecent(sessionDir)\n if (!recent) return 0\n const cwd = agent.team.path\n try {\n const sm = SessionManager.open(recent, sessionDir, cwd)\n return sm.getBranch().filter((e) => e.type === 'message').length\n } catch {\n return 0\n }\n}\n\n/** Newest `.jsonl` in a pi session directory by mtime, or null if empty. */\nfunction findMostRecent(sessionDir: string): string | null {\n if (!existsSync(sessionDir)) return null\n let newest: { path: string; mtimeMs: number } | null = null\n for (const entry of readdirSync(sessionDir)) {\n if (!entry.endsWith('.jsonl')) continue\n const path = join(sessionDir, entry)\n try {\n const s = statSync(path)\n if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs }\n } catch {\n // ignore races\n }\n }\n return newest?.path ?? null\n}\n","import type { Api, Model } from '@earendil-works/pi-ai'\nimport { InMemoryCredentialStore } from '@earendil-works/pi-ai'\nimport { ModelRuntime } from '@earendil-works/pi-coding-agent'\n\nconst PROVIDER_ALIASES: Record<string, string> = {\n bedrock: 'amazon-bedrock',\n 'azure-openai': 'azure-openai-responses',\n}\n\nexport const PROTECTED_PROVIDER_NAMES = [\n 'anthropic',\n 'openai',\n 'openai-codex',\n 'google',\n 'google-vertex',\n 'azure-openai',\n 'bedrock',\n 'mistral',\n 'groq',\n 'cerebras',\n 'xai',\n 'zai',\n 'huggingface',\n 'openrouter',\n 'vercel-ai-gateway',\n 'deepseek',\n 'fireworks',\n 'together',\n 'baseten',\n 'moonshotai',\n 'moonshotai-cn',\n 'kimi-coding',\n 'minimax',\n 'minimax-cn',\n 'qwen-token-plan',\n 'qwen-token-plan-cn',\n 'qwen-token-plan-individual',\n 'xiaomi',\n 'xiaomi-token-plan-ams',\n 'xiaomi-token-plan-cn',\n 'xiaomi-token-plan-sgp',\n 'ant-ling',\n 'nvidia',\n 'opencode',\n 'opencode-go',\n 'zai-coding-cn',\n 'github-copilot',\n 'cloudflare-ai-gateway',\n 'cloudflare-workers-ai',\n 'lmstudio',\n 'ollama',\n 'llamacpp',\n] as const\n\nexport type ProtectedProviderName = (typeof PROTECTED_PROVIDER_NAMES)[number]\n\nexport const LOCAL_PROVIDERS: Record<\n string,\n { baseUrl: string; apiKey: string; authHeader: boolean }\n> = {\n lmstudio: {\n baseUrl: 'http://127.0.0.1:1234/v1',\n apiKey: 'lm-studio',\n authHeader: false,\n },\n ollama: { baseUrl: 'http://127.0.0.1:11434/v1', apiKey: 'ollama', authHeader: false },\n llamacpp: { baseUrl: 'http://127.0.0.1:8080/v1', apiKey: 'no-key', authHeader: true },\n}\n\nexport const PROVIDER_API_KEY_ENV: Record<string, readonly string[]> = {\n anthropic: ['ANTHROPIC_OAUTH_TOKEN', 'ANTHROPIC_API_KEY'],\n openai: ['OPENAI_API_KEY'],\n google: ['GEMINI_API_KEY'],\n 'azure-openai': ['AZURE_OPENAI_API_KEY'],\n mistral: ['MISTRAL_API_KEY'],\n groq: ['GROQ_API_KEY'],\n cerebras: ['CEREBRAS_API_KEY'],\n xai: ['XAI_API_KEY'],\n zai: ['ZAI_API_KEY'],\n huggingface: ['HF_TOKEN'],\n openrouter: ['OPENROUTER_API_KEY'],\n 'vercel-ai-gateway': ['AI_GATEWAY_API_KEY'],\n deepseek: ['DEEPSEEK_API_KEY'],\n fireworks: ['FIREWORKS_API_KEY'],\n together: ['TOGETHER_API_KEY'],\n baseten: ['BASETEN_API_KEY'],\n moonshotai: ['MOONSHOT_API_KEY'],\n 'moonshotai-cn': ['MOONSHOT_CN_API_KEY'],\n 'kimi-coding': ['KIMI_API_KEY'],\n minimax: ['MINIMAX_API_KEY'],\n 'minimax-cn': ['MINIMAX_CN_API_KEY'],\n 'qwen-token-plan': ['QWEN_TOKEN_PLAN_API_KEY'],\n 'qwen-token-plan-cn': ['QWEN_TOKEN_PLAN_CN_API_KEY'],\n 'qwen-token-plan-individual': ['QWEN_TOKEN_PLAN_API_KEY'],\n xiaomi: ['XIAOMI_API_KEY'],\n 'xiaomi-token-plan-ams': ['XIAOMI_TOKEN_PLAN_AMS_API_KEY'],\n 'xiaomi-token-plan-cn': ['XIAOMI_TOKEN_PLAN_CN_API_KEY'],\n 'xiaomi-token-plan-sgp': ['XIAOMI_TOKEN_PLAN_SGP_API_KEY'],\n 'ant-ling': ['ANT_LING_API_KEY'],\n nvidia: ['NVIDIA_API_KEY'],\n opencode: ['OPENCODE_API_KEY'],\n 'opencode-go': ['OPENCODE_GO_API_KEY'],\n 'zai-coding-cn': ['ZAI_CODING_CN_API_KEY'],\n 'github-copilot': ['COPILOT_GITHUB_TOKEN'],\n 'cloudflare-ai-gateway': ['CLOUDFLARE_API_KEY'],\n 'cloudflare-workers-ai': ['CLOUDFLARE_API_KEY'],\n lmstudio: ['LMSTUDIO_API_KEY'],\n ollama: ['OLLAMA_API_KEY'],\n llamacpp: ['LLAMACPP_API_KEY'],\n}\n\nexport const PROVIDER_CREDENTIAL_ENV: Record<string, readonly string[]> = {\n bedrock: [\n 'AWS_PROFILE',\n 'AWS_BEARER_TOKEN_BEDROCK',\n 'AWS_ACCESS_KEY_ID',\n 'AWS_SECRET_ACCESS_KEY',\n 'AWS_SESSION_TOKEN',\n 'AWS_REGION',\n 'AWS_DEFAULT_REGION',\n ],\n 'google-vertex': [\n 'GOOGLE_CLOUD_PROJECT',\n 'GOOGLE_CLOUD_LOCATION',\n 'GOOGLE_APPLICATION_CREDENTIALS',\n ],\n 'cloudflare-ai-gateway': ['CLOUDFLARE_API_KEY', 'CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_GATEWAY_ID'],\n 'cloudflare-workers-ai': ['CLOUDFLARE_API_KEY', 'CLOUDFLARE_ACCOUNT_ID'],\n}\n\nexport interface BazilionPiRuntimeOptions {\n providerName: string\n env: NodeJS.ProcessEnv\n apiKey?: string\n baseUrl?: string\n modelId?: string\n}\n\nexport interface OpenAICodexPiRuntimeOptions {\n providerName: 'openai-codex'\n modelId: string\n accessToken: string\n}\n\nexport interface ProtectedPiRuntimeOptions {\n providerName: ProtectedProviderName\n modelId: string\n apiKey?: string\n baseUrl?: string\n credentialEnv?: Array<{ name: string; value: string }>\n}\n\nexport function piProviderName(providerName: string): string {\n return PROVIDER_ALIASES[providerName] ?? providerName\n}\n\nexport function providerApiKey(providerName: string, env: NodeJS.ProcessEnv): string | undefined {\n for (const name of PROVIDER_API_KEY_ENV[providerName] ?? []) {\n const value = env[name]\n if (value) return value\n }\n return LOCAL_PROVIDERS[providerName]?.apiKey\n}\n\nexport function providerBaseUrl(providerName: string, env: NodeJS.ProcessEnv): string | undefined {\n switch (providerName) {\n case 'lmstudio':\n return env.LMSTUDIO_URL ?? LOCAL_PROVIDERS.lmstudio?.baseUrl\n case 'ollama':\n return env.OLLAMA_URL ?? LOCAL_PROVIDERS.ollama?.baseUrl\n case 'llamacpp':\n return env.LLAMACPP_URL ?? LOCAL_PROVIDERS.llamacpp?.baseUrl\n case 'vercel-ai-gateway':\n return env.AI_GATEWAY_BASE_URL\n default:\n return undefined\n }\n}\n\nexport function providerCredentialEnv(\n providerName: string,\n env: NodeJS.ProcessEnv,\n): Record<string, string> | undefined {\n const result: Record<string, string> = {}\n for (const name of PROVIDER_CREDENTIAL_ENV[providerName] ?? []) {\n const value = env[name]\n if (value !== undefined) result[name] = value\n }\n return Object.keys(result).length > 0 ? result : undefined\n}\n\nfunction fallbackApi(providerName: string): Api {\n switch (providerName) {\n case 'anthropic':\n return 'anthropic-messages'\n case 'google':\n return 'google-generative-ai'\n case 'google-vertex':\n return 'google-vertex'\n case 'azure-openai':\n return 'azure-openai-responses'\n case 'bedrock':\n return 'bedrock-converse-stream'\n case 'openai-codex':\n return 'openai-codex-responses'\n default:\n return 'openai-completions'\n }\n}\n\nexport function fallbackPiModel(\n providerName: string,\n modelId: string,\n baseUrl?: string,\n): Model<Api> {\n return {\n id: modelId,\n name: modelId,\n api: fallbackApi(providerName),\n provider: piProviderName(providerName),\n baseUrl: baseUrl ?? '',\n reasoning: false,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 32_768,\n maxTokens: 4_096,\n }\n}\n\nexport async function createBazilionPiRuntime(\n options: BazilionPiRuntimeOptions,\n): Promise<ModelRuntime> {\n const providerName = options.providerName\n const canonicalName = piProviderName(providerName)\n const credentials = new InMemoryCredentialStore()\n const apiKey = options.apiKey ?? providerApiKey(providerName, options.env)\n const credentialEnv = providerCredentialEnv(providerName, options.env)\n\n if (apiKey || credentialEnv) {\n await credentials.modify(canonicalName, async () =>\n canonicalName === 'openai-codex' && apiKey\n ? {\n type: 'oauth',\n access: apiKey,\n // Bazilion owns refresh outside Pi. This deliberately empty value\n // satisfies Pi's canonical OAuth shape but is never used because\n // the token is long-lived only inside this one-shot runtime and\n // mid-turn replacement is supplied through Agent.getApiKey.\n refresh: '',\n expires: Number.MAX_SAFE_INTEGER,\n }\n : {\n type: 'api_key',\n ...(apiKey ? { key: apiKey } : {}),\n ...(credentialEnv ? { env: credentialEnv } : {}),\n },\n )\n }\n\n const runtime = await ModelRuntime.create({ credentials, modelsPath: null })\n const local = LOCAL_PROVIDERS[providerName]\n const baseUrl = options.baseUrl ?? providerBaseUrl(providerName, options.env)\n\n if (local) {\n const modelId = options.modelId ?? 'model-name'\n const model = fallbackPiModel(providerName, modelId, baseUrl ?? local.baseUrl)\n runtime.registerProvider(canonicalName, {\n baseUrl: baseUrl ?? local.baseUrl,\n api: 'openai-completions',\n authHeader: local.authHeader,\n apiKey: apiKey ?? local.apiKey,\n models: [model],\n })\n } else if (baseUrl) {\n runtime.registerProvider(canonicalName, { baseUrl })\n }\n\n return runtime\n}\n\n/**\n * Closed provider runtime for protected/review workers. It cannot consult an\n * environment record or register a non-Codex provider.\n */\nexport async function createOpenAICodexPiRuntime(\n options: OpenAICodexPiRuntimeOptions,\n): Promise<ModelRuntime> {\n if (options.providerName !== 'openai-codex') {\n throw new Error('protected execution supports only openai-codex')\n }\n if (!options.modelId.trim()) throw new Error('openai-codex model id is required')\n if (!options.accessToken.trim()) throw new Error('openai-codex access token is required')\n\n const credentials = new InMemoryCredentialStore()\n await credentials.modify('openai-codex', async () => ({\n type: 'oauth',\n access: options.accessToken,\n // The daemon-owned refresh credential never enters this process. Pi only\n // needs a non-expiring in-memory access-token projection for its startup\n // auth gate; Agent.getApiKey performs every real mid-turn refresh over IPC.\n refresh: '',\n expires: Number.MAX_SAFE_INTEGER,\n }))\n return ModelRuntime.create({ credentials, modelsPath: null })\n}\n\n/** Build a protected runtime only from the daemon's explicit wire projection. */\nexport async function createProtectedPiRuntime(\n options: ProtectedPiRuntimeOptions,\n): Promise<ModelRuntime> {\n const env: NodeJS.ProcessEnv = {}\n for (const field of options.credentialEnv ?? []) env[field.name] = field.value\n return createBazilionPiRuntime({\n providerName: options.providerName,\n modelId: options.modelId,\n env,\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n })\n}\n\nexport function resolvePiModel(\n runtime: ModelRuntime,\n providerName: string,\n modelId: string,\n baseUrl?: string,\n): Model<Api> {\n const known = runtime.getModel(piProviderName(providerName), modelId)\n if (known) return baseUrl ? { ...known, baseUrl } : known\n return fallbackPiModel(providerName, modelId, baseUrl)\n}\n","// OpenAI ChatGPT / Codex OAuth — token storage + refresh on top of pi-ai.\n//\n// Pi-ai ships a complete OAuth flow for the ChatGPT backend through its\n// `openai-codex` provider, so this module is\n// thin: it adapts the credential I/O to Bazilion's encrypted secrets store\n// and exposes a single `loadAccessToken(db, authToken)` call that refreshes\n// when the token is about to expire.\n//\n// Storage: Pi's complete OAuth credential blob lives under the\n// secrets key `OPENAI_CODEX_OAUTH` in the `secrets` table. The blob is\n// never copied into the env (unlike plain-API-key providers) — refresh is\n// stateful, so every call reads and writes through the live secrets store.\n\nimport type { OpenAICodexStatus } from '@bazilion/api-types'\nimport type { AuthInteraction, OAuthCredential, OAuthCredentials } from '@earendil-works/pi-ai'\nimport { openaiCodexProvider } from '@earendil-works/pi-ai/providers/openai-codex'\nimport { type BazilionDb, openSecrets } from '../../core/index.ts'\n\nexport const OPENAI_CODEX_SECRET_KEY = 'OPENAI_CODEX_OAUTH'\n\n/** Refresh when the access token has less than this much life left. */\nconst REFRESH_MARGIN_MS = 60_000\n\n/**\n * One refresh flight per daemon DB/auth context. Multiple Agents can reach the\n * expiry margin at the same time; without single-flight they would all submit\n * the same (potentially rotating) refresh token and race the credentials write.\n * The WeakMap lets closed DB handles and their nested maps be collected.\n */\nconst refreshFlights = new WeakMap<BazilionDb, Map<string, Promise<string>>>()\n\nfunction openAICodexOAuth() {\n const oauth = openaiCodexProvider().auth.oauth\n if (!oauth) throw new Error('Pi openai-codex provider does not expose OAuth')\n return oauth\n}\n\nexport async function loginOpenAICodex(interaction: AuthInteraction): Promise<OAuthCredential> {\n return openAICodexOAuth().login({\n ...interaction,\n signal: interaction.signal ?? new AbortController().signal,\n })\n}\n\nexport async function refreshOpenAICodexToken(\n credential: OAuthCredential,\n): Promise<OAuthCredential> {\n return openAICodexOAuth().refresh(credential, new AbortController().signal)\n}\n\nexport type StoredCredentials = OAuthCredential\n\nfunction readCredentials(db: BazilionDb, authToken: string): StoredCredentials | null {\n const raw = openSecrets(db, authToken).get(OPENAI_CODEX_SECRET_KEY)\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as Partial<StoredCredentials>\n if (\n typeof parsed.refresh === 'string' &&\n parsed.refresh.length > 0 &&\n typeof parsed.access === 'string' &&\n parsed.access.length > 0 &&\n typeof parsed.expires === 'number'\n ) {\n // Older Bazilion rows predate Pi's type discriminator. Normalizing here\n // keeps those rows readable while preserving all provider-specific fields.\n return { ...parsed, type: 'oauth' } as StoredCredentials\n }\n return null\n } catch {\n return null\n }\n}\n\nfunction writeCredentials(db: BazilionDb, authToken: string, creds: StoredCredentials): void {\n openSecrets(db, authToken).set(OPENAI_CODEX_SECRET_KEY, JSON.stringify(creds))\n}\n\nexport function clearCredentials(db: BazilionDb, authToken: string): void {\n openSecrets(db, authToken).remove(OPENAI_CODEX_SECRET_KEY)\n}\n\nexport function hasCredentials(db: BazilionDb, authToken: string): boolean {\n return readCredentials(db, authToken) !== null\n}\n\nfunction decodeAccountId(accessToken: string): string | null {\n const parts = accessToken.split('.')\n if (parts.length !== 3) return null\n try {\n const payload = JSON.parse(Buffer.from(parts[1] as string, 'base64').toString('utf8')) as {\n 'https://api.openai.com/auth'?: { chatgpt_account_id?: string }\n }\n return payload['https://api.openai.com/auth']?.chatgpt_account_id ?? null\n } catch {\n return null\n }\n}\n\nexport function getStatus(db: BazilionDb, authToken: string): OpenAICodexStatus {\n const creds = readCredentials(db, authToken)\n if (!creds) return { connected: false, expiresAt: null, accountId: null }\n return {\n connected: true,\n expiresAt: creds.expires,\n accountId: decodeAccountId(creds.access),\n }\n}\n\n/**\n * Returns a valid access token, refreshing via pi-ai if the stored one is\n * within `REFRESH_MARGIN_MS` of expiry. Throws when no credentials are stored\n * so callers can surface an actionable \"run `bazilion auth openai login`\"\n * error rather than a 401 from the upstream API.\n */\nexport async function loadAccessToken(db: BazilionDb, authToken: string): Promise<string> {\n const creds = readCredentials(db, authToken)\n if (!creds) {\n throw credentialsMissingError()\n }\n if (creds.expires > Date.now() + REFRESH_MARGIN_MS) return creds.access\n\n let dbFlights = refreshFlights.get(db)\n if (!dbFlights) {\n dbFlights = new Map()\n refreshFlights.set(db, dbFlights)\n }\n\n const existing = dbFlights.get(authToken)\n if (existing) return existing\n\n // Publish the flight before starting it. The microtask boundary makes the\n // credential re-read below happen after publication, so every concurrent\n // caller observes this same Promise instead of starting a second refresh.\n const pending = Promise.resolve().then(() => refreshExpiredAccessToken(db, authToken))\n dbFlights.set(authToken, pending)\n try {\n return await pending\n } finally {\n if (dbFlights.get(authToken) === pending) dbFlights.delete(authToken)\n if (dbFlights.size === 0) refreshFlights.delete(db)\n }\n}\n\nasync function refreshExpiredAccessToken(db: BazilionDb, authToken: string): Promise<string> {\n // Credentials may have been refreshed or replaced between the caller's\n // initial expiry check and this flight acquiring ownership. Re-read after\n // publishing the flight and skip the network call when another path won.\n const creds = readCredentials(db, authToken)\n if (!creds) throw credentialsMissingError()\n if (creds.expires > Date.now() + REFRESH_MARGIN_MS) return creds.access\n\n const next = await refreshOpenAICodexToken(creds)\n\n // A logout or a new login can happen while the network request is pending.\n // Never resurrect cleared credentials or overwrite a newer credential set.\n const current = readCredentials(db, authToken)\n if (!current) throw credentialsMissingError()\n if (!sameCredentials(current, creds)) {\n if (current.expires > Date.now() + REFRESH_MARGIN_MS) return current.access\n throw new Error('OpenAI ChatGPT OAuth credentials changed while refresh was in progress')\n }\n\n writeCredentials(db, authToken, next)\n return next.access\n}\n\nfunction credentialsMissingError(): Error {\n return new Error(\n 'OpenAI ChatGPT OAuth not configured — run `bazilion auth openai login` (or use the Connect button on /config)',\n )\n}\n\nfunction sameCredentials(left: StoredCredentials, right: StoredCredentials): boolean {\n return (\n left.refresh === right.refresh && left.access === right.access && left.expires === right.expires\n )\n}\n\n/** Persist credentials fetched by pi-ai's `loginOpenAICodex`. */\nexport function saveLoginCredentials(\n db: BazilionDb,\n authToken: string,\n creds: OAuthCredentials,\n): void {\n writeCredentials(db, authToken, { ...creds, type: 'oauth' })\n}\n","// Adapter from Bazilion's Provider interface → pi-ai's streamSimple.\n//\n// Pi-ai (`@earendil-works/pi-ai`) is Mario Zechner's unified LLM SDK — 15+\n// providers behind one event-stream API. This adapter is the boundary where\n// Pi messages become Bazilion's provider contract; everything downstream\n// (`runTurnStream`, `persistRun`, the worker entry, CLI, web) sees the same\n// `Provider.chat(ProviderRequest): Promise<ProviderResponse>` contract it\n// always has. That keeps the wire format, DB schema, and chat UI stable while\n// giving us cost/usage, thinking levels, prompt caching, and every provider\n// pi supports — for free.\n//\n// Model/auth/stream resolution is delegated to Bazilion's Pi runtime factory,\n// which uses Pi's public ModelRuntime API and preserves arbitrary model ids.\n\nimport type { ProviderMessage, ReasoningLevel, ToolCall, ToolDef } from '@bazilion/api-types'\nimport {\n type AssistantMessage,\n type Message as PiMessage,\n type Tool as PiTool,\n type ToolCall as PiToolCall,\n type TextContent,\n Type,\n} from '@earendil-works/pi-ai'\nimport type { ModelRuntime } from '@earendil-works/pi-coding-agent'\nimport { createBazilionPiRuntime, resolvePiModel } from './pi-runtime.ts'\nimport type { Provider, ProviderRequest, ProviderResponse, StopReason } from './types.ts'\n\nexport interface PiProviderConfig {\n /** Display name on the returned Provider; also the registry key (e.g. 'bedrock', 'azure-openai'). */\n providerName: string\n /** Override baseUrl for openai-compat endpoints (lmstudio, ollama, custom). */\n baseUrl?: string\n /** Merged daemon environment used for provider-specific ambient configuration. */\n env?: NodeJS.ProcessEnv\n /**\n * Static key or an async supplier. Suppliers are called at the top of each\n * chat() so OAuth-backed providers can refresh expiring tokens without\n * rebuilding the Provider instance (which the registry caches).\n */\n apiKey?: string | (() => string | Promise<string>)\n /** Test seam for a public-API ModelRuntime with a native faux provider. */\n runtimeFactory?: () => Promise<ModelRuntime>\n}\n\nfunction convertMessages(messages: ProviderMessage[]): PiMessage[] {\n const out: PiMessage[] = []\n const now = Date.now()\n for (const m of messages) {\n if (m.role === 'system') continue // pi takes system prompt separately\n if (m.role === 'user') {\n out.push({ role: 'user', content: m.content, timestamp: now })\n continue\n }\n if (m.role === 'assistant') {\n const content: AssistantMessage['content'] = []\n if (m.content) content.push({ type: 'text', text: m.content } satisfies TextContent)\n if (m.toolCalls) {\n for (const tc of m.toolCalls) {\n let parsed: Record<string, unknown> = {}\n try {\n parsed = JSON.parse(tc.arguments) as Record<string, unknown>\n } catch {\n // leave empty\n }\n content.push({\n type: 'toolCall',\n id: tc.id,\n name: tc.name,\n arguments: parsed,\n } satisfies PiToolCall)\n }\n }\n // Synthesize the AssistantMessage fields pi expects on replays — these\n // are only load-bearing for the LLM that gets the transcript; since we\n // don't persist usage/stopReason in Bazilion's message store, defaults\n // are fine.\n out.push({\n role: 'assistant',\n content,\n api: 'anthropic-messages',\n provider: 'anthropic',\n model: 'unknown',\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason: 'stop',\n timestamp: now,\n })\n continue\n }\n if (m.role === 'tool') {\n out.push({\n role: 'toolResult',\n toolCallId: m.toolCallId ?? '',\n toolName: m.toolName ?? 'tool',\n content: [{ type: 'text', text: m.content }],\n isError: false,\n timestamp: now,\n })\n }\n }\n return out\n}\n\nfunction convertTools(tools: ToolDef[] | undefined): PiTool[] | undefined {\n if (!tools || tools.length === 0) return undefined\n return tools.map((t) => ({\n name: t.name,\n description: t.description,\n // Type.Unsafe lets us pass raw JSON Schema through without re-authoring in\n // typebox. Providers validate against the schema, not typebox's TSchema.\n parameters: Type.Unsafe<unknown>(t.parameters as Record<string, unknown>),\n }))\n}\n\nfunction toBazilionStopReason(reason: string): StopReason {\n switch (reason) {\n case 'stop':\n return 'stop'\n case 'length':\n return 'length'\n case 'toolUse':\n return 'tool_use'\n default:\n return 'error'\n }\n}\n\nfunction extractFinalResponse(msg: AssistantMessage): ProviderResponse {\n let text = ''\n const toolCalls: ToolCall[] = []\n for (const block of msg.content) {\n if (block.type === 'text') {\n text += block.text\n } else if (block.type === 'toolCall') {\n toolCalls.push({\n id: block.id,\n name: block.name,\n arguments: JSON.stringify(block.arguments ?? {}),\n })\n }\n }\n const res: ProviderResponse = {\n content: text,\n toolCalls,\n stopReason: toBazilionStopReason(msg.stopReason),\n }\n if (msg.usage) {\n res.usage = {\n promptTokens: msg.usage.input,\n completionTokens: msg.usage.output,\n }\n }\n return res\n}\n\nfunction mapReasoning(\n r: ReasoningLevel | undefined,\n): 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | undefined {\n if (!r || r === 'off') return undefined\n return r\n}\n\nasync function resolveApiKey(cfg: PiProviderConfig): Promise<string | undefined> {\n if (typeof cfg.apiKey === 'function') return await cfg.apiKey()\n return cfg.apiKey\n}\n\nexport function piProvider(cfg: PiProviderConfig): Provider {\n return {\n name: cfg.providerName,\n async chat(req: ProviderRequest): Promise<ProviderResponse> {\n const apiKey = await resolveApiKey(cfg)\n const runtime = cfg.runtimeFactory\n ? await cfg.runtimeFactory()\n : await createBazilionPiRuntime({\n providerName: cfg.providerName,\n env: cfg.env ?? {},\n ...(apiKey ? { apiKey } : {}),\n ...(cfg.baseUrl ? { baseUrl: cfg.baseUrl } : {}),\n modelId: req.model,\n })\n const model = resolvePiModel(runtime, cfg.providerName, req.model, cfg.baseUrl)\n\n const stream = runtime.streamSimple(\n model,\n {\n systemPrompt: req.system ?? '',\n messages: convertMessages(req.messages),\n tools: convertTools(req.tools),\n },\n {\n signal: req.signal,\n apiKey,\n reasoning: mapReasoning(req.reasoning),\n maxTokens: req.maxTokens,\n temperature: req.temperature,\n },\n )\n\n let finalMessage: AssistantMessage | null = null\n for await (const event of stream) {\n if (event.type === 'text_delta' && req.onDelta) {\n req.onDelta(event.delta)\n } else if (event.type === 'done') {\n finalMessage = event.message\n } else if (event.type === 'error') {\n finalMessage = event.error\n }\n }\n if (!finalMessage) {\n throw new Error(`pi provider ${cfg.providerName} returned no terminal event`)\n }\n if (finalMessage.stopReason === 'aborted' || finalMessage.stopReason === 'error') {\n const msg = finalMessage.errorMessage ?? 'provider error'\n throw new Error(msg)\n }\n return extractFinalResponse(finalMessage)\n },\n }\n}\n","// Bazilion system-prompt builder. Feeds pi's `getAppendSystemPrompt()` hook\n// so the agent sees its persona + skills + workspaces + memory guidance\n// stacked on top of pi's built-in base prompt (which lists coding tools and\n// general guidelines). Pure filesystem read — no LLM, no DB.\n\nimport { existsSync, readdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { ResolvedAgent } from '@bazilion/api-types'\nimport { parseSkillContent } from '../../core/skills/parse.ts'\nimport { readRegularFileNoFollow, resolveRealDirectory } from '../safe-files.ts'\nimport type { BashSandboxMode } from '../shell/security.ts'\n\nexport const SANDBOX_SKILLS_DIR = '/skills'\n\nexport interface PromptSkill {\n name: string\n description: string\n body: string\n hostDir: string\n sandboxDir: string\n}\n\nexport interface BuildSystemPromptOptions {\n skills?: readonly PromptSkill[]\n sandboxMode?: BashSandboxMode\n /** Daemon-prepared fixed document bytes; protected workers must supply this. */\n homeDocuments?: ProtectedHomeDocuments\n}\n\nexport interface ProtectedHomeDocuments {\n 'AGENTS.md': string | null\n 'SOUL.md': string | null\n 'TOOLS.md': string | null\n 'IDENTITY.md': string | null\n 'BOOTSTRAP.md': string | null\n}\n\n/** Load only direct, installed skill directories attached to this agent. */\nexport function loadPromptSkills(\n skillsDir: string,\n attachedNames: readonly string[],\n): PromptSkill[] {\n if (!existsSync(skillsDir) || attachedNames.length === 0) return []\n const attached = new Set(attachedNames)\n const discovered = readdirSync(skillsDir, { withFileTypes: true })\n .filter((entry) => entry.isDirectory() && attached.has(entry.name))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n const loaded: PromptSkill[] = []\n for (const entry of discovered) {\n const hostDir = join(skillsDir, entry.name)\n try {\n const parsed = parseSkillContent(readRegularFileNoFollow(join(hostDir, 'SKILL.md')))\n const safeLabel = entry.name.replace(/[^A-Za-z0-9._-]/g, '_') || 'skill'\n loaded.push({\n name: entry.name,\n description: parsed.frontmatter.description,\n body: parsed.body.trim(),\n hostDir,\n sandboxDir: `${SANDBOX_SKILLS_DIR}/${loaded.length}-${safeLabel}`,\n })\n } catch {\n // The attachment surface already reports missing/parse-broken skills.\n // Keep turn creation resilient and name the unavailable attachment below.\n }\n }\n return loaded\n}\n\n// Prompt order: peers first (who else is around), then persona, then tooling\n// hints, then self-knowledge, then the wake-up playbook.\n//\n// BOOTSTRAP.md is intentionally NOT in this generic-context list — it gets\n// its own dedicated \"First-Run Ritual\" section below with explicit\n// anti-checklist framing, so models treat it as multi-turn Q&A guidance\n// rather than a one-shot script to execute. The system prompt regenerates\n// per turn, so the section auto-vanishes when `bootstrap_done` removes the\n// file (vs. wrapping the user message, which would persist in pi's session\n// JSONL forever and replay on every future turn).\nconst CONTEXT_FILE_ORDER = ['AGENTS.md', 'SOUL.md', 'TOOLS.md', 'IDENTITY.md'] as const\n\n/** Snapshot the exact Agent-home prompt inputs without following fixed-name links. */\nexport function loadProtectedHomeDocuments(agentDir: string): ProtectedHomeDocuments {\n const root = resolveRealDirectory(agentDir)\n const read = (file: keyof ProtectedHomeDocuments): string | null => {\n try {\n return readRegularFileNoFollow(join(root, file))\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw new Error(`private Agent home is unsafe at ${file}`)\n }\n }\n return {\n 'AGENTS.md': read('AGENTS.md'),\n 'SOUL.md': read('SOUL.md'),\n 'TOOLS.md': read('TOOLS.md'),\n 'IDENTITY.md': read('IDENTITY.md'),\n 'BOOTSTRAP.md': read('BOOTSTRAP.md'),\n }\n}\n\nexport function buildSystemPrompt(\n agent: ResolvedAgent,\n options: BuildSystemPromptOptions = {},\n): string {\n const parts: string[] = []\n const homeDocuments = options.homeDocuments ?? loadProtectedHomeDocuments(agent.agent.dir)\n\n const contextBlocks: string[] = []\n for (const file of CONTEXT_FILE_ORDER) {\n const content = homeDocuments[file]?.trimEnd()\n if (!content) continue\n contextBlocks.push(`## ${file}\\n\\n${content}`)\n }\n if (contextBlocks.length > 0) {\n parts.push(`# Project Context\\n\\n${contextBlocks.join('\\n\\n')}`)\n }\n\n // First-Run Ritual block — only emitted while BOOTSTRAP.md exists on disk.\n // The wording deliberately frames it as \"multi-turn Q&A\" not \"checklist\"\n // and lists hard rules at the top so tool-eager models still notice them\n // even if they skim the body.\n const bootstrap = homeDocuments['BOOTSTRAP.md']?.trimEnd()\n if (bootstrap) {\n parts.push(\n [\n '# First-Run Ritual',\n '',\n 'This is your first session. The document below is **conversational guidance**, not a checklist to execute in one shot. It describes a multi-turn Q&A you should have with the human, one question per turn.',\n '',\n '## Hard rules',\n '- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.',\n '- Each subsequent turn: at most one new question. Wait between turns.',\n '- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.',\n '',\n '## BOOTSTRAP.md',\n '',\n bootstrap,\n ].join('\\n'),\n )\n }\n\n parts.push(\n [\n '# Agent Home',\n '',\n 'Your private home holds who you are — identity, soul, and behaviour rules. It is not shared with other agents and cannot be overwritten by them. The files above (IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md) live in this home, plus BOOTSTRAP.md when you are still in your first-run ritual.',\n '',\n '- To change who you are (name, vibe, personality, how you behave): use `home_write`.',\n '- To inspect exact wording of your own files: use `home_read` or `home_list`.',\n '- To remember facts the user told you or things you learned: use `memory_write` — NOT `home_write`.',\n '- To produce work output (code, docs, artefacts): use the workspace tools currently exposed to you — those operate on your team workspace, not your private home.',\n ].join('\\n'),\n )\n\n if (agent.skills.length > 0) {\n const loaded = options.skills ?? []\n const loadedNames = new Set(loaded.map((skill) => skill.name))\n const unavailable = agent.skills.filter((name) => !loadedNames.has(name))\n const skillBlocks = loaded.map((skill) => {\n const runtimeDir = options.sandboxMode === 'docker' ? skill.sandboxDir : skill.hostDir\n return [\n `## ${skill.name}`,\n '',\n skill.description,\n '',\n `Runtime directory: \\`${runtimeDir}\\`. Resolve any relative scripts or assets mentioned by this skill from that directory.`,\n '',\n skill.body,\n ]\n .filter((line, index, lines) => line !== '' || lines[index - 1] !== '')\n .join('\\n')\n .trimEnd()\n })\n const unavailableNote =\n unavailable.length > 0\n ? `Attached but unavailable or invalid: ${unavailable.join(', ')}.`\n : ''\n parts.push(\n [\n '# Available Skills',\n '',\n 'Use these attached skill instructions when they are relevant to the task.',\n '',\n ...skillBlocks,\n ...(unavailableNote ? ['', unavailableNote] : []),\n ].join('\\n'),\n )\n }\n\n const teamLines = [\n '# Team',\n '',\n `- ${agent.team.id} (${agent.team.name}): ${options.sandboxMode === 'docker' ? '/workspace' : agent.team.path}`,\n '',\n 'Your team is where work product lives — code, docs, artefacts, shared scratch. It may be shared with other agents in the same team. Use the workspace tools currently exposed to work there. Never use workspace tools to edit your identity/soul/behaviour files — those live in your private home and are reached via `home_write` / `home_read`.',\n ]\n parts.push(teamLines.join('\\n'))\n\n if (agent.team.userMd.trim()) {\n parts.push(\n `# About the User\\n\\nShared context about the human you're working with in this team. Both you and the human curate it. To update: call \\`user_md_get\\` (returns current content + an etag), merge your change into the full text, then call \\`user_md_write\\` with the merged content and the etag. Use this for STABLE user-specific facts (preferences, role, working hours, how they like to be addressed) — and to CORRECT stale entries when the human tells you something different from what's recorded. For project knowledge use \\`memory_write\\` instead; for personal notes about yourself use \\`home_write\\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes — every agent in the team sees the new content in their system prompt on their next turn automatically.**\\n\\n${agent.team.userMd.trim()}`,\n )\n } else {\n parts.push(\n `# About the User\\n\\nThis team's USER.md is empty. As you learn STABLE facts about the human (preferences, role, working hours, how they like to be addressed), populate it via \\`user_md_get\\` then \\`user_md_write\\` (always get first — you need the etag). Reserve this for things you're confident are durable — project knowledge belongs in \\`memory_write\\`, personal notes about yourself in \\`home_write\\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes — every agent in the team sees the new content in their system prompt on their next turn automatically.**`,\n )\n }\n\n const reviewedLessons = boundReviewedLessons(agent.privateLessons)\n if (reviewedLessons.length > 0) {\n parts.push(\n `# Reviewed lessons\\n\\nThese private lessons were explicitly approved by the human for your behavior. Follow them when relevant; current user instructions and higher-priority policy still win.\\n\\n${reviewedLessons.map((lesson) => `- ${lesson}`).join('\\n')}`,\n )\n }\n\n parts.push(\n '# Memory\\n\\nYou share a persistent memory backend with every other agent in this team. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. This memory is for project knowledge — codebase notes, decisions, things the user told you about the work. For personal notes about yourself (preferences, persona quirks), use `home_write` on IDENTITY.md instead. Always check memory at the start of a session: another agent in the team may have already learned something useful. **Do NOT send peer messages announcing memory writes — every agent has access to the same store via `memory_search` and will find your note when they need it.**',\n )\n\n return parts.join('\\n\\n---\\n\\n')\n}\n\nfunction boundReviewedLessons(lessons: readonly string[], maxCharacters = 8_000): string[] {\n const selected: string[] = []\n let used = 0\n for (const raw of lessons) {\n const lesson = raw.trim()\n if (!lesson) continue\n const remaining = maxCharacters - used\n if (remaining <= 0) break\n selected.push(lesson.slice(0, remaining))\n used += Math.min(lesson.length, remaining)\n }\n return selected\n}\n","import {\n closeSync,\n constants,\n fstatSync,\n lstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n realpathSync,\n} from 'node:fs'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\n\n/** Resolve a real directory and reject a symlink at the selected root itself. */\nexport function resolveRealDirectory(path: string): string {\n const requested = resolve(path)\n const entry = lstatSync(requested)\n if (entry.isSymbolicLink() || !entry.isDirectory()) {\n throw new Error('expected a real directory')\n }\n return realpathSync(requested)\n}\n\n/**\n * Create or validate a direct/nested directory below a registered root.\n * The root itself may be a deliberate Team symlink; the selected directory\n * may not be a symlink and its canonical target must stay below that root.\n */\nexport function ensureContainedRealDirectory(\n path: string,\n root: string,\n options: { create?: boolean } = {},\n): string {\n const requestedRoot = resolve(root)\n const requested = resolve(path)\n if (!isContained(requestedRoot, requested)) throw new Error('directory escapes its root')\n const canonicalRoot = realpathSync(requestedRoot)\n try {\n const entry = lstatSync(requested)\n if (entry.isSymbolicLink() || !entry.isDirectory()) {\n throw new Error('expected a real directory')\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT' || !options.create) throw error\n const canonicalParent = realpathSync(dirname(requested))\n if (!isContained(canonicalRoot, canonicalParent)) throw new Error('directory escapes its root')\n mkdirSync(requested, { recursive: true, mode: 0o700 })\n const entry = lstatSync(requested)\n if (entry.isSymbolicLink() || !entry.isDirectory()) {\n throw new Error('expected a real directory')\n }\n }\n const canonical = realpathSync(requested)\n if (!isContained(canonicalRoot, canonical)) throw new Error('directory escapes its root')\n return canonical\n}\n\n/** Read one regular file while refusing a final-component symbolic link. */\nexport function readRegularFileNoFollow(path: string): string {\n const entry = lstatSync(path)\n if (entry.isSymbolicLink()) throw new Error('symbolic links are not allowed')\n if (!entry.isFile()) throw new Error('target is not a regular file')\n let fd: number | undefined\n try {\n fd = openSync(path, constants.O_RDONLY | noFollowFlag())\n if (!fstatSync(fd).isFile()) throw new Error('target is not a regular file')\n return readFileSync(fd, 'utf8')\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n}\n\nexport function noFollowFlag(): number {\n return typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0\n}\n\nfunction isContained(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n}\n","import { existsSync, lstatSync, realpathSync } from 'node:fs'\nimport { dirname, isAbsolute, join, relative, sep } from 'node:path'\nimport {\n createBashToolDefinition,\n createLocalBashOperations,\n defineTool,\n type ToolDefinition,\n} from '@earendil-works/pi-coding-agent'\nimport { type BashApprovalHost, createApprovalGatedBashTool } from './approval.ts'\nimport {\n createDockerBashOperations,\n createPreparedDockerBashOperations,\n type DockerReadOnlyMount,\n type ProtectedDockerRuntime,\n} from './docker.ts'\nimport {\n buildScrubbedShellEnv,\n resolveShellSecurityConfig,\n type ShellSecurityConfig,\n} from './security.ts'\n\nconst HOST_CODING_TOOL_NAMES = ['read', 'bash', 'edit', 'write', 'grep', 'find', 'ls'] as const\nconst HOST_FILE_TOOL_NAMES = ['read', 'edit', 'write', 'grep', 'find', 'ls'] as const\n\nconst CONTAINER_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\nexport const SANDBOX_INPUTS_DIR = '/inputs'\nexport const SANDBOX_MEMORY_DIR = '/workspace/memory'\n\nexport interface SessionShellToolOptions {\n /** Agent-private uploaded documents, exposed read-only at /inputs when present. */\n inputsDir?: string\n /** Attached skill directories, each mapped to a stable read-only container path. */\n skillMounts?: readonly DockerReadOnlyMount[]\n /** Turn-scoped dangerous-command approval bridge. Omit to deny risky commands. */\n approvalHost?: BashApprovalHost\n}\n\nexport interface SessionShellTools {\n config: ShellSecurityConfig\n /** Pi built-ins that remain host-backed and may be enabled for this session. */\n hostToolNames: readonly string[]\n /** Same-name replacement for Pi's built-in bash when isolation or approval is active. */\n customBash?: ToolDefinition\n}\n\n/** Build the mandatory Docker-only coding surface for a protected turn. */\nexport function createProtectedSessionShellTools(\n cwd: string,\n runtime: ProtectedDockerRuntime,\n approvalHost: BashApprovalHost,\n): SessionShellTools {\n const base = createBashToolDefinition(cwd, {\n operations: createPreparedDockerBashOperations(runtime),\n })\n const dockerBash = defineTool({\n ...base,\n label: 'bash (protected Docker sandbox)',\n description: `${base.description} Runs in an ephemeral, network-disabled Docker container with the Team workspace mounted read/write plus only the preflighted read-only memory, skill, and attachment mounts. Host files, host tools, and host credentials are unavailable.`,\n })\n return {\n config: {\n sandboxMode: 'docker',\n approvalMode: 'dangerous',\n sandboxImage: runtime.image,\n envAllowlist: [],\n },\n hostToolNames: [],\n customBash: createApprovalGatedBashTool(dockerBash, approvalHost),\n }\n}\n\n/**\n * Build the only environment visible to a sandboxed command.\n *\n * Operator-allowlisted values are copied first. Container-specific basics are\n * then pinned so host paths (especially HOME) cannot leak into the sandbox or\n * point programs back at an unavailable host directory.\n */\nexport function buildSandboxContainerEnv(\n env: NodeJS.ProcessEnv,\n allowlist: readonly string[],\n): NodeJS.ProcessEnv {\n return {\n ...buildScrubbedShellEnv(env, allowlist),\n PATH: CONTAINER_PATH,\n HOME: '/tmp',\n LANG: 'C.UTF-8',\n LC_ALL: 'C.UTF-8',\n SHELL: '/bin/bash',\n TMPDIR: '/tmp',\n }\n}\n\n/**\n * Resolve the session's host-vs-container coding-tool surface.\n *\n * Docker mode deliberately enables no host-backed Pi coding tools. The\n * returned custom bash is still allowlisted by name by its caller, while\n * read/edit/write/grep/find/ls stay inactive so absolute host paths cannot\n * bypass the container boundary.\n */\nexport function createSessionShellTools(\n cwd: string,\n env: NodeJS.ProcessEnv,\n options: SessionShellToolOptions = {},\n): SessionShellTools {\n const config = resolveShellSecurityConfig(env)\n if (config.sandboxMode === 'off' && config.approvalMode === 'off') {\n return { config, hostToolNames: HOST_CODING_TOOL_NAMES }\n }\n\n if (config.sandboxMode === 'off') {\n const base = createBashToolDefinition(cwd, {\n operations: createLocalBashOperations(),\n })\n const customBash = createApprovalGatedBashTool(base, options.approvalHost)\n return { config, hostToolNames: HOST_FILE_TOOL_NAMES, customBash }\n }\n\n const operations = createDockerBashOperations({\n image: config.sandboxImage,\n env: buildSandboxContainerEnv(env, config.envAllowlist),\n readOnlyMounts: [\n ...(existsSync(join(cwd, 'memory'))\n ? [safeReadOnlyMount(join(cwd, 'memory'), SANDBOX_MEMORY_DIR, cwd)]\n : []),\n ...(options.inputsDir\n ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname(options.inputsDir))]\n : []),\n ...(options.skillMounts ?? []).map((mount) =>\n safeReadOnlyMount(mount.source, mount.target, dirname(mount.source)),\n ),\n ],\n })\n const base = createBashToolDefinition(cwd, { operations })\n const dockerBash = defineTool({\n ...base,\n label: 'bash (Docker sandbox)',\n description: `${base.description} Runs in an ephemeral, network-disabled Docker container with the team workspace mounted read/write plus only bounded read-only memory, skill, and attachment mounts. Other host files and host credentials are unavailable.`,\n })\n const customBash =\n config.approvalMode === 'dangerous'\n ? createApprovalGatedBashTool(dockerBash, options.approvalHost)\n : dockerBash\n\n return { config, hostToolNames: [], customBash }\n}\n\nfunction safeReadOnlyMount(\n source: string,\n target: string,\n expectedRoot: string,\n): DockerReadOnlyMount {\n const info = lstatSync(source)\n if (info.isSymbolicLink() || !info.isDirectory()) {\n throw new Error(`Docker sandbox read-only mount must be a real directory: ${source}`)\n }\n\n const sourceRoot = realpathSync(expectedRoot)\n const canonicalSource = realpathSync(source)\n const rel = relative(sourceRoot, canonicalSource)\n const contained = rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n if (!contained) {\n throw new Error(`Docker sandbox read-only mount escaped its configured root: ${source}`)\n }\n\n return { source: canonicalSource, target, sourceRoot }\n}\n","import { type createBashToolDefinition, defineTool } from '@earendil-works/pi-coding-agent'\nimport { type CommandRisk, classifyBashCommand } from './security.ts'\n\nexport type BashApprovalDecision = 'approved' | 'denied'\n\nexport interface BashApprovalRequest {\n toolCallId: string\n command: string\n risks: readonly CommandRisk[]\n signal: AbortSignal | undefined\n}\n\n/**\n * Turn-scoped bridge used only for dangerous bash command approval.\n *\n * The transport lives outside the shell layer. Omitting the host is a valid\n * non-interactive posture: risky commands fail closed while safe commands\n * continue to the selected local or Docker execution backend.\n */\nexport interface BashApprovalHost {\n requestApproval(request: BashApprovalRequest): Promise<BashApprovalDecision>\n}\n\nexport class BashApprovalDeniedError extends Error {\n readonly risks: readonly CommandRisk[]\n\n constructor(risks: readonly CommandRisk[]) {\n const codes = [...new Set(risks.map((risk) => risk.code))]\n super(`Dangerous bash command denied (${codes.join(', ')})`)\n this.name = 'BashApprovalDeniedError'\n this.risks = risks\n }\n}\n\ntype BashToolDefinition = ReturnType<typeof createBashToolDefinition>\n\n/**\n * Wrap Pi's bash ToolDefinition at the tool-call boundary, where the stable\n * toolCallId is available. BashOperations is deliberately left untouched so\n * approval always happens before either the host or Docker backend executes.\n */\nexport function createApprovalGatedBashTool(base: BashToolDefinition, host?: BashApprovalHost) {\n return defineTool({\n ...base,\n description: `${base.description} Commands classified as dangerous require operator approval before execution.`,\n executionMode: 'sequential',\n async execute(toolCallId, params, signal, onUpdate, ctx) {\n const risks = classifyBashCommand(params.command)\n if (risks.length > 0) {\n const decision = host\n ? await host.requestApproval({\n toolCallId,\n command: params.command,\n risks,\n signal,\n })\n : 'denied'\n\n if (decision !== 'approved') throw new BashApprovalDeniedError(risks)\n }\n\n return base.execute(toolCallId, params, signal, onUpdate, ctx)\n },\n })\n}\n","import type { CommandRisk, CommandRiskCode, CommandRiskSeverity } from '@bazilion/api-types'\n\nexport type { CommandRisk, CommandRiskCode, CommandRiskSeverity } from '@bazilion/api-types'\n\nexport type CommandRiskSpan = CommandRisk['span']\n\nexport type BashSandboxMode = 'off' | 'docker'\n\nexport type BashApprovalPolicy = 'off' | 'dangerous'\n\nexport interface ShellSecurityConfig {\n sandboxMode: BashSandboxMode\n approvalMode: BashApprovalPolicy\n sandboxImage: string\n envAllowlist: readonly string[]\n}\n\nexport const DEFAULT_BASH_SANDBOX_IMAGE = 'debian:bookworm-slim'\n\nexport const SAFE_SHELL_ENV_KEYS = [\n 'PATH',\n 'HOME',\n 'LANG',\n 'LANGUAGE',\n 'LC_ALL',\n 'LC_CTYPE',\n 'TERM',\n 'SHELL',\n 'TMPDIR',\n 'TZ',\n] as const\n\nconst ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/\nconst RESERVED_OBJECT_KEYS = new Set(['__proto__', 'prototype', 'constructor'])\nconst DOCKER_IMAGE_RE = /^[A-Za-z0-9][A-Za-z0-9._/:@-]*$/\nconst DOCKER_CLIENT_CONTROL_ENV = new Set([\n 'DOCKER_HOST',\n 'DOCKER_CONTEXT',\n 'DOCKER_CONFIG',\n 'DOCKER_CERT_PATH',\n 'DOCKER_TLS_VERIFY',\n 'DOCKER_API_VERSION',\n 'XDG_CONFIG_HOME',\n 'XDG_RUNTIME_DIR',\n])\nconst SANDBOX_CONTROL_ENV = new Set(['BASH_ENV'])\n\nconst SENSITIVE_HOME_PATH_RE =\n /(?:~(?:[A-Za-z0-9_-]+)?|\\$(?:HOME|\\{HOME\\})|\\/(?:home|Users)\\/[^\\s'\"`;|&/]+|\\/root)\\/\\.(?:ssh|aws|gnupg)(?:\\/[^\\s'\"`;|&]*)?/gi\nconst SENSITIVE_FILE_RE =\n /(?:\\bauth\\.json\\b|\\bbazilion\\.db\\b|(?:^|[\\s/'\"=])\\.env(?:\\.[A-Za-z0-9_-]+)?(?=$|[\\s/'\"`;|&])|\\/etc\\/(?:shadow|sudoers)(?:\\.d\\/[^\\s'\"`;|&]+)?)/gim\nconst SENSITIVE_ENV_RE =\n /\\b(?:OPENAI_CODEX_OAUTH|AWS_ACCESS_KEY_ID|AWS_PROFILE|GOOGLE_APPLICATION_CREDENTIALS|BAZILION_TOKEN|(?:[A-Z][A-Z0-9_]*_)?(?:API_KEY|ACCESS_TOKEN|AUTH_TOKEN|OAUTH_TOKEN|CLIENT_SECRET|SECRET_ACCESS_KEY|PRIVATE_KEY))\\b/g\n\nconst REMOTE_PIPE_RE =\n /(?:^|[;&|(\\n])\\s*(?:command\\s+)?(?:curl|wget)\\b[^;&\\n]*(?:\\|\\s*(?:(?:sudo|env)\\s+)*(?:[^\\s;&|()]+\\/)?(?:sh|bash|dash|zsh|fish|node|python(?:3)?|perl|ruby)\\b)/gim\n\nconst COMMAND_BOUNDARY_RE = /[;&|)\\n]/\n\nexport class ShellSecurityConfigError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ShellSecurityConfigError'\n }\n}\n\nfunction isValidEnvName(name: string): boolean {\n return ENV_NAME_RE.test(name) && !RESERVED_OBJECT_KEYS.has(name)\n}\n\nfunction addRisk(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n input: {\n code: CommandRiskCode\n severity?: CommandRiskSeverity\n message: string\n start: number\n end: number\n },\n): void {\n const start = Math.max(0, input.start)\n const end = Math.min(command.length, Math.max(start, input.end))\n const key = `${input.code}:${start}:${end}`\n if (seen.has(key)) return\n seen.add(key)\n risks.push({\n code: input.code,\n severity: input.severity ?? 'danger',\n message: input.message,\n matchedText: command.slice(start, end),\n span: { start, end },\n })\n}\n\nfunction addRegexRisks(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n pattern: RegExp,\n code: CommandRiskCode,\n message: string,\n): void {\n pattern.lastIndex = 0\n for (const match of command.matchAll(pattern)) {\n if (match.index === undefined) continue\n let start = match.index\n let matchedText = match[0]\n\n // Some patterns consume a shell delimiter to express a command boundary.\n // Keep the reported span focused on the risky command itself.\n const leading = matchedText.match(/^[;&|(\\n\\s]+/)\n if (leading) {\n start += leading[0].length\n matchedText = matchedText.slice(leading[0].length)\n }\n\n addRisk(command, risks, seen, {\n code,\n message,\n start,\n end: start + matchedText.length,\n })\n }\n}\n\nfunction shellTokens(fragment: string): string[] {\n return Array.from(fragment.matchAll(/\"(?:\\\\.|[^\"\\\\])*\"|'[^']*'|[^\\s]+/g), (match) =>\n match[0].replace(/^(?:\"|')|(?:\"|')$/g, ''),\n )\n}\n\nfunction isBroadFilesystemTarget(rawTarget: string): boolean {\n const target = rawTarget.replace(/[;,]+$/, '')\n // An absolute target can point outside the Team regardless of its top-level\n // directory (/mnt, /srv, /data, custom mounts, ...). Relative project-local\n // targets such as ./dist remain intentionally below the approval threshold.\n if (target.startsWith('/')) return true\n return /^(?:\\.|\\.\\/\\*+|\\*+|~(?:\\/.*)?|\\$(?:HOME|\\{HOME\\})(?:\\/.*)?|\\.\\.(?:\\/.*)?)$/.test(target)\n}\n\nfunction classifyBroadFilesystemOperations(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n): void {\n const commandRe = /(?:^|[;&|(\\n])\\s*(?:command\\s+)?(rm|find|chmod|chown)\\b[^;&|)\\n]*/gim\n for (const match of command.matchAll(commandRe)) {\n if (match.index === undefined || !match[1]) continue\n const nameOffset = match[0].toLowerCase().indexOf(match[1].toLowerCase())\n const start = match.index + nameOffset\n const text = command.slice(start, match.index + match[0].length).trimEnd()\n const tokens = shellTokens(text)\n const name = tokens[0]?.toLowerCase()\n const args = tokens.slice(1)\n let risky = false\n\n if (name === 'rm') {\n const recursive = args.some(\n (arg) => arg === '--recursive' || (/^-[^-]+$/.test(arg) && /[rR]/.test(arg)),\n )\n risky = recursive && args.some((arg) => !arg.startsWith('-') && isBroadFilesystemTarget(arg))\n } else if (name === 'find') {\n const target = args.find((arg) => !arg.startsWith('-'))\n risky = args.includes('-delete') && target !== undefined && isBroadFilesystemTarget(target)\n } else if (name === 'chmod' || name === 'chown') {\n const recursive = args.some(\n (arg) => arg === '--recursive' || (/^-[^-]+$/.test(arg) && /R/.test(arg)),\n )\n risky = recursive && args.some((arg) => !arg.startsWith('-') && isBroadFilesystemTarget(arg))\n }\n\n if (!risky) continue\n addRisk(command, risks, seen, {\n code: 'broad-destructive-operation',\n message: 'Recursively changes or deletes a broad filesystem target.',\n start,\n end: start + text.length,\n })\n }\n}\n\nfunction isLocalHostname(rawHostname: string): boolean {\n const hostname = rawHostname\n .trim()\n .replace(/^.*@/, '')\n .replace(/^\\[|\\]$/g, '')\n .replace(/\\.$/, '')\n .toLowerCase()\n return (\n hostname === 'localhost' ||\n hostname.endsWith('.localhost') ||\n hostname === '::1' ||\n hostname === '0.0.0.0' ||\n /^127(?:\\.\\d{1,3}){3}$/.test(hostname)\n )\n}\n\nfunction hasExternalHttpDestination(segment: string): boolean {\n const urls = Array.from(segment.matchAll(/https?:\\/\\/[^\\s'\"`]+/gi), (match) => match[0])\n if (urls.length === 0) return true\n return urls.some((rawUrl) => {\n try {\n return !isLocalHostname(new URL(rawUrl).hostname)\n } catch {\n return true\n }\n })\n}\n\nfunction remoteTransferHosts(segment: string): string[] {\n const hosts: string[] = []\n const uriRe = /(?:sftp|ssh|rsync):\\/\\/(?:[^@\\s/]+@)?(\\[[^\\]]+\\]|[^\\s/:]+)(?::\\d+)?/gi\n for (const match of segment.matchAll(uriRe)) {\n if (match[1]) hosts.push(match[1])\n }\n\n const scpRe = /(?:^|\\s)(?:[^@\\s:]+@)?(\\[[^\\]]+\\]|[A-Za-z0-9._-]+):[^\\s]+/g\n for (const match of segment.matchAll(scpRe)) {\n if (match[1]) hosts.push(match[1])\n }\n return hosts\n}\n\nfunction invocationSegment(\n command: string,\n match: RegExpMatchArray,\n commandName: string,\n): {\n start: number\n text: string\n} | null {\n if (match.index === undefined) return null\n const nameOffset = match[0].toLowerCase().indexOf(commandName.toLowerCase())\n if (nameOffset < 0) return null\n const start = match.index + nameOffset\n const boundaryOffset = command.slice(start).search(COMMAND_BOUNDARY_RE)\n const end = boundaryOffset < 0 ? command.length : start + boundaryOffset\n return { start, text: command.slice(start, end).trimEnd() }\n}\n\nfunction classifyOutboundExfiltration(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n): void {\n const invocationRe = /(?:^|[;&|(\\n])\\s*(?:command\\s+)?(curl|wget|scp|rsync|nc|netcat|sftp)\\b/gim\n for (const match of command.matchAll(invocationRe)) {\n const name = match[1]?.toLowerCase()\n if (!name) continue\n const segment = invocationSegment(command, match, name)\n if (!segment) continue\n let risky = false\n\n if (name === 'curl') {\n const uploads =\n /(?:^|\\s)(?:-X\\s*POST\\b|-[dFT](?:\\s|[^\\s]))/.test(segment.text) ||\n /(?:^|\\s)(?:--request(?:=|\\s+)POST\\b|--data(?:-[a-z-]+)?(?:=|\\s)|--form(?:-string)?(?:=|\\s)|--upload-file(?:=|\\s))/i.test(\n segment.text,\n )\n risky = uploads && hasExternalHttpDestination(segment.text)\n } else if (name === 'wget') {\n const uploads =\n /(?:^|\\s)(?:--method(?:=|\\s+)POST\\b|--post-data(?:=|\\s)|--post-file(?:=|\\s)|--body-data(?:=|\\s)|--body-file(?:=|\\s))/i.test(\n segment.text,\n )\n risky = uploads && hasExternalHttpDestination(segment.text)\n } else if (name === 'scp' || name === 'rsync') {\n const hosts = remoteTransferHosts(segment.text)\n risky =\n hosts.some((host) => !isLocalHostname(host)) ||\n (hosts.length === 0 && /\\$/.test(segment.text))\n } else if (name === 'sftp') {\n const hosts = remoteTransferHosts(segment.text)\n if (hosts.length > 0) {\n risky = hosts.some((host) => !isLocalHostname(host))\n } else {\n const destination = shellTokens(segment.text).at(-1)\n risky = destination !== undefined && destination !== name && !isLocalHostname(destination)\n }\n } else {\n const tokens = shellTokens(segment.text)\n const host = tokens.find(\n (token, index) =>\n index > 0 &&\n !token.startsWith('-') &&\n !/^\\d+$/.test(token) &&\n !['tcp', 'udp'].includes(token.toLowerCase()),\n )\n risky = host !== undefined && !isLocalHostname(host)\n }\n\n if (!risky) continue\n addRisk(command, risks, seen, {\n code: 'outbound-exfiltration',\n message: 'Uploads data or opens a transfer connection to a non-local destination.',\n start: segment.start,\n end: segment.start + segment.text.length,\n })\n }\n}\n\nfunction classifyPrivilegeAndCredentialAccess(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n): void {\n const patterns = [\n /(?:^|[;&|(\\n])\\s*(?:sudo|su|doas|pkexec)\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*security\\s+(?:find-(?:generic|internet)-password|dump-keychain)\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*gcloud\\s+auth\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*aws\\s+(?:configure|sso\\s+login)\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*git\\s+credential(?:-[A-Za-z0-9_-]+|\\s+(?:fill|approve|reject))?\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*(?:docker|npm|gh)\\s+(?:login|auth\\s+token)\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*(?:pass\\s+(?:show|ls)|secret-tool\\s+lookup|ssh-add\\s+-[Ll])\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*(?:env|printenv|set|export\\s+-p)\\s*(?=$|[;&|)\\n])/gim,\n ]\n\n for (const pattern of patterns) {\n addRegexRisks(\n command,\n risks,\n seen,\n pattern,\n 'privilege-or-credential-access',\n 'Requests elevated privileges or accesses a credential store.',\n )\n }\n}\n\n/**\n * Conservatively identify shell commands that should hit the dangerous-command\n * approval tripwire. This is a classifier, not a shell parser or sandbox.\n */\nexport function classifyBashCommand(command: string): CommandRisk[] {\n const risks: CommandRisk[] = []\n const seen = new Set<string>()\n\n addRegexRisks(\n command,\n risks,\n seen,\n SENSITIVE_HOME_PATH_RE,\n 'sensitive-path-read',\n 'Accesses a path that commonly contains credentials.',\n )\n addRegexRisks(\n command,\n risks,\n seen,\n SENSITIVE_FILE_RE,\n 'sensitive-path-read',\n 'Accesses a sensitive environment, authentication, or database file.',\n )\n addRegexRisks(\n command,\n risks,\n seen,\n SENSITIVE_ENV_RE,\n 'sensitive-path-read',\n 'References an environment variable name commonly used for credentials.',\n )\n classifyBroadFilesystemOperations(command, risks, seen)\n addRegexRisks(\n command,\n risks,\n seen,\n REMOTE_PIPE_RE,\n 'remote-pipe-execution',\n 'Pipes downloaded content directly into an interpreter.',\n )\n classifyOutboundExfiltration(command, risks, seen)\n classifyPrivilegeAndCredentialAccess(command, risks, seen)\n\n return risks.sort((a, b) => a.span.start - b.span.start || a.code.localeCompare(b.code))\n}\n\nfunction parseEnvAllowlist(raw: string | undefined): string[] {\n if (raw === undefined || raw.trim() === '') return []\n const values = raw.split(',').map((value) => value.trim())\n if (values.some((value) => value === '')) {\n throw new ShellSecurityConfigError(\n 'BAZILION_BASH_SANDBOX_ENV_ALLOWLIST contains an empty environment variable name',\n )\n }\n\n const unique: string[] = []\n const seen = new Set<string>()\n for (const value of values) {\n if (!isValidEnvName(value)) {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST contains invalid environment variable name: ${value}`,\n )\n }\n if (DOCKER_CLIENT_CONTROL_ENV.has(value)) {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST cannot include Docker client control variable: ${value}`,\n )\n }\n if (SANDBOX_CONTROL_ENV.has(value)) {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST cannot include shell startup control variable: ${value}`,\n )\n }\n if (!seen.has(value)) {\n seen.add(value)\n unique.push(value)\n }\n }\n return unique\n}\n\n/** Resolve shell sandbox settings without silently accepting unsafe typos. */\nexport function resolveShellSecurityConfig(\n env: Readonly<NodeJS.ProcessEnv> = process.env,\n): ShellSecurityConfig {\n const rawMode = env.BAZILION_BASH_SANDBOX?.trim() ?? ''\n if (rawMode !== '' && rawMode !== 'off' && rawMode !== 'docker') {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX must be \"off\" or \"docker\", received: ${rawMode}`,\n )\n }\n\n const rawApproval = env.BAZILION_BASH_APPROVAL?.trim() ?? ''\n if (rawApproval !== '' && rawApproval !== 'off' && rawApproval !== 'dangerous') {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_APPROVAL must be \"off\" or \"dangerous\", received: ${rawApproval}`,\n )\n }\n\n const rawImage = env.BAZILION_BASH_SANDBOX_IMAGE?.trim() ?? ''\n if (rawImage !== '' && !DOCKER_IMAGE_RE.test(rawImage)) {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX_IMAGE is not a valid Docker image reference: ${rawImage}`,\n )\n }\n\n return {\n sandboxMode: rawMode === 'docker' ? 'docker' : 'off',\n approvalMode: rawApproval === 'dangerous' ? 'dangerous' : 'off',\n sandboxImage: rawImage || DEFAULT_BASH_SANDBOX_IMAGE,\n envAllowlist: parseEnvAllowlist(env.BAZILION_BASH_SANDBOX_ENV_ALLOWLIST),\n }\n}\n\nfunction assertEnvName(name: string): void {\n if (!isValidEnvName(name)) {\n throw new ShellSecurityConfigError(`invalid shell environment variable name: ${name}`)\n }\n if (DOCKER_CLIENT_CONTROL_ENV.has(name) || SANDBOX_CONTROL_ENV.has(name)) {\n throw new ShellSecurityConfigError(`unsafe shell environment variable name: ${name}`)\n }\n}\n\n/**\n * Copy only fixed shell basics and operator-approved keys into a fresh object.\n * Explicit keys are intentionally authoritative: an operator may opt a secret\n * into the sandbox, but it is never inherited accidentally.\n */\nexport function buildScrubbedShellEnv(\n env: Readonly<NodeJS.ProcessEnv>,\n allowedKeys: readonly string[] = [],\n): NodeJS.ProcessEnv {\n const scrubbed: NodeJS.ProcessEnv = {}\n const keys = new Set<string>(SAFE_SHELL_ENV_KEYS)\n for (const key of allowedKeys) {\n assertEnvName(key)\n keys.add(key)\n }\n\n for (const key of keys) {\n const value = env[key]\n if (value !== undefined) scrubbed[key] = value\n }\n return scrubbed\n}\n","import { spawn } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport { closeSync, constants, mkdtempSync, openSync, rmSync, writeFileSync } from 'node:fs'\nimport { access, lstat, realpath, stat } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { delimiter, isAbsolute, join, relative, sep } from 'node:path'\nimport type { BashOperations } from '@earendil-works/pi-coding-agent'\n\nconst CONTAINER_WORKDIR = '/workspace'\nconst MAX_TIMEOUT_MS = 2_147_483_647\nconst MAX_STDERR_CHARS = 16 * 1024\nconst CLEANUP_TIMEOUT_MS = 650\nconst CLEANUP_RETRY_DELAY_MS = 100\nconst CLEANUP_ATTEMPTS = 3\nconst DOCKER_CONTEXT_TIMEOUT_MS = 2_000\nconst DOCKER_CREATE_TIMEOUT_MS = 5_000\nconst DOCKER_PROBE_START_TIMEOUT_MS = 2_000\nconst ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/\nconst CONTAINER_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]+$/\nconst DOCKER_CLIENT_ENV_KEYS = [\n 'PATH',\n 'HOME',\n 'DOCKER_HOST',\n 'DOCKER_CONTEXT',\n 'DOCKER_CONFIG',\n 'DOCKER_CERT_PATH',\n 'DOCKER_TLS_VERIFY',\n 'DOCKER_API_VERSION',\n 'XDG_CONFIG_HOME',\n 'XDG_RUNTIME_DIR',\n] as const\nconst DOCKER_CLIENT_CONTROL_ENV = new Set<string>(DOCKER_CLIENT_ENV_KEYS)\nconst SANDBOX_CONTROL_ENV = new Set(['BASH_ENV'])\nconst CONTAINER_LITERAL_ENV_KEYS = new Set([\n 'PATH',\n 'HOME',\n 'LANG',\n 'LANGUAGE',\n 'LC_ALL',\n 'LC_CTYPE',\n 'TERM',\n 'SHELL',\n 'TMPDIR',\n 'TZ',\n])\n\nexport interface DockerBashOptions {\n image: string\n env: NodeJS.ProcessEnv\n dockerPath?: string\n /** Host environment used to resolve the active Docker context. Defaults to process.env. */\n hostEnv?: Readonly<NodeJS.ProcessEnv>\n /** Optional, narrowly scoped host directories exposed read-only. */\n readOnlyMounts?: readonly DockerReadOnlyMount[]\n}\n\nexport interface DockerReadOnlyMount {\n source: string\n target: string\n /** Canonical host root the resolved source must remain within. */\n sourceRoot?: string\n}\n\nexport interface ProtectedContainerEnvironment {\n PATH: string\n HOME: '/tmp'\n LANG: 'C.UTF-8'\n LC_ALL: 'C.UTF-8'\n SHELL: '/bin/bash'\n TMPDIR: '/tmp'\n}\n\nexport interface ProtectedDockerMount {\n source: string\n target: string\n sourceRoot: string\n}\n\nexport interface ProtectedDockerExecutableIdentity {\n device: string\n inode: string\n mode: string\n size: string\n modifiedTimeNs: string\n changedTimeNs: string\n}\n\n/** Fully inspected, serializable Docker facts carried by a protected worker. */\nexport interface ProtectedDockerRuntime {\n dockerPath: string\n executableIdentity: ProtectedDockerExecutableIdentity\n endpoint: string\n image: string\n imageId: string\n uid: number\n gid: number\n workspace: ProtectedDockerMount\n readOnlyMounts: ProtectedDockerMount[]\n containerEnv: ProtectedContainerEnvironment\n}\n\nexport interface ProtectedDockerPreflightInput {\n image: string\n workspaceDir: string\n /** Canonical boundary the registered Team workspace must remain within. */\n workspaceRoot?: string\n readOnlyMounts?: readonly Required<DockerReadOnlyMount>[]\n /** Absolute path or executable name resolved once in the daemon. */\n dockerPath?: string\n /** Daemon environment used only during preflight discovery. */\n hostEnv?: Readonly<NodeJS.ProcessEnv>\n}\n\nexport type ProtectedDockerReadiness =\n | { ready: true; image: string }\n | {\n ready: false\n image: string\n reason:\n | 'unsupported platform'\n | 'Docker executable is unavailable'\n | 'Docker must use a local Unix socket'\n | 'Docker socket is unavailable'\n | 'Docker image is unavailable'\n | 'Docker image declares writable volumes'\n | 'Docker preflight failed'\n }\n\ninterface ProtectedDockerEngineRuntime {\n dockerPath: string\n executableIdentity: ProtectedDockerExecutableIdentity\n endpoint: string\n image: string\n imageId: string\n uid: number\n gid: number\n}\n\nconst PROTECTED_CONTAINER_ENV: ProtectedContainerEnvironment = {\n PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',\n HOME: '/tmp',\n LANG: 'C.UTF-8',\n LC_ALL: 'C.UTF-8',\n SHELL: '/bin/bash',\n TMPDIR: '/tmp',\n}\n\nexport interface DockerRunSpecInput extends DockerBashOptions {\n command: string\n mountSource: string\n containerName: string\n uid: number\n gid: number\n /** Immutable local image id resolved after policy inspection. */\n resolvedImage?: string\n}\n\nexport interface DockerRunSpec {\n executable: string\n args: string[]\n containerName: string\n image: string\n mountSource: string\n /** Minimal environment for the Docker CLI; never the worker's merged env wholesale. */\n processEnv: NodeJS.ProcessEnv\n /** Docker env-file contents passed through an inherited anonymous descriptor. */\n envFileContent?: string\n}\n\n/** Build the complete, argument-array Docker invocation without touching the filesystem. */\nexport function buildDockerRunSpec(input: DockerRunSpecInput): DockerRunSpec {\n const executable = input.dockerPath ?? 'docker'\n validateDockerPath(executable)\n validateImage(input.image)\n if (input.resolvedImage !== undefined) validateImage(input.resolvedImage)\n validateContainerName(input.containerName)\n validateMountArgument(input.mountSource)\n const readOnlyMounts = input.readOnlyMounts ?? []\n validateReadOnlyMounts(readOnlyMounts, input.mountSource)\n validateHostIdentity(input.uid, input.gid)\n\n if (input.command.includes('\\0')) {\n throw new Error('Docker sandbox command cannot contain a NUL byte')\n }\n\n const args = [\n 'run',\n '--rm',\n '--pull',\n 'never',\n '--name',\n input.containerName,\n '--network',\n 'none',\n '--read-only',\n '--tmpfs',\n '/tmp:rw,nosuid,nodev,size=64m,mode=1777',\n '--user',\n `${input.uid}:${input.gid}`,\n '--cap-drop',\n 'ALL',\n '--security-opt',\n 'no-new-privileges=true',\n '--pids-limit',\n '256',\n '--entrypoint',\n '/bin/bash',\n '--workdir',\n CONTAINER_WORKDIR,\n // A bind mount is read/write unless `readonly` is present. No other mount is added.\n '--mount',\n `type=bind,source=${input.mountSource},target=${CONTAINER_WORKDIR},bind-recursive=disabled`,\n ]\n\n for (const mount of readOnlyMounts) {\n args.push(\n '--mount',\n `type=bind,source=${mount.source},target=${mount.target},readonly,bind-recursive=disabled`,\n )\n }\n\n const processEnv = buildDockerClientEnv(input.hostEnv ?? process.env)\n const envFileLines: string[] = []\n const containerEnvNames: string[] = []\n for (const [name, value] of sortedEnvironment(input.env)) {\n containerEnvNames.push(name)\n if (CONTAINER_LITERAL_ENV_KEYS.has(name)) {\n // These are shell mechanics, not operator-approved secret values. They\n // may differ from the host values needed by the Docker client itself.\n args.push('--env', `${name}=${value}`)\n } else {\n // Keep operator-approved values out of process argv and out of the\n // Docker client's own environment. The CLI reads these lines from its\n // stdin before asking the daemon to create the container.\n envFileLines.push(`${name}=${value}`)\n }\n }\n if (envFileLines.length > 0) args.push('--env-file', '/dev/fd/3')\n // Prevent an image-provided BASH_ENV from executing before the clean-env\n // wrapper. The final command environment below omits it entirely.\n args.push('--env', 'BASH_ENV=')\n\n const cleanAssignments = containerEnvNames.map((name) => `${name}=\"$${name}\"`).join(' ')\n const cleanEnvironmentCommand =\n `exec /usr/bin/env -i${cleanAssignments ? ` ${cleanAssignments}` : ''} ` +\n '/bin/bash --noprofile --norc -c \"$1\"'\n args.push(\n input.resolvedImage ?? input.image,\n '--noprofile',\n '--norc',\n '-c',\n cleanEnvironmentCommand,\n 'bazilion-sandbox',\n input.command,\n )\n\n return {\n executable,\n args,\n containerName: input.containerName,\n image: input.image,\n mountSource: input.mountSource,\n processEnv,\n ...(envFileLines.length > 0 ? { envFileContent: `${envFileLines.join('\\n')}\\n` } : {}),\n }\n}\n\n/**\n * Secret-free readiness projection for health/config surfaces. It verifies the\n * absolute executable, local Unix endpoint, socket access, immutable image id,\n * and image VOLUME policy without returning any host path or endpoint.\n */\nexport async function checkProtectedDockerReadiness(\n input: Pick<ProtectedDockerPreflightInput, 'image' | 'dockerPath' | 'hostEnv'>,\n): Promise<ProtectedDockerReadiness> {\n try {\n const engine = await preflightProtectedDockerEngine(input)\n await proveProtectedImageReadiness(engine)\n return { ready: true, image: input.image }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n let reason: Exclude<ProtectedDockerReadiness, { ready: true }>['reason']\n if (/numeric uid\\/gid|supported only on POSIX/i.test(message)) reason = 'unsupported platform'\n else if (/required \\/bin\\/bash and \\/usr\\/bin\\/env probe/i.test(message)) {\n reason = 'Docker preflight failed'\n } else if (/executable|ENOENT|not found|spawn/i.test(message)) {\n reason = 'Docker executable is unavailable'\n } else if (/local Unix-socket context/i.test(message)) {\n reason = 'Docker must use a local Unix socket'\n } else if (/Unix socket/i.test(message)) reason = 'Docker socket is unavailable'\n else if (/declares writable volumes/i.test(message)) {\n reason = 'Docker image declares writable volumes'\n } else if (\n /image.*unavailable|no such image|unable to find image|manifest unknown/i.test(message)\n ) {\n reason = 'Docker image is unavailable'\n } else reason = 'Docker preflight failed'\n return { ready: false, image: input.image, reason }\n }\n}\n\nasync function proveProtectedImageReadiness(engine: ProtectedDockerEngineRuntime): Promise<void> {\n const scratchDir = mkdtempSync(join(tmpdir(), 'bazilion-docker-readiness-'))\n try {\n const source = await resolveMountSource(scratchDir)\n await proveProtectedContainerCreation(\n engine,\n { source, sourceRoot: source, target: CONTAINER_WORKDIR },\n [],\n )\n } finally {\n rmSync(scratchDir, { recursive: true, force: true })\n }\n}\n\n/** Resolve every protected Docker fact before a worker is spawned. */\nexport async function preflightProtectedDockerRuntime(\n input: ProtectedDockerPreflightInput,\n): Promise<ProtectedDockerRuntime> {\n const engine = await preflightProtectedDockerEngine(input)\n const workspaceSource = await resolveMountSource(input.workspaceDir)\n const workspaceRoot = await resolveRequiredRoot(input.workspaceRoot ?? input.workspaceDir)\n if (!isWithin(workspaceRoot, workspaceSource)) {\n throw new Error('Docker sandbox workspace escaped its configured root')\n }\n const workspace: ProtectedDockerMount = {\n source: workspaceSource,\n target: CONTAINER_WORKDIR,\n sourceRoot: workspaceRoot,\n }\n\n const readOnlyMounts = await Promise.all(\n (input.readOnlyMounts ?? []).map((mount) => prepareProtectedReadOnlyMount(mount)),\n )\n validateReadOnlyMounts(readOnlyMounts, workspace.source)\n\n await proveProtectedContainerCreation(engine, workspace, readOnlyMounts)\n\n return {\n ...engine,\n workspace,\n readOnlyMounts,\n containerEnv: { ...PROTECTED_CONTAINER_ENV },\n }\n}\n\n/**\n * Execute against daemon-preflighted facts. Mutable paths, socket access, tag\n * identity, and VOLUME policy are rechecked immediately before every command.\n */\nexport function createPreparedDockerBashOperations(\n runtime: ProtectedDockerRuntime,\n): BashOperations {\n validatePreparedDockerRuntime(runtime)\n return {\n async exec(command, cwd, { onData, signal, timeout }) {\n const timeoutMs = resolveTimeoutMs(timeout)\n if (signal?.aborted) throw new Error('aborted')\n\n const workspace = await recheckProtectedMount(runtime.workspace)\n const requestedCwd = await resolveMountSource(cwd)\n if (requestedCwd !== workspace.source) {\n throw new Error('Docker sandbox execution workspace no longer matches preflight')\n }\n const readOnlyMounts = await Promise.all(\n runtime.readOnlyMounts.map((mount) => recheckProtectedMount(mount)),\n )\n await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity)\n await assertLocalDockerSocket(runtime.endpoint)\n const dockerClientEnv: NodeJS.ProcessEnv = { DOCKER_HOST: runtime.endpoint }\n const currentImageId = await inspectSandboxImage(\n runtime.dockerPath,\n runtime.image,\n dockerClientEnv,\n )\n if (currentImageId !== runtime.imageId) {\n throw new Error('Docker sandbox image no longer matches its preflighted immutable id')\n }\n await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity)\n if (signal?.aborted) throw new Error('aborted')\n\n const containerName = `bazilion-bash-${process.pid}-${randomUUID().replaceAll('-', '')}`\n const spec = buildDockerRunSpec({\n image: runtime.image,\n resolvedImage: runtime.imageId,\n env: { ...runtime.containerEnv },\n dockerPath: runtime.dockerPath,\n hostEnv: dockerClientEnv,\n command,\n mountSource: workspace.source,\n readOnlyMounts,\n containerName,\n uid: runtime.uid,\n gid: runtime.gid,\n })\n return executeDockerRun(spec, { onData, signal, timeout, timeoutMs })\n },\n }\n}\n\nasync function preflightProtectedDockerEngine(\n input: Pick<ProtectedDockerPreflightInput, 'image' | 'dockerPath' | 'hostEnv'>,\n): Promise<ProtectedDockerEngineRuntime> {\n validateImage(input.image)\n const { uid, gid } = hostIdentity()\n const hostEnv = input.hostEnv ?? process.env\n const dockerPath = await resolveAbsoluteDockerPath(input.dockerPath ?? 'docker', hostEnv)\n const executableIdentity = await inspectDockerExecutableIdentity(dockerPath)\n const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv)\n const endpoint = dockerClientEnv.DOCKER_HOST\n if (!endpoint) throw new Error('Docker sandbox local Unix socket is unavailable')\n await assertLocalDockerSocket(endpoint)\n const imageId = await inspectSandboxImage(dockerPath, input.image, dockerClientEnv)\n await assertDockerExecutableIdentity(dockerPath, executableIdentity)\n return { dockerPath, executableIdentity, endpoint, image: input.image, imageId, uid, gid }\n}\n\nasync function proveProtectedContainerCreation(\n engine: ProtectedDockerEngineRuntime,\n workspace: ProtectedDockerMount,\n readOnlyMounts: ProtectedDockerMount[],\n): Promise<void> {\n const containerName = `bazilion-preflight-${process.pid}-${randomUUID().replaceAll('-', '')}`\n const dockerClientEnv: NodeJS.ProcessEnv = { DOCKER_HOST: engine.endpoint }\n const spec = buildDockerRunSpec({\n image: engine.image,\n resolvedImage: engine.imageId,\n env: { ...PROTECTED_CONTAINER_ENV },\n dockerPath: engine.dockerPath,\n hostEnv: dockerClientEnv,\n command: 'true',\n mountSource: workspace.source,\n readOnlyMounts,\n containerName,\n uid: engine.uid,\n gid: engine.gid,\n })\n\n await assertDockerExecutableIdentity(engine.dockerPath, engine.executableIdentity)\n let preflightError: unknown\n try {\n await runProtectedContainerCreate(spec)\n // `docker container create` validates the pinned image and mount/runtime\n // configuration, but it does not resolve or execute the image entrypoint.\n // Start the exact container as well so readiness proves both required\n // executables (`/bin/bash` and `/usr/bin/env`) before a provider can be\n // prompted. The known `true` command still runs with the same no-network,\n // read-only, clean-environment policy used by protected model commands.\n await assertDockerExecutableIdentity(engine.dockerPath, engine.executableIdentity)\n await runProtectedContainerProbe(spec)\n } catch (error) {\n preflightError = error\n }\n\n try {\n await assertDockerExecutableIdentity(engine.dockerPath, engine.executableIdentity)\n await removePreflightContainer(engine.dockerPath, containerName, dockerClientEnv)\n await assertDockerExecutableIdentity(engine.dockerPath, engine.executableIdentity)\n } catch (cleanupError) {\n if (preflightError) {\n throw new Error(\n 'Docker sandbox preflight failed and probe container cleanup could not be confirmed',\n { cause: cleanupError },\n )\n }\n throw cleanupError\n }\n\n if (preflightError) throw preflightError\n}\n\nfunction runProtectedContainerCreate(spec: DockerRunSpec): Promise<void> {\n let envFileFd: number | undefined\n try {\n if (spec.envFileContent !== undefined) {\n envFileFd = openAnonymousEnvFile(spec.envFileContent)\n }\n } catch (error) {\n return Promise.reject(\n new Error(\n `Docker sandbox could not prepare its preflight environment: ${error instanceof Error ? error.message : String(error)}`,\n ),\n )\n }\n\n return new Promise((resolve, reject) => {\n let child: ReturnType<typeof spawn>\n try {\n child = spawn(spec.executable, ['container', 'create', ...spec.args.slice(1)], {\n stdio: ['ignore', 'ignore', 'pipe', envFileFd ?? 'ignore'],\n windowsHide: true,\n env: spec.processEnv,\n })\n } catch (error) {\n if (envFileFd !== undefined) closeSync(envFileFd)\n reject(error instanceof Error ? formatSpawnError(spec.executable, error) : error)\n return\n }\n if (envFileFd !== undefined) closeSync(envFileFd)\n\n let settled = false\n let stderr = ''\n let timeoutHandle: NodeJS.Timeout | undefined\n let killSettleHandle: NodeJS.Timeout | undefined\n let timedOut = false\n const finish = (error?: Error) => {\n if (settled) return\n settled = true\n if (timeoutHandle) clearTimeout(timeoutHandle)\n if (killSettleHandle) clearTimeout(killSettleHandle)\n if (error) reject(error)\n else resolve()\n }\n\n const timeoutError = () =>\n new Error(\n `Docker sandbox could not create its protected preflight container within ${DOCKER_CREATE_TIMEOUT_MS}ms`,\n )\n\n child.stderr?.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-MAX_STDERR_CHARS)\n })\n child.once('error', (error) => finish(formatSpawnError(spec.executable, error)))\n child.once('close', (code) => {\n if (timedOut) finish(timeoutError())\n else if (code === 0) finish()\n else finish(formatDockerCreateError(spec, stderr, code))\n })\n timeoutHandle = setTimeout(() => {\n timedOut = true\n child.kill('SIGKILL')\n // SIGKILL should close promptly. Keep a separate bound so a pathological\n // child cannot prevent the exact-name cleanup phase from running.\n killSettleHandle = setTimeout(() => finish(timeoutError()), CLEANUP_TIMEOUT_MS)\n }, DOCKER_CREATE_TIMEOUT_MS)\n })\n}\n\nfunction runProtectedContainerProbe(spec: DockerRunSpec): Promise<void> {\n return new Promise((resolve, reject) => {\n let child: ReturnType<typeof spawn>\n try {\n child = spawn(spec.executable, ['container', 'start', '--attach', spec.containerName], {\n stdio: ['ignore', 'ignore', 'pipe'],\n windowsHide: true,\n env: spec.processEnv,\n })\n } catch (error) {\n reject(error instanceof Error ? formatSpawnError(spec.executable, error) : error)\n return\n }\n\n let settled = false\n let stderr = ''\n let timeoutHandle: NodeJS.Timeout | undefined\n let killSettleHandle: NodeJS.Timeout | undefined\n let timedOut = false\n const finish = (error?: Error) => {\n if (settled) return\n settled = true\n if (timeoutHandle) clearTimeout(timeoutHandle)\n if (killSettleHandle) clearTimeout(killSettleHandle)\n if (error) reject(error)\n else resolve()\n }\n\n const timeoutError = () =>\n new Error(\n `Docker sandbox could not start its protected preflight container within ${DOCKER_PROBE_START_TIMEOUT_MS}ms`,\n )\n\n child.stderr?.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-MAX_STDERR_CHARS)\n })\n child.once('error', (error) => finish(formatSpawnError(spec.executable, error)))\n child.once('close', (code) => {\n if (timedOut) finish(timeoutError())\n else if (code === 0) finish()\n else finish(formatDockerProbeError(spec, stderr, code))\n })\n timeoutHandle = setTimeout(() => {\n timedOut = true\n child.kill('SIGKILL')\n // Bound the handoff to exact-name force cleanup even if the Docker CLI\n // itself does not report close promptly after termination.\n killSettleHandle = setTimeout(() => finish(timeoutError()), CLEANUP_TIMEOUT_MS)\n }, DOCKER_PROBE_START_TIMEOUT_MS)\n })\n}\n\nasync function removePreflightContainer(\n dockerPath: string,\n containerName: string,\n processEnv: NodeJS.ProcessEnv,\n): Promise<void> {\n const args = ['container', 'rm', '--force', containerName]\n let last: CleanupResult | undefined\n for (let attempt = 0; attempt < CLEANUP_ATTEMPTS; attempt += 1) {\n last = await runCleanupCommand(dockerPath, args, processEnv)\n if (last.ok) return\n if (attempt < CLEANUP_ATTEMPTS - 1) {\n await new Promise<void>((resolve) => setTimeout(resolve, CLEANUP_RETRY_DELAY_MS))\n }\n }\n // Repeated exact-name misses across the retry window confirm that a failed\n // or killed create did not materialize its container late.\n if (last?.missing) return\n throw new Error('Docker sandbox could not confirm cleanup of its preflight container')\n}\n\nasync function prepareProtectedReadOnlyMount(\n mount: Required<DockerReadOnlyMount>,\n): Promise<ProtectedDockerMount> {\n const sourceInfo = await lstat(mount.source)\n if (sourceInfo.isSymbolicLink() || !sourceInfo.isDirectory()) {\n throw new Error(`Docker sandbox read-only mount must be a real directory: ${mount.source}`)\n }\n const sourceRoot = await resolveRequiredRoot(mount.sourceRoot)\n const source = await resolveMountSource(mount.source)\n if (!isWithin(sourceRoot, source)) {\n throw new Error(`Docker sandbox read-only mount escaped its configured root: ${mount.source}`)\n }\n return { source, target: mount.target, sourceRoot }\n}\n\nasync function recheckProtectedMount(mount: ProtectedDockerMount): Promise<ProtectedDockerMount> {\n const sourceInfo = await lstat(mount.source)\n if (sourceInfo.isSymbolicLink() || !sourceInfo.isDirectory()) {\n throw new Error('Docker sandbox mount no longer matches preflight')\n }\n const sourceRoot = await resolveRequiredRoot(mount.sourceRoot)\n const source = await resolveMountSource(mount.source)\n if (sourceRoot !== mount.sourceRoot || source !== mount.source || !isWithin(sourceRoot, source)) {\n throw new Error('Docker sandbox mount no longer matches preflight')\n }\n return { ...mount, source, sourceRoot }\n}\n\nasync function resolveRequiredRoot(root: string): Promise<string> {\n if (!isAbsolute(root)) throw new Error('Docker sandbox mount root must be absolute')\n return resolveMountSource(root)\n}\n\nasync function resolveAbsoluteDockerPath(\n requested: string,\n hostEnv: Readonly<NodeJS.ProcessEnv>,\n): Promise<string> {\n validateDockerPath(requested)\n const candidates = isAbsolute(requested)\n ? [requested]\n : (hostEnv.PATH ?? '')\n .split(delimiter)\n .filter(Boolean)\n .map((dir) => join(dir, requested))\n for (const candidate of candidates) {\n try {\n await access(candidate, constants.X_OK)\n const info = await stat(candidate)\n if (!info.isFile()) continue\n const canonical = await realpath(candidate)\n if (!isAbsolute(canonical)) continue\n return canonical\n } catch {\n // Keep looking through the daemon's PATH; protected workers never inherit it.\n }\n }\n throw new Error(`Docker sandbox executable is unavailable: ${requested}`)\n}\n\nasync function inspectDockerExecutableIdentity(\n dockerPath: string,\n): Promise<ProtectedDockerExecutableIdentity> {\n try {\n await access(dockerPath, constants.X_OK)\n const info = await stat(dockerPath, { bigint: true })\n if (!info.isFile()) throw new Error('not a regular file')\n return {\n device: info.dev.toString(),\n inode: info.ino.toString(),\n mode: info.mode.toString(),\n size: info.size.toString(),\n modifiedTimeNs: info.mtimeNs.toString(),\n changedTimeNs: info.ctimeNs.toString(),\n }\n } catch {\n throw new Error(`Docker sandbox executable is unavailable: ${dockerPath}`)\n }\n}\n\nasync function assertDockerExecutableIdentity(\n dockerPath: string,\n expected: ProtectedDockerExecutableIdentity,\n): Promise<void> {\n let current: ProtectedDockerExecutableIdentity\n try {\n current = await inspectDockerExecutableIdentity(dockerPath)\n } catch {\n throw new Error('Docker sandbox executable no longer matches its preflighted identity')\n }\n if (JSON.stringify(current) !== JSON.stringify(expected)) {\n throw new Error('Docker sandbox executable no longer matches its preflighted identity')\n }\n}\n\nasync function assertLocalDockerSocket(endpoint: string): Promise<void> {\n const prefix = 'unix://'\n if (!endpoint.startsWith(prefix) || !isAbsolute(endpoint.slice(prefix.length))) {\n throw new Error('Docker sandbox requires a local Unix-socket context')\n }\n const socketPath = endpoint.slice(prefix.length)\n try {\n const info = await stat(socketPath)\n if (!info.isSocket()) throw new Error('not a Unix socket')\n await access(socketPath, constants.R_OK | constants.W_OK)\n } catch {\n throw new Error('Docker sandbox local Unix socket is unavailable')\n }\n}\n\nfunction validatePreparedDockerRuntime(runtime: ProtectedDockerRuntime): void {\n if (!isAbsolute(runtime.dockerPath)) {\n throw new Error('protected Docker executable must be absolute')\n }\n validateDockerPath(runtime.dockerPath)\n validateDockerExecutableIdentity(runtime.executableIdentity)\n validateImage(runtime.image)\n validateImage(runtime.imageId)\n validateHostIdentity(runtime.uid, runtime.gid)\n if (!runtime.endpoint.startsWith('unix://')) {\n throw new Error('protected Docker endpoint must be a local Unix socket')\n }\n validateReadOnlyMounts(runtime.readOnlyMounts, runtime.workspace.source)\n const expectedEnvironment = JSON.stringify(PROTECTED_CONTAINER_ENV)\n if (JSON.stringify(runtime.containerEnv) !== expectedEnvironment) {\n throw new Error('protected Docker container environment does not match policy')\n }\n}\n\nfunction validateDockerExecutableIdentity(identity: ProtectedDockerExecutableIdentity): void {\n if (typeof identity !== 'object' || identity === null) {\n throw new Error('protected Docker executable identity is invalid')\n }\n const fields = [\n identity.device,\n identity.inode,\n identity.mode,\n identity.size,\n identity.modifiedTimeNs,\n identity.changedTimeNs,\n ]\n if (fields.some((field) => typeof field !== 'string' || !/^\\d+$/.test(field))) {\n throw new Error('protected Docker executable identity is invalid')\n }\n}\n\n/**\n * Execute each bash command in a fresh, exact-name Docker container.\n *\n * The `env` supplied to `exec` by pi is intentionally ignored. The only values\n * passed into the container are the environment captured in `options.env`.\n */\nexport function createDockerBashOperations(options: DockerBashOptions): BashOperations {\n const image = options.image\n const env = { ...options.env }\n const dockerPath = options.dockerPath ?? 'docker'\n const configuredReadOnlyMounts = options.readOnlyMounts ?? []\n const hostEnv = { ...(options.hostEnv ?? process.env) }\n\n validateDockerPath(dockerPath)\n validateImage(image)\n\n return {\n async exec(command, cwd, { onData, signal, timeout }) {\n const timeoutMs = resolveTimeoutMs(timeout)\n if (signal?.aborted) throw new Error('aborted')\n\n const mountSource = await resolveMountSource(cwd)\n const readOnlyMounts = await Promise.all(\n configuredReadOnlyMounts.map(async (mount) => {\n const source = await resolveMountSource(mount.source)\n if (mount.sourceRoot && !isWithin(mount.sourceRoot, source)) {\n throw new Error(\n `Docker sandbox read-only mount escaped its configured root: ${mount.source}`,\n )\n }\n return { source, target: mount.target }\n }),\n )\n if (signal?.aborted) throw new Error('aborted')\n\n const { uid, gid } = hostIdentity()\n const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv)\n const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv)\n if (signal?.aborted) throw new Error('aborted')\n const containerName = `bazilion-bash-${process.pid}-${randomUUID().replaceAll('-', '')}`\n const spec = buildDockerRunSpec({\n image,\n env,\n dockerPath,\n command,\n mountSource,\n containerName,\n uid,\n gid,\n resolvedImage,\n readOnlyMounts,\n hostEnv: dockerClientEnv,\n })\n\n return executeDockerRun(spec, { onData, signal, timeout, timeoutMs })\n },\n }\n}\n\ninterface DockerExecutionOptions {\n onData: (data: Buffer) => void\n signal?: AbortSignal\n timeout?: number\n timeoutMs?: number\n}\n\nfunction executeDockerRun(\n spec: DockerRunSpec,\n options: DockerExecutionOptions,\n): Promise<{ exitCode: number | null }> {\n let envFileFd: number | undefined\n try {\n if (spec.envFileContent !== undefined) {\n envFileFd = openAnonymousEnvFile(spec.envFileContent)\n }\n } catch (error) {\n return Promise.reject(\n new Error(\n `Docker sandbox could not prepare its environment: ${error instanceof Error ? error.message : String(error)}`,\n ),\n )\n }\n\n return new Promise((resolve, reject) => {\n const child = spawn(spec.executable, spec.args, {\n stdio: ['ignore', 'pipe', 'pipe', envFileFd ?? 'ignore'],\n windowsHide: true,\n env: spec.processEnv,\n })\n if (envFileFd !== undefined) closeSync(envFileFd)\n\n let settled = false\n let termination: 'aborted' | 'timeout' | undefined\n let timeoutHandle: NodeJS.Timeout | undefined\n let stderr = ''\n\n const requestTermination = (reason: 'aborted' | 'timeout') => {\n termination ??= reason\n child.kill('SIGKILL')\n }\n\n const onAbort = () => requestTermination('aborted')\n\n const finish = async (exitCode: number | null, spawnError?: Error) => {\n if (settled) return\n settled = true\n\n if (timeoutHandle) clearTimeout(timeoutHandle)\n options.signal?.removeEventListener('abort', onAbort)\n\n if (!termination && options.signal?.aborted) termination = 'aborted'\n\n if (termination) {\n await removeContainer(spec.executable, spec.containerName, spec.processEnv)\n reject(\n termination === 'aborted'\n ? new Error('aborted')\n : new Error(`timeout:${options.timeout}`),\n )\n return\n }\n\n if (spawnError) {\n reject(formatSpawnError(spec.executable, spawnError))\n return\n }\n\n if (exitCode === 125) {\n await removeContainer(spec.executable, spec.containerName, spec.processEnv)\n reject(formatDockerRunError(spec, stderr))\n return\n }\n\n if (exitCode === null) {\n await removeContainer(spec.executable, spec.containerName, spec.processEnv)\n reject(new Error(`Docker sandbox CLI for container \"${spec.containerName}\" was killed`))\n return\n }\n\n resolve({ exitCode })\n }\n\n child.stdout?.on('data', options.onData)\n child.stderr?.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-MAX_STDERR_CHARS)\n options.onData(data)\n })\n child.once('error', (error) => void finish(null, error))\n child.once('close', (exitCode) => void finish(exitCode))\n\n if (options.timeoutMs !== undefined) {\n timeoutHandle = setTimeout(() => requestTermination('timeout'), options.timeoutMs)\n }\n\n if (options.signal) {\n options.signal.addEventListener('abort', onAbort, { once: true })\n if (options.signal.aborted) onAbort()\n }\n })\n}\n\nfunction openAnonymousEnvFile(content: string): number {\n const dir = mkdtempSync(join(tmpdir(), 'bazilion-docker-env-'))\n const path = join(dir, 'environment')\n let fd: number | undefined\n try {\n writeFileSync(path, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 })\n fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW)\n // The descriptor remains valid after unlink. Docker inherits it as fd 3,\n // while no pathname or secret-bearing file survives the spawn boundary.\n rmSync(dir, { recursive: true, force: true })\n return fd\n } catch (error) {\n if (fd !== undefined) closeSync(fd)\n rmSync(dir, { recursive: true, force: true })\n throw error\n }\n}\n\nfunction resolveTimeoutMs(timeout: number | undefined): number | undefined {\n if (timeout === undefined) return undefined\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new Error('Invalid timeout: must be a finite number of seconds')\n }\n\n const timeoutMs = timeout * 1_000\n if (timeoutMs > MAX_TIMEOUT_MS) {\n throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_MS / 1_000} seconds`)\n }\n return timeoutMs\n}\n\nasync function resolveMountSource(cwd: string): Promise<string> {\n try {\n await access(cwd, constants.F_OK)\n const info = await stat(cwd)\n if (!info.isDirectory()) {\n throw new Error(`Docker sandbox mount is not a directory: ${cwd}`)\n }\n return await realpath(cwd)\n } catch (error) {\n if (error instanceof Error && error.message.startsWith('Docker sandbox mount is not')) {\n throw error\n }\n\n const code = (error as NodeJS.ErrnoException).code\n if (code === 'ENOENT') {\n throw new Error(`Docker sandbox mount does not exist: ${cwd}`)\n }\n throw new Error(\n `Docker sandbox cannot access mount \"${cwd}\": ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n}\n\nfunction hostIdentity(): { uid: number; gid: number } {\n if (typeof process.getuid !== 'function' || typeof process.getgid !== 'function') {\n throw new Error('Docker sandbox requires a host platform with numeric uid/gid support')\n }\n return { uid: process.getuid(), gid: process.getgid() }\n}\n\nfunction sortedEnvironment(env: NodeJS.ProcessEnv): Array<[string, string]> {\n const entries: Array<[string, string]> = []\n for (const [name, value] of Object.entries(env)) {\n if (value === undefined) continue\n if (!ENV_NAME.test(name)) {\n throw new Error(\n `Docker sandbox environment variable name is invalid: ${JSON.stringify(name)}`,\n )\n }\n if (DOCKER_CLIENT_CONTROL_ENV.has(name) && !CONTAINER_LITERAL_ENV_KEYS.has(name)) {\n throw new Error(`Docker sandbox cannot pass Docker client control variable: ${name}`)\n }\n if (SANDBOX_CONTROL_ENV.has(name)) {\n throw new Error(`Docker sandbox cannot pass shell startup control variable: ${name}`)\n }\n if (value.includes('\\0')) {\n throw new Error(`Docker sandbox environment variable ${name} contains a NUL byte`)\n }\n if (value.includes('\\n') || value.includes('\\r')) {\n throw new Error(`Docker sandbox environment variable ${name} cannot contain a line break`)\n }\n entries.push([name, value])\n }\n return entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n}\n\nasync function resolveLocalDockerClientEnv(\n dockerPath: string,\n hostEnv: Readonly<NodeJS.ProcessEnv>,\n): Promise<NodeJS.ProcessEnv> {\n const discoveryEnv = buildDockerClientEnv(hostEnv)\n const endpoint = await inspectDockerEndpoint(dockerPath, discoveryEnv)\n const prefix = 'unix://'\n if (!endpoint.startsWith(prefix) || !isAbsolute(endpoint.slice(prefix.length))) {\n throw new Error(\n `Docker sandbox requires a local Unix-socket context; active endpoint is ${JSON.stringify(endpoint)}`,\n )\n }\n\n // Pin all run/cleanup calls to the inspected endpoint. This prevents an\n // ambient remote context or a later context-file change from redirecting\n // bind mounts to another machine.\n const pinned: NodeJS.ProcessEnv = { DOCKER_HOST: endpoint }\n if (hostEnv.PATH !== undefined) pinned.PATH = hostEnv.PATH\n if (hostEnv.HOME !== undefined) pinned.HOME = hostEnv.HOME\n return pinned\n}\n\nasync function inspectDockerEndpoint(\n dockerPath: string,\n processEnv: NodeJS.ProcessEnv,\n): Promise<string> {\n const raw = await runDockerInspection(\n dockerPath,\n ['context', 'inspect', '--format', '{{json .Endpoints.docker.Host}}'],\n processEnv,\n 'the active context',\n )\n try {\n const parsed = JSON.parse(raw) as unknown\n if (typeof parsed !== 'string' || parsed.length === 0) throw new Error('empty endpoint')\n return parsed\n } catch {\n throw new Error(\n `Docker sandbox could not resolve the active Docker endpoint: ${raw || 'empty context response'}`,\n )\n }\n}\n\nasync function inspectSandboxImage(\n dockerPath: string,\n image: string,\n processEnv: NodeJS.ProcessEnv,\n): Promise<string> {\n let raw: string\n try {\n raw = await runDockerInspection(\n dockerPath,\n ['image', 'inspect', '--format', '{{json .Id}}\\t{{json (index .Config \"Volumes\")}}', image],\n processEnv,\n `local image ${JSON.stringify(image)}`,\n )\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error)\n if (/no such image|unable to find image|manifest unknown/i.test(detail)) {\n throw new Error(`Docker sandbox image \"${image}\" is unavailable: ${detail}`)\n }\n throw error\n }\n\n const separator = raw.indexOf('\\t')\n if (separator < 0) {\n throw new Error(`Docker sandbox received an invalid inspection result for image \"${image}\"`)\n }\n let id: unknown\n let volumes: unknown\n try {\n id = JSON.parse(raw.slice(0, separator))\n volumes = JSON.parse(raw.slice(separator + 1))\n } catch {\n throw new Error(`Docker sandbox received an invalid inspection result for image \"${image}\"`)\n }\n if (typeof id !== 'string' || !id.startsWith('sha256:')) {\n throw new Error(`Docker sandbox received an invalid image id for \"${image}\"`)\n }\n if (volumes !== null && typeof volumes === 'object' && Object.keys(volumes).length > 0) {\n throw new Error(\n `Docker sandbox image \"${image}\" declares writable volumes (${Object.keys(volumes).sort().join(', ')}); use an image without VOLUME declarations`,\n )\n }\n if (volumes !== null && (typeof volumes !== 'object' || Array.isArray(volumes))) {\n throw new Error(`Docker sandbox received invalid volume metadata for image \"${image}\"`)\n }\n return id\n}\n\nfunction runDockerInspection(\n dockerPath: string,\n args: string[],\n processEnv: NodeJS.ProcessEnv,\n subject: string,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(dockerPath, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n env: processEnv,\n })\n let settled = false\n let stdout = ''\n let stderr = ''\n const finish = (error?: Error) => {\n if (settled) return\n settled = true\n clearTimeout(timeoutHandle)\n if (error) {\n reject(error)\n return\n }\n resolve(stdout.trim())\n }\n const timeoutHandle = setTimeout(() => {\n child.kill('SIGKILL')\n finish(\n new Error(\n `Docker sandbox could not inspect ${subject} within ${DOCKER_CONTEXT_TIMEOUT_MS}ms`,\n ),\n )\n }, DOCKER_CONTEXT_TIMEOUT_MS)\n\n child.stdout.on('data', (data: Buffer) => {\n stdout = `${stdout}${data.toString('utf8')}`.slice(-4_096)\n })\n child.stderr.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-4_096)\n })\n child.once('error', (error) => finish(formatSpawnError(dockerPath, error)))\n child.once('close', (code) => {\n if (code === 0) finish()\n else\n finish(\n new Error(\n `Docker sandbox could not inspect ${subject}: ${stderr.trim() || `Docker exited with status ${code}`}`,\n ),\n )\n })\n })\n}\n\nfunction buildDockerClientEnv(hostEnv: Readonly<NodeJS.ProcessEnv>): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {}\n for (const key of DOCKER_CLIENT_ENV_KEYS) {\n const value = hostEnv[key]\n if (value !== undefined) env[key] = value\n }\n return env\n}\n\nfunction validateDockerPath(dockerPath: string): void {\n if (!dockerPath.trim()) throw new Error('Docker sandbox executable path is required')\n if (dockerPath.includes('\\0'))\n throw new Error('Docker sandbox executable path contains a NUL byte')\n}\n\nfunction validateImage(image: string): void {\n if (!image.trim()) throw new Error('Docker sandbox image is required')\n if (image.startsWith('-')) {\n throw new Error(`Docker sandbox image cannot start with \"-\": ${JSON.stringify(image)}`)\n }\n if (image.includes('\\0')) throw new Error('Docker sandbox image contains a NUL byte')\n}\n\nfunction validateContainerName(containerName: string): void {\n if (!CONTAINER_NAME.test(containerName)) {\n throw new Error(`Docker sandbox container name is invalid: ${JSON.stringify(containerName)}`)\n }\n}\n\nfunction validateMountArgument(mountSource: string): void {\n if (!mountSource) throw new Error('Docker sandbox mount path is required')\n if (mountSource.includes('\\0') || mountSource.includes(',')) {\n throw new Error(\n `Docker sandbox mount path cannot be represented safely: ${JSON.stringify(mountSource)}`,\n )\n }\n}\n\nfunction validateReadOnlyMounts(\n mounts: readonly DockerReadOnlyMount[],\n workspaceSource: string,\n): void {\n const targets = new Set<string>([CONTAINER_WORKDIR, '/tmp'])\n for (const mount of mounts) {\n validateMountArgument(mount.source)\n if (\n mount.sourceRoot !== undefined &&\n (!isAbsolute(mount.sourceRoot) ||\n mount.sourceRoot.includes('\\0') ||\n mount.sourceRoot.includes(','))\n ) {\n throw new Error(\n `Docker sandbox read-only mount root is invalid: ${JSON.stringify(mount.sourceRoot)}`,\n )\n }\n if (\n !mount.target.startsWith('/') ||\n mount.target.includes('\\0') ||\n mount.target.includes(',') ||\n mount.target.split('/').includes('..')\n ) {\n throw new Error(\n `Docker sandbox read-only mount target is invalid: ${JSON.stringify(mount.target)}`,\n )\n }\n if (mount.source === workspaceSource) {\n throw new Error('Docker sandbox cannot mount the writable workspace again as an input')\n }\n if (targets.has(mount.target)) {\n throw new Error(`Docker sandbox mount target is duplicated or reserved: ${mount.target}`)\n }\n targets.add(mount.target)\n }\n}\n\nfunction isWithin(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n}\n\nfunction validateHostIdentity(uid: number, gid: number): void {\n if (!Number.isSafeInteger(uid) || uid < 0 || !Number.isSafeInteger(gid) || gid < 0) {\n throw new Error(`Docker sandbox received an invalid host uid/gid: ${uid}:${gid}`)\n }\n}\n\nfunction formatSpawnError(dockerPath: string, error: Error): Error {\n const code = (error as NodeJS.ErrnoException).code\n if (code === 'ENOENT') {\n return new Error(`Docker sandbox unavailable: executable \"${dockerPath}\" was not found`)\n }\n if (code === 'EACCES') {\n return new Error(`Docker sandbox unavailable: executable \"${dockerPath}\" is not executable`)\n }\n return new Error(`Docker sandbox could not start \"${dockerPath}\": ${error.message}`)\n}\n\nfunction formatDockerRunError(spec: DockerRunSpec, stderr: string): Error {\n const detail = stderr.trim() || 'Docker exited with status 125 without an error message'\n if (/no such image|unable to find image|manifest unknown|pull access denied/i.test(detail)) {\n return new Error(`Docker sandbox image \"${spec.image}\" is unavailable: ${detail}`)\n }\n if (\n /mounts denied|invalid mount config|bind source path does not exist|mount.*failed/i.test(detail)\n ) {\n return new Error(`Docker sandbox mount failed for \"${spec.mountSource}\": ${detail}`)\n }\n if (/cannot connect to the docker daemon|is the docker daemon running/i.test(detail)) {\n return new Error(`Docker sandbox unavailable: ${detail}`)\n }\n return new Error(`Docker sandbox failed to start container \"${spec.containerName}\": ${detail}`)\n}\n\nfunction formatDockerCreateError(\n spec: DockerRunSpec,\n stderr: string,\n exitCode: number | null,\n): Error {\n const detail =\n stderr.trim() ||\n `Docker exited with ${exitCode === null ? 'no status' : `status ${exitCode}`} without an error message`\n if (\n /mounts denied|invalid mount config|bind source path does not exist|mount.*failed/i.test(detail)\n ) {\n return new Error(`Docker sandbox preflight mount failed for \"${spec.mountSource}\": ${detail}`)\n }\n if (/cannot connect to the docker daemon|is the docker daemon running/i.test(detail)) {\n return new Error(`Docker sandbox preflight could not reach the local Docker daemon: ${detail}`)\n }\n return new Error(`Docker sandbox preflight could not create the required container: ${detail}`)\n}\n\nfunction formatDockerProbeError(\n spec: DockerRunSpec,\n stderr: string,\n exitCode: number | null,\n): Error {\n const detail =\n stderr.trim() ||\n `Docker exited with ${exitCode === null ? 'no status' : `status ${exitCode}`} without an error message`\n return new Error(\n `Docker sandbox image \"${spec.image}\" could not run the required /bin/bash and /usr/bin/env probe: ${detail}`,\n )\n}\n\nasync function removeContainer(\n dockerPath: string,\n containerName: string,\n processEnv: NodeJS.ProcessEnv,\n): Promise<void> {\n const args = ['container', 'rm', '--force', containerName]\n let last: CleanupResult | undefined\n for (let attempt = 0; attempt < CLEANUP_ATTEMPTS; attempt += 1) {\n last = await runCleanupCommand(dockerPath, args, processEnv)\n if (last.ok) return\n if (attempt < CLEANUP_ATTEMPTS - 1) {\n await new Promise<void>((resolve) => setTimeout(resolve, CLEANUP_RETRY_DELAY_MS))\n }\n }\n\n if (last && !last.missing) {\n console.warn(\n `Docker sandbox could not confirm cleanup of container \"${containerName}\": ${last.error || 'cleanup command failed'}`,\n )\n }\n}\n\ninterface CleanupResult {\n ok: boolean\n missing: boolean\n error: string\n}\n\nfunction runCleanupCommand(\n dockerPath: string,\n args: string[],\n processEnv: NodeJS.ProcessEnv,\n): Promise<CleanupResult> {\n return new Promise((resolve) => {\n let done = false\n let stderr = ''\n const child = spawn(dockerPath, args, {\n stdio: ['ignore', 'ignore', 'pipe'],\n windowsHide: true,\n env: processEnv,\n })\n const finish = (ok: boolean, error = '') => {\n if (done) return\n done = true\n clearTimeout(timeoutHandle)\n const detail = error || stderr.trim()\n resolve({\n ok,\n missing: /no such container/i.test(detail),\n error: detail,\n })\n }\n const timeoutHandle = setTimeout(() => {\n child.kill('SIGKILL')\n finish(false, `cleanup timed out after ${CLEANUP_TIMEOUT_MS}ms`)\n }, CLEANUP_TIMEOUT_MS)\n\n child.stderr.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-2_048)\n })\n child.once('error', (error) => finish(false, error.message))\n child.once('close', (code) => finish(code === 0))\n })\n}\n","import {\n constants,\n existsSync,\n lstatSync,\n mkdirSync,\n mkdtempSync,\n realpathSync,\n rmSync,\n statSync,\n} from 'node:fs'\nimport { access } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { isAbsolute, join, relative, sep } from 'node:path'\nimport type {\n Attachment,\n BashApprovalMode,\n ReasoningLevel,\n ResolvedAgent,\n} from '@bazilion/api-types'\nimport {\n PROTECTED_PROVIDER_NAMES,\n PROVIDER_CREDENTIAL_ENV,\n type ProtectedProviderName,\n} from '../providers/pi-runtime.ts'\nimport type { PromptSkill, ProtectedHomeDocuments } from '../session/prompt.ts'\nimport type { ProtectedDockerRuntime } from '../shell/docker.ts'\nimport type { InjectedMcpTool } from './ipc-protocol.ts'\n\nexport const REDACTED_VALUE = '[REDACTED]'\n\nexport interface ProtectedProviderWorkerRuntime {\n providerName: ProtectedProviderName\n modelId: string\n reasoningLevel: ReasoningLevel\n /** Selected provider credential only; unrelated daemon secrets never cross this wire. */\n apiKey?: string\n baseUrl?: string\n /** Closed provider-specific fields installed in Pi's in-memory credential store, not process.env. */\n credentialEnv?: Array<{ name: string; value: string }>\n /** Explicit Google service-account/ADC JSON materialized only inside this turn's scratch tree. */\n credentialFile?: { envName: 'GOOGLE_APPLICATION_CREDENTIALS'; content: string }\n}\n\nexport interface ProtectedWorkerPaths {\n agentDir: string\n teamDir: string\n memoryDir: string\n sessionDir: string\n uploadsDir?: string\n /** Prompt bodies and exact, daemon-resolved read-only Docker mount paths. */\n skills: PromptSkill[]\n /** Daemon-prepared fixed-file snapshot; protected workers never follow home-file symlinks. */\n homeDocuments: ProtectedHomeDocuments\n}\n\nexport interface ConfiguredOperatorHttpWorkerSpec {\n kind: 'configured_operator_http'\n /** Pre-resolved agent record — the worker never queries the DB itself. */\n agent: ResolvedAgent\n message: string\n enabledProviders: string[]\n apiKey?: string\n browserEnabled?: boolean\n mcpTools?: InjectedMcpTool[]\n images?: Attachment[]\n turnId: string\n bashApprovalMode: BashApprovalMode\n}\n\nexport interface ProtectedWorkerSpec {\n kind: 'protected'\n agent: ResolvedAgent\n message: string\n images?: Attachment[]\n turnId: string\n bashApprovalMode: 'auto_deny'\n runtime: ProtectedProviderWorkerRuntime\n paths: ProtectedWorkerPaths\n docker: ProtectedDockerRuntime\n /** The only general web capability in the protected normal surface. */\n webFetchEnabled: true\n}\n\nexport interface RestrictedReviewWorkerSpec {\n kind: 'restricted_review'\n agentId: string\n message: string\n turnId: string\n runtime: ProtectedProviderWorkerRuntime\n review: {\n reviewId: string\n evidence: Array<{ sessionId: string; entryOrdinal: number }>\n }\n}\n\nexport type WorkerTurnSpec = ConfiguredOperatorHttpWorkerSpec | ProtectedWorkerSpec\n\nexport interface MinimalWorkerScratch {\n root: string\n homeDir: string\n tempDir: string\n piAgentDir: string\n reviewCwd: string\n reviewSessionDir: string\n}\n\nexport type WorkerInput =\n | (ConfiguredOperatorHttpWorkerSpec & { apiKeyRefreshEnabled: boolean })\n | (ProtectedWorkerSpec & {\n apiKeyRefreshEnabled: true\n scratch: MinimalWorkerScratch\n })\n | (RestrictedReviewWorkerSpec & {\n apiKeyRefreshEnabled: true\n scratch: MinimalWorkerScratch\n })\n\nconst REASONING_LEVELS = new Set<ReasoningLevel>([\n 'off',\n 'minimal',\n 'low',\n 'medium',\n 'high',\n 'xhigh',\n])\n\nconst CONFIGURED_KEYS = new Set([\n 'kind',\n 'agent',\n 'message',\n 'enabledProviders',\n 'apiKey',\n 'apiKeyRefreshEnabled',\n 'browserEnabled',\n 'mcpTools',\n 'images',\n 'turnId',\n 'bashApprovalMode',\n])\nconst CONFIGURED_REQUIRED_KEYS = new Set([\n 'kind',\n 'agent',\n 'message',\n 'enabledProviders',\n 'apiKeyRefreshEnabled',\n 'turnId',\n 'bashApprovalMode',\n])\nconst PROTECTED_KEYS = new Set([\n 'kind',\n 'agent',\n 'message',\n 'images',\n 'turnId',\n 'bashApprovalMode',\n 'runtime',\n 'paths',\n 'docker',\n 'webFetchEnabled',\n 'apiKeyRefreshEnabled',\n 'scratch',\n])\nconst PROTECTED_REQUIRED_KEYS = new Set([...PROTECTED_KEYS].filter((key) => key !== 'images'))\nconst REVIEW_KEYS = new Set([\n 'kind',\n 'agentId',\n 'message',\n 'turnId',\n 'runtime',\n 'review',\n 'apiKeyRefreshEnabled',\n 'scratch',\n])\n\n/** Create the complete scratch tree used by one protected/review child. */\nexport function createMinimalWorkerScratch(parentDir = tmpdir()): MinimalWorkerScratch {\n const root = mkdtempSync(join(parentDir, 'bazilion-worker-'))\n const scratch: MinimalWorkerScratch = {\n root,\n homeDir: join(root, 'home'),\n tempDir: join(root, 'tmp'),\n piAgentDir: join(root, 'pi'),\n reviewCwd: join(root, 'review-work'),\n reviewSessionDir: join(root, 'review-session'),\n }\n try {\n for (const path of Object.values(scratch).slice(1)) {\n mkdirSync(path, { recursive: true, mode: 0o700 })\n }\n return scratch\n } catch (error) {\n rmSync(root, { recursive: true, force: true })\n throw error\n }\n}\n\nexport function cleanupMinimalWorkerScratch(scratch: MinimalWorkerScratch): void {\n assertMinimalWorkerScratch(scratch)\n rmSync(scratch.root, { recursive: true, force: true })\n}\n\n/**\n * Construct the exact process environment for a protected/review child.\n * Docker/provider/tool configuration is deliberately not represented here.\n */\nexport function minimalWorkerProcessEnv(\n scratch: MinimalWorkerScratch,\n platform: NodeJS.Platform = process.platform,\n hostEnv: Readonly<NodeJS.ProcessEnv> = process.env,\n): NodeJS.ProcessEnv {\n assertMinimalWorkerScratch(scratch)\n if (platform === 'win32') {\n const systemRoot = requireCanonicalWindowsDirectory(\n hostEnv.SystemRoot ?? hostEnv.WINDIR,\n 'SystemRoot',\n )\n const windir = requireCanonicalWindowsDirectory(hostEnv.WINDIR ?? systemRoot, 'WINDIR')\n const comSpec = requireCanonicalWindowsFile(hostEnv.ComSpec, 'ComSpec')\n const pathExt = hostEnv.PATHEXT\n if (!pathExt || pathExt.includes('\\0') || pathExt.includes('\\n') || pathExt.includes('\\r')) {\n throw new Error('minimal worker bootstrap requires a valid PATHEXT on Windows')\n }\n return {\n USERPROFILE: scratch.homeDir,\n TMPDIR: scratch.tempDir,\n TMP: scratch.tempDir,\n TEMP: scratch.tempDir,\n SystemRoot: systemRoot,\n WINDIR: windir,\n ComSpec: comSpec,\n PATHEXT: pathExt,\n }\n }\n return {\n HOME: scratch.homeDir,\n TMPDIR: scratch.tempDir,\n TMP: scratch.tempDir,\n TEMP: scratch.tempDir,\n LANG: 'C',\n LC_ALL: 'C',\n }\n}\n\nexport function validateMinimalWorkerProcessEnv(\n env: Readonly<NodeJS.ProcessEnv>,\n scratch: MinimalWorkerScratch,\n platform: NodeJS.Platform = process.platform,\n): void {\n const expected = minimalWorkerProcessEnv(scratch, platform, env)\n const actualKeys = Object.keys(env).sort()\n const expectedKeys = Object.keys(expected).sort()\n if (\n actualKeys.length !== expectedKeys.length ||\n actualKeys.some((key, index) => key !== expectedKeys[index])\n ) {\n throw new Error(`minimal worker environment has unexpected keys: ${actualKeys.join(', ')}`)\n }\n for (const key of expectedKeys) {\n if (env[key] !== expected[key]) {\n throw new Error(`minimal worker environment has an invalid ${key} value`)\n }\n }\n}\n\nexport function parseWorkerInput(value: unknown): WorkerInput {\n const input = objectRecord(value, 'worker input')\n const kind = input.kind\n if (kind === 'configured_operator_http') {\n assertExactKeys(input, CONFIGURED_KEYS, 'configured worker input', CONFIGURED_REQUIRED_KEYS)\n if (!isResolvedAgent(input.agent)) throw new Error('worker: configured input requires an agent')\n requireString(input.message, 'message')\n if (!Array.isArray(input.enabledProviders) || !input.enabledProviders.every(isString)) {\n throw new Error('worker: configured input requires enabledProviders')\n }\n requireString(input.turnId, 'turnId')\n if (input.bashApprovalMode !== 'interactive' && input.bashApprovalMode !== 'auto_deny') {\n throw new Error('worker: configured input requires a valid bashApprovalMode')\n }\n if (typeof input.apiKeyRefreshEnabled !== 'boolean') {\n throw new Error('worker: configured input requires apiKeyRefreshEnabled')\n }\n if (input.apiKeyRefreshEnabled) {\n const providerName = input.agent.model.split(':', 1)[0] ?? ''\n if (providerName !== 'openai-codex' || !nonEmptyString(input.apiKey)) {\n throw new Error(\n 'worker: API key refresh requires an openai-codex turn with an initial token',\n )\n }\n }\n return value as WorkerInput\n }\n\n if (kind === 'protected') {\n assertExactKeys(input, PROTECTED_KEYS, 'protected worker input', PROTECTED_REQUIRED_KEYS)\n if (!isResolvedAgent(input.agent)) throw new Error('worker: protected input requires an agent')\n requireString(input.message, 'message')\n requireString(input.turnId, 'turnId')\n if (input.bashApprovalMode !== 'auto_deny') {\n throw new Error('worker: protected input requires auto-deny shell approval')\n }\n if (input.webFetchEnabled !== true) {\n throw new Error('worker: protected input requires guarded web_fetch')\n }\n assertProtectedProviderRuntime(input.runtime)\n const runtime = input.runtime as ProtectedProviderWorkerRuntime\n const separator = (input.agent as ResolvedAgent).model.indexOf(':')\n if (\n runtime.providerName !== (input.agent as ResolvedAgent).model.slice(0, separator) ||\n runtime.modelId !== (input.agent as ResolvedAgent).model.slice(separator + 1)\n ) {\n throw new Error('worker: protected runtime does not match the resolved Agent model')\n }\n assertProtectedWorkerPaths(input.paths, input.agent)\n assertProtectedDockerRuntime(input.docker, input.paths as ProtectedWorkerPaths)\n if (input.apiKeyRefreshEnabled !== true) {\n throw new Error('worker: protected input requires bound API key refresh')\n }\n assertMinimalWorkerScratch(input.scratch)\n return value as WorkerInput\n }\n\n if (kind === 'restricted_review') {\n assertExactKeys(input, REVIEW_KEYS, 'restricted review worker input', REVIEW_KEYS)\n requireString(input.agentId, 'agentId')\n requireString(input.message, 'message')\n requireString(input.turnId, 'turnId')\n assertProtectedProviderRuntime(input.runtime)\n if (input.apiKeyRefreshEnabled !== true) {\n throw new Error('worker: restricted review requires bound API key refresh')\n }\n assertMinimalWorkerScratch(input.scratch)\n const review = objectRecord(input.review, 'review metadata')\n assertExactKeys(\n review,\n new Set(['reviewId', 'evidence']),\n 'review metadata',\n new Set(['reviewId', 'evidence']),\n )\n requireString(review.reviewId, 'reviewId')\n if (!Array.isArray(review.evidence)) throw new Error('worker: review evidence must be an array')\n for (const item of review.evidence) {\n const evidence = objectRecord(item, 'review evidence')\n assertExactKeys(\n evidence,\n new Set(['sessionId', 'entryOrdinal']),\n 'review evidence',\n new Set(['sessionId', 'entryOrdinal']),\n )\n requireString(evidence.sessionId, 'sessionId')\n if (!Number.isInteger(evidence.entryOrdinal) || Number(evidence.entryOrdinal) < 0) {\n throw new Error('worker: review evidence entryOrdinal must be a non-negative integer')\n }\n }\n return value as WorkerInput\n }\n\n throw new Error('worker: unknown or missing runtime kind')\n}\n\nexport function assertProtectedProviderRuntime(\n value: unknown,\n): asserts value is ProtectedProviderWorkerRuntime {\n const runtime = objectRecord(value, 'protected provider runtime')\n assertExactKeys(\n runtime,\n new Set([\n 'providerName',\n 'modelId',\n 'reasoningLevel',\n 'apiKey',\n 'baseUrl',\n 'credentialEnv',\n 'credentialFile',\n ]),\n 'protected provider runtime',\n new Set(['providerName', 'modelId', 'reasoningLevel']),\n )\n if (!PROTECTED_PROVIDER_NAMES.includes(runtime.providerName as ProtectedProviderName)) {\n throw new Error('protected execution requires a registered provider')\n }\n if (!nonEmptyString(runtime.modelId))\n throw new Error('protected provider runtime requires a model id')\n if (!REASONING_LEVELS.has(runtime.reasoningLevel as ReasoningLevel)) {\n throw new Error('protected provider runtime requires a valid reasoning level')\n }\n if (runtime.apiKey !== undefined && !nonEmptyString(runtime.apiKey)) {\n throw new Error('protected provider runtime contains an invalid selected credential')\n }\n if (runtime.baseUrl !== undefined) requireString(runtime.baseUrl, 'baseUrl')\n if (runtime.credentialEnv !== undefined) {\n if (!Array.isArray(runtime.credentialEnv)) {\n throw new Error('protected provider credential fields must be an array')\n }\n const allowed = new Set(PROVIDER_CREDENTIAL_ENV[String(runtime.providerName)] ?? [])\n const seen = new Set<string>()\n for (const rawField of runtime.credentialEnv) {\n const field = objectRecord(rawField, 'protected provider credential field')\n assertExactKeys(\n field,\n new Set(['name', 'value']),\n 'protected provider credential field',\n new Set(['name', 'value']),\n )\n requireString(field.name, 'credential field name')\n requireString(field.value, 'credential field value')\n if (!allowed.has(field.name as string) || seen.has(field.name as string)) {\n throw new Error('protected provider runtime contains an unexpected credential field')\n }\n seen.add(field.name as string)\n }\n }\n if (runtime.credentialFile !== undefined) {\n const file = objectRecord(runtime.credentialFile, 'protected provider credential file')\n assertExactKeys(\n file,\n new Set(['envName', 'content']),\n 'protected provider credential file',\n new Set(['envName', 'content']),\n )\n if (\n runtime.providerName !== 'google-vertex' ||\n file.envName !== 'GOOGLE_APPLICATION_CREDENTIALS' ||\n !nonEmptyString(file.content) ||\n Buffer.byteLength(file.content as string, 'utf8') > 64 * 1024\n ) {\n throw new Error('protected provider runtime contains an invalid credential file')\n }\n }\n if (\n !nonEmptyString(runtime.apiKey) &&\n !Array.isArray(runtime.credentialEnv) &&\n runtime.credentialFile === undefined\n ) {\n throw new Error('protected provider runtime requires selected credentials')\n }\n}\n\nexport function protectedRuntimeSecrets(runtime: ProtectedProviderWorkerRuntime): string[] {\n return [\n runtime.apiKey,\n ...(runtime.credentialEnv ?? []).map((field) => field.value),\n runtime.credentialFile?.content,\n ].filter((value): value is string => Boolean(value))\n}\n\nexport function redactExactValue(text: string, value: string | undefined): string {\n return value ? text.replaceAll(value, REDACTED_VALUE) : text\n}\n\nexport function redactExactValues(text: string, values: readonly string[]): string {\n let redacted = text\n for (const value of [...new Set(values)].filter(Boolean).sort((a, b) => b.length - a.length)) {\n redacted = redacted.replaceAll(value, REDACTED_VALUE)\n }\n return redacted\n}\n\n/** Exact-value redactor that safely handles matches split across stream chunks. */\nexport class ExactValueStreamRedactor {\n readonly #values: string[] = []\n #pending = ''\n\n constructor(value: string | readonly string[]) {\n const values = typeof value === 'string' ? [value] : value\n for (const item of values) this.add(item)\n }\n\n add(value: string): void {\n if (!value) throw new Error('stream redactor requires a non-empty value')\n if (!this.#values.includes(value)) this.#values.push(value)\n }\n\n push(chunk: string): string {\n const data = this.#pending + chunk\n const maxLength = Math.max(...this.#values.map((value) => value.length))\n const cutoff = Math.max(0, data.length - (maxLength - 1))\n let output = ''\n let cursor = 0\n while (cursor < cutoff) {\n const match = this.#nextMatch(data, cursor)\n if (!match || match.index >= cutoff) {\n output += data.slice(cursor, cutoff)\n cursor = cutoff\n break\n }\n output += data.slice(cursor, match.index) + REDACTED_VALUE\n cursor = match.index + match.value.length\n }\n this.#pending = data.slice(cursor)\n return output\n }\n\n flush(): string {\n const output = redactExactValues(this.#pending, this.#values)\n this.#pending = ''\n return output\n }\n\n #nextMatch(data: string, cursor: number): { index: number; value: string } | null {\n let next: { index: number; value: string } | null = null\n for (const value of this.#values) {\n const index = data.indexOf(value, cursor)\n if (index < 0) continue\n if (\n !next ||\n index < next.index ||\n (index === next.index && value.length > next.value.length)\n ) {\n next = { index, value }\n }\n }\n return next\n }\n}\n\nexport function redactJsonValue<T>(value: T, secrets: string | readonly string[] | undefined): T {\n if (!secrets || (Array.isArray(secrets) && secrets.length === 0)) return value\n const values = typeof secrets === 'string' ? [secrets] : secrets\n if (typeof value === 'string') return redactExactValues(value, values) as T\n if (Array.isArray(value)) return value.map((item) => redactJsonValue(item, values)) as T\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, redactJsonValue(item, values)]),\n ) as T\n }\n return value\n}\n\nfunction assertProtectedWorkerPaths(value: unknown, agent: ResolvedAgent): void {\n const paths = objectRecord(value, 'protected worker paths')\n assertExactKeys(\n paths,\n new Set([\n 'agentDir',\n 'teamDir',\n 'memoryDir',\n 'sessionDir',\n 'uploadsDir',\n 'skills',\n 'homeDocuments',\n ]),\n 'protected worker paths',\n new Set(['agentDir', 'teamDir', 'memoryDir', 'sessionDir', 'skills', 'homeDocuments']),\n )\n for (const key of ['agentDir', 'teamDir', 'memoryDir', 'sessionDir'] as const) {\n requireAbsolutePath(paths[key], key)\n }\n if (paths.uploadsDir !== undefined) requireAbsolutePath(paths.uploadsDir, 'uploadsDir')\n if (paths.agentDir !== agent.agent.dir || paths.teamDir !== agent.team.path) {\n throw new Error('worker: protected paths do not match the resolved Agent')\n }\n const canonicalAgentDir = requireCanonicalRealDirectory(\n paths.agentDir as string,\n 'Agent directory',\n )\n const canonicalTeamDir = resolveCanonicalDirectory(paths.teamDir as string, 'Team directory')\n requireCanonicalContainedDirectory(\n canonicalAgentDir,\n paths.sessionDir as string,\n 'session directory',\n )\n if (paths.uploadsDir !== undefined) {\n requireCanonicalContainedDirectory(\n canonicalAgentDir,\n paths.uploadsDir as string,\n 'uploads directory',\n )\n }\n requireCanonicalContainedDirectory(\n canonicalTeamDir,\n paths.memoryDir as string,\n 'memory directory',\n )\n if (!Array.isArray(paths.skills)) throw new Error('worker: protected skills must be an array')\n for (const item of paths.skills) {\n const skill = objectRecord(item, 'protected skill')\n assertExactKeys(\n skill,\n new Set(['name', 'description', 'body', 'hostDir', 'sandboxDir']),\n 'protected skill',\n )\n requireString(skill.name, 'skill name')\n requireString(skill.description, 'skill description')\n requireString(skill.body, 'skill body')\n requireAbsolutePath(skill.hostDir, 'skill hostDir')\n requireCanonicalRealDirectory(skill.hostDir, 'skill hostDir')\n if (typeof skill.sandboxDir !== 'string' || !skill.sandboxDir.startsWith('/skills/')) {\n throw new Error('worker: protected skill sandbox path must be under /skills')\n }\n }\n const homeDocuments = objectRecord(paths.homeDocuments, 'protected home documents')\n const homeDocumentKeys = new Set([\n 'AGENTS.md',\n 'SOUL.md',\n 'TOOLS.md',\n 'IDENTITY.md',\n 'BOOTSTRAP.md',\n ])\n assertExactKeys(homeDocuments, homeDocumentKeys, 'protected home documents', homeDocumentKeys)\n for (const [name, contents] of Object.entries(homeDocuments)) {\n if (contents !== null && typeof contents !== 'string') {\n throw new Error(`worker: protected home document ${name} must be text or null`)\n }\n }\n}\n\nfunction assertProtectedDockerRuntime(\n value: unknown,\n paths: ProtectedWorkerPaths,\n): asserts value is ProtectedDockerRuntime {\n const runtime = objectRecord(value, 'protected Docker runtime')\n assertExactKeys(\n runtime,\n new Set([\n 'dockerPath',\n 'executableIdentity',\n 'endpoint',\n 'image',\n 'imageId',\n 'uid',\n 'gid',\n 'workspace',\n 'readOnlyMounts',\n 'containerEnv',\n ]),\n 'protected Docker runtime',\n new Set([\n 'dockerPath',\n 'executableIdentity',\n 'endpoint',\n 'image',\n 'imageId',\n 'uid',\n 'gid',\n 'workspace',\n 'readOnlyMounts',\n 'containerEnv',\n ]),\n )\n requireAbsolutePath(runtime.dockerPath, 'Docker executable')\n const executableIdentity = objectRecord(\n runtime.executableIdentity,\n 'protected Docker executable identity',\n )\n const executableIdentityKeys = new Set([\n 'device',\n 'inode',\n 'mode',\n 'size',\n 'modifiedTimeNs',\n 'changedTimeNs',\n ])\n assertExactKeys(\n executableIdentity,\n executableIdentityKeys,\n 'protected Docker executable identity',\n executableIdentityKeys,\n )\n for (const [name, field] of Object.entries(executableIdentity)) {\n if (typeof field !== 'string' || !/^\\d+$/.test(field)) {\n throw new Error(`worker: protected Docker executable identity has invalid ${name}`)\n }\n }\n if (\n typeof runtime.endpoint !== 'string' ||\n !runtime.endpoint.startsWith('unix://') ||\n !isAbsolute(runtime.endpoint.slice('unix://'.length)) ||\n containsUnsafeDockerArgument(runtime.endpoint)\n ) {\n throw new Error('worker: protected Docker endpoint must be a local Unix socket')\n }\n if (\n !nonEmptyString(runtime.image) ||\n runtime.image.startsWith('-') ||\n runtime.image.includes('\\0') ||\n runtime.image.includes('\\n') ||\n runtime.image.includes('\\r')\n ) {\n throw new Error('worker: protected Docker image is required')\n }\n if (typeof runtime.imageId !== 'string' || !/^sha256:[a-fA-F0-9]{64}$/.test(runtime.imageId)) {\n throw new Error('worker: protected Docker runtime requires an immutable image id')\n }\n if (!Number.isSafeInteger(runtime.uid) || !Number.isSafeInteger(runtime.gid)) {\n throw new Error('worker: protected Docker runtime requires a numeric host identity')\n }\n if (Number(runtime.uid) < 0 || Number(runtime.gid) < 0) {\n throw new Error('worker: protected Docker runtime requires a non-negative host identity')\n }\n const workspace = objectRecord(runtime.workspace, 'protected Docker workspace')\n assertExactKeys(\n workspace,\n new Set(['source', 'target', 'sourceRoot']),\n 'protected Docker workspace',\n new Set(['source', 'target', 'sourceRoot']),\n )\n requireAbsolutePath(workspace.source, 'Docker workspace source')\n requireAbsolutePath(workspace.sourceRoot, 'Docker workspace root')\n assertSafeDockerHostPath(workspace.source, 'Docker workspace source')\n assertSafeDockerHostPath(workspace.sourceRoot, 'Docker workspace root')\n if (workspace.target !== '/workspace') {\n throw new Error('worker: protected Docker workspace target must be /workspace')\n }\n const canonicalTeamDir = resolveCanonicalDirectory(paths.teamDir, 'Team directory')\n if (workspace.source !== canonicalTeamDir || workspace.sourceRoot !== canonicalTeamDir) {\n throw new Error('worker: protected Docker workspace does not match the canonical Team root')\n }\n if (!Array.isArray(runtime.readOnlyMounts)) {\n throw new Error('worker: protected Docker read-only mounts must be an array')\n }\n const mountTargets = new Set(['/workspace', '/tmp'])\n for (const item of runtime.readOnlyMounts) {\n const mount = objectRecord(item, 'protected Docker read-only mount')\n assertExactKeys(\n mount,\n new Set(['source', 'target', 'sourceRoot']),\n 'protected Docker read-only mount',\n new Set(['source', 'target', 'sourceRoot']),\n )\n requireAbsolutePath(mount.source, 'Docker read-only mount source')\n requireAbsolutePath(mount.sourceRoot, 'Docker read-only mount root')\n assertSafeDockerHostPath(mount.source, 'Docker read-only mount source')\n assertSafeDockerHostPath(mount.sourceRoot, 'Docker read-only mount root')\n if (\n typeof mount.target !== 'string' ||\n !mount.target.startsWith('/') ||\n containsUnsafeDockerArgument(mount.target) ||\n mount.target.split('/').includes('..') ||\n (!mount.target.startsWith('/skills/') &&\n mount.target !== '/inputs' &&\n mount.target !== '/workspace/memory')\n ) {\n throw new Error('worker: protected Docker read-only mount target is not permitted')\n }\n if (!lexicallyWithin(mount.sourceRoot, mount.source)) {\n throw new Error('worker: protected Docker read-only mount escaped its root')\n }\n if (mountTargets.has(mount.target)) {\n throw new Error('worker: protected Docker mount target is duplicated')\n }\n mountTargets.add(mount.target)\n }\n assertExactProtectedDockerMounts(\n runtime.readOnlyMounts as ProtectedDockerRuntime['readOnlyMounts'],\n paths,\n )\n const containerEnv = objectRecord(runtime.containerEnv, 'protected Docker container environment')\n const expectedContainerEnv = {\n PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',\n HOME: '/tmp',\n LANG: 'C.UTF-8',\n LC_ALL: 'C.UTF-8',\n SHELL: '/bin/bash',\n TMPDIR: '/tmp',\n }\n const expectedKeys = new Set(Object.keys(expectedContainerEnv))\n assertExactKeys(\n containerEnv,\n expectedKeys,\n 'protected Docker container environment',\n expectedKeys,\n )\n for (const [key, expected] of Object.entries(expectedContainerEnv)) {\n if (containerEnv[key] !== expected) {\n throw new Error(`worker: protected Docker container environment has invalid ${key}`)\n }\n }\n}\n\nfunction assertExactProtectedDockerMounts(\n mounts: ProtectedDockerRuntime['readOnlyMounts'],\n paths: ProtectedWorkerPaths,\n): void {\n const expected = new Map<string, string>([\n ['/workspace/memory', paths.memoryDir],\n ...((paths.uploadsDir ? [['/inputs', paths.uploadsDir]] : []) as Array<[string, string]>),\n ...paths.skills.map((skill) => [skill.sandboxDir, skill.hostDir] as [string, string]),\n ])\n if (mounts.length !== expected.size) {\n throw new Error('worker: protected Docker read-only mount set is incomplete')\n }\n for (const mount of mounts) {\n const source = expected.get(mount.target)\n if (!source || mount.source !== source) {\n throw new Error('worker: protected Docker read-only mount set does not match protected paths')\n }\n }\n}\n\nfunction assertSafeDockerHostPath(value: unknown, name: string): asserts value is string {\n if (typeof value !== 'string' || containsUnsafeDockerArgument(value)) {\n throw new Error(`worker: ${name} cannot be represented safely`)\n }\n}\n\nfunction containsUnsafeDockerArgument(value: string): boolean {\n return value.includes('\\0') || value.includes(',') || value.includes('\\n') || value.includes('\\r')\n}\n\nfunction requireCanonicalRealDirectory(value: unknown, name: string): string {\n if (typeof value !== 'string') throw new Error(`worker: ${name} must be a path`)\n try {\n const entry = lstatSync(value)\n if (entry.isSymbolicLink() || !entry.isDirectory()) {\n throw new Error('unsafe entry')\n }\n const canonical = realpathSync(value)\n if (canonical !== value) throw new Error('non-canonical entry')\n return canonical\n } catch {\n throw new Error(`worker: ${name} must be a real directory`)\n }\n}\n\nfunction resolveCanonicalDirectory(value: string, name: string): string {\n try {\n const entry = statSync(value)\n if (!entry.isDirectory()) throw new Error('not a directory')\n return realpathSync(value)\n } catch {\n throw new Error(`worker: ${name} is unavailable or unsafe`)\n }\n}\n\nfunction requireCanonicalContainedDirectory(root: string, value: string, name: string): string {\n const canonical = requireCanonicalRealDirectory(value, name)\n if (!lexicallyWithin(root, canonical)) throw new Error(`worker: ${name} escaped its root`)\n return canonical\n}\n\nfunction assertMinimalWorkerScratch(value: unknown): asserts value is MinimalWorkerScratch {\n const scratch = objectRecord(value, 'minimal worker scratch')\n assertExactKeys(\n scratch,\n new Set(['root', 'homeDir', 'tempDir', 'piAgentDir', 'reviewCwd', 'reviewSessionDir']),\n 'minimal worker scratch',\n new Set(['root', 'homeDir', 'tempDir', 'piAgentDir', 'reviewCwd', 'reviewSessionDir']),\n )\n requireAbsolutePath(scratch.root, 'scratch root')\n for (const key of [\n 'homeDir',\n 'tempDir',\n 'piAgentDir',\n 'reviewCwd',\n 'reviewSessionDir',\n ] as const) {\n requireAbsolutePath(scratch[key], key)\n if (!lexicallyWithin(scratch.root as string, scratch[key] as string)) {\n throw new Error(`worker: ${key} escaped the scratch root`)\n }\n }\n}\n\nfunction requireCanonicalWindowsDirectory(value: string | undefined, name: string): string {\n if (!value || !isAbsolute(value) || !existsSync(value) || !statSync(value).isDirectory()) {\n throw new Error(`minimal worker bootstrap requires a valid ${name} directory`)\n }\n return realpathSync(value)\n}\n\nfunction requireCanonicalWindowsFile(value: string | undefined, name: string): string {\n if (!value || !isAbsolute(value) || !existsSync(value) || !statSync(value).isFile()) {\n throw new Error(`minimal worker bootstrap requires a valid ${name} executable`)\n }\n return realpathSync(value)\n}\n\nfunction objectRecord(value: unknown, label: string): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`worker: ${label} must be an object`)\n }\n return value as Record<string, unknown>\n}\n\nfunction assertExactKeys(\n value: Record<string, unknown>,\n allowed: ReadonlySet<string>,\n label: string,\n required: ReadonlySet<string> = allowed,\n) {\n const unexpected = Object.keys(value).filter((key) => !allowed.has(key))\n if (unexpected.length > 0) {\n throw new Error(`worker: ${label} contains unexpected fields: ${unexpected.sort().join(', ')}`)\n }\n const missing = [...required].filter((key) => !(key in value))\n if (missing.length > 0) {\n throw new Error(`worker: ${label} is missing required fields: ${missing.sort().join(', ')}`)\n }\n}\n\nfunction isResolvedAgent(value: unknown): value is ResolvedAgent {\n if (!value || typeof value !== 'object') return false\n const resolved = value as Partial<ResolvedAgent>\n return Boolean(\n resolved.agent &&\n typeof resolved.agent.id === 'string' &&\n typeof resolved.agent.dir === 'string' &&\n resolved.team &&\n typeof resolved.team.id === 'string' &&\n typeof resolved.team.path === 'string' &&\n typeof resolved.model === 'string',\n )\n}\n\nfunction requireString(value: unknown, name: string): asserts value is string {\n if (typeof value !== 'string') throw new Error(`worker: ${name} must be a string`)\n}\n\nfunction nonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === 'string'\n}\n\nfunction requireAbsolutePath(value: unknown, name: string): asserts value is string {\n if (typeof value !== 'string' || !isAbsolute(value) || value.includes('\\0')) {\n throw new Error(`worker: ${name} must be an absolute path`)\n }\n}\n\nfunction lexicallyWithin(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n}\n\n/** Test/preflight helper for checking that every scratch directory is accessible. */\nexport async function checkMinimalWorkerScratch(scratch: MinimalWorkerScratch): Promise<void> {\n assertMinimalWorkerScratch(scratch)\n for (const path of Object.values(scratch)) await access(path, constants.R_OK | constants.W_OK)\n}\n","// Adapter: Bazilion ToolHandler → pi-coding-agent ToolDefinition.\n//\n// Pi expects tools to return `AgentToolResult<TDetails>` =\n// `{ content: (TextContent | ImageContent)[]; details: TDetails; terminate?: boolean }`.\n// Our handlers return plain strings. The adapter wraps the string into a single\n// text content block with empty details — the same fidelity we had before.\n//\n// Pi's tool execute contract: throw on failure. The agent loop wraps thrown\n// errors as tool-result messages with `isError: true`. We preserve this shape\n// directly because our handlers already throw on bad args / runtime failures.\n//\n// What's in this module:\n// - `ourToolToPiTool` — single-handler wrapper.\n// - `createBazilionCustomTools` — composed suite of the Bazilion-specific\n// tools (memory_*, messaging, bootstrap_done, web_search/fetch). File I/O\n// tools are *not* here — pi's createCodingTools(cwd, …) replaces them.\n\nimport type { ResolvedAgent } from '@bazilion/api-types'\nimport type { ToolDefinition } from '@earendil-works/pi-coding-agent'\nimport { Type } from 'typebox'\nimport type { MemoryBackend } from '../memory/types.ts'\nimport { bootstrapTool } from '../tools/bootstrap.ts'\nimport { browserTools } from '../tools/browser.ts'\nimport { deliverFileTool, type FileSink } from '../tools/deliver-file.ts'\nimport { homeTools } from '../tools/home.ts'\nimport { mcpProxyTools } from '../tools/mcp.ts'\nimport { memoryTools } from '../tools/memory.ts'\nimport { messagingTools } from '../tools/messaging.ts'\nimport type { ToolHandler, ToolOutput } from '../tools/types.ts'\nimport { userMdTools } from '../tools/user-md.ts'\nimport { protectedWebFetchTool, webTools } from '../tools/web.ts'\nimport type {\n BrowserHost,\n InjectedMcpTool,\n McpHost,\n MessagingHost,\n UserMdHost,\n} from '../worker/ipc-protocol.ts'\n\n/**\n * Wrap a Bazilion `ToolHandler` as a pi `ToolDefinition` so it can be passed\n * through `customTools` to `createAgentSession`.\n *\n * Design note: we keep the same tool name + description + parameter JSON schema\n * that the handler already declares. Pi wants a `TypeBox` schema; we pass the\n * JSONSchema through `Type.Unsafe` so the LLM validation happens on pi's side\n * without us having to re-author schemas in typebox syntax.\n */\nexport function ourToolToPiTool(h: ToolHandler): ToolDefinition {\n return {\n name: h.def.name,\n label: h.def.name,\n description: h.def.description,\n parameters: Type.Unsafe<Record<string, unknown>>(h.def.parameters as Record<string, unknown>),\n async execute(_toolCallId, params) {\n const out = await h.invoke(params as Record<string, unknown>)\n return {\n content: toPiContent(out),\n details: {},\n }\n },\n }\n}\n\n/**\n * Map a Bazilion `ToolOutput` to pi's `(TextContent | ImageContent)[]`. A bare\n * string becomes a single text block (the legacy shape); a part array maps\n * 1:1 — pi's content blocks have the same field names.\n */\nfunction toPiContent(\n out: ToolOutput,\n): Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }> {\n if (typeof out === 'string') return [{ type: 'text', text: out }]\n return out.map((p) =>\n p.type === 'text'\n ? { type: 'text', text: p.text }\n : { type: 'image', data: p.data, mimeType: p.mimeType },\n )\n}\n\nexport interface BazilionCustomToolsOpts {\n agent: ResolvedAgent\n memory: MemoryBackend\n /** If provided, enables inter-agent messaging tools. */\n messagingHost?: MessagingHost\n /** If provided, enables the `user_md_append` tool. */\n userMdHost?: UserMdHost\n /** If provided, enables the `browser_*` tools (proxied to the daemon pool). */\n browserHost?: BrowserHost\n /** If provided alongside `mcpTools`, enables the discovered MCP proxy tools. */\n mcpHost?: McpHost\n /** MCP tools discovered daemon-side, exposed as proxy tools. */\n mcpTools?: InjectedMcpTool[]\n /** If provided, enables the `deliver_file` tool (emits a `file` event). */\n fileSink?: FileSink\n /** Merged env (process.env + secrets). */\n env?: NodeJS.ProcessEnv\n}\n\nexport interface ProtectedBazilionCustomToolsOpts {\n agent: ResolvedAgent\n memory: MemoryBackend\n messagingHost: MessagingHost\n userMdHost: UserMdHost\n fileSink: FileSink\n}\n\n/**\n * Build the list of Bazilion-specific custom tools in the shape pi expects.\n *\n * Excludes file-I/O tools (`read`/`bash`/`edit`/`write`/`grep`/`find`/`ls`) —\n * those come from pi's `createCodingTools(cwd, …)` now that we've adopted the\n * richer toolset. Also excludes the legacy `workspace_list/read/write`\n * triumvirate: pi's tools work against a single cwd (the agent's default\n * workspace), and mounted non-default workspaces are reachable via absolute\n * paths through pi's `bash`/`read`/`edit`.\n */\nexport function createBazilionCustomTools(opts: BazilionCustomToolsOpts): ToolDefinition[] {\n const handlers: ToolHandler[] = [\n ...memoryTools(opts.memory),\n ...homeTools(opts.agent.agent.dir),\n bootstrapTool(opts.agent.agent.dir),\n ...webTools({ env: opts.env }),\n ]\n if (opts.messagingHost) {\n handlers.push(...messagingTools(opts.messagingHost, opts.agent.agent.id))\n }\n if (opts.userMdHost) {\n handlers.push(...userMdTools(opts.userMdHost, opts.agent.team.id))\n }\n if (opts.browserHost) {\n handlers.push(...browserTools(opts.browserHost, opts.agent.agent.id))\n }\n if (opts.mcpHost && opts.mcpTools && opts.mcpTools.length > 0) {\n handlers.push(...mcpProxyTools(opts.mcpHost, opts.mcpTools))\n }\n if (opts.fileSink) {\n handlers.push(deliverFileTool(opts.agent.team.path, opts.fileSink))\n }\n return handlers.map(ourToolToPiTool)\n}\n\n/**\n * Closed normal-turn capability set. No environment is accepted, so search,\n * Firecrawl, browser, MCP, and provider/tool credentials cannot be restored by\n * configuration inside a protected worker.\n */\nexport function createProtectedBazilionCustomTools(\n opts: ProtectedBazilionCustomToolsOpts,\n): ToolDefinition[] {\n const handlers: ToolHandler[] = [\n ...memoryTools(opts.memory),\n ...homeTools(opts.agent.agent.dir),\n bootstrapTool(opts.agent.agent.dir),\n protectedWebFetchTool(),\n ...messagingTools(opts.messagingHost, opts.agent.agent.id),\n ...userMdTools(opts.userMdHost, opts.agent.team.id),\n deliverFileTool(opts.agent.team.path, opts.fileSink),\n ]\n return handlers.map(ourToolToPiTool)\n}\n","import { existsSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { ToolHandler } from './types.ts'\n\nexport function bootstrapTool(agentDir: string): ToolHandler {\n return {\n def: {\n name: 'bootstrap_done',\n description:\n 'Call this once you have finished your bootstrap conversation (introduced yourself, learned the user, updated IDENTITY.md). Removes BOOTSTRAP.md so it does not appear in future sessions.',\n parameters: { type: 'object', properties: {} },\n },\n async invoke() {\n const path = join(agentDir, 'BOOTSTRAP.md')\n if (existsSync(path)) {\n rmSync(path)\n return 'BOOTSTRAP.md removed. Bootstrap is complete.'\n }\n return 'BOOTSTRAP.md was already removed.'\n },\n }\n}\n","// Browser automation tools (worker-side definitions).\n//\n// These are thin proxies: each tool's `invoke` forwards to the daemon-side\n// Playwright pool over IPC via the injected `BrowserHost`. The browser session\n// (stateful, persistent across turns) lives entirely in the daemon — see\n// `lib/browser/pool.ts`. Results are multimodal: snapshots/console/network come\n// back as text, `browser_take_screenshot` as a base64 image.\n//\n// Perception is accessibility-tree-first. `browser_snapshot` returns an aria\n// tree with `[ref=eN]` element refs; every interaction targets an element by\n// that `ref`. No vision model is needed for routine automation — screenshots\n// are the exception, not the rule.\n\nimport type { BrowserHost } from '../worker/ipc-protocol.ts'\nimport type { ToolHandler } from './types.ts'\n\nconst REF_DESC = 'Element ref from the latest browser_snapshot, e.g. \"e5\".'\n\n/**\n * Build the `browser_*` tool suite bound to one agent's session. Each tool\n * forwards to `host.invoke(agentId, <action>, args)`.\n */\nexport function browserTools(host: BrowserHost, agentId: string): ToolHandler[] {\n const proxy = (\n name: string,\n action: string,\n description: string,\n properties: Record<string, unknown>,\n required: string[] = [],\n ): ToolHandler => ({\n def: {\n name,\n description,\n parameters: { type: 'object', properties, required, additionalProperties: false },\n },\n invoke: (args) => host.invoke(agentId, action, args),\n })\n\n return [\n proxy(\n 'browser_navigate',\n 'navigate',\n 'Open a URL in the browser and return an accessibility snapshot of the loaded page. Starts a persistent browser session that survives across turns (cookies/logins carry over).',\n { url: { type: 'string', description: 'Absolute URL to navigate to.' } },\n ['url'],\n ),\n proxy(\n 'browser_snapshot',\n 'snapshot',\n 'Capture the current page as an accessibility tree (YAML) with [ref=eN] element references. This is the primary way to \"see\" the page — prefer it over screenshots. Use the refs with browser_click/type/etc.',\n {},\n ),\n proxy(\n 'browser_click',\n 'click',\n 'Click an element identified by its snapshot ref. Returns a fresh snapshot.',\n { ref: { type: 'string', description: REF_DESC } },\n ['ref'],\n ),\n proxy(\n 'browser_type',\n 'type',\n 'Type text into an input/textarea identified by its ref. Set submit=true to press Enter afterwards. Returns a fresh snapshot.',\n {\n ref: { type: 'string', description: REF_DESC },\n text: { type: 'string', description: 'Text to type into the field.' },\n submit: { type: 'boolean', description: 'Press Enter after typing.' },\n },\n ['ref', 'text'],\n ),\n proxy(\n 'browser_hover',\n 'hover',\n 'Hover the pointer over an element identified by its ref. Returns a fresh snapshot.',\n { ref: { type: 'string', description: REF_DESC } },\n ['ref'],\n ),\n proxy(\n 'browser_select',\n 'select',\n 'Select option(s) in a <select> element identified by its ref.',\n {\n ref: { type: 'string', description: REF_DESC },\n values: {\n type: 'array',\n items: { type: 'string' },\n description: 'Option values to select.',\n },\n },\n ['ref', 'values'],\n ),\n proxy(\n 'browser_fill_form',\n 'fill_form',\n 'Fill multiple form fields in one call. Each field is { ref, value }.',\n {\n fields: {\n type: 'array',\n description: 'Fields to fill.',\n items: {\n type: 'object',\n properties: {\n ref: { type: 'string', description: REF_DESC },\n value: { type: 'string' },\n },\n required: ['ref', 'value'],\n },\n },\n },\n ['fields'],\n ),\n proxy(\n 'browser_press_key',\n 'press_key',\n 'Press a keyboard key (e.g. \"Enter\", \"Escape\", \"ArrowDown\", \"Control+A\").',\n { key: { type: 'string', description: 'Key or chord to press.' } },\n ['key'],\n ),\n proxy(\n 'browser_go_back',\n 'go_back',\n 'Navigate back to the previous page in history. Returns a fresh snapshot.',\n {},\n ),\n proxy(\n 'browser_tabs',\n 'tabs',\n 'Manage tabs. op=\"list\" lists open tabs; \"new\" opens a tab (optional url); \"select\" focuses tab at index; \"close\" closes tab at index (defaults to active).',\n {\n op: { type: 'string', enum: ['list', 'new', 'select', 'close'] },\n index: { type: 'number', description: 'Tab index for select/close.' },\n url: { type: 'string', description: 'URL to open for op=new.' },\n },\n ['op'],\n ),\n proxy(\n 'browser_take_screenshot',\n 'take_screenshot',\n 'Take a PNG screenshot of the current page (returned as an image). Use only for visual verification or canvas/pixel content the accessibility snapshot cannot represent — prefer browser_snapshot for interaction.',\n { full_page: { type: 'boolean', description: 'Capture the full scrollable page.' } },\n ),\n proxy(\n 'browser_console',\n 'console',\n 'Return recent browser console messages (logs, warnings, errors).',\n {},\n ),\n proxy(\n 'browser_network',\n 'network',\n 'Return recent network requests issued by the page (method, status, URL).',\n {},\n ),\n ]\n}\n","// `deliver_file` — the agent's outbound document channel.\n//\n// The agent produces a file in its workspace (write a report, render a chart,\n// zip something up) and calls deliver_file(path) to hand it to the user. The\n// file lives on the daemon's filesystem (the worker is a subprocess on the same\n// host), so we read it directly, base64-encode it, and emit it as a `file`\n// SessionEvent via an injected sink — the worker writes that frame to stdout,\n// the daemon forwards it, and each client surfaces it (web download link,\n// Telegram document, CLI save-to-disk).\n\nimport {\n closeSync,\n constants,\n fstatSync,\n openSync,\n readFileSync,\n realpathSync,\n statSync,\n} from 'node:fs'\nimport { basename, extname, isAbsolute, relative, resolve, sep } from 'node:path'\nimport type { ToolHandler } from './types.ts'\n\nconst MAX_DELIVER_BYTES = 25 * 1024 * 1024\n\nconst MIME: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.txt': 'text/plain',\n '.md': 'text/markdown',\n '.csv': 'text/csv',\n '.json': 'application/json',\n '.html': 'text/html',\n '.zip': 'application/zip',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n}\n\nexport type FileSink = (file: { name: string; mimeType: string; data: string }) => void\n\nfunction isWithin(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n}\n\nexport function deliverFileTool(cwd: string, sink: FileSink): ToolHandler {\n const workspacePath = resolve(cwd)\n let workspaceRealPath: string | null = null\n try {\n workspaceRealPath = realpathSync(workspacePath)\n } catch {\n // Keep tool construction side-effect free. Invocation reports the broken\n // workspace only if the agent actually tries to deliver a file.\n }\n\n return {\n def: {\n name: 'deliver_file',\n description:\n 'Send a file from your workspace to the user so they can download it (web), receive it as a document (Telegram), or save it (CLI). Use this to deliver reports, exports, or any artifact you produced. Max 25 MB.',\n parameters: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Path to a file, relative to your workspace.',\n },\n },\n required: ['path'],\n additionalProperties: false,\n },\n },\n async invoke(args) {\n const p = String(args.path ?? '')\n if (!p) throw new Error('deliver_file: path is required')\n if (isAbsolute(p)) {\n throw new Error(`deliver_file: path must stay within the workspace: ${p}`)\n }\n\n const abs = resolve(workspacePath, p)\n if (!isWithin(workspacePath, abs)) {\n throw new Error(`deliver_file: path must stay within the workspace: ${p}`)\n }\n\n if (!workspaceRealPath) {\n throw new Error('deliver_file: workspace is unavailable')\n }\n\n let real: string\n let expected: ReturnType<typeof statSync>\n try {\n real = realpathSync(abs)\n expected = statSync(real)\n } catch {\n throw new Error(`deliver_file: no such file: ${p}`)\n }\n\n if (!isWithin(workspaceRealPath, real)) {\n throw new Error(`deliver_file: path must stay within the workspace: ${p}`)\n }\n\n // Use a no-follow descriptor after canonical validation. Besides making\n // the regular-file check explicit, this closes the common final-symlink\n // swap window between realpath/stat/read. Non-blocking mode prevents a\n // non-regular path such as a FIFO from hanging before fstat can reject it.\n let fd: number\n try {\n fd = openSync(real, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK)\n } catch {\n throw new Error(`deliver_file: no such file: ${p}`)\n }\n\n let data: string\n try {\n const stat = fstatSync(fd)\n if (!stat.isFile()) {\n throw new Error(`deliver_file: not a regular file: ${p}`)\n }\n if (stat.dev !== expected.dev || stat.ino !== expected.ino) {\n throw new Error(`deliver_file: path changed during validation: ${p}`)\n }\n let confirmedReal: string\n try {\n confirmedReal = realpathSync(abs)\n } catch {\n throw new Error(`deliver_file: path changed during validation: ${p}`)\n }\n if (confirmedReal !== real) {\n throw new Error(`deliver_file: path changed during validation: ${p}`)\n }\n if (stat.size > MAX_DELIVER_BYTES) {\n throw new Error(\n `deliver_file: \"${basename(abs)}\" is too large (${(stat.size / 1024 / 1024).toFixed(1)} MB > 25 MB)`,\n )\n }\n data = readFileSync(fd).toString('base64')\n } finally {\n closeSync(fd)\n }\n\n const name = basename(abs)\n const mimeType = MIME[extname(abs).toLowerCase()] ?? 'application/octet-stream'\n sink({ name, mimeType, data })\n return `Delivered \"${name}\" (${mimeType}) to the user.`\n },\n }\n}\n","import {\n closeSync,\n constants,\n fstatSync,\n lstatSync,\n openSync,\n readdirSync,\n writeFileSync,\n} from 'node:fs'\nimport { join } from 'node:path'\nimport { noFollowFlag, readRegularFileNoFollow, resolveRealDirectory } from '../safe-files.ts'\nimport type { ToolHandler } from './types.ts'\n\n// Files the agent may read/write in its private home directory.\n// BOOTSTRAP.md is readable but not writable — its lifecycle belongs to\n// the `bootstrap_done` tool, not `home_write`.\nconst HOME_FILES_READABLE = [\n 'IDENTITY.md',\n 'SOUL.md',\n 'BOOTSTRAP.md',\n 'AGENTS.md',\n 'TOOLS.md',\n] as const\n\nconst HOME_FILES_WRITABLE = ['IDENTITY.md', 'SOUL.md', 'AGENTS.md', 'TOOLS.md'] as const\n\nexport type PrivateHomeFile = (typeof HOME_FILES_READABLE)[number]\nexport type PrivateHomeSnapshot = Partial<Record<PrivateHomeFile, string>>\n\n/**\n * Read the complete fixed Agent-document surface without following links.\n * Protected preparation calls this before spawn; prompt construction uses the\n * returned bytes so a planted fixed-name symlink can never inject host data.\n */\nexport function loadPrivateHomeSnapshot(agentDir: string): PrivateHomeSnapshot {\n const homeRoot = resolvePrivateHomeRoot(agentDir)\n const snapshot: PrivateHomeSnapshot = {}\n for (const file of HOME_FILES_READABLE) {\n const path = privateHomeFile(homeRoot, file)\n try {\n snapshot[file] = readRegularFileNoFollow(path)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue\n throw new Error(`private Agent home is unsafe at ${file}`)\n }\n }\n return snapshot\n}\n\nexport function homeTools(agentDir: string): ToolHandler[] {\n const homeRoot = resolvePrivateHomeRoot(agentDir)\n return [\n {\n def: {\n name: 'home_read',\n description:\n 'Read one of your own home files — your identity, soul, or behaviour rules. These files are private to you and are also injected into your system prompt; read them when you need to quote exact wording or check current state.',\n parameters: {\n type: 'object',\n properties: {\n file: { type: 'string', enum: [...HOME_FILES_READABLE] },\n },\n required: ['file'],\n },\n },\n async invoke(args) {\n const file = String(args.file ?? '')\n if (!HOME_FILES_READABLE.includes(file as (typeof HOME_FILES_READABLE)[number])) {\n throw new Error(\n `home_read: \"file\" must be one of ${HOME_FILES_READABLE.join(', ')}; got \"${file}\"`,\n )\n }\n const path = privateHomeFile(homeRoot, file)\n try {\n return readRegularFileNoFollow(path)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`home_read: could not read ${file}: ${msg}`)\n }\n },\n },\n {\n def: {\n name: 'home_write',\n description:\n \"Overwrite one of your own home files. Use this to update your name, personality, or persistent self-definition. Do NOT use this for work output (use `write` / `edit` — those land in your team's shared directory) or for facts you want to remember (use `memory_write`). BOOTSTRAP.md is not writable here; call `bootstrap_done` to retire it.\",\n parameters: {\n type: 'object',\n properties: {\n file: { type: 'string', enum: [...HOME_FILES_WRITABLE] },\n content: { type: 'string', description: 'new full file content' },\n },\n required: ['file', 'content'],\n },\n },\n async invoke(args) {\n const file = String(args.file ?? '')\n if (!HOME_FILES_WRITABLE.includes(file as (typeof HOME_FILES_WRITABLE)[number])) {\n throw new Error(\n `home_write: \"file\" must be one of ${HOME_FILES_WRITABLE.join(', ')}; got \"${file}\"`,\n )\n }\n const content = typeof args.content === 'string' ? args.content : ''\n const path = privateHomeFile(homeRoot, file)\n let fd: number | undefined\n try {\n rejectExistingSymlinkOrNonFile(path)\n fd = openSync(\n path,\n constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | noFollowFlag(),\n 0o600,\n )\n if (!fstatSync(fd).isFile()) throw new Error('target is not a regular file')\n writeFileSync(fd, content, 'utf8')\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`home_write: could not write ${file}: ${msg}`)\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n return `wrote ${file} (${Buffer.byteLength(content, 'utf8')} bytes)`\n },\n },\n {\n def: {\n name: 'home_list',\n description: 'List your home files with their sizes.',\n parameters: { type: 'object', properties: {} },\n },\n async invoke() {\n const entries: string[] = []\n for (const file of HOME_FILES_READABLE) {\n const path = privateHomeFile(homeRoot, file)\n try {\n const s = lstatSync(path)\n if (s.isSymbolicLink() || !s.isFile()) continue\n entries.push(`${file} (${s.size}b)`)\n } catch {\n // file not present — skip\n }\n }\n if (entries.length === 0) {\n const dirEntries = (() => {\n try {\n return readdirSync(homeRoot)\n } catch {\n return []\n }\n })()\n return `(no home files found; agent dir contains: ${dirEntries.join(', ') || 'nothing'})`\n }\n return entries.join('\\n')\n },\n },\n ]\n}\n\nfunction resolvePrivateHomeRoot(agentDir: string): string {\n try {\n return resolveRealDirectory(agentDir)\n } catch {\n throw new Error('home tools require a real private Agent directory')\n }\n}\n\nfunction privateHomeFile(homeRoot: string, file: string): string {\n // `file` has already passed a closed enum check. Keeping path construction\n // here makes the fixed-root boundary explicit for every filesystem call.\n return join(homeRoot, file)\n}\n\nfunction rejectSymlinkOrNonFile(path: string): void {\n const entry = lstatSync(path)\n if (entry.isSymbolicLink()) throw new Error('symbolic links are not allowed')\n if (!entry.isFile()) throw new Error('target is not a regular file')\n}\n\nfunction rejectExistingSymlinkOrNonFile(path: string): void {\n try {\n rejectSymlinkOrNonFile(path)\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code\n if (code === 'ENOENT') return\n throw error\n }\n}\n","// MCP proxy tools (worker-side definitions).\n//\n// The daemon discovers each enabled MCP server's tools (`tools/list`) before\n// the worker spawns and ships them in `WorkerInput.mcpTools`. Here we turn each\n// into a Bazilion `ToolHandler` whose `invoke` forwards to the daemon-side\n// connection pool over IPC via the injected `McpHost`. The LLM sees the\n// namespaced name (`mcp__<server>__<tool>`); the proxy carries the original\n// `serverId` + `rawName` so the daemon routes the `tools/call` correctly.\n\nimport type { InjectedMcpTool, McpHost } from '../worker/ipc-protocol.ts'\nimport type { ToolHandler } from './types.ts'\n\n/** Build proxy tools for every discovered MCP tool. */\nexport function mcpProxyTools(host: McpHost, tools: InjectedMcpTool[]): ToolHandler[] {\n return tools.map((t) => ({\n def: {\n name: t.toolName,\n description: t.description,\n parameters: t.inputSchema,\n },\n invoke: (args) => host.invoke(t.serverId, t.rawName, args),\n }))\n}\n","import type { MemoryBackend } from '../memory/types.ts'\nimport type { ToolHandler } from './types.ts'\n\nexport function memoryTools(memory: MemoryBackend): ToolHandler[] {\n return [\n {\n def: {\n name: 'memory_write',\n description:\n 'Write or update a memory note in the GROUP-SHARED memory. All agents in this team can read what you write. Use it for project knowledge, codebase notes, decisions, and findings — anything other agents in the team should benefit from. For personal notes about yourself (preferences, persona) use `home_write` on IDENTITY.md. For STABLE facts about the human you\\'re working with (their preferences, role, working hours, how they like to be addressed) use `user_md_get` then `user_md_write` — those land in every agent\\'s system prompt directly. Key is a path-like string with a markdown extension, e.g. \"auth-flow.md\" or \"decisions/2026-05-migration.md\".',\n parameters: {\n type: 'object',\n properties: {\n key: { type: 'string', description: 'memory key (relative path)' },\n content: { type: 'string', description: 'note content (plain text or markdown)' },\n },\n required: ['key', 'content'],\n },\n },\n async invoke(args) {\n const key = String(args.key ?? '')\n const content = String(args.content ?? '')\n if (!key) throw new Error('memory_write: \"key\" is required')\n const entry = await memory.write(key, content)\n return `wrote ${entry.key} (${entry.content.length} bytes)`\n },\n },\n {\n def: {\n name: 'memory_search',\n description:\n 'Search the team-shared memory by substring. Returns matching entry keys with short snippets around the match.',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string' },\n limit: { type: 'number', description: 'max results (default 10)' },\n },\n required: ['query'],\n },\n },\n async invoke(args) {\n const query = String(args.query ?? '')\n if (!query) throw new Error('memory_search: \"query\" is required')\n const limit = typeof args.limit === 'number' ? args.limit : 10\n const hits = await memory.search(query, { limit })\n if (hits.length === 0) return 'no matches'\n return hits.map((h) => `${h.key}: ${h.snippet.replaceAll('\\n', ' ')}`).join('\\n')\n },\n },\n {\n def: {\n name: 'memory_read',\n description: 'Read a single entry from the team-shared memory by key.',\n parameters: {\n type: 'object',\n properties: { key: { type: 'string' } },\n required: ['key'],\n },\n },\n async invoke(args) {\n const key = String(args.key ?? '')\n if (!key) throw new Error('memory_read: \"key\" is required')\n const entry = await memory.read(key)\n return entry.content\n },\n },\n {\n def: {\n name: 'memory_list',\n description: 'List every entry in the team-shared memory with its byte size.',\n parameters: { type: 'object', properties: {} },\n },\n async invoke() {\n const all = await memory.list()\n if (all.length === 0) return '(empty)'\n return all.map((e) => `${e.key} (${e.content.length}b)`).join('\\n')\n },\n },\n ]\n}\n","import type { MessagingHost } from '../worker/ipc-protocol.ts'\nimport type { ToolHandler } from './types.ts'\n\ninterface MessagePayload {\n text: string\n [key: string]: unknown\n}\n\nfunction decodeText(payload: string): string {\n try {\n const parsed = JSON.parse(payload) as MessagePayload\n if (parsed && typeof parsed.text === 'string') return parsed.text\n } catch {\n // not JSON; return raw payload\n }\n return payload\n}\n\nexport function messagingTools(host: MessagingHost, fromAgentId: string): ToolHandler[] {\n return [\n {\n def: {\n name: 'send_message',\n description:\n 'Send a message to another agent. Use the recipient\\'s agent id (UUID). Use this ONLY for things the recipient needs to ACT on: delegating a task, asking a peer for information you cannot get yourself, escalating a decision. Do NOT use it for status updates or to announce changes to team-shared resources — USER.md and the team memory backend both propagate to every agent in the team automatically on their next turn, so messages like \"I updated USER.md\" or \"I wrote a new memory note\" are pure noise and will trigger an inbox-wake loop on the recipient.',\n parameters: {\n type: 'object',\n properties: {\n to: { type: 'string', description: 'Recipient agent id' },\n text: { type: 'string', description: 'Message text' },\n reply_to: {\n type: 'string',\n description: 'Optional: id of the message you are replying to',\n },\n },\n required: ['to', 'text'],\n },\n },\n async invoke(args) {\n const to = String(args.to ?? '')\n const text = String(args.text ?? '')\n if (!to) throw new Error('send_message: \"to\" is required')\n if (!text) throw new Error('send_message: \"text\" is required')\n if (!(await host.agentExists(to))) {\n throw new Error(`send_message: agent not found: ${to}`)\n }\n const replyTo = typeof args.reply_to === 'string' ? args.reply_to : null\n const { messageId } = await host.sendMessage({\n from: fromAgentId,\n to,\n payload: JSON.stringify({ text }),\n replyTo,\n })\n return `sent message ${messageId}`\n },\n },\n {\n def: {\n name: 'approval_status',\n description:\n 'Check the current status of a communication approval request that you created.',\n parameters: {\n type: 'object',\n properties: {\n approval_id: {\n type: 'string',\n description: 'Approval request id returned by a protected communication attempt',\n },\n },\n required: ['approval_id'],\n },\n },\n async invoke(args) {\n const approvalId = String(args.approval_id ?? '')\n if (!approvalId) throw new Error('approval_status: \"approval_id\" is required')\n const approval = await host.approvalStatus(fromAgentId, approvalId)\n if (!approval) throw new Error(`approval_status: approval not found: ${approvalId}`)\n const decision = approval.decidedBy\n ? `; decided by ${approval.decidedBy}${approval.decisionReason ? ` (${approval.decisionReason})` : ''}`\n : ''\n const failure = approval.deliveryError ? `; delivery error: ${approval.deliveryError}` : ''\n return `approval ${approval.id}: ${approval.status}; expires ${approval.expiresAt}${decision}${failure}`\n },\n },\n {\n def: {\n name: 'read_inbox',\n description: 'Read messages addressed to you. Marks unread messages as read by default.',\n parameters: {\n type: 'object',\n properties: {\n include_read: {\n type: 'boolean',\n description: 'Also include already-read messages (default false)',\n },\n },\n },\n },\n async invoke(args) {\n const includeRead = args.include_read === true\n const messages = await host.listInbox(fromAgentId, { unreadOnly: !includeRead })\n if (messages.length === 0) return '(no messages)'\n const lines: string[] = []\n for (const m of messages) {\n lines.push(`from ${m.fromAgentId} [${m.id}]: ${decodeText(m.payload)}`)\n if (!m.readAt) await host.markRead(m.id)\n }\n return lines.join('\\n')\n },\n },\n {\n def: {\n name: 'wait_for_reply',\n description:\n 'Block until a reply to a message you sent arrives, or until the timeout expires.',\n parameters: {\n type: 'object',\n properties: {\n message_id: {\n type: 'string',\n description: 'id of the message you sent',\n },\n timeout_ms: {\n type: 'number',\n description: 'max wait in milliseconds (default 30000)',\n },\n poll_ms: {\n type: 'number',\n description: 'poll interval in milliseconds (default 200)',\n },\n },\n required: ['message_id'],\n },\n },\n async invoke(args) {\n const messageId = String(args.message_id ?? '')\n if (!messageId) throw new Error('wait_for_reply: \"message_id\" is required')\n const timeout = typeof args.timeout_ms === 'number' ? args.timeout_ms : 30000\n const poll = typeof args.poll_ms === 'number' ? args.poll_ms : 200\n const start = Date.now()\n while (Date.now() - start < timeout) {\n const replies = await host.findReplies(fromAgentId, messageId)\n if (replies.length > 0) {\n const r = replies[0]\n if (r) {\n if (!r.readAt) await host.markRead(r.id)\n return `reply from ${r.fromAgentId} [${r.id}]: ${decodeText(r.payload)}`\n }\n }\n await new Promise((r) => setTimeout(r, poll))\n }\n return `no reply within ${timeout}ms`\n },\n },\n ]\n}\n","// Team-shared USER.md read/write surface for agents.\n//\n// USER.md is inlined into every agent's system prompt every turn (capped at\n// 12 KB on the daemon-side host). Agents previously could only read it; now\n// they can also update it via read-modify-write so they can correct stale\n// facts, not just stack new ones. Concurrency between multiple agents in\n// the same team is handled via optimistic etag checks — see\n// `lib/user-md-host.ts` for the full rationale.\n//\n// Two tools:\n// - `user_md_get` — returns current content + an etag.\n// - `user_md_write` — replaces content; requires `if_match` to equal the\n// most recent etag, else returns a conflict.\n//\n// Project knowledge (codebase notes, decisions) still goes to `memory_write`.\n// Personal notes about the agent itself go to `home_write` on IDENTITY.md.\n\nimport type { UserMdHost } from '../worker/ipc-protocol.ts'\nimport type { ToolHandler } from './types.ts'\n\nexport function userMdTools(host: UserMdHost, teamId: string): ToolHandler[] {\n return [\n {\n def: {\n name: 'user_md_get',\n description:\n 'Read the team-shared USER.md (facts every agent in the team knows about the human). Returns the current content followed by an `etag:` line — you MUST pass that etag back as `if_match` on the next `user_md_write` so the daemon can detect concurrent edits by other agents in the team. Always call this immediately before any `user_md_write`.',\n parameters: {\n type: 'object',\n properties: {},\n },\n },\n async invoke() {\n const { content, etag } = await host.get(teamId)\n const body = content.length > 0 ? content : '(USER.md is empty)'\n return `${body}\\n\\n---\\netag: ${etag}`\n },\n },\n {\n def: {\n name: 'user_md_write',\n description:\n 'Replace the team-shared USER.md with new content. Use this for STABLE user-specific facts (preferences, role, working hours, how the human likes to be addressed). MANDATORY workflow: (1) call `user_md_get` first, (2) integrate your change into the full content preserving everything unrelated, (3) call `user_md_write` with the merged content and the etag you got from `user_md_get`. If another agent in the team wrote to USER.md between your get and write, this returns an etag-mismatch error — just call `user_md_get` again and retry the merge. The full result must fit under 12 KB. For project knowledge use `memory_write`; for notes about yourself use `home_write` on IDENTITY.md.',\n parameters: {\n type: 'object',\n properties: {\n content: {\n type: 'string',\n description:\n 'Full new contents of USER.md (this is a complete replacement, NOT an append). Include everything you want to keep.',\n },\n if_match: {\n type: 'string',\n description:\n 'The etag returned by your most recent `user_md_get`. The write fails if USER.md changed in the meantime.',\n },\n },\n required: ['content', 'if_match'],\n },\n },\n async invoke(args) {\n const content = String(args.content ?? '')\n const ifMatch = String(args.if_match ?? '')\n if (!ifMatch) {\n throw new Error(\n 'user_md_write: \"if_match\" is required — call user_md_get first to obtain the current etag.',\n )\n }\n const { etag, totalBytes } = await host.write(teamId, content, ifMatch)\n return `wrote USER.md (${totalBytes} bytes, new etag: ${etag})`\n },\n },\n ]\n}\n","import { fetch as undiciFetch } from 'undici'\nimport type { ToolHandler } from './types.ts'\nimport {\n type ExtractMode,\n type ExtractResult,\n extractReadable,\n markdownToPlain,\n} from './web-extract.ts'\nimport { guardedFetch, SsrFBlockedError } from './web-ssrf.ts'\n\nconst DEFAULT_USER_AGENT =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'\nconst DEFAULT_CACHE_TTL_MS = 15 * 60_000\nconst DEFAULT_CACHE_MAX = 100\nconst DEFAULT_MAX_LENGTH = 20_000\nconst DEFAULT_TIMEOUT_MS = 30_000\n// Raw body cap applied before parsing. Multi-megabyte HTML (Mintlify/Next.js\n// docs sites, ad-heavy pages) can stall or OOM Readability+linkedom's\n// synchronous DOM walk. 3 MB comfortably covers real articles; anything\n// larger gets truncated and the caller sees a note in the output.\nconst DEFAULT_MAX_BODY_BYTES = 3 * 1024 * 1024\n// If the primary Readability path returns fewer than this many characters\n// from a 2xx HTML response, and FIRECRAWL_API_KEY is configured, retry the\n// fetch via Firecrawl which renders JS server-side. Threshold is heuristic:\n// real articles routinely exceed 200 chars; pages that bottom out below it\n// are almost always JS-shell pages where extraction collapsed.\nconst FIRECRAWL_FALLBACK_THRESHOLD = 200\nconst FIRECRAWL_DEFAULT_URL = 'https://api.firecrawl.dev'\n\ninterface SearchResult {\n title: string\n url: string\n snippet: string\n}\n\nfunction stripHtml(s: string): string {\n return s\n .replace(/<[^>]*>/g, '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/ /g, ' ')\n .trim()\n}\n\n// --- search backends ---\n\nasync function braveSearch(\n query: string,\n limit: number,\n apiKey: string,\n fetchFn: typeof fetch,\n): Promise<SearchResult[]> {\n const params = new URLSearchParams({ q: query, count: String(limit) })\n const res = await fetchFn(`https://api.search.brave.com/res/v1/web/search?${params}`, {\n headers: {\n accept: 'application/json',\n 'accept-encoding': 'gzip',\n 'x-subscription-token': apiKey,\n },\n })\n if (!res.ok) throw new Error(`Brave Search: ${res.status} ${await res.text()}`)\n const data = (await res.json()) as {\n web?: { results?: { title?: string; url?: string; description?: string }[] }\n }\n return (data.web?.results ?? []).slice(0, limit).map((r) => ({\n title: r.title ?? '',\n url: r.url ?? '',\n snippet: r.description ? stripHtml(r.description) : '',\n }))\n}\n\nasync function searxngSearch(\n query: string,\n limit: number,\n baseURL: string,\n fetchFn: typeof fetch,\n): Promise<SearchResult[]> {\n const params = new URLSearchParams({ q: query, format: 'json' })\n const res = await fetchFn(`${baseURL}/search?${params}`, {\n headers: { accept: 'application/json' },\n })\n if (!res.ok) throw new Error(`SearXNG: ${res.status} ${await res.text()}`)\n const data = (await res.json()) as {\n results?: { title?: string; url?: string; content?: string }[]\n }\n return (data.results ?? []).slice(0, limit).map((r) => ({\n title: r.title ?? '',\n url: r.url ?? '',\n snippet: r.content ?? '',\n }))\n}\n\n// --- error formatting ---\n\n/**\n * Flatten an Error and its `cause` chain into a single readable string.\n * undici surfaces network failures as `TypeError: fetch failed` with the\n * real reason (UND_ERR_*, ECONNRESET, certificate errors, …) stashed in\n * `err.cause`; without unwrapping it the agent only ever sees \"fetch\n * failed\" and has no path to diagnose or work around the problem.\n */\nfunction describeError(err: unknown): string {\n if (!(err instanceof Error)) return String(err)\n const parts: string[] = []\n const seen = new Set<unknown>()\n let cur: unknown = err\n while (cur instanceof Error && !seen.has(cur)) {\n seen.add(cur)\n const code = (cur as { code?: string }).code\n parts.push(code ? `[${code}] ${cur.message}` : cur.message)\n cur = (cur as { cause?: unknown }).cause\n }\n return parts.join(' — cause: ')\n}\n\n// --- bounded body reader ---\n\n/**\n * Stream `res.body` and accumulate up to `maxBytes`, then truncate. Returns\n * the decoded text (using the charset from content-type, falling back to\n * UTF-8) and a flag the caller can surface to the agent so it knows the\n * page was larger than the cap.\n */\nasync function readBodyCapped(\n res: Response,\n maxBytes: number,\n): Promise<{ text: string; truncated: boolean }> {\n const ct = res.headers.get('content-type') ?? ''\n const charset = /charset=([^;]+)/i.exec(ct)?.[1]?.trim().toLowerCase() || 'utf-8'\n const decoder = new TextDecoder(charset, { fatal: false })\n const reader = res.body?.getReader()\n if (!reader) {\n const text = await res.text()\n if (text.length > maxBytes) return { text: text.slice(0, maxBytes), truncated: true }\n return { text, truncated: false }\n }\n const chunks: Uint8Array[] = []\n let total = 0\n let truncated = false\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (!value) continue\n if (total + value.byteLength > maxBytes) {\n const keep = maxBytes - total\n if (keep > 0) chunks.push(value.subarray(0, keep))\n truncated = true\n try {\n await reader.cancel()\n } catch {\n // body already settled\n }\n break\n }\n chunks.push(value)\n total += value.byteLength\n }\n const sum = chunks.reduce((s, c) => s + c.byteLength, 0)\n const merged = new Uint8Array(sum)\n let off = 0\n for (const c of chunks) {\n merged.set(c, off)\n off += c.byteLength\n }\n return { text: decoder.decode(merged), truncated }\n}\n\n// --- Firecrawl fallback ---\n\ninterface FirecrawlResponse {\n success?: boolean\n data?: {\n markdown?: string\n metadata?: { title?: string }\n }\n error?: string\n}\n\n/**\n * Last-resort HTML rendering via Firecrawl's `/v1/scrape` endpoint. Used\n * when the primary Readability path returns near-empty content from a 2xx\n * HTML response (JS-only shells, anti-bot walls, login redirects). Returns\n * `null` when Firecrawl is not configured or the call fails so the caller\n * falls back to the primary extraction unchanged — Firecrawl is best-effort,\n * never an error source.\n */\nasync function firecrawlScrape(\n url: string,\n mode: ExtractMode,\n env: NodeJS.ProcessEnv,\n fetchFn: typeof fetch,\n timeoutMs: number,\n): Promise<ExtractResult | null> {\n const apiKey = env.FIRECRAWL_API_KEY\n if (!apiKey) return null\n const base = (env.FIRECRAWL_URL ?? FIRECRAWL_DEFAULT_URL).replace(/\\/$/, '')\n const ac = new AbortController()\n const t = setTimeout(() => ac.abort(new Error('firecrawl timeout')), timeoutMs)\n try {\n const res = await fetchFn(`${base}/v1/scrape`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${apiKey}`,\n accept: 'application/json',\n },\n body: JSON.stringify({\n url,\n formats: ['markdown'],\n onlyMainContent: true,\n }),\n signal: ac.signal,\n })\n if (!res.ok) return null\n const body = (await res.json()) as FirecrawlResponse\n if (!body.success || !body.data?.markdown) return null\n const md = body.data.markdown\n const title = body.data.metadata?.title\n const text = mode === 'text' ? markdownToPlain(md) : md\n return title ? { text, title } : { text }\n } catch {\n return null\n } finally {\n clearTimeout(t)\n }\n}\n\n// --- per-URL cache ---\n\ninterface CacheEntry {\n value: ExtractResult\n expiresAt: number\n}\n\nfunction cacheGet(cache: Map<string, CacheEntry>, key: string): ExtractResult | null {\n const entry = cache.get(key)\n if (!entry) return null\n if (entry.expiresAt < Date.now()) {\n cache.delete(key)\n return null\n }\n // Refresh LRU position\n cache.delete(key)\n cache.set(key, entry)\n return entry.value\n}\n\nfunction cacheSet(\n cache: Map<string, CacheEntry>,\n key: string,\n value: ExtractResult,\n ttlMs: number,\n maxEntries: number,\n): void {\n cache.set(key, { value, expiresAt: Date.now() + ttlMs })\n while (cache.size > maxEntries) {\n const oldest = cache.keys().next().value\n if (!oldest) break\n cache.delete(oldest)\n }\n}\n\n// --- tool factory ---\n\nexport interface WebToolsOpts {\n fetchImpl?: typeof fetch\n env?: NodeJS.ProcessEnv\n /** Disable SSRF checks (tests against 127.0.0.1 mock servers). Default false. */\n allowPrivate?: boolean\n /** Cache TTL in ms. Default 15 min. */\n cacheTtlMs?: number\n /** Max cache entries. Default 100. */\n cacheMax?: number\n /** Fetch timeout in ms. Default 30s. */\n timeoutMs?: number\n /** Cap on raw response body bytes before parsing. Default 3 MB. */\n maxBodyBytes?: number\n /** Disable the Firecrawl fallback even when FIRECRAWL_API_KEY is set. */\n firecrawlDisabled?: boolean\n}\n\n/**\n * web_search backends (tried in order):\n * 1. Brave Search (env: BRAVE_API_KEY) — free tier, 2000 req/month\n * 2. SearXNG (env: SEARXNG_URL) — self-hosted, unlimited\n * 3. Error with setup instructions if neither is configured\n *\n * web_fetch: guarded fetch (SSRF-blocked private IPs) + Readability + markdown,\n * with per-URL in-memory cache (15 min TTL).\n */\nexport function webTools(opts?: WebToolsOpts): ToolHandler[] {\n return buildWebTools(opts, true)\n}\n\nexport type ProtectedWebFetchOpts = Omit<WebToolsOpts, 'env' | 'allowPrivate' | 'firecrawlDisabled'>\n\n/** The credential-free, public-address-only web capability for protected turns. */\nexport function protectedWebFetchTool(opts?: ProtectedWebFetchOpts): ToolHandler {\n const tool = buildWebTools(\n { ...opts, env: {}, allowPrivate: false, firecrawlDisabled: true },\n false,\n )[0]\n if (!tool) throw new Error('protected web_fetch tool construction failed')\n return tool\n}\n\nfunction buildWebTools(opts: WebToolsOpts | undefined, includeSearch: boolean): ToolHandler[] {\n // Default to undici 8's fetch so search-backend HTTP shares the same\n // stack as guardedFetch (and stays out of Node 24's bundled undici 7).\n const fetchFn = opts?.fetchImpl ?? (undiciFetch as unknown as typeof fetch)\n const env = opts?.env ?? process.env\n const allowPrivate = opts?.allowPrivate ?? false\n const cacheTtlMs = opts?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS\n const cacheMax = opts?.cacheMax ?? DEFAULT_CACHE_MAX\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const maxBodyBytes = opts?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n const firecrawlDisabled = opts?.firecrawlDisabled ?? false\n const cache = new Map<string, CacheEntry>()\n\n const tools: ToolHandler[] = []\n if (includeSearch) {\n tools.push({\n def: {\n name: 'web_search',\n description:\n 'Search the internet. Returns a list of titles, URLs, and snippets. Requires BRAVE_API_KEY or SEARXNG_URL to be configured.',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query' },\n limit: { type: 'number', description: 'Max results to return (default 5)' },\n },\n required: ['query'],\n },\n },\n async invoke(args) {\n const query = String(args.query ?? '')\n if (!query) throw new Error('web_search: query is required')\n const limit = typeof args.limit === 'number' ? args.limit : 5\n\n const braveKey = env.BRAVE_API_KEY\n if (braveKey) {\n const results = await braveSearch(query, limit, braveKey, fetchFn)\n return results.length === 0 ? 'no results' : formatResults(results)\n }\n\n const searxngUrl = env.SEARXNG_URL\n if (searxngUrl) {\n const results = await searxngSearch(query, limit, searxngUrl, fetchFn)\n return results.length === 0 ? 'no results' : formatResults(results)\n }\n\n throw new Error(\n 'web_search: no search backend configured. Set BRAVE_API_KEY (free at https://brave.com/search/api/) or SEARXNG_URL.',\n )\n },\n })\n }\n tools.push({\n def: {\n name: 'web_fetch',\n description: firecrawlDisabled\n ? 'Fetch a public HTTP or HTTPS URL and return its readable content. Private, loopback, link-local, metadata, and DNS-rebinding targets are blocked. HTML is extracted via Readability and converted to markdown. Results are cached for 15 minutes.'\n : 'Fetch a URL and return its readable content. HTML is extracted via Readability and converted to markdown. When the primary extraction returns near-empty content from a 2xx HTML page (typical of JS-only shells), the tool automatically retries via Firecrawl if FIRECRAWL_API_KEY is configured. Results are cached for 15 minutes.',\n parameters: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The URL to fetch (http or https)' },\n max_length: {\n type: 'number',\n description: 'Max characters to return (default 20000)',\n },\n extract_mode: {\n type: 'string',\n enum: ['markdown', 'text'],\n description: 'Output format for HTML pages. Default \"markdown\".',\n },\n },\n required: ['url'],\n },\n },\n async invoke(args) {\n const url = String(args.url ?? '')\n if (!url) throw new Error('web_fetch: url is required')\n const maxLen = typeof args.max_length === 'number' ? args.max_length : DEFAULT_MAX_LENGTH\n const mode: ExtractMode = args.extract_mode === 'text' ? 'text' : 'markdown'\n const cacheKey = `${mode}|${url}`\n\n const cached = cacheGet(cache, cacheKey)\n if (cached) return formatOutput(cached, maxLen)\n\n let result: GuardedFetchResultShape | null = null\n try {\n result = await guardedFetch({\n url,\n fetchImpl: opts?.fetchImpl,\n allowPrivate,\n timeoutMs,\n init: {\n headers: {\n 'user-agent': DEFAULT_USER_AGENT,\n accept: 'text/html,application/xhtml+xml,application/json,text/plain,*/*',\n 'accept-language': 'en-US,en;q=0.9',\n },\n },\n })\n if (!result.response.ok) {\n throw new Error(`${result.response.status} ${result.response.statusText}`)\n }\n const ct = result.response.headers.get('content-type') ?? ''\n const isHtml = ct.includes('text/html') || ct.includes('xhtml')\n const { text: body, truncated } = await readBodyCapped(result.response, maxBodyBytes)\n let extracted: ExtractResult\n if (isHtml) {\n extracted = extractReadable(body, result.finalUrl, mode)\n } else if (ct.includes('application/json')) {\n try {\n extracted = { text: JSON.stringify(JSON.parse(body), null, 2) }\n } catch {\n extracted = { text: body }\n }\n } else {\n extracted = { text: body }\n }\n // Firecrawl fallback: only meaningful for HTML pages where the\n // primary extractor collapsed. Skip JSON / plain-text bodies and\n // skip when extraction already produced something substantial.\n if (isHtml && !firecrawlDisabled && extracted.text.length < FIRECRAWL_FALLBACK_THRESHOLD) {\n const rescued = await firecrawlScrape(result.finalUrl, mode, env, fetchFn, timeoutMs)\n if (rescued) {\n extracted = {\n ...rescued,\n text: `${rescued.text}\\n\\n[content rendered via Firecrawl fallback — primary extraction returned ${extracted.text.length} chars]`,\n }\n }\n }\n if (truncated) {\n extracted = {\n ...extracted,\n text: `${extracted.text}\\n\\n[raw body truncated at ${maxBodyBytes} bytes before extraction — page exceeded the size cap]`,\n }\n }\n cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax)\n return formatOutput(extracted, maxLen)\n } catch (err) {\n if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`)\n throw new Error(`web_fetch: ${describeError(err)}`)\n } finally {\n if (result) await result.release()\n }\n },\n })\n return tools\n}\n\ntype GuardedFetchResultShape = Awaited<ReturnType<typeof guardedFetch>>\n\nfunction formatOutput(r: ExtractResult, maxLen: number): string {\n const body = r.title ? `# ${r.title}\\n\\n${r.text}` : r.text\n if (body.length > maxLen) return `${body.slice(0, maxLen)}\\n\\n[truncated at ${maxLen} chars]`\n return body\n}\n\nfunction formatResults(results: SearchResult[]): string {\n return results.map((r, i) => `${i + 1}. ${r.title}\\n ${r.url}\\n ${r.snippet}`).join('\\n\\n')\n}\n","import { Readability } from '@mozilla/readability'\nimport { parseHTML } from 'linkedom'\n\nexport type ExtractMode = 'markdown' | 'text'\n\nexport interface ExtractResult {\n text: string\n title?: string\n}\n\nfunction decodeEntities(value: string): string {\n return value\n .replace(/ /gi, ' ')\n .replace(/&/gi, '&')\n .replace(/"/gi, '\"')\n .replace(/'/gi, \"'\")\n .replace(/</gi, '<')\n .replace(/>/gi, '>')\n .replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))\n .replace(/&#(\\d+);/gi, (_, dec) => String.fromCharCode(Number.parseInt(dec, 10)))\n}\n\nfunction stripTags(value: string): string {\n return decodeEntities(value.replace(/<[^>]+>/g, ''))\n}\n\nfunction normalizeWhitespace(value: string): string {\n return value\n .replace(/\\r/g, '')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/[ \\t]{2,}/g, ' ')\n .trim()\n}\n\nfunction htmlToMarkdown(html: string): { text: string; title?: string } {\n const titleMatch = html.match(/<title[^>]*>([\\s\\S]*?)<\\/title>/i)\n const title = titleMatch ? normalizeWhitespace(stripTags(titleMatch[1] ?? '')) : undefined\n let text = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, '')\n .replace(/<nav[\\s\\S]*?<\\/nav>/gi, '')\n .replace(/<header[\\s\\S]*?<\\/header>/gi, '')\n .replace(/<footer[\\s\\S]*?<\\/footer>/gi, '')\n text = text.replace(/<a\\s+[^>]*href=[\"']([^\"']+)[\"'][^>]*>([\\s\\S]*?)<\\/a>/gi, (_, href, body) => {\n const label = normalizeWhitespace(stripTags(body))\n return label ? `[${label}](${href})` : href\n })\n text = text.replace(/<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi, (_, level, body) => {\n const n = Math.max(1, Math.min(6, Number.parseInt(level, 10)))\n return `\\n${'#'.repeat(n)} ${normalizeWhitespace(stripTags(body))}\\n`\n })\n text = text.replace(/<li[^>]*>([\\s\\S]*?)<\\/li>/gi, (_, body) => {\n const label = normalizeWhitespace(stripTags(body))\n return label ? `\\n- ${label}` : ''\n })\n text = text\n .replace(/<(br|hr)\\s*\\/?>/gi, '\\n')\n .replace(/<\\/(p|div|section|article|tr|ul|ol|table|blockquote)>/gi, '\\n')\n text = stripTags(text)\n return { text: normalizeWhitespace(text), title }\n}\n\nexport function markdownToPlain(md: string): string {\n let t = md\n t = t.replace(/!\\[[^\\]]*]\\([^)]+\\)/g, '')\n t = t.replace(/\\[([^\\]]+)]\\([^)]+\\)/g, '$1')\n t = t.replace(/```[\\s\\S]*?```/g, (block) =>\n block.replace(/```[^\\n]*\\n?/g, '').replace(/```/g, ''),\n )\n t = t.replace(/`([^`]+)`/g, '$1')\n t = t.replace(/^#{1,6}\\s+/gm, '')\n t = t.replace(/^\\s*[-*+]\\s+/gm, '')\n t = t.replace(/^\\s*\\d+\\.\\s+/gm, '')\n return normalizeWhitespace(t)\n}\n\n/**\n * Extract readable content from HTML using Readability, with a regex-based\n * markdown fallback when Readability can't identify an article.\n */\nexport function extractReadable(html: string, url: string, mode: ExtractMode): ExtractResult {\n const fallback = (): ExtractResult => {\n const r = htmlToMarkdown(html)\n return mode === 'text' ? { text: markdownToPlain(r.text), title: r.title } : r\n }\n try {\n const { document } = parseHTML(html)\n try {\n ;(document as unknown as { baseURI?: string }).baseURI = url\n } catch {\n // best-effort\n }\n type ReadabilityArg = ConstructorParameters<typeof Readability>[0]\n const parsed = new Readability(document as unknown as ReadabilityArg, {\n charThreshold: 0,\n }).parse()\n if (!parsed?.content) return fallback()\n const title = parsed.title || undefined\n if (mode === 'text') {\n const text = normalizeWhitespace(parsed.textContent ?? '')\n return text ? { text, title } : fallback()\n }\n const rendered = htmlToMarkdown(parsed.content)\n return { text: rendered.text, title: title ?? rendered.title }\n } catch {\n return fallback()\n }\n}\n","import { lookup as dnsLookupCb, type LookupAddress } from 'node:dns'\nimport { lookup as dnsLookup } from 'node:dns/promises'\nimport { isIP } from 'node:net'\nimport { Agent, type Dispatcher, fetch as undiciFetch } from 'undici'\n\nexport class SsrFBlockedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SsrFBlockedError'\n }\n}\n\nconst BLOCKED_HOSTNAMES = new Set(['localhost', 'metadata.google.internal'])\n\nfunction normalizeHostname(hostname: string): string {\n let h = hostname.trim().toLowerCase().replace(/\\.$/, '')\n if (h.startsWith('[') && h.endsWith(']')) h = h.slice(1, -1)\n return h\n}\n\nfunction parseIpv4(address: string): number[] | null {\n const parts = address.split('.')\n if (parts.length !== 4 || parts.some((part) => !/^\\d{1,3}$/.test(part))) return null\n const nums = parts.map(Number)\n if (nums.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return null\n return nums\n}\n\nfunction parseIpv6(address: string): number[] | null {\n const zoneIndex = address.indexOf('%')\n let source = zoneIndex === -1 ? address : address.slice(0, zoneIndex)\n if (isIP(source) !== 6) return null\n\n if (source.includes('.')) {\n const separator = source.lastIndexOf(':')\n const ipv4 = parseIpv4(source.slice(separator + 1))\n if (separator === -1 || !ipv4) return null\n const [a, b, c, d] = ipv4 as [number, number, number, number]\n source = `${source.slice(0, separator)}:${((a << 8) | b).toString(16)}:${((c << 8) | d).toString(16)}`\n }\n\n const halves = source.split('::')\n if (halves.length > 2) return null\n const left = halves[0] ? halves[0].split(':') : []\n const right = halves[1] ? halves[1].split(':') : []\n const omitted = 8 - left.length - right.length\n if ((halves.length === 1 && omitted !== 0) || (halves.length === 2 && omitted < 1)) return null\n return [...left, ...Array.from({ length: omitted }, () => '0'), ...right].map((word) =>\n Number.parseInt(word, 16),\n )\n}\n\nfunction isPrivateIpv4(parts: number[]): boolean {\n const [a, b] = parts as [number, number, number, number]\n if (a === 0 || a === 10 || a === 127) return true\n if (a === 169 && b === 254) return true\n if (a === 172 && b >= 16 && b <= 31) return true\n if (a === 192 && b === 168) return true\n if (a === 100 && b >= 64 && b <= 127) return true\n return false\n}\n\nfunction embeddedIpv4(words: number[]): number[] | null {\n const [a, b, c, d, e, f, g, h] = words as [\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n ]\n const low32 = [g >> 8, g & 0xff, h >> 8, h & 0xff]\n\n // IPv4-compatible (::/96), IPv4-mapped (::ffff:0:0/96), and the\n // historical IPv4-translatable (::ffff:0:0:0/96) representations.\n if (\n (a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 0) ||\n (a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 0xffff) ||\n (a === 0 && b === 0 && c === 0 && d === 0 && e === 0xffff && f === 0)\n ) {\n return low32\n }\n\n // RFC 6052's well-known /96 NAT64 prefix.\n if (a === 0x64 && b === 0xff9b && c === 0 && d === 0 && e === 0 && f === 0) {\n return low32\n }\n\n // RFC 8215's local-use /48 translation prefix. With a /48 prefix, the\n // IPv4 octets occupy bytes 6, 7, 9, and 10; byte 8 is the reserved u byte.\n if (a === 0x64 && b === 0xff9b && c === 1 && e >> 8 === 0) {\n return [d >> 8, d & 0xff, e & 0xff, f >> 8]\n }\n\n // 6to4 embeds its IPv4 relay address immediately after 2002::/16.\n if (a === 0x2002) return [b >> 8, b & 0xff, c >> 8, c & 0xff]\n return null\n}\n\nexport function isPrivateIpAddress(address: string): boolean {\n let norm = address.trim().toLowerCase()\n if (norm.startsWith('[') && norm.endsWith(']')) norm = norm.slice(1, -1)\n if (!norm) return false\n const ipv4 = parseIpv4(norm)\n if (ipv4) return isPrivateIpv4(ipv4)\n const words = parseIpv6(norm)\n if (!words) return false\n const [first] = words as [number, ...number[]]\n if (words.every((word) => word === 0)) return true\n if (words.slice(0, 7).every((word) => word === 0) && words[7] === 1) return true\n if ((first & 0xfe00) === 0xfc00) return true // fc00::/7 unique-local\n if ((first & 0xffc0) === 0xfe80) return true // fe80::/10 link-local\n if ((first & 0xffc0) === 0xfec0) return true // fec0::/10 deprecated site-local\n if ((first & 0xff00) === 0xff00) return true // ff00::/8 multicast\n const embedded = embeddedIpv4(words)\n return embedded ? isPrivateIpv4(embedded) : false\n}\n\nexport function isBlockedHostname(hostname: string): boolean {\n const h = normalizeHostname(hostname)\n if (!h) return false\n if (BLOCKED_HOSTNAMES.has(h)) return true\n return h.endsWith('.localhost') || h.endsWith('.local') || h.endsWith('.internal')\n}\n\ntype LookupCallback = (\n err: NodeJS.ErrnoException | null,\n address: string | LookupAddress[],\n family?: number,\n) => void\n\nfunction createPinnedLookup(hostname: string, addresses: string[]): typeof dnsLookupCb {\n const normalized = normalizeHostname(hostname)\n const records = addresses.map((address) => ({\n address,\n family: (address.includes(':') ? 6 : 4) as 4 | 6,\n }))\n let index = 0\n return ((host: string, options?: unknown, callback?: unknown) => {\n const cb: LookupCallback =\n typeof options === 'function' ? (options as LookupCallback) : (callback as LookupCallback)\n if (!cb) return\n if (normalizeHostname(host) !== normalized) {\n if (typeof options === 'function' || options === undefined) {\n return (dnsLookupCb as unknown as (h: string, cb: LookupCallback) => void)(host, cb)\n }\n return (dnsLookupCb as unknown as (h: string, o: unknown, cb: LookupCallback) => void)(\n host,\n options,\n cb,\n )\n }\n const opts =\n typeof options === 'object' && options !== null\n ? (options as { all?: boolean; family?: number })\n : {}\n const family = typeof options === 'number' ? options : (opts.family ?? 0)\n const candidates =\n family === 4 || family === 6 ? records.filter((r) => r.family === family) : records\n const usable = candidates.length > 0 ? candidates : records\n if (opts.all) {\n cb(null, usable as LookupAddress[])\n return\n }\n const chosen = usable[index % usable.length]\n if (!chosen) return\n index += 1\n cb(null, chosen.address, chosen.family)\n }) as typeof dnsLookupCb\n}\n\nasync function resolveAndCheck(hostname: string): Promise<string[]> {\n const norm = normalizeHostname(hostname)\n if (!norm) throw new SsrFBlockedError('Invalid hostname')\n if (isBlockedHostname(norm)) throw new SsrFBlockedError(`Blocked hostname: ${hostname}`)\n if (isPrivateIpAddress(norm)) throw new SsrFBlockedError('Blocked: private IP literal')\n const results = await dnsLookup(norm, { all: true })\n if (results.length === 0) throw new SsrFBlockedError(`Cannot resolve: ${hostname}`)\n for (const r of results) {\n if (isPrivateIpAddress(r.address)) throw new SsrFBlockedError('Blocked: resolves to private IP')\n }\n return Array.from(new Set(results.map((r) => r.address)))\n}\n\ntype FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>\n\nexport interface GuardedFetchOptions {\n url: string\n fetchImpl?: FetchLike\n init?: RequestInit\n maxRedirects?: number\n timeoutMs?: number\n signal?: AbortSignal\n /** If true, skip all SSRF checks (for tests hitting 127.0.0.1 mocks). */\n allowPrivate?: boolean\n}\n\nexport interface GuardedFetchResult {\n response: Response\n finalUrl: string\n release: () => Promise<void>\n}\n\nfunction isRedirectStatus(s: number): boolean {\n return s === 301 || s === 302 || s === 303 || s === 307 || s === 308\n}\n\nfunction withoutCrossOriginCredentials(\n headersInit: ConstructorParameters<typeof Headers>[0] | undefined,\n): Headers | undefined {\n if (!headersInit) return undefined\n const headers = new Headers(headersInit)\n headers.delete('authorization')\n headers.delete('cookie')\n headers.delete('proxy-authorization')\n return headers\n}\n\nasync function closeDispatcher(d: Dispatcher | null): Promise<void> {\n if (!d) return\n try {\n await d.close()\n } catch {\n // ignore\n }\n}\n\nexport async function guardedFetch(opts: GuardedFetchOptions): Promise<GuardedFetchResult> {\n // Use undici's own fetch (not globalThis.fetch) so the `dispatcher` Agent\n // we attach below is the same undici major version as the fetch impl\n // consuming it. Node 24 ships undici 7.x as its built-in fetch; mixing\n // a standalone undici 8.x Agent with the 7.x dispatcher fails with\n // `UND_ERR_INVALID_ARG: invalid onRequestStart method` because the\n // diagnostics-channel handler signatures changed between majors.\n const fetcher: FetchLike = opts.fetchImpl ?? (undiciFetch as unknown as FetchLike)\n const maxRedirects = opts.maxRedirects ?? 3\n const abortController = new AbortController()\n const timeoutId = opts.timeoutMs\n ? setTimeout(() => abortController.abort(new Error('timeout')), opts.timeoutMs)\n : null\n if (opts.signal) {\n if (opts.signal.aborted) abortController.abort(opts.signal.reason)\n else\n opts.signal.addEventListener('abort', () => abortController.abort(opts.signal?.reason), {\n once: true,\n })\n }\n\n let current = opts.url\n let requestInit = { ...(opts.init ?? {}) }\n const visited = new Set<string>()\n let redirects = 0\n let dispatcher: Dispatcher | null = null\n\n const release = async (): Promise<void> => {\n if (timeoutId) clearTimeout(timeoutId)\n await closeDispatcher(dispatcher)\n dispatcher = null\n }\n\n while (true) {\n let parsed: URL\n try {\n parsed = new URL(current)\n } catch {\n await release()\n throw new Error('Invalid URL')\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n await release()\n throw new Error('Invalid URL: must be http or https')\n }\n\n // Pin DNS only when we control the dispatcher (native fetch path).\n const canPin = !opts.fetchImpl\n if (!opts.allowPrivate) {\n const addrs = await resolveAndCheck(parsed.hostname)\n await closeDispatcher(dispatcher)\n dispatcher = canPin\n ? new Agent({ connect: { lookup: createPinnedLookup(parsed.hostname, addrs) } })\n : null\n }\n\n const init: RequestInit = {\n ...requestInit,\n redirect: 'manual',\n signal: abortController.signal,\n }\n // `dispatcher` is a Node fetch extension not reflected in the DOM\n // RequestInit type — and the undici Dispatcher type brand can diverge\n // from the one @types/node bundles. Stamp it on via a cast.\n if (dispatcher) (init as unknown as { dispatcher: Dispatcher }).dispatcher = dispatcher\n\n let res: Response\n try {\n res = await fetcher(parsed.toString(), init)\n } catch (err) {\n await release()\n throw err\n }\n\n if (isRedirectStatus(res.status)) {\n const loc = res.headers.get('location')\n if (!loc) {\n await release()\n throw new Error(`Redirect ${res.status} missing Location header`)\n }\n redirects += 1\n if (redirects > maxRedirects) {\n await release()\n throw new Error(`Too many redirects (> ${maxRedirects})`)\n }\n const next = new URL(loc, parsed).toString()\n if (visited.has(next)) {\n await release()\n throw new Error('Redirect loop')\n }\n visited.add(next)\n void res.body?.cancel()\n if (new URL(next).origin !== parsed.origin) {\n requestInit = {\n ...requestInit,\n headers: withoutCrossOriginCredentials(requestInit.headers),\n }\n }\n current = next\n continue\n }\n\n return { response: res, finalUrl: parsed.toString(), release }\n }\n}\n","import type { WorkerIpcCall } from './ipc-client.ts'\nimport type { ApiKeyRefreshArgs, ApiKeyRefreshHost } from './ipc-protocol.ts'\n\nexport interface ApiKeyRefreshTurnContext {\n providerName: string\n agentId: string\n turnId: string\n}\n\n/** Build the worker-side callback passed to pi's `createBazilionSession`. */\nexport function createIpcApiKeyRefresher(\n call: WorkerIpcCall,\n context: ApiKeyRefreshTurnContext,\n): (providerName: string) => Promise<string> {\n return async (providerName) => {\n assertSelectedProvider(providerName, context.providerName)\n return call<string>('refreshApiKey', {\n providerName,\n agentId: context.agentId,\n turnId: context.turnId,\n } satisfies ApiKeyRefreshArgs)\n }\n}\n\n/**\n * Validate and execute one daemon-side refresh request. This is deliberately\n * separate from the generic IPC dispatcher so the credential-bearing result\n * cannot accidentally enter the stdout `ChatFrame` queue.\n */\nexport async function refreshApiKeyForTurn(\n args: ApiKeyRefreshArgs,\n context: ApiKeyRefreshTurnContext,\n host: ApiKeyRefreshHost | undefined,\n signal?: AbortSignal,\n): Promise<string> {\n if (args.agentId !== context.agentId || args.turnId !== context.turnId) {\n throw new Error('API key refresh request does not belong to this worker turn')\n }\n assertSelectedProvider(args.providerName, context.providerName)\n if (!host) throw new Error('provider credential refresh is unavailable for this worker turn')\n if (signal?.aborted) throw refreshCancelledError()\n\n let token: string\n try {\n const pending = host.refresh(args.providerName, signal)\n token = await waitForRefresh(pending, signal)\n } catch (error) {\n if (error instanceof ApiKeyRefreshCancelledError) throw error\n // OAuth libraries and HTTP clients can include request context in their\n // errors. Never forward that detail across IPC where it could become a\n // model- or user-visible authentication error.\n throw new Error('provider credential refresh failed — reconnect on /config and retry')\n }\n if (typeof token !== 'string' || token.length === 0) {\n throw new Error('API key refresh returned an invalid token')\n }\n return token\n}\n\nfunction assertSelectedProvider(requested: string, expected: string): void {\n if (!expected || requested !== expected) {\n throw new Error('unexpected API key refresh provider')\n }\n}\n\nfunction waitForRefresh(pending: Promise<string>, signal?: AbortSignal): Promise<string> {\n if (!signal) return pending\n return new Promise((resolve, reject) => {\n let settled = false\n const finish = (callback: () => void): void => {\n if (settled) return\n settled = true\n signal.removeEventListener('abort', onAbort)\n callback()\n }\n const onAbort = (): void => finish(() => reject(refreshCancelledError()))\n\n signal.addEventListener('abort', onAbort, { once: true })\n if (signal.aborted) {\n onAbort()\n return\n }\n pending.then(\n (value) => finish(() => resolve(value)),\n (error: unknown) => finish(() => reject(error)),\n )\n })\n}\n\nclass ApiKeyRefreshCancelledError extends Error {}\n\nfunction refreshCancelledError(): ApiKeyRefreshCancelledError {\n return new ApiKeyRefreshCancelledError('API key refresh cancelled with the worker turn')\n}\n","import { randomUUID } from 'node:crypto'\nimport type { IpcReply, IpcRequest, RpcMethod } from './ipc-protocol.ts'\n\nexport type WorkerIpcCall = <T>(method: RpcMethod, args: unknown) => Promise<T>\n\nexport interface WorkerIpcChannel {\n send?: (message: IpcRequest, done: (error: Error | null) => void) => void\n onMessage(listener: (message: unknown) => void): void\n onDisconnect(listener: () => void): void\n}\n\n/**\n * Correlated request client for the worker's private Node IPC channel. A\n * channel disconnect rejects and clears every pending request, so an OAuth\n * refresh cannot pin a worker after its parent or turn has gone away.\n */\nexport function createIpcClient(channel: WorkerIpcChannel): WorkerIpcCall {\n type Pending = { resolve: (value: unknown) => void; reject: (error: Error) => void }\n const pending = new Map<string, Pending>()\n let disconnected = false\n\n channel.onMessage((message) => {\n if (!isIpcReply(message)) return\n const entry = pending.get(message.id)\n if (!entry) return\n pending.delete(message.id)\n if (message.ok) entry.resolve(message.result)\n else entry.reject(new Error(message.error))\n })\n\n channel.onDisconnect(() => {\n disconnected = true\n for (const entry of pending.values()) {\n entry.reject(new Error('worker IPC channel disconnected'))\n }\n pending.clear()\n })\n\n return <T>(method: RpcMethod, args: unknown): Promise<T> => {\n if (disconnected) {\n return Promise.reject(new Error('worker IPC channel disconnected'))\n }\n const send = channel.send\n if (!send) {\n return Promise.reject(new Error('worker: no IPC channel — daemon must spawn with stdio:ipc'))\n }\n const id = randomUUID()\n const message = { type: 'rpc', id, method, args } as unknown as IpcRequest\n return new Promise<T>((resolve, reject) => {\n pending.set(id, { resolve: (value) => resolve(value as T), reject })\n try {\n send(message, (error) => {\n if (!error) return\n pending.delete(id)\n reject(error)\n })\n } catch (error) {\n pending.delete(id)\n reject(error instanceof Error ? error : new Error(String(error)))\n }\n })\n }\n}\n\nfunction isIpcReply(message: unknown): message is IpcReply {\n if (!message || typeof message !== 'object') return false\n const candidate = message as Record<string, unknown>\n return (\n candidate.type === 'rpc-reply' &&\n typeof candidate.id === 'string' &&\n typeof candidate.ok === 'boolean'\n )\n}\n"],"mappings":"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","names":["randomUUID","Type","join","readFileSync","randomUUID","randomUUID","mkdirSync","rmSync","writeFileSync","join","existsSync","readFileSync","join","existsSync","readFileSync","writeFileSync","join","existsSync","rmSync","mkdirSync","writeFileSync","join","existsSync","join","readdirSync","readFileSync","join","join","join","existsSync","rmSync","randomUUID","writeFileSync","join","randomUUID","randomUUID","randomUUID","randomUUID","createHash","randomBytes","randomUUID","randomUUID","createHash","randomBytes","randomUUID","createHash","randomBytes","randomUUID","existsSync","readFileSync","existsSync","readdirSync","rmSync","statSync","join","resolve","readFileSync","randomUUID","existsSync","renameSync","rmSync","randomUUID","createHash","randomUUID","randomUUID","randomUUID","readdirSync","rmSync","rmSync","mkdirSync","readdirSync","readFileSync","rmSync","writeFileSync","dirname","join","resolve","sep","join","sep","mkdirSync","resolve","dirname","readFileSync","writeFileSync","readdirSync","rmSync","existsSync","mkdirSync","readdirSync","statSync","writeFileSync","basename","join","existsSync","readdirSync","join","closeSync","constants","fstatSync","lstatSync","mkdirSync","openSync","readFileSync","realpathSync","dirname","isAbsolute","relative","resolve","lstatSync","openSync","constants","fstatSync","readFileSync","closeSync","existsSync","readdirSync","join","existsSync","lstatSync","realpathSync","dirname","isAbsolute","join","relative","sep","defineTool","randomUUID","closeSync","constants","mkdtempSync","openSync","rmSync","writeFileSync","tmpdir","isAbsolute","join","relative","sep","DOCKER_CLIENT_CONTROL_ENV","SANDBOX_CONTROL_ENV","randomUUID","isAbsolute","constants","isAbsolute","randomUUID","resolve","closeSync","mkdtempSync","join","tmpdir","writeFileSync","openSync","rmSync","DOCKER_CLIENT_CONTROL_ENV","SANDBOX_CONTROL_ENV","detail","relative","sep","resolve","detail","defineTool","base","customBash","existsSync","join","dirname","lstatSync","realpathSync","relative","sep","isAbsolute","constants","existsSync","lstatSync","mkdirSync","mkdtempSync","realpathSync","rmSync","statSync","access","tmpdir","isAbsolute","join","relative","sep","isAbsolute","lstatSync","realpathSync","statSync","existsSync","relative","sep","Type","existsSync","rmSync","join","closeSync","constants","fstatSync","openSync","readFileSync","realpathSync","statSync","basename","isAbsolute","relative","resolve","sep","isWithin","stat","closeSync","constants","fstatSync","lstatSync","openSync","readdirSync","writeFileSync","join","openSync","constants","fstatSync","writeFileSync","closeSync","lstatSync","readdirSync","join","undiciFetch","undiciFetch","Type","existsSync","mkdirSync","join","writeFileSync","existsSync","readdirSync","join","statSync","randomUUID","resolve","randomUUID","Type","memory","messagingHost","userMdHost","bashApprovalHost","refreshApiKey","handle"]}
|
|
1
|
+
{"version":3,"sources":["../../daemon/src/runtime/worker/entry.ts","../../daemon/src/core/agent/delete.ts","../../daemon/src/core/agent/move.ts","../../daemon/src/core/agent/resolve.ts","../../daemon/src/core/profile/identity.ts","../../daemon/src/core/repos/agentLessonProposals.ts","../../daemon/src/core/agent/spawn.ts","../../daemon/src/core/profile/load.ts","../../daemon/src/core/profile/seed.ts","../../daemon/src/core/team/register.ts","../../daemon/src/core/profile/create.ts","../../daemon/src/core/skills/discover.ts","../../daemon/src/core/db/client.ts","../../daemon/src/core/db/migrate.ts","../../daemon/src/core/paths.ts","../../daemon/src/core/profile/delete.ts","../../daemon/src/core/repos/teamTemplates.ts","../../daemon/src/core/profile/update.ts","../../daemon/src/core/repos/agentReviews.ts","../../daemon/src/core/repos/communicationApprovals.ts","../../daemon/src/core/services.ts","../../daemon/src/core/repos/config.ts","../../daemon/src/core/repos/mcpServers.ts","../../daemon/src/core/repos/messages.ts","../../daemon/src/core/repos/secrets.ts","../../daemon/src/core/repos/telegram-pairing.ts","../../daemon/src/core/repos/triggerDispatches.ts","../../daemon/src/core/repos/triggers.ts","../../daemon/src/core/repos/webSessions.ts","../../daemon/src/core/repos/webTokens.ts","../../daemon/src/core/secrets.ts","../../daemon/src/core/skills/import.ts","../../daemon/src/core/skills/parse.ts","../../daemon/src/core/team/delete.ts","../../daemon/src/core/team-policy/adopt.ts","../../daemon/src/core/team-policy/authorization.ts","../../daemon/src/core/team-policy/source.ts","../../daemon/src/core/team-policy/spawn.ts","../../daemon/src/core/team-policy/rm-with-retry.ts","../../daemon/src/runtime/memory/qmd.ts","../../daemon/src/lib/native-module-error.ts","../../daemon/src/runtime/pi/events.ts","../../daemon/src/lib/result-source.ts","../../daemon/src/runtime/pi/coding.ts","../../daemon/src/core/coding-environment/config.ts","../../daemon/src/lib/coding-environment/diagnostics.ts","../../daemon/src/runtime/coding-directory.ts","../../daemon/src/lib/repository-context/files.ts","../../daemon/src/runtime/shell/approval.ts","../../daemon/src/runtime/shell/security.ts","../../daemon/src/runtime/shell/docker.ts","../../daemon/src/runtime/shell/coding.ts","../../daemon/src/runtime/pi/repository-context.ts","../../daemon/src/runtime/pi/repository-context-contract.ts","../../daemon/src/runtime/pi/session.ts","../../daemon/src/runtime/providers/pi-runtime.ts","../../daemon/src/runtime/auth/openai-codex.ts","../../daemon/src/runtime/providers/pi-adapter.ts","../../daemon/src/runtime/session/prompt.ts","../../daemon/src/runtime/safe-files.ts","../../daemon/src/runtime/shell/tooling.ts","../../daemon/src/runtime/worker/runtime.ts","../../daemon/src/runtime/tools/ask-user.ts","../../daemon/src/runtime/pi/tools.ts","../../daemon/src/runtime/tools/bootstrap.ts","../../daemon/src/runtime/tools/browser.ts","../../daemon/src/runtime/tools/deliver-file.ts","../../daemon/src/runtime/tools/home.ts","../../daemon/src/runtime/tools/mcp.ts","../../daemon/src/runtime/tools/memory.ts","../../daemon/src/runtime/tools/messaging.ts","../../daemon/src/runtime/tools/user-md.ts","../../daemon/src/runtime/tools/web.ts","../../daemon/src/runtime/tools/web-extract.ts","../../daemon/src/runtime/tools/web-ssrf.ts","../../daemon/src/runtime/worker/api-key-refresh.ts","../../daemon/src/runtime/worker/ipc-client.ts"],"sourcesContent":["import type { DockerResourceLifecycle } from '../shell/docker.ts'\n// One-shot worker entry. Configured operator HTTP retains its legacy runtime;\n// protected normal turns and restricted reviews use closed typed inputs.\n\nimport { randomUUID } from 'node:crypto'\nimport type { ChatFrame } from '@bazilion/api-types'\nimport type { AgentSession, ToolDefinition } from '@earendil-works/pi-coding-agent'\nimport { Type } from 'typebox'\nimport { resolvePaths } from '../../core/index.ts'\nimport { qmdBackend } from '../memory/qmd.ts'\nimport { piMessagesToProviderView, translatePiEvent } from '../pi/events.ts'\nimport {\n type BazilionSessionHandle,\n createBazilionSession,\n createProtectedBazilionSession,\n createRestrictedReviewSession,\n} from '../pi/session.ts'\nimport type { BashApprovalHost as ShellBashApprovalHost } from '../shell/approval.ts'\nimport { createIpcApiKeyRefresher } from './api-key-refresh.ts'\nimport { createIpcClient, type WorkerIpcCall } from './ipc-client.ts'\nimport type {\n BashApprovalResult,\n BrowserHost,\n McpHost,\n MessagingHost,\n UserMdGetResult,\n UserMdHost,\n UserMdWriteResult,\n} from './ipc-protocol.ts'\nimport {\n parseWorkerInput,\n protectedRuntimeSecrets,\n redactJsonValue,\n validateMinimalWorkerProcessEnv,\n type WorkerInput,\n} from './runtime.ts'\n\ninterface ReviewWorkerProposal {\n scope: 'private' | 'shared'\n text: string\n evidenceEntryIds: Array<{ sessionId: string; entryOrdinal: number }>\n}\n\ntype WorkerFrame = ChatFrame | { kind: 'review_result'; proposals: ReviewWorkerProposal[] }\n\nconst activeAccessTokens: string[] = []\n\nfunction emit(frame: WorkerFrame): void {\n process.stdout.write(`${JSON.stringify(redactJsonValue(frame, activeAccessTokens))}\\n`)\n}\n\nasync function readInput(): Promise<WorkerInput> {\n const chunks: Buffer[] = []\n for await (const chunk of process.stdin) chunks.push(chunk as Buffer)\n const raw = Buffer.concat(chunks).toString('utf8').trim()\n if (!raw) throw new Error('worker: empty stdin input')\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n throw new Error('worker: stdin is not valid JSON')\n }\n return parseWorkerInput(parsed)\n}\n\nfunction createWorkerIpcCall(): WorkerIpcCall {\n return createIpcClient({\n send: process.send\n ? (ipcMessage, done) => {\n process.send?.(ipcMessage, undefined, undefined, done)\n }\n : undefined,\n onMessage: (listener) => process.on('message', listener),\n onDisconnect: (listener) => process.on('disconnect', listener),\n })\n}\n\nfunction createTrackedIpcApiKeyRefresher(\n call: WorkerIpcCall,\n context: { providerName: string; agentId: string; turnId: string },\n): (providerName: string) => Promise<string> {\n const refresh = createIpcApiKeyRefresher(call, context)\n return async (providerName) => {\n const token = await refresh(providerName)\n if (token && !activeAccessTokens.includes(token)) activeAccessTokens.push(token)\n return token\n }\n}\n\nfunction createIpcMessagingHost(call: WorkerIpcCall): MessagingHost {\n return {\n agentExists: (agentId) => call<boolean>('agentExists', { agentId }),\n sendMessage: (input) => call<{ messageId: string }>('sendMessage', input),\n listInbox: (agentId, opts) => call('listInbox', { agentId, unreadOnly: opts.unreadOnly }),\n markRead: async (messageId) => {\n await call<null>('markRead', { messageId })\n },\n findReplies: (agentId, replyTo) => call('findReplies', { agentId, replyTo }),\n approvalStatus: (agentId, approvalId) => call('approvalStatus', { agentId, approvalId }),\n }\n}\n\nfunction createIpcDockerLifecycle(\n namespace: string | undefined,\n call: WorkerIpcCall,\n): DockerResourceLifecycle | undefined {\n if (!namespace) return undefined\n return {\n name: (kind) => `bazilion-${namespace}-${kind}-${randomUUID()}`,\n beforeCreate: async (identity) => {\n await call('containerBeforeCreate', { containerName: identity.containerName })\n },\n afterCreate: async (containerName) => {\n await call('containerAfterCreate', { containerName })\n },\n afterRemove: async (containerName) => {\n await call('containerAfterRemove', { containerName })\n },\n }\n}\n\nfunction createIpcUserMdHost(call: WorkerIpcCall): UserMdHost {\n return {\n get: (teamId) => call<UserMdGetResult>('userMdGet', { teamId }),\n write: (teamId, content, ifMatch) =>\n call<UserMdWriteResult>('userMdWrite', { teamId, content, ifMatch }),\n }\n}\n\nfunction createIpcBrowserHost(call: WorkerIpcCall): BrowserHost {\n return {\n invoke: (agentId, action, args) => call('browserInvoke', { agentId, action, args }),\n }\n}\n\nfunction createIpcMcpHost(call: WorkerIpcCall): McpHost {\n return {\n invoke: (serverId, toolName, args) => call('mcpInvoke', { serverId, toolName, args }),\n }\n}\n\nfunction createIpcBashApprovalHost(\n call: WorkerIpcCall,\n input: {\n agentId: string\n teamId: string\n turnId: string\n mode: 'interactive' | 'auto_deny'\n },\n): ShellBashApprovalHost {\n return {\n async requestApproval(request) {\n const result = await call<BashApprovalResult>('bashApproval', {\n id: randomUUID(),\n turnId: input.turnId,\n toolCallId: request.toolCallId,\n agentId: input.agentId,\n teamId: input.teamId,\n command: request.command,\n risks: [...request.risks],\n mode: input.mode,\n })\n return result.decision === 'allow' ? 'approved' : 'denied'\n },\n }\n}\n\ninterface ReviewState {\n proposals: ReviewWorkerProposal[]\n submitted(): boolean\n validationFailures(): number\n recordValidationFailure(): void\n tools: ToolDefinition[]\n}\n\nfunction createReviewState(\n evidenceItems: Array<{ sessionId: string; entryOrdinal: number }>,\n): ReviewState {\n const proposals: ReviewWorkerProposal[] = []\n let proposalBatchSubmitted = false\n let reviewValidationFailures = 0\n const allowedEvidence = new Set(\n evidenceItems.map((item) => `${item.sessionId}:${item.entryOrdinal}`),\n )\n const tools: ToolDefinition[] = [\n {\n name: 'propose_lesson',\n label: 'propose_lesson',\n description:\n 'Submit zero to five evidence-backed lesson proposals in one final batch. This is the only available action.',\n parameters: Type.Object({\n proposals: Type.Array(\n Type.Object({\n scope: Type.Union([Type.Literal('private'), Type.Literal('shared')]),\n text: Type.String({ minLength: 1, maxLength: 500 }),\n evidenceEntryIds: Type.Array(\n Type.Object({ sessionId: Type.String(), entryOrdinal: Type.Integer({ minimum: 0 }) }),\n { minItems: 1 },\n ),\n }),\n { maxItems: 5 },\n ),\n }),\n async execute(_toolCallId, params) {\n if (proposalBatchSubmitted) throw new Error('propose_lesson may be called only once')\n const input = params as { proposals: ReviewWorkerProposal[] }\n const validated: ReviewWorkerProposal[] = []\n for (const candidate of input.proposals) {\n const text = candidate.text.trim()\n if (!text || text.length > 500) throw new Error('proposal text must be 1-500 characters')\n for (const evidence of candidate.evidenceEntryIds) {\n if (!allowedEvidence.has(`${evidence.sessionId}:${evidence.entryOrdinal}`)) {\n throw new Error('proposal cites evidence outside the supplied digest')\n }\n }\n validated.push({ ...candidate, text })\n }\n proposalBatchSubmitted = true\n proposals.push(...validated)\n return {\n content: [{ type: 'text', text: `accepted ${proposals.length} proposals` }],\n details: {},\n terminate: true,\n }\n },\n },\n ]\n return {\n proposals,\n submitted: () => proposalBatchSubmitted,\n validationFailures: () => reviewValidationFailures,\n recordValidationFailure: () => {\n reviewValidationFailures += 1\n },\n tools,\n }\n}\n\nasync function createSessionForInput(\n input: WorkerInput,\n ipcCall: WorkerIpcCall,\n): Promise<{ handle: BazilionSessionHandle; reviewState?: ReviewState }> {\n if (input.kind !== 'restricted_review' && !input.repositoryContext)\n throw new Error('Coding turn requires daemon-prepared repository context')\n if (input.kind === 'configured_operator_http') {\n const paths = resolvePaths()\n const memory = qmdBackend(`${input.agent.team.path}/memory`)\n await memory.init()\n const messagingHost = createIpcMessagingHost(ipcCall)\n const userMdHost = createIpcUserMdHost(ipcCall)\n const browserHost = input.browserEnabled ? createIpcBrowserHost(ipcCall) : undefined\n const mcpHost = input.mcpTools?.length ? createIpcMcpHost(ipcCall) : undefined\n const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {\n agentId: input.agent.agent.id,\n teamId: input.agent.team.id,\n turnId: input.turnId,\n mode: input.bashApprovalMode,\n })\n const refreshApiKey = input.apiKeyRefreshEnabled\n ? createTrackedIpcApiKeyRefresher(ipcCall, {\n providerName: input.agent.model.split(':', 1)[0] ?? '',\n agentId: input.agent.agent.id,\n turnId: input.turnId,\n })\n : undefined\n const handle = await createBazilionSession({\n agent: input.agent,\n preparedDocker: input.configuredDocker?.docker,\n dockerLifecycle: createIpcDockerLifecycle(input.containerNamespace, ipcCall),\n conversation: input.conversation,\n repositoryContext: input.repositoryContext,\n repositoryContextHost: (target) => ipcCall('repositoryContext', { target }),\n codingHost: { invoke: (request) => ipcCall('coding', request) },\n paths,\n env: process.env,\n memory,\n enabledProviders: new Set(input.enabledProviders),\n messagingHost,\n userMdHost,\n apiKey: input.apiKey,\n refreshApiKey,\n browserHost,\n mcpHost,\n mcpTools: input.mcpTools,\n bashApprovalHost,\n askUser: input.questionEnabled\n ? (toolCallId, question) => ipcCall('askUser', { toolCallId, question })\n : undefined,\n fileSink: async (file, source) => {\n const result = await ipcCall<import('@bazilion/api-types').ResultReference>(\n 'publishResult',\n { ...file, ...source },\n )\n emit({ kind: 'event', event: { type: 'file', ...file, result } })\n return result\n },\n })\n return { handle }\n }\n\n const refreshApiKey = createTrackedIpcApiKeyRefresher(ipcCall, {\n providerName: input.runtime.providerName,\n agentId: input.kind === 'protected' ? input.agent.agent.id : input.agentId,\n turnId: input.turnId,\n })\n if (input.kind === 'restricted_review') {\n const reviewState = createReviewState(input.review.evidence)\n const handle = await createRestrictedReviewSession({\n runtime: input.runtime,\n scratch: input.scratch,\n systemPrompt: REVIEW_SYSTEM_PROMPT,\n tools: reviewState.tools,\n refreshApiKey,\n })\n return { handle, reviewState }\n }\n\n const memory = qmdBackend(input.paths.memoryDir)\n await memory.init()\n const messagingHost = createIpcMessagingHost(ipcCall)\n const userMdHost = createIpcUserMdHost(ipcCall)\n const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {\n agentId: input.agent.agent.id,\n teamId: input.agent.team.id,\n turnId: input.turnId,\n mode: 'auto_deny',\n })\n const handle = await createProtectedBazilionSession({\n agent: input.agent,\n conversation: input.conversation,\n repositoryContext: input.repositoryContext,\n repositoryContextHost: (target) => ipcCall('repositoryContext', { target }),\n codingHost: { invoke: (request) => ipcCall('coding', request) },\n runtime: input.runtime,\n paths: input.paths,\n scratch: input.scratch,\n docker: input.docker,\n dockerLifecycle: createIpcDockerLifecycle(input.containerNamespace, ipcCall),\n memory,\n messagingHost,\n userMdHost,\n bashApprovalHost,\n askUser: input.questionEnabled\n ? (toolCallId, question) => ipcCall('askUser', { toolCallId, question })\n : undefined,\n refreshApiKey,\n fileSink: async (file, source) => {\n const result = await ipcCall<import('@bazilion/api-types').ResultReference>('publishResult', {\n ...file,\n ...source,\n })\n emit({ kind: 'event', event: { type: 'file', ...file, result } })\n return result\n },\n })\n return { handle }\n}\n\nasync function main(): Promise<void> {\n let aborted = false\n let session: AgentSession | null = null\n const onSignal = (): void => {\n aborted = true\n if (session) void session.abort()\n else {\n try {\n emit({ kind: 'event', event: { type: 'error', error: 'cancelled' } })\n } catch {}\n process.exit(0)\n }\n }\n process.on('SIGTERM', onSignal)\n process.on('SIGINT', onSignal)\n\n const input = await readInput()\n if (input.kind !== 'configured_operator_http') {\n validateMinimalWorkerProcessEnv(process.env, input.scratch)\n }\n const initialAccessTokens =\n input.kind === 'configured_operator_http'\n ? input.apiKey\n ? [input.apiKey]\n : []\n : protectedRuntimeSecrets(input.runtime)\n activeAccessTokens.push(...initialAccessTokens.filter(Boolean))\n const ipcCall = createWorkerIpcCall()\n const { handle, reviewState } = await createSessionForInput(input, ipcCall)\n session = handle.session\n\n const consumptionAcks = new Set<Promise<void>>()\n const unsubscribe = session.subscribe((piEvent) => {\n if (\n input.kind !== 'restricted_review' &&\n input.questionEnabled &&\n piEvent.type === 'message_end' &&\n piEvent.message.role === 'toolResult' &&\n piEvent.message.toolName === 'ask_user' &&\n !piEvent.message.isError\n ) {\n const message = piEvent.message\n const content = message.content[0]\n if (content?.type === 'text') {\n try {\n const result = JSON.parse(content.text) as { questionId?: unknown }\n if (typeof result.questionId === 'string') {\n // Pi notifies subscribers before synchronous session append. Wait for that\n // append, then let the daemon verify the actual canonical bytes.\n const ack = new Promise<void>((resolve) => setImmediate(resolve))\n .then(() =>\n ipcCall<void>('questionConsumed', {\n questionId: result.questionId,\n toolCallId: message.toolCallId,\n }),\n )\n .catch(() => {\n /* Missing evidence remains unconfirmed; never replay a tool. */\n })\n .finally(() => {\n consumptionAcks.delete(ack)\n })\n consumptionAcks.add(ack)\n }\n } catch {\n /* Invalid result cannot become consumption evidence. */\n }\n }\n }\n if (\n reviewState &&\n piEvent.type === 'tool_execution_end' &&\n piEvent.toolName === 'propose_lesson' &&\n piEvent.isError\n ) {\n reviewState.recordValidationFailure()\n if (reviewState.validationFailures() >= 2) void session?.abort()\n }\n for (const event of translatePiEvent(piEvent)) emit({ kind: 'event', event })\n })\n\n try {\n const promptMessage = redactJsonValue(input.message, activeAccessTokens)\n const sanitizedImages =\n input.kind === 'restricted_review'\n ? []\n : redactJsonValue(input.images ?? [], activeAccessTokens)\n const promptImages =\n input.kind === 'restricted_review'\n ? []\n : sanitizedImages.map((image) => ({\n type: 'image' as const,\n data: image.data,\n mimeType: image.mimeType,\n }))\n await session.prompt(\n promptMessage,\n promptImages.length > 0 ? { images: promptImages } : undefined,\n )\n await session.agent.waitForIdle()\n await Promise.all(consumptionAcks)\n\n if (reviewState) {\n if (reviewState.validationFailures() >= 2) {\n throw new Error('review output remained invalid after one schema-repair attempt')\n }\n if (!reviewState.submitted()) throw new Error('review output did not submit a proposal batch')\n emit({ kind: 'review_result', proposals: reviewState.proposals })\n } else {\n emit({ kind: 'done', messages: piMessagesToProviderView(session.agent.state.messages) })\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (!aborted) emit({ kind: 'event', event: { type: 'error', error: message } })\n emit({ kind: 'fatal', error: message })\n process.exitCode = 1\n } finally {\n unsubscribe()\n process.off('SIGTERM', onSignal)\n process.off('SIGINT', onSignal)\n handle.dispose()\n try {\n process.disconnect?.()\n } catch {}\n }\n}\n\nmain().catch((error) => {\n try {\n emit({ kind: 'fatal', error: error instanceof Error ? error.message : String(error) })\n } catch {}\n process.exit(1)\n})\n\nconst REVIEW_SYSTEM_PROMPT = `You are a restricted learning reviewer. Analyze only the supplied\ntranscript digest and approved-lesson index. You cannot act on the world. Call propose_lesson once\nwith zero to five proposals, then stop.\n\nIf propose_lesson rejects malformed output, correct the schema exactly once. If that repair is also\nrejected, stop; the review will fail.\n\nClassify before proposing:\n1. Stable human preference or biography belongs to USER.md: do not propose it.\n2. Agent-specific verified behavior or strategy is private.\n3. Reusable verified Team project knowledge, decisions, or procedures are shared.\n4. Everything else produces no proposal.\n\nZero proposals is a successful result. Never save transient or environment-dependent failures,\nunresolved attempts, guesses, contradicted claims, one-off narratives, status updates, secrets,\ncredentials, sensitive personal data, raw logs, copied text, or duplicates. Each proposal must cite\nat least one supplied evidence entry and one observation may appear in only one proposal.`\n","import { randomUUID } from 'node:crypto'\nimport { existsSync, renameSync, rmSync } from 'node:fs'\nimport type { BazilionDb } from '../db/client.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport { pruneBindingAndEmptyCohort } from './move.ts'\n\nexport function deleteAgent(db: BazilionDb, id: string, expectedTeamRevision: number): void {\n const agent = agentRepo.get(db, id)\n if (!agent) throw new Error(`agent not found: ${id}`)\n const fullId = agent.id\n const stagedDir = `${agent.dir}.deleting-${randomUUID()}`\n const hadDir = existsSync(agent.dir)\n if (hadDir) renameSync(agent.dir, stagedDir)\n\n try {\n db.raw.transaction(() => {\n const teamPolicy = teamPolicyRepo.get(db, agent.teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${agent.teamId}`)\n if (teamPolicy.revision !== expectedTeamRevision) {\n throw new Error(\n `team_revision_conflict: expected ${expectedTeamRevision}, current ${teamPolicy.revision}`,\n )\n }\n // messages.from_agent_id and to_agent_id reference agents(id) with no ON\n // DELETE rule, and messages.reply_to references messages(id) the same way,\n // so a naive DELETE of the agent fails if it has any mailbox history.\n // Null out inbound reply pointers to this agent's messages, then purge the\n // messages themselves, then let agentRepo.remove cascade the rest\n // (agent_skills, runs, events). agents.team_id is `ON DELETE RESTRICT`\n // from the team side, but the agent row itself goes away freely.\n db.raw.run(\n `UPDATE messages SET reply_to = NULL\n WHERE reply_to IN (SELECT id FROM messages WHERE from_agent_id = ? OR to_agent_id = ?)`,\n [fullId, fullId],\n )\n db.raw.run('DELETE FROM messages WHERE from_agent_id = ? OR to_agent_id = ?', [\n fullId,\n fullId,\n ])\n pruneBindingAndEmptyCohort(db, fullId, agent.teamId)\n db.raw.run(\n `DELETE FROM team_policy_edges WHERE team_id = ? AND\n ((source_kind = 'agent' AND source_id = ?) OR\n (target_kind = 'agent' AND target_id = ?))`,\n [agent.teamId, fullId, fullId],\n )\n db.raw.run('DELETE FROM team_agent_state WHERE agent_id = ?', [fullId])\n agentRepo.remove(db, fullId)\n teamPolicyRepo.bumpRevision(db, agent.teamId)\n })()\n } catch (error) {\n if (hadDir && existsSync(stagedDir)) renameSync(stagedDir, agent.dir)\n throw error\n }\n\n if (hadDir && existsSync(stagedDir)) {\n rmSync(stagedDir, { recursive: true, force: true })\n }\n}\n","import { readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Agent, TeamPolicyPlacement } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\n\nexport function moveAgentCanonical(\n db: BazilionDb,\n paths: Paths,\n agentId: string,\n input: {\n destinationTeamId: string\n sourceExpectedRevision: number\n destinationExpectedRevision: number\n placement: Exclude<TeamPolicyPlacement, 'template_snapshot'>\n },\n): Agent {\n const agent = agentRepo.get(db, agentId)\n if (!agent) throw new Error(`agent not found: ${agentId}`)\n if (!teamRepo.get(db, input.destinationTeamId, paths)) {\n throw new Error(`team not found: ${input.destinationTeamId}`)\n }\n if (agent.teamId === input.destinationTeamId) return agent\n const metadataPath = join(agent.dir, 'agent.json')\n const beforeMetadata = readFileSync(metadataPath, 'utf8')\n try {\n return db.raw.transaction(() => {\n const current = agentRepo.get(db, agent.id)\n if (!current || current.teamId !== agent.teamId) {\n throw new Error(`team_revision_conflict: Agent membership changed for ${agent.id}`)\n }\n const source = teamPolicyRepo.get(db, current.teamId)\n const destination = teamPolicyRepo.get(db, input.destinationTeamId)\n if (!source || !destination) throw new Error('team_policy_missing')\n if (source.revision !== input.sourceExpectedRevision) {\n throw new Error(\n `source_team_revision_conflict: expected ${input.sourceExpectedRevision}, current ${source.revision}`,\n )\n }\n if (destination.revision !== input.destinationExpectedRevision) {\n throw new Error(\n `destination_team_revision_conflict: expected ${input.destinationExpectedRevision}, current ${destination.revision}`,\n )\n }\n db.raw.run(\n `DELETE FROM team_policy_edges WHERE team_id = ? AND\n ((source_kind = 'agent' AND source_id = ?) OR (target_kind = 'agent' AND target_id = ?))`,\n [current.teamId, current.id, current.id],\n )\n db.raw.run('DELETE FROM team_agent_state WHERE agent_id = ?', [current.id])\n pruneBindingAndEmptyCohort(db, current.id, current.teamId)\n agentRepo.setGroup(db, current.id, input.destinationTeamId)\n const metadata = JSON.parse(beforeMetadata) as Record<string, unknown>\n metadata.teamId = input.destinationTeamId\n writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\\n`)\n teamPolicyRepo.insertAgentState(db, input.destinationTeamId, current.id)\n teamPolicyRepo.addPlacementEdges(\n db,\n input.destinationTeamId,\n current.id,\n input.placement,\n current.profileId,\n )\n teamPolicyRepo.bumpRevision(db, current.teamId)\n teamPolicyRepo.bumpRevision(db, input.destinationTeamId)\n const moved = agentRepo.get(db, current.id)\n if (!moved) throw new Error(`agent vanished after move: ${current.id}`)\n return moved\n })()\n } catch (error) {\n writeFileSync(metadataPath, beforeMetadata)\n throw error\n }\n}\n\nexport function pruneBindingAndEmptyCohort(db: BazilionDb, agentId: string, teamId: string): void {\n const binding = db.raw\n .query<{ instantiation_id: string }, [string]>(\n 'SELECT instantiation_id FROM source_slot_bindings WHERE agent_id = ?',\n )\n .get(agentId)\n if (!binding) return\n db.raw.run('DELETE FROM source_slot_bindings WHERE agent_id = ?', [agentId])\n const remaining =\n db.raw\n .query<{ count: number }, [string]>(\n 'SELECT COUNT(*) AS count FROM source_slot_bindings WHERE instantiation_id = ?',\n )\n .get(binding.instantiation_id)?.count ?? 0\n const baseline = teamPolicyRepo.get(db, teamId)?.baselineInstantiationId\n if (remaining === 0 && baseline !== binding.instantiation_id) {\n db.raw.run('DELETE FROM template_instantiations WHERE id = ?', [binding.instantiation_id])\n }\n}\n","import { join } from 'node:path'\nimport type { ResolvedAgent } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport { loadIdentityFromFile } from '../profile/identity.ts'\nimport * as agentLessonProposalRepo from '../repos/agentLessonProposals.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport * as teamRepo from '../repos/teams.ts'\n\nexport function resolveAgent(db: BazilionDb, paths: Paths, agentId: string): ResolvedAgent {\n // `agentRepo.get` accepts either a full UUID or an unambiguous prefix.\n const agent = agentRepo.get(db, agentId)\n if (!agent) throw new Error(`agent not found: ${agentId}`)\n\n // Parse the agent's OWN IDENTITY.md (not the profile's) so the detail page\n // can show avatar/creature. `null` when the file is missing or still holds\n // only placeholder values.\n agent.identity = loadIdentityFromFile(join(agent.dir, 'IDENTITY.md'))\n\n const profile = profileRepo.get(db, agent.profileId)\n if (!profile) {\n throw new Error(`profile not found for agent ${agentId}: ${agent.profileId}`)\n }\n\n const team = teamRepo.get(db, agent.teamId, paths)\n if (!team) {\n throw new Error(`team not found for agent ${agentId}: ${agent.teamId}`)\n }\n\n return {\n agent,\n profile,\n model: agent.modelOverride ?? profile.defaultModel,\n reasoningLevel: agent.reasoningLevel,\n team,\n skills: agentRepo.listAttachedSkills(db, agentId),\n privateLessons: agentLessonProposalRepo\n .listForAgent(db, agent.id, { status: 'approved', limit: 100 })\n .filter((proposal) => proposal.scope === 'private')\n .map((proposal) => proposal.text),\n }\n}\n","import { readFileSync } from 'node:fs'\nimport type { AgentIdentityFile } from '@bazilion/api-types'\n\nconst IDENTITY_PLACEHOLDER_VALUES = new Set([\n 'pick something you like',\n 'ai? robot? familiar? ghost in the machine? something weirder?',\n 'how do you come across? sharp? warm? chaotic? calm?',\n 'your signature - pick one that feels right',\n 'workspace-relative path, http(s) url, or data uri',\n 'an http(s) url or data uri - optional',\n])\n\nfunction normalizeIdentityValue(value: string): string {\n let normalized = value.trim()\n normalized = normalized.replace(/^[*_]+|[*_]+$/g, '').trim()\n if (normalized.startsWith('(') && normalized.endsWith(')')) {\n normalized = normalized.slice(1, -1).trim()\n }\n normalized = normalized.replace(/[\\u2013\\u2014]/g, '-')\n normalized = normalized.replace(/\\s+/g, ' ').toLowerCase()\n return normalized\n}\n\nfunction isIdentityPlaceholder(value: string): boolean {\n return IDENTITY_PLACEHOLDER_VALUES.has(normalizeIdentityValue(value))\n}\n\nexport function parseIdentityMarkdown(content: string): AgentIdentityFile {\n const identity: AgentIdentityFile = {}\n for (const line of content.split(/\\r?\\n/)) {\n const cleaned = line.trim().replace(/^\\s*-\\s*/, '')\n const colonIndex = cleaned.indexOf(':')\n if (colonIndex === -1) continue\n const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, '').trim().toLowerCase()\n const value = cleaned\n .slice(colonIndex + 1)\n .replace(/^[*_]+|[*_]+$/g, '')\n .trim()\n if (!value) continue\n if (isIdentityPlaceholder(value)) continue\n if (label === 'name') identity.name = value\n else if (label === 'emoji') identity.emoji = value\n else if (label === 'creature') identity.creature = value\n else if (label === 'vibe') identity.vibe = value\n else if (label === 'theme') identity.theme = value\n else if (label === 'avatar') identity.avatar = value\n }\n return identity\n}\n\nexport function identityHasValues(identity: AgentIdentityFile): boolean {\n return Boolean(\n identity.name ||\n identity.emoji ||\n identity.theme ||\n identity.creature ||\n identity.vibe ||\n identity.avatar,\n )\n}\n\nexport function loadIdentityFromFile(path: string): AgentIdentityFile | null {\n let content: string\n try {\n content = readFileSync(path, 'utf8')\n } catch {\n return null\n }\n const parsed = parseIdentityMarkdown(content)\n return identityHasValues(parsed) ? parsed : null\n}\n","import { randomUUID } from 'node:crypto'\nimport type {\n AgentLessonEvidence,\n AgentLessonProposal,\n AgentLessonScope,\n AgentLessonStatus,\n} from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawProposal {\n id: string\n review_id: string\n agent_id: string\n scope: string\n text: string\n evidence_json: string\n status: string\n version: number\n decided_at: number | null\n revoked_at: number | null\n applied_key: string | null\n created_at: number\n updated_at: number\n}\n\nfunction toProposal(row: RawProposal): AgentLessonProposal {\n return {\n id: row.id,\n reviewId: row.review_id,\n agentId: row.agent_id,\n scope: row.scope as AgentLessonScope,\n text: row.text,\n evidence: JSON.parse(row.evidence_json) as AgentLessonEvidence[],\n status: row.status as AgentLessonStatus,\n version: row.version,\n decidedAt: row.decided_at,\n revokedAt: row.revoked_at,\n appliedKey: row.applied_key,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nexport function insert(\n db: BazilionDb,\n input: {\n reviewId: string\n agentId: string\n scope: AgentLessonScope\n text: string\n evidence: AgentLessonEvidence[]\n now?: number\n },\n): AgentLessonProposal {\n const now = input.now ?? Date.now()\n const id = randomUUID()\n db.raw.run(\n `INSERT INTO agent_lesson_proposals\n (id, review_id, agent_id, scope, text, evidence_json, status, version, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, 'pending', 1, ?, ?)`,\n [\n id,\n input.reviewId,\n input.agentId,\n input.scope,\n input.text.trim(),\n JSON.stringify(input.evidence),\n now,\n now,\n ],\n )\n const proposal = get(db, id)\n if (!proposal) throw new Error('lesson proposal insert failed')\n return proposal\n}\n\nexport function get(db: BazilionDb, id: string): AgentLessonProposal | null {\n const row = db.raw\n .query<RawProposal, [string]>('SELECT * FROM agent_lesson_proposals WHERE id = ?')\n .get(id)\n return row ? toProposal(row) : null\n}\n\nexport function listForReview(db: BazilionDb, reviewId: string): AgentLessonProposal[] {\n return db.raw\n .query<RawProposal, [string]>(\n 'SELECT * FROM agent_lesson_proposals WHERE review_id = ? ORDER BY created_at ASC',\n )\n .all(reviewId)\n .map(toProposal)\n}\n\nexport function listForAgent(\n db: BazilionDb,\n agentId: string,\n options: { status?: AgentLessonStatus; limit?: number } = {},\n): AgentLessonProposal[] {\n const limit = options.limit ?? 100\n const rows = options.status\n ? db.raw\n .query<RawProposal, [string, AgentLessonStatus, number]>(\n `SELECT * FROM agent_lesson_proposals WHERE agent_id = ? AND status = ?\n ORDER BY created_at DESC LIMIT ?`,\n )\n .all(agentId, options.status, limit)\n : db.raw\n .query<RawProposal, [string, number]>(\n `SELECT * FROM agent_lesson_proposals WHERE agent_id = ?\n ORDER BY created_at DESC LIMIT ?`,\n )\n .all(agentId, limit)\n return rows.map(toProposal)\n}\n\nfunction changedProposal(db: BazilionDb, id: string, changes: number): AgentLessonProposal | null {\n return changes === 1 ? get(db, id) : null\n}\n\nexport function updatePending(\n db: BazilionDb,\n id: string,\n version: number,\n input: { text: string; scope: AgentLessonScope },\n now = Date.now(),\n): AgentLessonProposal | null {\n const result = db.raw.run(\n `UPDATE agent_lesson_proposals SET text = ?, scope = ?, version = version + 1, updated_at = ?\n WHERE id = ? AND version = ? AND status = 'pending'`,\n [input.text.trim(), input.scope, now, id, version],\n )\n return changedProposal(db, id, Number(result.changes))\n}\n\nexport function approve(\n db: BazilionDb,\n id: string,\n version: number,\n appliedKey: string | null,\n now = Date.now(),\n): AgentLessonProposal | null {\n const result = db.raw.run(\n `UPDATE agent_lesson_proposals SET status = 'approved', applied_key = ?, decided_at = ?,\n version = version + 1, updated_at = ?\n WHERE id = ? AND version = ? AND status = 'pending'`,\n [appliedKey, now, now, id, version],\n )\n return changedProposal(db, id, Number(result.changes))\n}\n\nexport function reject(\n db: BazilionDb,\n id: string,\n version: number,\n now = Date.now(),\n): AgentLessonProposal | null {\n const result = db.raw.run(\n `UPDATE agent_lesson_proposals SET status = 'rejected', decided_at = ?,\n version = version + 1, updated_at = ?\n WHERE id = ? AND version = ? AND status = 'pending'`,\n [now, now, id, version],\n )\n return changedProposal(db, id, Number(result.changes))\n}\n\nexport function revoke(\n db: BazilionDb,\n id: string,\n version: number,\n now = Date.now(),\n): AgentLessonProposal | null {\n const result = db.raw.run(\n `UPDATE agent_lesson_proposals SET status = 'revoked', revoked_at = ?,\n version = version + 1, updated_at = ?\n WHERE id = ? AND version = ? AND status = 'approved'`,\n [now, now, id, version],\n )\n return changedProposal(db, id, Number(result.changes))\n}\n","import { randomUUID } from 'node:crypto'\nimport { mkdirSync, rmSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Agent, ReasoningLevel, TeamPolicyPlacement } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport { loadProfile } from '../profile/load.ts'\nimport { DEFAULT_TEAM_ID } from '../profile/seed.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\nimport { discoverSkills } from '../skills/discover.ts'\n\nexport interface SpawnAgentInput {\n profileId: string\n name?: string\n modelOverride?: string | null\n reasoningLevel?: ReasoningLevel\n /**\n * Team the new agent joins. One agent belongs to exactly one team. When\n * omitted, falls back to the seeded 'default' team — the same fallback\n * used everywhere we need a sensible cwd. Must refer to an existing team.\n */\n teamId?: string\n /** Internal batch-spawn switch; the caller regenerates/bump the aggregate once. */\n deferTeamPolicyUpdate?: boolean\n teamExpectedRevision?: number\n placement?: Exclude<TeamPolicyPlacement, 'template_snapshot'>\n}\n\nexport function spawnAgent(db: BazilionDb, paths: Paths, input: SpawnAgentInput): Agent {\n const loaded = loadProfile(db, input.profileId)\n const id = randomUUID()\n const dir = paths.agentDir(id)\n try {\n return db.raw.transaction(() => {\n // Agent's private home: identity files + sessions. Memory now lives at\n // the team level (`teams/<slug>/memory/`), shared by all member agents,\n // so we no longer create per-agent memory dirs here.\n mkdirSync(dir, { recursive: true })\n mkdirSync(join(dir, 'sessions'), { recursive: true })\n\n // Copy profile templates verbatim so the agent can diverge per-instance.\n // BOOTSTRAP.md is intentionally NOT personalized with the spawn slug —\n // the slug is a routing label, not necessarily the persona name. Bootstrap\n // is a pure conversation: the agent asks, the human answers, IDENTITY.md\n // is populated from that exchange.\n writeFileSync(join(dir, 'SOUL.md'), loaded.files.soul)\n writeFileSync(join(dir, 'IDENTITY.md'), loaded.files.identity)\n if (loaded.files.bootstrap !== null) {\n writeFileSync(join(dir, 'BOOTSTRAP.md'), loaded.files.bootstrap)\n }\n if (loaded.files.agents !== null) {\n writeFileSync(join(dir, 'AGENTS.md'), loaded.files.agents)\n }\n if (loaded.files.tools !== null) {\n writeFileSync(join(dir, 'TOOLS.md'), loaded.files.tools)\n }\n\n const reasoningLevel: ReasoningLevel = input.reasoningLevel ?? 'medium'\n\n // Resolve team: explicit input wins; otherwise fall back to the seeded\n // 'default'. If neither exists, error out — an agent can't live without a\n // team (the FK `agents.team_id REFERENCES teams(id)` enforces it too).\n const teamId = input.teamId ?? DEFAULT_TEAM_ID\n const team = teamRepo.get(db, teamId, paths)\n if (!team) {\n throw new Error(\n `spawnAgent: team \"${teamId}\" does not exist. Pass an explicit --team or complete first-run setup first.`,\n )\n }\n let placement = input.placement\n if (!input.deferTeamPolicyUpdate) {\n const teamPolicy = teamPolicyRepo.get(db, team.id)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${team.id}`)\n const expectedRevision = input.teamExpectedRevision ?? teamPolicy.revision\n placement ??= 'profile_defaults'\n if (teamPolicy.revision !== expectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${expectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n }\n\n const agentJson = {\n profileId: input.profileId,\n name: input.name ?? loaded.profile.name,\n modelOverride: input.modelOverride ?? null,\n reasoningLevel,\n teamId: team.id,\n }\n writeFileSync(join(dir, 'agent.json'), `${JSON.stringify(agentJson, null, 2)}\\n`)\n\n const agent = agentRepo.insert(db, {\n id,\n profileId: input.profileId,\n name: agentJson.name,\n modelOverride: agentJson.modelOverride,\n reasoningLevel,\n status: 'idle',\n dir,\n teamId: team.id,\n })\n\n // Skills come from the profile only — `skills_mode='all'` attaches every\n // installed skill, `'selected'` attaches the profile's default list.\n // Per-agent skill changes happen post-spawn via `agent skill add/rm`.\n const skills =\n loaded.profile.skillsMode === 'all'\n ? discoverSkills(paths).map((s) => s.name)\n : loaded.defaultSkills\n for (const s of skills) agentRepo.attachSkill(db, id, s)\n\n if (!input.deferTeamPolicyUpdate) {\n teamPolicyRepo.insertAgentState(db, team.id, agent.id)\n teamPolicyRepo.addPlacementEdges(\n db,\n team.id,\n agent.id,\n placement as Exclude<TeamPolicyPlacement, 'template_snapshot'>,\n input.profileId,\n )\n teamPolicyRepo.bumpRevision(db, team.id)\n }\n\n return agent\n })()\n } catch (error) {\n rmSync(dir, { recursive: true, force: true })\n throw error\n }\n}\n","import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { LoadedProfile } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport * as profileCommunicationDefaultsRepo from '../repos/profileCommunicationDefaults.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport { parseIdentityMarkdown } from './identity.ts'\n\nfunction readOptional(path: string): string | null {\n return existsSync(path) ? readFileSync(path, 'utf8') : null\n}\n\nexport function loadProfile(db: BazilionDb, id: string): LoadedProfile {\n const profile = profileRepo.get(db, id)\n if (!profile) throw new Error(`profile not found: ${id}`)\n\n const soul = readFileSync(join(profile.dir, 'SOUL.md'), 'utf8')\n const identityRaw = readFileSync(join(profile.dir, 'IDENTITY.md'), 'utf8')\n const bootstrap = readOptional(join(profile.dir, 'BOOTSTRAP.md'))\n const agents = readOptional(join(profile.dir, 'AGENTS.md'))\n const tools = readOptional(join(profile.dir, 'TOOLS.md'))\n\n const parsedIdentity = parseIdentityMarkdown(identityRaw)\n const anyIdentity =\n parsedIdentity.name ||\n parsedIdentity.emoji ||\n parsedIdentity.theme ||\n parsedIdentity.creature ||\n parsedIdentity.vibe ||\n parsedIdentity.avatar\n\n return {\n profile,\n defaultSkills: profileRepo.getDefaultSkills(db, id),\n files: {\n soul,\n identity: identityRaw,\n bootstrap,\n agents,\n tools,\n },\n identity: anyIdentity ? parsedIdentity : null,\n communicationDefaults: profileCommunicationDefaultsRepo.get(db, id),\n }\n}\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Profile, Team } from '@bazilion/api-types'\nimport { isSetupComplete, listAvailableModels } from '../availableModels.ts'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport * as teamRepo from '../repos/teams.ts'\nimport { registerTeam } from '../team/register.ts'\nimport { createProfile } from './create.ts'\nimport {\n DEFAULT_AGENTS,\n DEFAULT_BOOTSTRAP,\n DEFAULT_IDENTITY,\n DEFAULT_SOUL,\n DEFAULT_TOOLS,\n} from './templates.ts'\n\nexport const DEFAULT_PROFILE_ID = 'default'\nexport const DEFAULT_TEAM_ID = 'default'\n\nconst DEFAULT_PROFILE_FILES: [string, string][] = [\n ['SOUL.md', DEFAULT_SOUL],\n ['IDENTITY.md', DEFAULT_IDENTITY],\n ['BOOTSTRAP.md', DEFAULT_BOOTSTRAP],\n ['AGENTS.md', DEFAULT_AGENTS],\n ['TOOLS.md', DEFAULT_TOOLS],\n]\n\n/**\n * Bring the on-disk `default` profile's template files up to the current\n * defaults. The `default` profile is bazilion-managed — it always reflects\n * the shipped templates, so operators who want a customized template make\n * their own profile (which is never touched here). Pre-1.0, single-user:\n * we just overwrite rather than diffing against historical defaults.\n *\n * Write-if-differs so a steady-state boot is a no-op (and the log stays\n * quiet). No-op when the default profile isn't on disk yet — a fresh install\n * gets the current templates via seedDefaults → createProfile. Returns the\n * names of files that were (re)written.\n */\nexport function refreshDefaultProfileTemplates(paths: Paths): string[] {\n const dir = paths.profileDir(DEFAULT_PROFILE_ID)\n if (!existsSync(dir)) return []\n const written: string[] = []\n for (const [name, content] of DEFAULT_PROFILE_FILES) {\n const path = join(dir, name)\n if (!existsSync(path) || readFileSync(path, 'utf8') !== content) {\n writeFileSync(path, content)\n written.push(name)\n }\n }\n return written\n}\n\nexport interface SeedDefaultsInput {\n /** `provider:model` used as the profile's defaultModel. */\n model: string\n}\n\nexport interface SeedResult {\n profile: Profile\n team: Team\n /** true if the helper created the profile this call (vs. returned an existing one). */\n profileCreated: boolean\n /** true if the helper created the team this call. */\n teamCreated: boolean\n}\n\n/**\n * Seed the on-disk + DB defaults users land on after finishing first-run setup:\n * a `default` team at `~/.bazilion/teams/default/` and a `default` profile\n * wired to the just-enabled model. Fresh agents spawned from the default\n * profile land in the default team unless another is specified.\n *\n * Idempotent: re-seeding reuses whichever pieces already exist, so it's safe\n * to call whenever the setup state changes.\n */\nexport function seedDefaults(db: BazilionDb, paths: Paths, input: SeedDefaultsInput): SeedResult {\n let team = teamRepo.get(db, DEFAULT_TEAM_ID, paths)\n let teamCreated = false\n if (!team) {\n team = registerTeam(db, { id: DEFAULT_TEAM_ID, name: 'Default' }, paths)\n teamCreated = true\n }\n\n let profile = profileRepo.get(db, DEFAULT_PROFILE_ID)\n let profileCreated = false\n if (!profile) {\n // skillsMode='all' so freshly-spawned default agents inherit every\n // installed skill — the friendlier first-run posture. Custom profiles\n // still default to 'selected' (createProfile's own default).\n profile = createProfile(db, paths, {\n id: DEFAULT_PROFILE_ID,\n name: 'Default',\n defaultModel: input.model,\n skillsMode: 'all',\n communicationDefaults: {\n userInput: true,\n userOutput: true,\n outsideTeamInput: false,\n outsideTeamOutput: false,\n peerDefault: 'allow_all',\n },\n })\n profileCreated = true\n }\n\n return { profile, team, profileCreated, teamCreated }\n}\n\n/**\n * Safe to call from any endpoint that can change setup state (toggling a\n * provider, editing model lists). No-op when either setup isn't complete\n * (nothing to seed against yet) or the default profile already exists.\n * Returns the seed result only on the cold-start transition.\n */\nexport function ensureSetupSeeded(db: BazilionDb, paths: Paths): SeedResult | null {\n if (!isSetupComplete(db)) return null\n if (profileRepo.get(db, DEFAULT_PROFILE_ID)) return null\n const first = listAvailableModels(db)[0]\n if (!first) return null\n return seedDefaults(db, paths, { model: first.value })\n}\n","// Register a team: pick a slug, materialize `~/.bazilion/teams/<slug>/`\n// (real dir or symlink), insert the row.\n//\n// Two modes:\n// - default: creates a real directory at `paths.teamDir(slug)`.\n// - `--link <target>`: creates a symlink at `paths.teamDir(slug)` →\n// `<target>`. The link target must exist and be a directory — that's\n// the \"I want my agents working on my existing project tree\" path.\n//\n// Slug = the row's id. Names are humanized labels separate from the slug;\n// callers may pass `name` explicitly or let it default to the slug.\n\nimport { existsSync, mkdirSync, rmSync, statSync, symlinkSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport type { Team } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport { validateSlug } from '../profile/validate.ts'\nimport * as teamRepo from '../repos/teams.ts'\n\nexport interface RegisterTeamInput {\n /** Slug. Becomes the row id AND the directory name under `teams/`. */\n id: string\n /** Human-readable label. Defaults to `id`. */\n name?: string\n /**\n * If set, materialize `paths.teamDir(id)` as a symlink to this absolute\n * path instead of as a real directory. Target must exist and be a dir.\n */\n link?: string\n}\n\nexport function registerTeam(db: BazilionDb, input: RegisterTeamInput, paths: Paths): Team {\n validateSlug(input.id)\n\n if (teamRepo.get(db, input.id, paths)) {\n throw new Error(`team already registered: ${input.id}`)\n }\n\n const slot = paths.teamDir(input.id)\n if (existsSync(slot)) {\n throw new Error(`team slot already on disk at ${slot} (move or remove it first)`)\n }\n\n // Make sure the parent `teams/` dir exists — the daemon's bootstrap creates\n // it but tests sometimes resolve a custom $BAZILION_HOME without going\n // through bootstrap.\n mkdirSync(paths.teamsDir, { recursive: true })\n\n if (input.link) {\n const target = resolve(input.link)\n if (!existsSync(target)) {\n throw new Error(`--link target does not exist: ${target}`)\n }\n if (!statSync(target).isDirectory()) {\n throw new Error(`--link target is not a directory: ${target}`)\n }\n symlinkSync(target, slot, 'dir')\n } else {\n mkdirSync(slot, { recursive: true })\n }\n\n // Memory subdir is the qmd index root for this team — created here so\n // the first tool call doesn't have to ensure it.\n mkdirSync(resolve(slot, 'memory'), { recursive: true })\n\n try {\n return db.raw.transaction(() => {\n return teamRepo.insert(db, { id: input.id, name: input.name ?? input.id }, paths)\n })()\n } catch (error) {\n // Remove only the canonical slot. For linked Teams rmSync removes the\n // symlink itself and never touches its target.\n rmSync(slot, { recursive: true, force: true })\n throw error\n }\n}\n","import { mkdirSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Profile, ProfileCommunicationDefaults, SkillsMode } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as profileCommunicationDefaultsRepo from '../repos/profileCommunicationDefaults.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport {\n DEFAULT_AGENTS,\n DEFAULT_BOOTSTRAP,\n DEFAULT_IDENTITY,\n DEFAULT_SOUL,\n DEFAULT_TOOLS,\n} from './templates.ts'\nimport { validateSlug } from './validate.ts'\n\nexport interface CreateProfileInput {\n id: string\n name?: string\n defaultModel: string\n skillsMode?: SkillsMode\n defaultSkills?: string[]\n communicationDefaults?: ProfileCommunicationDefaults\n templates?: {\n soul?: string\n identity?: string\n /** undefined = default bootstrap, null = skip bootstrap, string = override */\n bootstrap?: string | null\n /** undefined = default template, null = skip, string = override */\n agents?: string | null\n /** undefined = default template, null = skip, string = override */\n tools?: string | null\n /** opt-in (off by default): null/undefined = skip, string = seed */\n }\n}\n\nexport function createProfile(db: BazilionDb, paths: Paths, input: CreateProfileInput): Profile {\n validateSlug(input.id)\n\n const dir = paths.profileDir(input.id)\n mkdirSync(dir, { recursive: true })\n\n const soul = input.templates?.soul ?? DEFAULT_SOUL\n const identity = input.templates?.identity ?? DEFAULT_IDENTITY\n // SOUL + IDENTITY are always written. BOOTSTRAP/AGENTS/TOOLS are default-on\n // (undefined → default template, null → skip, string → override).\n const bootstrap =\n input.templates?.bootstrap === null ? null : (input.templates?.bootstrap ?? DEFAULT_BOOTSTRAP)\n const agents =\n input.templates?.agents === null ? null : (input.templates?.agents ?? DEFAULT_AGENTS)\n const tools = input.templates?.tools === null ? null : (input.templates?.tools ?? DEFAULT_TOOLS)\n\n writeFileSync(join(dir, 'SOUL.md'), soul)\n writeFileSync(join(dir, 'IDENTITY.md'), identity)\n if (bootstrap !== null) {\n writeFileSync(join(dir, 'BOOTSTRAP.md'), bootstrap)\n }\n if (agents !== null) {\n writeFileSync(join(dir, 'AGENTS.md'), agents)\n }\n if (tools !== null) {\n writeFileSync(join(dir, 'TOOLS.md'), tools)\n }\n\n const skillsMode: SkillsMode = input.skillsMode ?? 'selected'\n const profileJson = {\n name: input.name ?? input.id,\n defaultModel: input.defaultModel,\n skillsMode,\n defaultSkills: input.defaultSkills ?? [],\n }\n writeFileSync(join(dir, 'profile.json'), `${JSON.stringify(profileJson, null, 2)}\\n`)\n\n const profile = profileRepo.insert(db, {\n id: input.id,\n name: profileJson.name,\n dir,\n defaultModel: input.defaultModel,\n skillsMode,\n })\n\n if (skillsMode === 'selected' && input.defaultSkills && input.defaultSkills.length > 0) {\n profileRepo.setDefaultSkills(db, input.id, input.defaultSkills)\n }\n if (input.communicationDefaults) {\n profileCommunicationDefaultsRepo.set(db, input.id, input.communicationDefaults)\n }\n\n return input.communicationDefaults\n ? { ...profile, communicationDefaults: input.communicationDefaults }\n : profile\n}\n","import { existsSync, readdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Paths } from '../paths.ts'\n\nexport interface DiscoveredSkill {\n name: string\n dir: string\n skillFile: string\n}\n\n/**\n * Walk the bazilion skill library and return every directory that contains a\n * SKILL.md file. Pure filesystem read — does not parse the markdown.\n */\nexport function discoverSkills(paths: Paths): DiscoveredSkill[] {\n if (!existsSync(paths.skillsDir)) return []\n\n const entries = readdirSync(paths.skillsDir, { withFileTypes: true })\n const skills: DiscoveredSkill[] = []\n for (const entry of entries) {\n if (!entry.isDirectory()) continue\n const dir = join(paths.skillsDir, entry.name)\n const skillFile = join(dir, 'SKILL.md')\n if (!existsSync(skillFile)) continue\n skills.push({ name: entry.name, dir, skillFile })\n }\n return skills.sort((a, b) => a.name.localeCompare(b.name))\n}\n","import { backup, DatabaseSync, type SQLInputValue } from 'node:sqlite'\n\nexport interface QueryStmt<RowType, ParamsType extends unknown[]> {\n get(...params: ParamsType): RowType | null\n all(...params: ParamsType): RowType[]\n run(...params: ParamsType): { changes: number; lastInsertRowid: number | bigint }\n}\n\nexport interface QueryableDatabase {\n query<RowType, ParamsType extends unknown[]>(sql: string): QueryStmt<RowType, ParamsType>\n run(sql: string, params?: unknown[]): { changes: number; lastInsertRowid: number | bigint }\n exec(sql: string): void\n transaction<T>(fn: () => T): () => T\n}\n\nexport interface BazilionDb {\n raw: QueryableDatabase\n /**\n * Copy a transactionally-consistent image of the live database to `path`.\n * SQLite's online-backup API includes committed WAL contents without\n * requiring the daemon to close its connection or checkpoint the WAL.\n */\n backupTo(path: string): Promise<number>\n close(): void\n}\n\nfunction wrap(rawDb: DatabaseSync): QueryableDatabase {\n const cache = new Map<string, ReturnType<DatabaseSync['prepare']>>()\n let transactionDepth = 0\n let savepointSequence = 0\n function getStmt(sql: string) {\n let s = cache.get(sql)\n if (!s) {\n s = rawDb.prepare(sql)\n cache.set(sql, s)\n }\n return s\n }\n\n return {\n query<R, P extends unknown[]>(sql: string): QueryStmt<R, P> {\n const stmt = getStmt(sql)\n return {\n get(...params: P): R | null {\n const result = stmt.get(...(params as SQLInputValue[]))\n return (result as R | undefined) ?? null\n },\n all(...params: P): R[] {\n return stmt.all(...(params as SQLInputValue[])) as R[]\n },\n run(...params: P) {\n return stmt.run(...(params as SQLInputValue[])) as {\n changes: number\n lastInsertRowid: number | bigint\n }\n },\n }\n },\n run(sql, params) {\n const stmt = getStmt(sql)\n return stmt.run(...((params ?? []) as SQLInputValue[])) as {\n changes: number\n lastInsertRowid: number | bigint\n }\n },\n exec(sql) {\n rawDb.exec(sql)\n },\n // node:sqlite has no callable `transaction` wrapper; use manual BEGIN/COMMIT/ROLLBACK.\n transaction<T>(fn: () => T): () => T {\n return () => {\n const outermost = transactionDepth === 0\n const savepoint = outermost ? null : `bazilion_nested_${++savepointSequence}`\n if (outermost) rawDb.exec('BEGIN')\n else rawDb.exec(`SAVEPOINT ${savepoint}`)\n transactionDepth++\n try {\n const result = fn()\n if (outermost) rawDb.exec('COMMIT')\n else rawDb.exec(`RELEASE SAVEPOINT ${savepoint}`)\n return result\n } catch (err) {\n if (outermost) rawDb.exec('ROLLBACK')\n else {\n rawDb.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`)\n rawDb.exec(`RELEASE SAVEPOINT ${savepoint}`)\n }\n throw err\n } finally {\n transactionDepth--\n }\n }\n },\n }\n}\n\nfunction applyPragmas(rawDb: DatabaseSync, includeWal: boolean): void {\n if (includeWal) {\n try {\n rawDb.exec('PRAGMA journal_mode = WAL')\n } catch {\n // some sqlite builds reject WAL on :memory: — ignore\n }\n }\n rawDb.exec('PRAGMA foreign_keys = ON')\n}\n\nexport function openDb(path: string): BazilionDb {\n // Bazilion is the normal sole writer, but online backups and operator-side\n // diagnostics can briefly overlap another WAL connection. Wait for those\n // transient locks instead of turning unrelated reads/writes (including\n // token usage telemetry) into immediate SQLITE_BUSY failures.\n const raw = new DatabaseSync(path, { timeout: 5_000 })\n applyPragmas(raw, true)\n return {\n raw: wrap(raw),\n backupTo(path) {\n return backup(raw, path)\n },\n close() {\n raw.close()\n },\n }\n}\n\nexport function openInMemoryDb(): BazilionDb {\n const raw = new DatabaseSync(':memory:')\n applyPragmas(raw, false)\n return {\n raw: wrap(raw),\n backupTo(path) {\n return backup(raw, path)\n },\n close() {\n raw.close()\n },\n }\n}\n\nexport function inTx<T>(db: BazilionDb, fn: () => T): T {\n return db.raw.transaction(fn)()\n}\n","import { readdirSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { type BazilionDb, openInMemoryDb } from './client.ts'\n\nconst migrationsDir = join(dirname(fileURLToPath(import.meta.url)), 'migrations')\nconst schemaMigrationsSql = `\n CREATE TABLE IF NOT EXISTS schema_migrations (\n version TEXT PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )\n`\n\nexport const INCOMPATIBLE_DATABASE_MESSAGE =\n 'Bazilion cannot start because this home uses an incompatible alpha database schema. ' +\n 'Preserve the entire Bazilion home directory as a filesystem backup first, keeping ' +\n 'bazilion.db and auth.json together. Then perform the reset with ' +\n '`bazilion uninstall --yes` (from the repository root of a source checkout: ' +\n '`pnpm tsx apps/cli/src/index.ts uninstall --yes`) and start Bazilion again. ' +\n 'Use `--all` only if you also want to remove logs and installed skills.'\n\nexport class IncompatibleDatabaseError extends Error {\n constructor() {\n super(INCOMPATIBLE_DATABASE_MESSAGE)\n this.name = 'IncompatibleDatabaseError'\n }\n}\n\ninterface MigrationFile {\n version: string\n sql: string\n}\n\ninterface SchemaObject {\n type: string\n name: string\n tbl_name: string\n sql: string\n}\n\nlet canonicalSchemaObjects: SchemaObject[] | null = null\n\nfunction migrationFiles(): MigrationFile[] {\n return readdirSync(migrationsDir)\n .filter((file) => file.endsWith('.sql'))\n .sort()\n .map((file) => ({\n version: file.replace(/\\.sql$/, ''),\n sql: readFileSync(join(migrationsDir, file), 'utf8'),\n }))\n}\n\nfunction normalizeSql(sql: string): string {\n return sql.replace(/\\s+/g, ' ').trim()\n}\n\nfunction schemaObjects(db: BazilionDb): SchemaObject[] {\n return db.raw\n .query<SchemaObject, []>(\n `SELECT type, name, tbl_name, sql\n FROM sqlite_schema\n WHERE sql IS NOT NULL AND substr(name, 1, 7) <> 'sqlite_'\n ORDER BY type, name`,\n )\n .all()\n}\n\nfunction schemaPayload(rows: SchemaObject[]): string {\n return rows\n .map((row) => [row.type, row.name, row.tbl_name, normalizeSql(row.sql)].join('\\0'))\n .join('\\n')\n}\n\nfunction expectedSchema(files: MigrationFile[]): SchemaObject[] {\n if (canonicalSchemaObjects !== null) return canonicalSchemaObjects\n\n const canonical = openInMemoryDb()\n try {\n canonical.raw.exec(schemaMigrationsSql)\n for (const file of files) canonical.raw.exec(file.sql)\n canonicalSchemaObjects = schemaObjects(canonical)\n return canonicalSchemaObjects\n } finally {\n canonical.close()\n }\n}\n\n/**\n * Enforce the alpha clean-install database contract before any runtime work\n * starts. A fresh database may contain only the empty migration ledger; an\n * initialized database must match this release's ledger and complete schema.\n */\nexport function assertMigrationCompatibility(db: BazilionDb): void {\n const files = migrationFiles()\n const expectedVersions = files.map((file) => file.version)\n let appliedVersions: string[]\n try {\n appliedVersions = db.raw\n .query<{ version: string }, []>('SELECT version FROM schema_migrations ORDER BY version')\n .all()\n .map((row) => row.version)\n } catch {\n throw new IncompatibleDatabaseError()\n }\n\n const actualObjects = schemaObjects(db)\n const expectedObjects = expectedSchema(files)\n\n if (appliedVersions.length === 0) {\n const migrationTable = actualObjects.filter(\n (row) => row.type === 'table' && row.name === 'schema_migrations',\n )\n const expectedMigrationTable = expectedObjects.filter(\n (row) => row.type === 'table' && row.name === 'schema_migrations',\n )\n if (\n actualObjects.length !== 1 ||\n migrationTable.length !== 1 ||\n schemaPayload(migrationTable) !== schemaPayload(expectedMigrationTable)\n ) {\n throw new IncompatibleDatabaseError()\n }\n return\n }\n\n if (\n appliedVersions.length !== expectedVersions.length ||\n appliedVersions.some((version, index) => version !== expectedVersions[index]) ||\n schemaPayload(actualObjects) !== schemaPayload(expectedObjects)\n ) {\n throw new IncompatibleDatabaseError()\n }\n}\n\nexport function runMigrations(db: BazilionDb): void {\n const existingObjects = schemaObjects(db)\n if (\n existingObjects.length > 0 &&\n !existingObjects.some((row) => row.type === 'table' && row.name === 'schema_migrations')\n ) {\n // Do not mutate an unknown pre-ledger/corrupt database merely to discover\n // that it is incompatible with the clean-install contract.\n throw new IncompatibleDatabaseError()\n }\n db.raw.exec(schemaMigrationsSql)\n assertMigrationCompatibility(db)\n\n const applied = new Set(\n db.raw\n .query<{ version: string }, []>('SELECT version FROM schema_migrations')\n .all()\n .map((r) => r.version),\n )\n\n for (const file of migrationFiles()) {\n if (applied.has(file.version)) continue\n const tx = db.raw.transaction(() => {\n db.raw.exec(file.sql)\n db.raw.run('INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)', [\n file.version,\n Date.now(),\n ])\n })\n tx()\n }\n\n assertMigrationCompatibility(db)\n}\n","import { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface Paths {\n home: string\n db: string\n /**\n * Bootstrap auth file shared by the daemon and the CLI: `{token, remote?}`.\n * - `token` is the plaintext of the bootstrap web token. The daemon reads\n * it once at startup to derive the encryption key for the `secrets`\n * table (PBKDF2 over it) and to validate that it matches a row in\n * `web_tokens` (so a corrupted file fails loudly). The CLI reads it as\n * its loopback bearer.\n * - `remote` (set via `bazilion login`) is a CLI-only override pointing at\n * a remote daemon. The local daemon ignores this field.\n *\n * One file replaces the previous `config.json` + `secrets.enc` split:\n * encrypted secrets and plaintext config now live as DB rows.\n */\n authFile: string\n profilesDir: string\n agentsDir: string\n skillsDir: string\n teamsDir: string\n logsDir: string\n profileDir(id: string): string\n agentDir(id: string): string\n skillDir(name: string): string\n teamDir(slug: string): string\n}\n\nexport function resolvePaths(home?: string): Paths {\n const root = home ?? process.env.BAZILION_HOME ?? join(homedir(), '.bazilion')\n return {\n home: root,\n db: join(root, 'bazilion.db'),\n authFile: join(root, 'auth.json'),\n profilesDir: join(root, 'profiles'),\n agentsDir: join(root, 'agents'),\n skillsDir: join(root, 'skills'),\n teamsDir: join(root, 'teams'),\n logsDir: join(root, 'logs'),\n profileDir(id) {\n return join(root, 'profiles', id)\n },\n agentDir(id) {\n return join(root, 'agents', id)\n },\n skillDir(name) {\n return join(root, 'skills', name)\n },\n teamDir(slug) {\n return join(root, 'teams', slug)\n },\n }\n}\n","import { existsSync, rmSync } from 'node:fs'\nimport type { BazilionDb } from '../db/client.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport * as teamTemplateRepo from '../repos/teamTemplates.ts'\n\nexport function deleteProfile(db: BazilionDb, id: string): void {\n const profile = profileRepo.get(db, id)\n if (!profile) throw new Error(`profile not found: ${id}`)\n\n // Check for agents still using this profile\n const agents = agentRepo.list(db, { includeArchived: true }).filter((a) => a.profileId === id)\n if (agents.length > 0) {\n const names = agents.map((a) => `${a.name} (${a.id.slice(0, 8)})`).join(', ')\n throw new Error(\n `profile_in_use: cannot delete profile \"${id}\": ${agents.length} agent(s) still reference it: ${names}. Delete or re-profile them first.`,\n )\n }\n\n // Check the canonical Team roster and every retained immutable snapshot.\n // Their Profile foreign keys are RESTRICT, so surface a stable domain error\n // instead of an opaque SQLite constraint failure.\n const refTemplates = teamTemplateRepo.findReferencingProfile(db, id)\n if (refTemplates.length > 0) {\n const names = refTemplates.map((template) => `${template.name} (${template.id})`).join(', ')\n throw new Error(\n `profile_in_use: cannot delete profile \"${id}\": ${refTemplates.length} Team Template(s) still reference it: ${names}. Remove the slot(s) first.`,\n )\n }\n\n // Remove from DB (CASCADE deletes profile_default_skills)\n profileRepo.remove(db, id)\n\n // Remove the profile directory from disk\n if (existsSync(profile.dir)) {\n rmSync(profile.dir, { recursive: true, force: true })\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type {\n ReasoningLevel,\n TeamTemplate,\n TeamTemplateDetail,\n TeamTemplateEdge,\n TeamTemplateRevision,\n TeamTemplateSlot,\n} from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawTemplate {\n id: string\n name: string\n user_md: string | null\n current_revision: number\n deleted_at: number | null\n created_at: number\n updated_at: number\n}\n\ninterface RawSlot {\n template_id: string\n slot_id: string\n position: number\n profile_id: string\n agent_name: string\n model_override: string | null\n reasoning_level: ReasoningLevel | null\n position_x: number | null\n position_y: number | null\n display_json: string | null\n tombstoned_at: number | null\n}\n\ninterface RawEdge {\n template_id: string\n source_kind: TeamTemplateEdge['sourceKind']\n source_id: string\n target_kind: TeamTemplateEdge['targetKind']\n target_id: string\n posture: TeamTemplateEdge['posture']\n}\n\nexport interface CanonicalSlotInput {\n slotId?: string\n /** Request-local reference for a new server-allocated slot. Never persisted. */\n clientKey?: string\n profileId: string\n agentName: string\n modelOverride?: string | null\n reasoningLevel?: ReasoningLevel | null\n layoutPosition?: { x: number; y: number } | null\n display?: Record<string, unknown> | null\n}\n\nexport interface CanonicalEdgeInput {\n sourceKind: TeamTemplateEdge['sourceKind']\n sourceId?: string | null\n targetKind: TeamTemplateEdge['targetKind']\n targetId?: string | null\n posture?: TeamTemplateEdge['posture']\n}\n\nexport interface CanonicalDefinitionInput {\n expectedRevision: number\n slots: CanonicalSlotInput[]\n edges: CanonicalEdgeInput[]\n /** Daemon-only workflows may pass UUIDs they just allocated. HTTP callers never set this. */\n allowAllocatedSlotIds?: boolean\n}\n\nfunction toTemplate(row: RawTemplate): TeamTemplate {\n return {\n id: row.id,\n name: row.name,\n userMd: row.user_md,\n currentRevision: row.current_revision,\n deletedAt: row.deleted_at,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nfunction toSlot(row: RawSlot): TeamTemplateSlot {\n return {\n templateId: row.template_id,\n slotId: row.slot_id,\n position: row.position,\n profileId: row.profile_id,\n agentName: row.agent_name,\n modelOverride: row.model_override,\n reasoningLevel: row.reasoning_level,\n layoutPosition:\n row.position_x === null || row.position_y === null\n ? null\n : { x: row.position_x, y: row.position_y },\n display: row.display_json ? (JSON.parse(row.display_json) as Record<string, unknown>) : null,\n tombstonedAt: row.tombstoned_at,\n }\n}\n\nfunction toEdge(row: RawEdge): TeamTemplateEdge {\n return {\n templateId: row.template_id,\n sourceKind: row.source_kind,\n sourceId: row.source_id || null,\n targetKind: row.target_kind,\n targetId: row.target_id || null,\n posture: row.posture,\n }\n}\n\nexport function get(db: BazilionDb, id: string): TeamTemplate | null {\n const row = db.raw\n .query<RawTemplate, [string]>('SELECT * FROM team_templates WHERE id = ?')\n .get(id)\n return row ? toTemplate(row) : null\n}\n\nexport function list(db: BazilionDb): TeamTemplate[] {\n return db.raw\n .query<RawTemplate, []>('SELECT * FROM team_templates ORDER BY created_at ASC')\n .all()\n .map(toTemplate)\n}\n\nexport function detail(db: BazilionDb, id: string): TeamTemplateDetail | null {\n const template = get(db, id)\n if (!template) return null\n const currentSnapshot = revision(db, id, template.currentRevision)\n if (!currentSnapshot)\n throw new Error(`template_snapshot_missing: ${id}@${template.currentRevision}`)\n return { template, slots: slots(db, id), edges: edges(db, id), currentSnapshot }\n}\n\nexport function insertCanonical(\n db: BazilionDb,\n input: { id: string; name: string; userMd?: string | null },\n): TeamTemplateDetail {\n const now = Date.now()\n db.raw.transaction(() => {\n db.raw.run(\n `INSERT INTO team_templates\n (id, name, user_md, current_revision, deleted_at, created_at, updated_at)\n VALUES (?, ?, ?, 1, NULL, ?, ?)`,\n [input.id, input.name, input.userMd ?? null, now, now],\n )\n snapshotCurrent(db, input.id, 1, now)\n })()\n return requireDetail(db, input.id)\n}\n\nexport function insertCanonicalDefinition(\n db: BazilionDb,\n input: {\n id: string\n name: string\n userMd?: string | null\n slots: CanonicalSlotInput[]\n edges: CanonicalEdgeInput[]\n },\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n validateCanonicalDefinition(input.slots, input.edges)\n if (input.slots.some((slot) => !slot.slotId)) {\n throw new Error('invalid_template_definition: initial slots require allocated ids')\n }\n const now = Date.now()\n db.raw.run(\n `INSERT INTO team_templates\n (id, name, user_md, current_revision, deleted_at, created_at, updated_at)\n VALUES (?, ?, ?, 1, NULL, ?, ?)`,\n [input.id, input.name, input.userMd ?? null, now, now],\n )\n for (let position = 0; position < input.slots.length; position++) {\n const slot = input.slots[position]\n if (!slot?.slotId) continue\n db.raw.run(\n `INSERT INTO team_template_slots\n (template_id, slot_id, position, profile_id, agent_name, model_override,\n reasoning_level, position_x, position_y, display_json, tombstoned_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n [\n input.id,\n slot.slotId,\n position,\n slot.profileId,\n slot.agentName,\n slot.modelOverride ?? null,\n slot.reasoningLevel ?? null,\n slot.layoutPosition?.x ?? null,\n slot.layoutPosition?.y ?? null,\n slot.display ? JSON.stringify(slot.display) : null,\n ],\n )\n }\n for (const edge of input.edges) insertTemplateEdge(db, input.id, edge)\n snapshotCurrent(db, input.id, 1, now)\n return requireDetail(db, input.id)\n })()\n}\n\nexport function updateCanonicalMetadata(\n db: BazilionDb,\n id: string,\n input: { expectedRevision: number; name?: string; userMd?: string | null },\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n const template = requireMutable(db, id)\n requireRevision(template, input.expectedRevision)\n const name = input.name ?? template.name\n const userMd = Object.hasOwn(input, 'userMd') ? (input.userMd ?? null) : template.userMd\n if (name === template.name && userMd === template.userMd) return requireDetail(db, id)\n const now = Date.now()\n const next = template.currentRevision + 1\n db.raw.run(\n `UPDATE team_templates SET name = ?, user_md = ?, current_revision = ?, updated_at = ?\n WHERE id = ?`,\n [name, userMd, next, now, id],\n )\n snapshotCurrent(db, id, next, now)\n return requireDetail(db, id)\n })()\n}\n\nexport function replaceCanonicalDefinition(\n db: BazilionDb,\n templateId: string,\n input: CanonicalDefinitionInput,\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n const template = requireMutable(db, templateId)\n requireRevision(template, input.expectedRevision)\n const current = slots(db, templateId, { includeTombstoned: true })\n const currentById = new Map(current.map((slot) => [slot.slotId, slot]))\n const activeIds = new Set<string>()\n const resolvedRefs = new Map<string, string>()\n for (const value of input.slots) {\n if (value.slotId && !currentById.has(value.slotId) && !input.allowAllocatedSlotIds) {\n throw new Error(\n `invalid_template_definition: caller cannot allocate slot id ${value.slotId}`,\n )\n }\n if (value.slotId && value.clientKey) {\n throw new Error('invalid_template_definition: slotId and clientKey are mutually exclusive')\n }\n const reference = value.slotId ?? value.clientKey\n if (!reference) continue\n if (resolvedRefs.has(reference)) {\n throw new Error(`invalid_template_definition: duplicate slot reference ${reference}`)\n }\n resolvedRefs.set(reference, value.slotId ?? randomUUID())\n }\n const resolvedEdges = input.edges.map((edge) => ({\n ...edge,\n sourceId:\n edge.sourceKind === 'slot'\n ? (resolvedRefs.get(edge.sourceId ?? '') ?? edge.sourceId)\n : null,\n targetId:\n edge.targetKind === 'slot'\n ? (resolvedRefs.get(edge.targetId ?? '') ?? edge.targetId)\n : null,\n }))\n const resolvedSlots = input.slots.map((slot) => ({\n ...slot,\n slotId: slot.slotId ?? (slot.clientKey ? resolvedRefs.get(slot.clientKey) : randomUUID()),\n }))\n validateCanonicalDefinition(resolvedSlots, resolvedEdges)\n const now = Date.now()\n // Free the partial unique position index before applying an arbitrary reorder.\n db.raw.run(\n `UPDATE team_template_slots SET position = position + 1000000\n WHERE template_id = ? AND tombstoned_at IS NULL`,\n [templateId],\n )\n for (let position = 0; position < resolvedSlots.length; position++) {\n const value = resolvedSlots[position]\n if (!value) continue\n const slotId = value.slotId ?? randomUUID()\n if (activeIds.has(slotId))\n throw new Error(`invalid_template_definition: duplicate slot ${slotId}`)\n activeIds.add(slotId)\n const existing = currentById.get(slotId)\n if (existing?.tombstonedAt !== null && existing) {\n throw new Error(\n `invalid_template_definition: tombstoned slot cannot be re-added: ${slotId}`,\n )\n }\n const coordinates = value.layoutPosition ?? null\n const params = [\n position,\n value.profileId,\n value.agentName,\n value.modelOverride ?? null,\n value.reasoningLevel ?? null,\n coordinates?.x ?? null,\n coordinates?.y ?? null,\n value.display ? JSON.stringify(value.display) : null,\n ]\n if (existing) {\n db.raw.run(\n `UPDATE team_template_slots SET position = ?, profile_id = ?, agent_name = ?,\n model_override = ?, reasoning_level = ?, position_x = ?, position_y = ?, display_json = ?\n WHERE template_id = ? AND slot_id = ?`,\n [...params, templateId, slotId],\n )\n } else {\n db.raw.run(\n `INSERT INTO team_template_slots\n (template_id, slot_id, position, profile_id, agent_name, model_override,\n reasoning_level, position_x, position_y, display_json, tombstoned_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n [templateId, slotId, ...params],\n )\n }\n }\n for (const old of current) {\n if (old.tombstonedAt === null && !activeIds.has(old.slotId)) {\n db.raw.run(\n 'UPDATE team_template_slots SET tombstoned_at = ? WHERE template_id = ? AND slot_id = ?',\n [now, templateId, old.slotId],\n )\n }\n }\n db.raw.run('DELETE FROM team_template_edges WHERE template_id = ?', [templateId])\n for (const edge of resolvedEdges) insertTemplateEdge(db, templateId, edge)\n const next = template.currentRevision + 1\n db.raw.run(`UPDATE team_templates SET current_revision = ?, updated_at = ? WHERE id = ?`, [\n next,\n now,\n templateId,\n ])\n snapshotCurrent(db, templateId, next, now)\n return requireDetail(db, templateId)\n })()\n}\n\nexport function cloneCanonical(\n db: BazilionDb,\n sourceId: string,\n input: { expectedRevision: number; id: string; name?: string },\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n const source = requireMutable(db, sourceId)\n requireRevision(source, input.expectedRevision)\n const snapshot = revision(db, sourceId, input.expectedRevision)\n if (!snapshot)\n throw new Error(`template_snapshot_missing: ${sourceId}@${input.expectedRevision}`)\n const translated = new Map(snapshot.slots.map((slot) => [slot.slotId, randomUUID()]))\n return insertCanonicalDefinition(db, {\n id: input.id,\n name: input.name ?? `${source.name} copy`,\n userMd: snapshot.userMd,\n slots: snapshot.slots.map((slot) => ({\n slotId: translated.get(slot.slotId),\n profileId: slot.profileId,\n agentName: slot.agentName,\n modelOverride: slot.modelOverride,\n reasoningLevel: slot.reasoningLevel,\n layoutPosition: slot.layoutPosition,\n display: slot.display,\n })),\n edges: snapshot.edges.map((edge) => ({\n sourceKind: edge.sourceKind,\n sourceId: edge.sourceKind === 'slot' ? translated.get(edge.sourceId ?? '') : null,\n targetKind: edge.targetKind,\n targetId: edge.targetKind === 'slot' ? translated.get(edge.targetId ?? '') : null,\n })),\n })\n })()\n}\n\nexport function removeCanonical(\n db: BazilionDb,\n id: string,\n expectedRevision: number,\n): 'deleted' | 'tombstoned' {\n return db.raw.transaction(() => {\n const template = requireMutable(db, id)\n requireRevision(template, expectedRevision)\n const lineage = db.raw\n .query<{ count: number }, [string]>(\n 'SELECT COUNT(*) AS count FROM template_instantiations WHERE template_id = ?',\n )\n .get(id)?.count\n if ((lineage ?? 0) > 0) {\n const now = Date.now()\n db.raw.run('UPDATE team_templates SET deleted_at = ?, updated_at = ? WHERE id = ?', [\n now,\n now,\n id,\n ])\n return 'tombstoned'\n }\n db.raw.run('DELETE FROM team_templates WHERE id = ?', [id])\n return 'deleted'\n })()\n}\n\nexport function slots(\n db: BazilionDb,\n templateId: string,\n options: { includeTombstoned?: boolean } = {},\n): TeamTemplateSlot[] {\n const where = options.includeTombstoned ? '' : 'AND tombstoned_at IS NULL'\n return db.raw\n .query<RawSlot, [string]>(\n `SELECT * FROM team_template_slots\n WHERE template_id = ? ${where}\n ORDER BY position ASC, slot_id ASC`,\n )\n .all(templateId)\n .map(toSlot)\n}\n\nexport function edges(db: BazilionDb, templateId: string): TeamTemplateEdge[] {\n return db.raw\n .query<RawEdge, [string]>(\n `SELECT * FROM team_template_edges WHERE template_id = ?\n ORDER BY source_kind, source_id, target_kind, target_id`,\n )\n .all(templateId)\n .map(toEdge)\n}\n\nexport function revision(\n db: BazilionDb,\n templateId: string,\n revisionNumber: number,\n): TeamTemplateRevision | null {\n const header = db.raw\n .query<{ name: string; user_md: string | null; created_at: number }, [string, number]>(\n `SELECT name, user_md, created_at FROM team_template_revisions\n WHERE template_id = ? AND revision = ?`,\n )\n .get(templateId, revisionNumber)\n if (!header) return null\n const revisionSlots = db.raw\n .query<RawSlot, [string, number]>(\n `SELECT template_id, slot_id, position, profile_id, agent_name, model_override,\n reasoning_level, position_x, position_y, display_json, NULL AS tombstoned_at\n FROM team_template_revision_slots\n WHERE template_id = ? AND revision = ? ORDER BY position ASC`,\n )\n .all(templateId, revisionNumber)\n .map(toSlot)\n const revisionEdges = db.raw\n .query<RawEdge, [string, number]>(\n `SELECT template_id, source_kind, source_id, target_kind, target_id, posture\n FROM team_template_revision_edges\n WHERE template_id = ? AND revision = ?\n ORDER BY source_kind, source_id, target_kind, target_id`,\n )\n .all(templateId, revisionNumber)\n .map(toEdge)\n return {\n templateId,\n revision: revisionNumber,\n name: header.name,\n userMd: header.user_md,\n slots: revisionSlots,\n edges: revisionEdges,\n createdAt: header.created_at,\n }\n}\n\nfunction snapshotCurrent(\n db: BazilionDb,\n templateId: string,\n revisionNumber: number,\n now: number,\n): void {\n const template = get(db, templateId)\n if (!template) throw new Error(`team template not found: ${templateId}`)\n db.raw.run(\n `INSERT INTO team_template_revisions (template_id, revision, name, user_md, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n [templateId, revisionNumber, template.name, template.userMd, now],\n )\n db.raw.run(\n `INSERT INTO team_template_revision_slots\n SELECT template_id, ?, slot_id, position, profile_id, agent_name, model_override,\n reasoning_level, position_x, position_y, display_json\n FROM team_template_slots WHERE template_id = ? AND tombstoned_at IS NULL`,\n [revisionNumber, templateId],\n )\n db.raw.run(\n `INSERT INTO team_template_revision_edges\n SELECT template_id, ?, source_kind, source_id, target_kind, target_id, posture\n FROM team_template_edges WHERE template_id = ?`,\n [revisionNumber, templateId],\n )\n}\n\nexport function pruneRevision(db: BazilionDb, templateId: string, revisionNumber: number): void {\n const template = get(db, templateId)\n if (!template) throw new Error(`team template not found: ${templateId}`)\n if (template.currentRevision === revisionNumber) {\n throw new Error(`template_revision_in_use: revision ${revisionNumber} is current`)\n }\n const retained = db.raw\n .query<{ count: number }, [string, number]>(\n `SELECT COUNT(*) AS count FROM template_instantiations\n WHERE template_id = ? AND template_revision = ?`,\n )\n .get(templateId, revisionNumber)?.count\n if ((retained ?? 0) > 0) {\n throw new Error(`template_revision_in_use: revision ${revisionNumber} has live lineage`)\n }\n db.raw.run('DELETE FROM team_template_revisions WHERE template_id = ? AND revision = ?', [\n templateId,\n revisionNumber,\n ])\n}\n\nexport function findReferencingProfile(\n db: BazilionDb,\n profileId: string,\n): Array<{ id: string; name: string }> {\n return db.raw\n .query<{ id: string; name: string }, [string, string]>(\n `SELECT DISTINCT t.id, t.name FROM team_templates t\n WHERE EXISTS (\n SELECT 1 FROM team_template_slots s\n WHERE s.template_id = t.id AND s.profile_id = ?\n ) OR EXISTS (\n SELECT 1 FROM team_template_revision_slots s\n WHERE s.template_id = t.id AND s.profile_id = ?\n )`,\n )\n .all(profileId, profileId)\n}\n\nfunction requireDetail(db: BazilionDb, id: string): TeamTemplateDetail {\n const found = detail(db, id)\n if (!found) throw new Error(`team template not found: ${id}`)\n return found\n}\n\nfunction requireMutable(db: BazilionDb, id: string): TeamTemplate {\n const template = get(db, id)\n if (!template) throw new Error(`team template not found: ${id}`)\n if (template.deletedAt !== null) throw new Error(`template_deleted: ${id}`)\n return template\n}\n\nfunction requireRevision(template: TeamTemplate, expected: number): void {\n if (!Number.isInteger(expected) || expected < 1 || expected !== template.currentRevision) {\n throw new Error(\n `template_revision_conflict: expected ${expected}, current ${template.currentRevision}`,\n )\n }\n}\n\nfunction validateCanonicalDefinition(\n definitionSlots: CanonicalSlotInput[],\n definitionEdges: CanonicalEdgeInput[],\n): void {\n if (!Array.isArray(definitionSlots) || !Array.isArray(definitionEdges)) {\n throw new Error('invalid_template_definition: slots and edges arrays are required')\n }\n const ids = new Set(definitionSlots.map((slot) => slot.slotId).filter(Boolean) as string[])\n if (ids.size !== definitionSlots.filter((slot) => slot.slotId).length) {\n throw new Error('invalid_template_definition: duplicate slot id')\n }\n const edgeKeys = new Set<string>()\n for (const edge of definitionEdges) {\n if (\n edge.posture !== undefined &&\n edge.posture !== 'allow' &&\n edge.posture !== 'approval_required'\n ) {\n throw new Error(`invalid_template_definition: invalid edge posture ${edge.posture}`)\n }\n for (const endpoint of [\n [edge.sourceKind, edge.sourceId],\n [edge.targetKind, edge.targetId],\n ] as const) {\n const [kind, id] = endpoint\n if (!['user', 'outside_team', 'slot'].includes(kind)) {\n throw new Error(`invalid_template_definition: invalid endpoint kind ${kind}`)\n }\n if ((kind === 'slot') !== (typeof id === 'string' && id.length > 0)) {\n throw new Error('invalid_template_definition: slot endpoints require an id')\n }\n if (kind === 'slot' && !ids.has(id ?? '')) {\n throw new Error(`invalid_template_definition: unknown slot endpoint ${id}`)\n }\n }\n if (edge.sourceKind !== 'slot' && edge.targetKind !== 'slot') {\n throw new Error('invalid_template_definition: boundary-to-boundary edge')\n }\n if (edge.sourceKind === edge.targetKind && edge.sourceId === edge.targetId) {\n throw new Error('invalid_template_definition: self edge')\n }\n const key = `${edge.sourceKind}:${edge.sourceId ?? ''}>${edge.targetKind}:${edge.targetId ?? ''}`\n if (edgeKeys.has(key)) throw new Error(`invalid_template_definition: duplicate edge ${key}`)\n edgeKeys.add(key)\n }\n}\n\nfunction insertTemplateEdge(db: BazilionDb, templateId: string, edge: CanonicalEdgeInput): void {\n db.raw.run(\n `INSERT INTO team_template_edges\n (template_id, source_kind, source_id, target_kind, target_id, posture)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [\n templateId,\n edge.sourceKind,\n edge.sourceId ?? '',\n edge.targetKind,\n edge.targetId ?? '',\n edge.posture ?? 'allow',\n ],\n )\n}\n","import { writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { Profile, ProfileCommunicationDefaults, SkillsMode } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as profileCommunicationDefaultsRepo from '../repos/profileCommunicationDefaults.ts'\nimport * as profileRepo from '../repos/profiles.ts'\n\nexport interface UpdateProfileInput {\n name?: string\n defaultModel?: string\n skillsMode?: SkillsMode\n defaultSkills?: string[]\n communicationDefaults?: ProfileCommunicationDefaults | null\n}\n\n/**\n * Update the mutable settings in profile.json + the DB row in lockstep.\n * Profile files (SOUL/IDENTITY/BOOTSTRAP) and the memory backend are NOT\n * touched — those have their own paths.\n */\nexport function updateProfile(\n db: BazilionDb,\n paths: Paths,\n id: string,\n input: UpdateProfileInput,\n): Profile {\n const existing = profileRepo.get(db, id)\n if (!existing) throw new Error(`profile not found: ${id}`)\n\n const nextSkillsMode: SkillsMode = input.skillsMode ?? existing.skillsMode\n\n const next = {\n name: input.name ?? existing.name,\n defaultModel: input.defaultModel ?? existing.defaultModel,\n skillsMode: nextSkillsMode,\n }\n profileRepo.update(db, id, next)\n\n if (input.defaultSkills !== undefined) {\n profileRepo.setDefaultSkills(db, id, input.defaultSkills)\n }\n if (Object.hasOwn(input, 'communicationDefaults')) {\n profileCommunicationDefaultsRepo.set(db, id, input.communicationDefaults ?? null)\n }\n\n const skills = profileRepo.getDefaultSkills(db, id)\n const profileJson = {\n name: next.name,\n defaultModel: next.defaultModel,\n skillsMode: next.skillsMode,\n defaultSkills: skills,\n }\n writeFileSync(\n join(paths.profileDir(id), 'profile.json'),\n `${JSON.stringify(profileJson, null, 2)}\\n`,\n )\n\n const updated = profileRepo.get(db, id)\n if (!updated) throw new Error(`profile vanished after update: ${id}`)\n return {\n ...updated,\n communicationDefaults: profileCommunicationDefaultsRepo.get(db, id),\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { AgentReview, AgentReviewStatus, AgentReviewTrigger } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawReview {\n id: string\n agent_id: string\n status: string\n trigger_kind: string\n source_session_id: string | null\n source_start_ordinal: number | null\n source_end_ordinal: number | null\n input_characters: number\n turns_reviewed: number\n proposal_count: number\n attempt_count: number\n next_attempt_at: number\n lease_expires_at: number | null\n started_at: number | null\n finished_at: number | null\n last_error: string | null\n created_at: number\n updated_at: number\n}\n\nfunction toReview(row: RawReview): AgentReview {\n return {\n id: row.id,\n agentId: row.agent_id,\n status: row.status as AgentReviewStatus,\n trigger: row.trigger_kind as AgentReviewTrigger,\n sourceSessionId: row.source_session_id,\n sourceStartOrdinal: row.source_start_ordinal,\n sourceEndOrdinal: row.source_end_ordinal,\n inputCharacters: row.input_characters,\n turnsReviewed: row.turns_reviewed,\n proposalCount: row.proposal_count,\n attemptCount: row.attempt_count,\n nextAttemptAt: row.next_attempt_at,\n leaseExpiresAt: row.lease_expires_at,\n startedAt: row.started_at,\n finishedAt: row.finished_at,\n lastError: row.last_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nfunction insert(\n db: BazilionDb,\n agentId: string,\n trigger: AgentReviewTrigger,\n now: number,\n): AgentReview {\n const id = randomUUID()\n db.raw.run(\n `INSERT INTO agent_reviews\n (id, agent_id, status, trigger_kind, input_characters, turns_reviewed,\n proposal_count, attempt_count, next_attempt_at, created_at, updated_at)\n VALUES (?, ?, 'pending', ?, 0, 0, 0, 0, ?, ?, ?)`,\n [id, agentId, trigger, now, now, now],\n )\n const review = get(db, id)\n if (!review) throw new Error('agent review enqueue failed')\n return review\n}\n\nexport function get(db: BazilionDb, id: string): AgentReview | null {\n const row = db.raw.query<RawReview, [string]>('SELECT * FROM agent_reviews WHERE id = ?').get(id)\n return row ? toReview(row) : null\n}\n\nexport function getOpenForAgent(db: BazilionDb, agentId: string): AgentReview | null {\n const row = db.raw\n .query<RawReview, [string]>(\n `SELECT * FROM agent_reviews WHERE agent_id = ?\n AND status IN ('pending','running','retrying') LIMIT 1`,\n )\n .get(agentId)\n return row ? toReview(row) : null\n}\n\nexport function enqueueManual(db: BazilionDb, agentId: string, now = Date.now()): AgentReview {\n return db.raw.transaction(\n () => getOpenForAgent(db, agentId) ?? insert(db, agentId, 'manual', now),\n )()\n}\n\n/** Count one successful interactive turn and atomically enqueue when cadence becomes due. */\nexport function recordSuccessfulUserTurn(\n db: BazilionDb,\n agentId: string,\n now = Date.now(),\n): AgentReview | null {\n return db.raw.transaction(() => {\n const updated = db.raw.run(\n `UPDATE agents SET review_turns_since_last = review_turns_since_last + 1\n WHERE id = ? AND review_enabled = 1`,\n [agentId],\n )\n if (Number(updated.changes) !== 1) return null\n const state = db.raw\n .query<{ review_turns_since_last: number; review_every_n_turns: number }, [string]>(\n `SELECT review_turns_since_last, review_every_n_turns FROM agents WHERE id = ?`,\n )\n .get(agentId)\n if (!state || state.review_turns_since_last < state.review_every_n_turns) return null\n const open = getOpenForAgent(db, agentId)\n if (open) return open\n const review = insert(db, agentId, 'cadence', now)\n db.raw.run('UPDATE agents SET review_turns_since_last = 0 WHERE id = ?', [agentId])\n return review\n })()\n}\n\nexport function listClaimable(db: BazilionDb, now = Date.now()): AgentReview[] {\n return db.raw\n .query<RawReview, [number, number]>(\n `SELECT * FROM agent_reviews\n WHERE (status IN ('pending','retrying') AND next_attempt_at <= ?)\n OR (status = 'running' AND lease_expires_at <= ?)\n ORDER BY created_at ASC`,\n )\n .all(now, now)\n .map(toReview)\n}\n\nexport function claim(\n db: BazilionDb,\n id: string,\n options: { now?: number; leaseMs?: number } = {},\n): AgentReview | null {\n const now = options.now ?? Date.now()\n const leaseExpiresAt = now + (options.leaseMs ?? 5 * 60_000)\n return db.raw.transaction(() => {\n const result = db.raw.run(\n `UPDATE agent_reviews SET status = 'running', attempt_count = attempt_count + 1,\n lease_expires_at = ?, started_at = ?, finished_at = NULL, updated_at = ?\n WHERE id = ? AND (\n (status IN ('pending','retrying') AND next_attempt_at <= ?)\n OR (status = 'running' AND lease_expires_at <= ?)\n )`,\n [leaseExpiresAt, now, now, id, now, now],\n )\n return Number(result.changes) === 1 ? get(db, id) : null\n })()\n}\n\nexport function defer(db: BazilionDb, id: string, nextAttemptAt = Date.now() + 1_000): void {\n db.raw.run(\n `UPDATE agent_reviews SET status = 'pending',\n attempt_count = CASE WHEN attempt_count > 0 THEN attempt_count - 1 ELSE 0 END,\n next_attempt_at = ?, lease_expires_at = NULL, started_at = NULL, updated_at = ?\n WHERE id = ? AND status = 'running'`,\n [nextAttemptAt, Date.now(), id],\n )\n}\n\nexport function getLatestCompleted(db: BazilionDb, agentId: string): AgentReview | null {\n const row = db.raw\n .query<RawReview, [string]>(\n `SELECT * FROM agent_reviews WHERE agent_id = ? AND status = 'completed'\n ORDER BY finished_at DESC LIMIT 1`,\n )\n .get(agentId)\n return row ? toReview(row) : null\n}\n\nexport function setSource(\n db: BazilionDb,\n id: string,\n source: {\n sessionId: string\n startOrdinal: number\n endOrdinal: number\n inputCharacters: number\n turnsReviewed: number\n },\n): void {\n db.raw.run(\n `UPDATE agent_reviews SET source_session_id = ?, source_start_ordinal = ?,\n source_end_ordinal = ?, input_characters = ?, turns_reviewed = ?, updated_at = ?\n WHERE id = ? AND status = 'running'`,\n [\n source.sessionId,\n source.startOrdinal,\n source.endOrdinal,\n source.inputCharacters,\n source.turnsReviewed,\n Date.now(),\n id,\n ],\n )\n}\n\nexport function complete(\n db: BazilionDb,\n id: string,\n proposalCount: number,\n now = Date.now(),\n): void {\n db.raw.run(\n `UPDATE agent_reviews SET status = 'completed', proposal_count = ?, lease_expires_at = NULL,\n finished_at = ?, last_error = NULL, updated_at = ? WHERE id = ? AND status = 'running'`,\n [proposalCount, now, now, id],\n )\n}\n\nexport function cancel(db: BazilionDb, id: string, reason: string, now = Date.now()): void {\n db.raw.run(\n `UPDATE agent_reviews SET status = 'cancelled', lease_expires_at = NULL, finished_at = ?,\n last_error = ?, updated_at = ? WHERE id = ? AND status IN ('pending','running','retrying')`,\n [now, reason.slice(0, 2_000), now, id],\n )\n}\n\nexport function fail(\n db: BazilionDb,\n id: string,\n error: string,\n options: { now?: number; maxAttempts?: number; retryDelayMs?: number } = {},\n): void {\n const now = options.now ?? Date.now()\n const current = get(db, id)\n if (current?.status !== 'running') return\n const terminal = current.attemptCount >= (options.maxAttempts ?? 3)\n const retryDelay = (options.retryDelayMs ?? 5_000) * 2 ** Math.max(0, current.attemptCount - 1)\n db.raw.run(\n `UPDATE agent_reviews SET status = ?, next_attempt_at = ?, lease_expires_at = NULL,\n finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,\n [\n terminal ? 'failed' : 'retrying',\n terminal ? now : now + retryDelay,\n terminal ? now : null,\n error.slice(0, 2_000),\n now,\n id,\n ],\n )\n}\n\nexport function listForAgent(db: BazilionDb, agentId: string, limit = 50): AgentReview[] {\n return db.raw\n .query<RawReview, [string, number]>(\n 'SELECT * FROM agent_reviews WHERE agent_id = ? ORDER BY created_at DESC LIMIT ?',\n )\n .all(agentId, limit)\n .map(toReview)\n}\n","import { createHash, randomUUID } from 'node:crypto'\nimport type {\n CommunicationApproval,\n CommunicationApprovalDetail,\n CommunicationApprovalEvent,\n CommunicationApprovalStatus,\n CommunicationAuthorizationResult,\n CommunicationEndpoint,\n} from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { AuthorizationInput } from '../team-policy/authorization.ts'\n\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1_000\n\ninterface RawApproval {\n id: string\n attempt_kind: string\n attempt_id: string\n fingerprint: string\n operation: string\n source_kind: CommunicationEndpoint['kind']\n source_id: string\n target_kind: CommunicationEndpoint['kind']\n target_id: string\n source_team_id: string | null\n target_team_id: string | null\n channel: CommunicationApproval['channel']\n origin: string\n requester: string\n policy_refs_json: string\n required_edge_ids_json: string\n payload_kind: string\n payload_json: string\n status: CommunicationApprovalStatus\n expires_at: number\n decided_at: number | null\n decided_by: string | null\n decision_reason: string | null\n delivery_error: string | null\n created_at: number\n updated_at: number\n}\n\ninterface RawEvent {\n id: string\n approval_id: string\n event: CommunicationApprovalEvent['event']\n actor: string\n detail: string | null\n created_at: number\n}\n\nfunction endpoint(kind: CommunicationEndpoint['kind'], id: string, teamId: string | null) {\n if (kind === 'agent') return { kind, id } as const\n return { kind, teamId: teamId ?? '__missing__' } as const\n}\n\nfunction toApproval(row: RawApproval): CommunicationApproval {\n return {\n id: row.id,\n attemptKind: row.attempt_kind,\n attemptId: row.attempt_id,\n operation: row.operation,\n source: endpoint(row.source_kind, row.source_id, row.source_team_id),\n target: endpoint(row.target_kind, row.target_id, row.target_team_id),\n sourceTeamId: row.source_team_id,\n targetTeamId: row.target_team_id,\n channel: row.channel,\n origin: row.origin,\n requester: row.requester,\n policyRefs: JSON.parse(row.policy_refs_json),\n requiredEdgeIds: JSON.parse(row.required_edge_ids_json),\n payloadKind: row.payload_kind,\n status: row.status,\n expiresAt: row.expires_at,\n decidedAt: row.decided_at,\n decidedBy: row.decided_by,\n decisionReason: row.decision_reason,\n deliveryError: row.delivery_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nfunction event(row: RawEvent): CommunicationApprovalEvent {\n return {\n id: row.id,\n approvalId: row.approval_id,\n event: row.event,\n actor: row.actor,\n detail: row.detail,\n createdAt: row.created_at,\n }\n}\n\nfunction raw(db: BazilionDb, id: string): RawApproval | null {\n return db.raw\n .query<RawApproval, [string]>('SELECT * FROM communication_approvals WHERE id = ?')\n .get(id)\n}\n\nfunction rawByAttempt(db: BazilionDb, kind: string, id: string): RawApproval | null {\n return db.raw\n .query<RawApproval, [string, string]>(\n 'SELECT * FROM communication_approvals WHERE attempt_kind = ? AND attempt_id = ?',\n )\n .get(kind, id)\n}\n\nfunction appendEvent(\n db: BazilionDb,\n approvalId: string,\n name: CommunicationApprovalEvent['event'],\n actor: string,\n detail: string | null = null,\n now = Date.now(),\n): void {\n db.raw.run(\n `INSERT INTO communication_approval_events\n (id, approval_id, event, actor, detail, created_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [randomUUID(), approvalId, name, actor, detail, now],\n )\n}\n\nfunction teams(db: BazilionDb, input: AuthorizationInput): [string | null, string | null] {\n const team = (value: CommunicationEndpoint): string | null =>\n value.kind === 'agent'\n ? (db.raw\n .query<{ team_id: string }, [string]>('SELECT team_id FROM agents WHERE id = ?')\n .get(value.id)?.team_id ?? null)\n : value.teamId\n const source = team(input.source) ?? team(input.target)\n const target = team(input.target) ?? team(input.source)\n return [source, target]\n}\n\nfunction fingerprint(\n input: AuthorizationInput,\n operation: string,\n payloadKind: string,\n payload: unknown,\n): string {\n return createHash('sha256')\n .update(JSON.stringify({ input, operation, payloadKind, payload }))\n .digest('hex')\n}\n\nexport function request(\n db: BazilionDb,\n input: AuthorizationInput,\n operation: string,\n authorization: CommunicationAuthorizationResult,\n payloadKind: string,\n payload: unknown,\n options: { requester?: string; ttlMs?: number; now?: number; expiresAt?: number } = {},\n): CommunicationApproval {\n if (authorization.decision !== 'approval_required')\n throw new Error('approval_request_invalid: authorization does not require approval')\n const now = options.now ?? Date.now()\n if (\n options.expiresAt !== undefined &&\n (!Number.isSafeInteger(options.expiresAt) || options.expiresAt < 0)\n )\n throw new Error('approval_request_invalid: invalid source deadline')\n const digest = fingerprint(input, operation, payloadKind, payload)\n try {\n return db.raw.transaction(() => {\n const existing = rawByAttempt(db, input.attemptKind, input.attemptId)\n if (existing) {\n if (existing.fingerprint !== digest)\n throw new Error('approval_attempt_conflict: attempt identity has different semantics')\n expireOne(db, existing, now)\n return toApproval(raw(db, existing.id) as RawApproval)\n }\n const id = randomUUID()\n const [sourceGroup, targetGroup] = teams(db, input)\n const expiresAt = Math.min(\n now + (options.ttlMs ?? DEFAULT_TTL_MS),\n options.expiresAt ?? Infinity,\n )\n db.raw.run(\n `INSERT INTO communication_approvals\n (id, attempt_kind, attempt_id, fingerprint, operation, source_kind, source_id,\n target_kind, target_id, source_team_id, target_team_id, channel, origin,\n requester, policy_refs_json, required_edge_ids_json, payload_kind, payload_json,\n status, expires_at, decided_at, decided_by, decision_reason, delivery_error,\n created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?,\n NULL, NULL, NULL, NULL, ?, ?)`,\n [\n id,\n input.attemptKind,\n input.attemptId,\n digest,\n operation,\n input.source.kind,\n input.source.kind === 'agent' ? input.source.id : '',\n input.target.kind,\n input.target.kind === 'agent' ? input.target.id : '',\n sourceGroup,\n targetGroup,\n authorization.channel,\n input.origin,\n options.requester ?? input.origin,\n JSON.stringify(authorization.policyRefs),\n JSON.stringify(authorization.requiredEdgeIds),\n payloadKind,\n JSON.stringify(payload),\n expiresAt,\n now,\n now,\n ],\n )\n appendEvent(db, id, 'requested', options.requester ?? input.origin, null, now)\n return toApproval(raw(db, id) as RawApproval)\n })()\n } catch (error) {\n const raced = rawByAttempt(db, input.attemptKind, input.attemptId)\n if (!raced) throw error\n if (raced.fingerprint !== digest)\n throw new Error('approval_attempt_conflict: attempt identity has different semantics')\n return toApproval(raced)\n }\n}\n\nfunction expireOne(db: BazilionDb, row: RawApproval, now: number): boolean {\n if (row.status !== 'pending' || row.expires_at > now) return false\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'expired', decided_at = ?, decided_by = 'system',\n decision_reason = 'approval expired', updated_at = ?\n WHERE id = ? AND status = 'pending' AND expires_at <= ?`,\n [now, now, row.id, now],\n ).changes\n if (changed) appendEvent(db, row.id, 'expired', 'system', 'approval expired', now)\n return changed > 0\n}\n\nexport function expirePending(db: BazilionDb, now = Date.now()): number {\n return db.raw.transaction(() => {\n const rows = db.raw\n .query<RawApproval, [number]>(\n \"SELECT * FROM communication_approvals WHERE status = 'pending' AND expires_at <= ?\",\n )\n .all(now)\n for (const row of rows) expireOne(db, row, now)\n return rows.length\n })()\n}\n\nexport function get(\n db: BazilionDb,\n id: string,\n includePayload = false,\n): CommunicationApprovalDetail | CommunicationApproval | null {\n expirePending(db)\n const row = raw(db, id)\n if (!row) return null\n const approval = toApproval(row)\n if (!includePayload) return approval\n return detailFromRow(db, row)\n}\n\nexport function getByAttempt(\n db: BazilionDb,\n attemptKind: string,\n attemptId: string,\n includePayload = false,\n): CommunicationApprovalDetail | CommunicationApproval | null {\n expirePending(db)\n const row = rawByAttempt(db, attemptKind, attemptId)\n if (!row) return null\n return includePayload ? detailFromRow(db, row) : toApproval(row)\n}\n\nfunction detailFromRow(db: BazilionDb, row: RawApproval): CommunicationApprovalDetail {\n const events = db.raw\n .query<RawEvent, [string]>(\n 'SELECT * FROM communication_approval_events WHERE approval_id = ? ORDER BY created_at, rowid',\n )\n .all(row.id)\n .map(event)\n return { ...toApproval(row), payload: JSON.parse(row.payload_json), events }\n}\n\nexport function list(\n db: BazilionDb,\n filters: { status?: CommunicationApprovalStatus; teamId?: string; limit?: number } = {},\n): CommunicationApproval[] {\n expirePending(db)\n const limit = Math.min(Math.max(filters.limit ?? 50, 1), 100)\n return db.raw\n .query<RawApproval, [string, string, string, string, string, number]>(\n `SELECT * FROM communication_approvals\n WHERE (? = '' OR status = ?)\n AND (? = '' OR source_team_id = ? OR target_team_id = ?)\n ORDER BY created_at DESC, id DESC LIMIT ?`,\n )\n .all(\n filters.status ?? '',\n filters.status ?? '',\n filters.teamId ?? '',\n filters.teamId ?? '',\n filters.teamId ?? '',\n limit,\n )\n .map(toApproval)\n}\n\nexport function decide(\n db: BazilionDb,\n id: string,\n decision: 'deny' | 'cancel',\n actor: string,\n reason?: string,\n now = Date.now(),\n): CommunicationApproval {\n expirePending(db, now)\n return db.raw.transaction(() => {\n const row = raw(db, id)\n if (!row) throw new Error(`approval_not_found: ${id}`)\n const current = raw(db, id) as RawApproval\n if (current.status !== 'pending')\n throw new Error(`approval_state_conflict: current ${current.status}`)\n const status = decision === 'deny' ? 'denied' : 'cancelled'\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = ?, decided_at = ?, decided_by = ?, decision_reason = ?, updated_at = ?\n WHERE id = ? AND status = 'pending'`,\n [status, now, actor, reason ?? null, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent decision')\n appendEvent(db, id, decision === 'deny' ? 'denied' : 'cancelled', actor, reason ?? null, now)\n return toApproval(raw(db, id) as RawApproval)\n })()\n}\n\nexport function claimDelivery(\n db: BazilionDb,\n id: string,\n actor: string,\n revalidate: (row: CommunicationApprovalDetail) => CommunicationAuthorizationResult,\n now = Date.now(),\n): CommunicationApprovalDetail {\n expirePending(db, now)\n const outcome = db.raw.transaction(\n (): { detail?: CommunicationApprovalDetail; revalidationFailed?: true } => {\n const rawCurrent = raw(db, id)\n if (!rawCurrent) throw new Error(`approval_not_found: ${id}`)\n const detail = detailFromRow(db, raw(db, id) as RawApproval)\n if (detail.status !== 'pending')\n throw new Error(`approval_state_conflict: current ${detail.status}`)\n const authorization = revalidate(detail)\n const refsMatch =\n JSON.stringify(authorization.policyRefs) === JSON.stringify(detail.policyRefs) &&\n JSON.stringify(authorization.requiredEdgeIds) === JSON.stringify(detail.requiredEdgeIds)\n if (authorization.decision !== 'approval_required' || !refsMatch) {\n db.raw.run(\n `UPDATE communication_approvals\n SET status = 'denied', decided_at = ?, decided_by = ?, decision_reason = ?,\n updated_at = ? WHERE id = ? AND status = 'pending'`,\n [now, actor, 'policy or membership changed', now, id],\n )\n appendEvent(db, id, 'denied', actor, 'policy or membership changed', now)\n return { revalidationFailed: true }\n }\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'delivering', decided_at = ?, decided_by = ?, updated_at = ?\n WHERE id = ? AND status = 'pending'`,\n [now, actor, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent approval')\n appendEvent(db, id, 'approved', actor, null, now)\n appendEvent(db, id, 'delivery_started', actor, null, now)\n return {\n detail: {\n ...detail,\n status: 'delivering' as const,\n decidedAt: now,\n decidedBy: actor,\n },\n }\n },\n )()\n if (outcome.revalidationFailed)\n throw new Error('approval_revalidation_failed: policy or membership changed')\n if (!outcome.detail) throw new Error('approval_state_conflict: delivery claim failed')\n return outcome.detail\n}\n\nexport interface SchedulerApprovalGrantResult {\n approval: CommunicationApproval\n granted: boolean\n failureKind?: 'revalidation' | 'delivery'\n error?: string\n}\n\n/**\n * Convert a scheduler approval into a durable grant without executing the\n * Agent turn in the HTTP request. The scheduler remains the only owner of\n * dispatch leases, retries, restart recovery, and final execution status.\n */\nexport function grantSchedulerTrigger(\n db: BazilionDb,\n id: string,\n actor: string,\n revalidate: (row: CommunicationApprovalDetail) => CommunicationAuthorizationResult,\n validateDelivery: (row: CommunicationApprovalDetail) => string | null,\n now = Date.now(),\n): SchedulerApprovalGrantResult {\n expirePending(db, now)\n return db.raw.transaction(() => {\n const current = raw(db, id)\n if (!current) throw new Error(`approval_not_found: ${id}`)\n const detail = detailFromRow(db, current)\n if (detail.status !== 'pending')\n throw new Error(`approval_state_conflict: current ${detail.status}`)\n if (detail.operation !== 'scheduler_trigger' || detail.payloadKind !== 'scheduler_trigger') {\n throw new Error('approval_payload_conflict: not a scheduler trigger approval')\n }\n\n const authorization = revalidate(detail)\n const refsMatch =\n JSON.stringify(authorization.policyRefs) === JSON.stringify(detail.policyRefs) &&\n JSON.stringify(authorization.requiredEdgeIds) === JSON.stringify(detail.requiredEdgeIds)\n if (authorization.decision !== 'approval_required' || !refsMatch) {\n const error = 'policy or membership changed'\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'denied', decided_at = ?, decided_by = ?, decision_reason = ?,\n updated_at = ? WHERE id = ? AND status = 'pending'`,\n [now, actor, error, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent approval')\n appendEvent(db, id, 'denied', actor, error, now)\n return {\n approval: toApproval(raw(db, id) as RawApproval),\n granted: false,\n failureKind: 'revalidation' as const,\n error,\n }\n }\n\n let deliveryError: string | null = null\n try {\n deliveryError = validateDelivery(detail)\n } catch (error) {\n deliveryError = error instanceof Error ? error.message : String(error)\n }\n if (deliveryError) {\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'delivery_failed', decided_at = ?, decided_by = ?,\n delivery_error = ?, updated_at = ?\n WHERE id = ? AND status = 'pending'`,\n [now, actor, deliveryError, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent approval')\n appendEvent(db, id, 'approved', actor, null, now)\n appendEvent(db, id, 'delivery_started', actor, null, now)\n appendEvent(db, id, 'delivery_failed', actor, deliveryError, now)\n return {\n approval: toApproval(raw(db, id) as RawApproval),\n granted: false,\n failureKind: 'delivery' as const,\n error: deliveryError,\n }\n }\n\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = 'delivered', decided_at = ?, decided_by = ?, delivery_error = NULL,\n updated_at = ? WHERE id = ? AND status = 'pending'`,\n [now, actor, now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: concurrent approval')\n appendEvent(db, id, 'approved', actor, null, now)\n appendEvent(db, id, 'delivery_started', actor, null, now)\n appendEvent(db, id, 'delivered', actor, null, now)\n return { approval: toApproval(raw(db, id) as RawApproval), granted: true }\n })()\n}\n\nexport function finishDelivery(\n db: BazilionDb,\n id: string,\n ok: boolean,\n actor: string,\n error?: string,\n now = Date.now(),\n): CommunicationApproval {\n return db.raw.transaction(() => {\n const status = ok ? 'delivered' : 'delivery_failed'\n const changed = db.raw.run(\n `UPDATE communication_approvals\n SET status = ?, delivery_error = ?, updated_at = ?\n WHERE id = ? AND status = 'delivering'`,\n [status, ok ? null : (error ?? 'delivery failed'), now, id],\n ).changes\n if (changed !== 1) throw new Error('approval_state_conflict: delivery is not claimable')\n appendEvent(\n db,\n id,\n ok ? 'delivered' : 'delivery_failed',\n actor,\n ok ? null : (error ?? 'delivery failed'),\n now,\n )\n return toApproval(raw(db, id) as RawApproval)\n })()\n}\n","// Per-service field registry — the shape of the /config page.\n//\n// Each entry describes one \"thing\" the user might configure: an LLM provider\n// (Anthropic, LM Studio, …) or an ancillary service (Brave Search, SearXNG).\n// Fields know which storage backend they live in: `secret` → the encrypted\n// `secrets` table, `config` → the plaintext `config` table. The registry is\n// the single source of truth for the UI layout and for the generic\n// field-write endpoint's dispatch.\n//\n// When adding a new provider or service, append an entry here and the\n// config page + CLI pick it up automatically.\n\nexport type FieldKind = 'secret' | 'config'\n\nexport interface ServiceField {\n /** Env var name — canonical key in both stores. */\n envVar: string\n kind: FieldKind\n label: string\n placeholder?: string\n description?: string\n}\n\nexport type ServiceCategory = 'provider' | 'service' | 'integration'\n\nexport interface ServiceDef {\n /** Matches the provider-registry key for providers (e.g. 'anthropic'). */\n id: string\n displayName: string\n category: ServiceCategory\n /** Display grouping label shown above the card on the /config tabs. */\n team?: string\n /** Sign-up link, docs, or 1-line description shown on the card. */\n hint?: string\n fields: ServiceField[]\n}\n\n/**\n * One-liner: what's shown on each service card.\n * Order here is the display order on the config page.\n */\nexport const SERVICES: ServiceDef[] = [\n // --- LLM providers (configured via API keys / URLs) ---\n // Top 3: openai-codex (ChatGPT OAuth), openai (API key), anthropic.\n // Everything else in rough popularity order; locals last.\n {\n id: 'openai-codex',\n displayName: 'OpenAI ChatGPT (OAuth)',\n category: 'provider',\n hint: 'Use your ChatGPT Plus/Pro/Team account (same login as Codex CLI)',\n // No form fields — credentials come from an OAuth flow. The /config page\n // renders a Connect/Disconnect card using /api/auth/openai instead of the\n // standard field inputs.\n fields: [],\n },\n {\n id: 'openai',\n displayName: 'OpenAI',\n category: 'provider',\n hint: 'GPT models · platform.openai.com',\n fields: [{ envVar: 'OPENAI_API_KEY', kind: 'secret', label: 'API key', placeholder: 'sk-...' }],\n },\n {\n id: 'anthropic',\n displayName: 'Anthropic',\n category: 'provider',\n hint: 'Claude models · console.anthropic.com',\n fields: [\n { envVar: 'ANTHROPIC_API_KEY', kind: 'secret', label: 'API key', placeholder: 'sk-ant-...' },\n {\n envVar: 'ANTHROPIC_OAUTH_TOKEN',\n kind: 'secret',\n label: 'OAuth token (alternative to API key)',\n description: 'Takes precedence over ANTHROPIC_API_KEY when set',\n },\n ],\n },\n {\n id: 'google',\n displayName: 'Google (Gemini)',\n category: 'provider',\n hint: 'Gemini models · ai.google.dev (free tier available)',\n fields: [\n { envVar: 'GEMINI_API_KEY', kind: 'secret', label: 'API key', placeholder: 'AIza...' },\n ],\n },\n {\n id: 'google-vertex',\n displayName: 'Google Vertex AI',\n category: 'provider',\n hint: 'Authenticates via `gcloud auth application-default login`',\n fields: [\n {\n envVar: 'GOOGLE_CLOUD_PROJECT',\n kind: 'config',\n label: 'GCP project ID',\n placeholder: 'my-project-123456',\n },\n {\n envVar: 'GOOGLE_CLOUD_LOCATION',\n kind: 'config',\n label: 'GCP region',\n placeholder: 'us-central1',\n },\n {\n envVar: 'GOOGLE_VERTEX_CREDENTIALS_JSON',\n kind: 'secret',\n label: 'Explicit credentials JSON (protected turns)',\n description:\n 'Service-account or ADC JSON projected only into the selected protected worker; ambient ADC files remain unsupported.',\n },\n ],\n },\n {\n id: 'azure-openai',\n displayName: 'Azure OpenAI',\n category: 'provider',\n fields: [{ envVar: 'AZURE_OPENAI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'bedrock',\n displayName: 'Amazon Bedrock',\n category: 'provider',\n hint: 'Authenticates via AWS SDK env (AWS_PROFILE or AWS_ACCESS_KEY_ID/SECRET)',\n fields: [],\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n category: 'provider',\n hint: 'Use a GitHub Copilot subscription to call Claude/GPT/Gemini via Copilot',\n fields: [\n {\n envVar: 'COPILOT_GITHUB_TOKEN',\n kind: 'secret',\n label: 'GitHub token',\n description:\n 'Generic GH_TOKEN/GITHUB_TOKEN are ignored — set this scoped variable explicitly (or run `bazilion auth copilot login` once available).',\n },\n ],\n },\n {\n id: 'deepseek',\n displayName: 'DeepSeek',\n category: 'provider',\n hint: 'DeepSeek V4 Flash / Pro · platform.deepseek.com',\n fields: [{ envVar: 'DEEPSEEK_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'mistral',\n displayName: 'Mistral',\n category: 'provider',\n hint: 'mistral.ai',\n fields: [{ envVar: 'MISTRAL_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xai',\n displayName: 'xAI',\n category: 'provider',\n hint: 'Grok · x.ai',\n fields: [{ envVar: 'XAI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'groq',\n displayName: 'Groq',\n category: 'provider',\n hint: 'Fast inference · groq.com',\n fields: [{ envVar: 'GROQ_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'cerebras',\n displayName: 'Cerebras',\n category: 'provider',\n fields: [{ envVar: 'CEREBRAS_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'fireworks',\n displayName: 'Fireworks AI',\n category: 'provider',\n hint: 'DeepSeek/GLM/Kimi via fireworks.ai',\n fields: [{ envVar: 'FIREWORKS_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'together',\n displayName: 'Together AI',\n category: 'provider',\n hint: 'Open-weight models · together.ai',\n fields: [{ envVar: 'TOGETHER_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'baseten',\n displayName: 'Baseten',\n category: 'provider',\n hint: 'Hosted open-weight models · baseten.co',\n fields: [{ envVar: 'BASETEN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'moonshotai',\n displayName: 'Moonshot AI',\n category: 'provider',\n hint: 'Kimi K2/K2.5/K2.6/K2.7 · platform.moonshot.ai',\n fields: [{ envVar: 'MOONSHOT_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'moonshotai-cn',\n displayName: 'Moonshot AI (CN)',\n category: 'provider',\n hint: 'Kimi models via the China endpoint · platform.moonshot.cn',\n fields: [{ envVar: 'MOONSHOT_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'kimi-coding',\n displayName: 'Kimi Coding',\n category: 'provider',\n hint: 'Coding-tuned Kimi endpoint · platform.moonshot.cn',\n fields: [{ envVar: 'KIMI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'minimax',\n displayName: 'MiniMax',\n category: 'provider',\n hint: 'MiniMax M2/M3 family · platform.minimaxi.com',\n fields: [{ envVar: 'MINIMAX_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'minimax-cn',\n displayName: 'MiniMax (CN)',\n category: 'provider',\n hint: 'MiniMax M2/M3 family via the China endpoint',\n fields: [{ envVar: 'MINIMAX_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'qwen-token-plan',\n displayName: 'Qwen Token Plan',\n category: 'provider',\n hint: 'Coding-model subscription endpoint in Singapore',\n fields: [{ envVar: 'QWEN_TOKEN_PLAN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'qwen-token-plan-cn',\n displayName: 'Qwen Token Plan (CN)',\n category: 'provider',\n hint: 'Coding-model subscription endpoint in China',\n fields: [{ envVar: 'QWEN_TOKEN_PLAN_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'qwen-token-plan-individual',\n displayName: 'Qwen Token Plan (Individual)',\n category: 'provider',\n hint: 'Individual subscription models via the international endpoint',\n fields: [{ envVar: 'QWEN_TOKEN_PLAN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xiaomi',\n displayName: 'Xiaomi MiMo',\n category: 'provider',\n hint: 'API billing endpoint · platform.xiaomimimo.com',\n fields: [{ envVar: 'XIAOMI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xiaomi-token-plan-ams',\n displayName: 'Xiaomi MiMo Token Plan (AMS)',\n category: 'provider',\n hint: 'MiMo token-plan endpoint in Amsterdam',\n fields: [{ envVar: 'XIAOMI_TOKEN_PLAN_AMS_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xiaomi-token-plan-cn',\n displayName: 'Xiaomi MiMo Token Plan (CN)',\n category: 'provider',\n hint: 'MiMo token-plan endpoint in China',\n fields: [{ envVar: 'XIAOMI_TOKEN_PLAN_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'xiaomi-token-plan-sgp',\n displayName: 'Xiaomi MiMo Token Plan (SGP)',\n category: 'provider',\n hint: 'MiMo token-plan endpoint in Singapore',\n fields: [{ envVar: 'XIAOMI_TOKEN_PLAN_SGP_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'ant-ling',\n displayName: 'Ant Ling',\n category: 'provider',\n hint: 'Ling/Ring models · api.ant-ling.com',\n fields: [{ envVar: 'ANT_LING_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'nvidia',\n displayName: 'NVIDIA NIM',\n category: 'provider',\n hint: 'NVIDIA hosted models · build.nvidia.com',\n fields: [{ envVar: 'NVIDIA_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'zai',\n displayName: 'zAI',\n category: 'provider',\n fields: [{ envVar: 'ZAI_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'zai-coding-cn',\n displayName: 'zAI Coding (CN)',\n category: 'provider',\n hint: 'GLM coding endpoint · open.bigmodel.cn',\n fields: [{ envVar: 'ZAI_CODING_CN_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'huggingface',\n displayName: 'Hugging Face',\n category: 'provider',\n hint: 'Inference endpoints · huggingface.co',\n fields: [{ envVar: 'HF_TOKEN', kind: 'secret', label: 'Access token', placeholder: 'hf_...' }],\n },\n {\n id: 'cloudflare-ai-gateway',\n displayName: 'Cloudflare AI Gateway',\n category: 'provider',\n hint: 'Per-gateway routing to OpenAI/Anthropic/Workers AI',\n fields: [\n { envVar: 'CLOUDFLARE_API_KEY', kind: 'secret', label: 'API key' },\n { envVar: 'CLOUDFLARE_ACCOUNT_ID', kind: 'config', label: 'Account ID' },\n { envVar: 'CLOUDFLARE_GATEWAY_ID', kind: 'config', label: 'Gateway ID' },\n ],\n },\n {\n id: 'cloudflare-workers-ai',\n displayName: 'Cloudflare Workers AI',\n category: 'provider',\n hint: 'Inference on Cloudflare Workers · ai.cloudflare.com',\n fields: [\n { envVar: 'CLOUDFLARE_API_KEY', kind: 'secret', label: 'API key' },\n { envVar: 'CLOUDFLARE_ACCOUNT_ID', kind: 'config', label: 'Account ID' },\n ],\n },\n {\n id: 'openrouter',\n displayName: 'OpenRouter',\n category: 'provider',\n hint: 'Proxy for 200+ models · openrouter.ai',\n fields: [\n { envVar: 'OPENROUTER_API_KEY', kind: 'secret', label: 'API key', placeholder: 'sk-or-...' },\n ],\n },\n {\n id: 'vercel-ai-gateway',\n displayName: 'Vercel AI Gateway',\n category: 'provider',\n fields: [\n { envVar: 'AI_GATEWAY_API_KEY', kind: 'secret', label: 'API key' },\n {\n envVar: 'AI_GATEWAY_BASE_URL',\n kind: 'config',\n label: 'Base URL (optional)',\n placeholder: 'https://ai-gateway.vercel.sh/v1',\n },\n ],\n },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n category: 'provider',\n hint: 'OpenAI-compatible proxy from the OpenCode CLI',\n fields: [{ envVar: 'OPENCODE_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'opencode-go',\n displayName: 'OpenCode Go',\n category: 'provider',\n hint: 'OpenCode hosted Go endpoint',\n fields: [{ envVar: 'OPENCODE_GO_API_KEY', kind: 'secret', label: 'API key' }],\n },\n {\n id: 'lmstudio',\n displayName: 'LM Studio',\n category: 'provider',\n hint: 'Local inference · lmstudio.ai',\n fields: [\n {\n envVar: 'LMSTUDIO_URL',\n kind: 'config',\n label: 'Endpoint URL',\n placeholder: 'http://127.0.0.1:1234/v1',\n },\n {\n envVar: 'LMSTUDIO_API_KEY',\n kind: 'secret',\n label: 'API key (rarely needed)',\n },\n ],\n },\n {\n id: 'ollama',\n displayName: 'Ollama',\n category: 'provider',\n hint: 'Local inference · ollama.com',\n fields: [\n {\n envVar: 'OLLAMA_URL',\n kind: 'config',\n label: 'Endpoint URL',\n placeholder: 'http://127.0.0.1:11434/v1',\n },\n {\n envVar: 'OLLAMA_API_KEY',\n kind: 'secret',\n label: 'API key (rarely needed)',\n },\n ],\n },\n {\n id: 'llamacpp',\n displayName: 'llama.cpp',\n category: 'provider',\n hint: 'Local inference · llama.cpp llama-server (OpenAI-compat /v1 endpoint)',\n fields: [\n {\n envVar: 'LLAMACPP_URL',\n kind: 'config',\n label: 'Endpoint URL',\n placeholder: 'http://127.0.0.1:8080/v1',\n },\n {\n envVar: 'LLAMACPP_API_KEY',\n kind: 'secret',\n label: 'API key (only if started with --api-key)',\n description:\n 'llama-server runs without auth by default. Set this only if you launched the server with the `--api-key KEY` flag.',\n },\n ],\n },\n\n // --- Ancillary services (web search, etc) ---\n {\n id: 'firecrawl',\n displayName: 'Firecrawl',\n category: 'service',\n team: 'Web Search',\n hint: 'web_fetch fallback for JS-heavy/blocked pages · firecrawl.dev (free tier available)',\n fields: [\n {\n envVar: 'FIRECRAWL_API_KEY',\n kind: 'secret',\n label: 'API key',\n placeholder: 'fc-...',\n description:\n 'When set, web_fetch automatically falls back to Firecrawl if the primary Readability extraction yields too little content.',\n },\n {\n envVar: 'FIRECRAWL_URL',\n kind: 'config',\n label: 'Base URL (optional, for self-hosted)',\n placeholder: 'https://api.firecrawl.dev',\n },\n ],\n },\n {\n id: 'brave-search',\n displayName: 'Brave Search',\n category: 'service',\n team: 'Web Search',\n hint: 'Web search tool · free tier at brave.com/search/api/',\n fields: [{ envVar: 'BRAVE_API_KEY', kind: 'secret', label: 'API key', placeholder: 'BSA...' }],\n },\n {\n id: 'searxng',\n displayName: 'SearXNG',\n category: 'service',\n team: 'Web Search',\n hint: 'Self-hosted meta-search engine · searxng.org',\n fields: [\n {\n envVar: 'SEARXNG_URL',\n kind: 'config',\n label: 'Instance URL',\n placeholder: 'https://searxng.example.com',\n },\n ],\n },\n {\n id: 'shell-security',\n displayName: 'Agent Shell Security',\n category: 'service',\n team: 'Agent Runtime',\n hint: 'Optional dangerous-command approval and Docker isolation. Disabled by default.',\n fields: [\n {\n envVar: 'BAZILION_BASH_APPROVAL',\n kind: 'config',\n label: 'Dangerous-command approval',\n placeholder: 'off',\n description:\n 'Use off (current behavior) or dangerous (classified bash commands require per-call approval before execution).',\n },\n {\n envVar: 'BAZILION_BASH_SANDBOX',\n kind: 'config',\n label: 'Sandbox mode',\n placeholder: 'off',\n description:\n 'Use off (current host tools) or docker (containerized bash only; host-backed coding tools are hidden).',\n },\n {\n envVar: 'BAZILION_BASH_SANDBOX_IMAGE',\n kind: 'config',\n label: 'Docker image',\n placeholder: 'debian:bookworm-slim',\n description:\n 'Local image with /bin/bash and /usr/bin/env and no VOLUME declarations. Bazilion never falls back to host execution if it is unavailable or incompatible.',\n },\n {\n envVar: 'BAZILION_BASH_SANDBOX_ENV_ALLOWLIST',\n kind: 'config',\n label: 'Extra environment allowlist',\n placeholder: 'MY_SAFE_VARIABLE,ANOTHER_SAFE_VARIABLE',\n description:\n 'Optional comma-separated variable names copied into the container. Provider credentials remain excluded unless explicitly named.',\n },\n {\n envVar: 'BAZILION_AGENT_LOOP_MAX_HOPS',\n kind: 'config',\n label: 'Agent-message hop limit',\n placeholder: '8',\n description:\n 'Maximum causal hops in an agent-to-agent message chain before the daemon stops it. Default 8.',\n },\n ],\n },\n {\n id: 'browser',\n displayName: 'Browser Automation',\n category: 'service',\n team: 'Browser',\n hint: 'Playwright-driven browser tools (navigate, snapshot, click, screenshot). Chromium is installed with Bazilion.',\n fields: [\n {\n envVar: 'BROWSER_ENABLED',\n kind: 'config',\n label: 'Enable browser tools',\n placeholder: 'true',\n description: 'Expose the browser_* tools to agents (true/false). Default true.',\n },\n {\n envVar: 'BROWSER_HEADLESS',\n kind: 'config',\n label: 'Headless',\n placeholder: 'true',\n description: 'Run Chromium headless (true/false). Default true.',\n },\n {\n envVar: 'BROWSER_ALLOW_PRIVATE_NETWORK',\n kind: 'config',\n label: 'Allow private network',\n placeholder: 'false',\n description:\n 'Permit the browser to reach loopback/private IPs (SSRF guard off). Default false — only enable for local dev.',\n },\n {\n envVar: 'BROWSER_IDLE_MS',\n kind: 'config',\n label: 'Idle timeout (ms)',\n placeholder: '900000',\n description: 'Close an idle browser session after this many ms. Default 900000 (15 min).',\n },\n {\n envVar: 'BROWSER_MAX_SESSIONS',\n kind: 'config',\n label: 'Max concurrent sessions',\n placeholder: '4',\n description: 'Cap on simultaneously-open browser sessions (LRU-evicted). Default 4.',\n },\n ],\n },\n\n // --- External integrations (chat bridges, etc) ---\n // Each integration has its own dedicated /config/integrations/* page with\n // workflow-specific UI (preflight health, setup wizard, …). The fields\n // here exist so the keys are allowlisted in the config/secrets stores and\n // surfaced through the generic `PUT /api/config/fields/:envVar` endpoint.\n // Daemon-managed internal state keys (watermarks, derived topic ids) live\n // in repos/config.ts:INTERNAL_CONFIG_KEYS instead, since the user never\n // edits them.\n {\n id: 'telegram',\n displayName: 'Telegram',\n category: 'integration',\n hint: 'Forum-supergroup bot for talking to your agents from a phone',\n fields: [\n {\n envVar: 'TELEGRAM_BOT_TOKEN',\n kind: 'secret',\n label: 'Bot token',\n placeholder: '1234567890:ABC...',\n description: 'Get one from @BotFather → /newbot. Disable Privacy Mode in Bot Settings.',\n },\n {\n envVar: 'TELEGRAM_CHAT_ID',\n kind: 'config',\n label: 'Supergroup chat ID',\n placeholder: '-1001234567890',\n description: 'Numeric id of the forum-enabled supergroup the bot is admin in.',\n },\n ],\n },\n]\n\n/**\n * Fast lookup: envVar → the field definition + owning service.\n * Rebuilt once at module init — the list is static.\n */\nconst FIELD_INDEX: Map<string, { service: ServiceDef; field: ServiceField }> = (() => {\n const m = new Map<string, { service: ServiceDef; field: ServiceField }>()\n for (const service of SERVICES) {\n for (const field of service.fields) {\n m.set(field.envVar, { service, field })\n }\n }\n return m\n})()\n\nexport function findFieldByEnvVar(\n envVar: string,\n): { service: ServiceDef; field: ServiceField } | undefined {\n return FIELD_INDEX.get(envVar)\n}\n\nexport function servicesByCategory(category: ServiceCategory): ServiceDef[] {\n return SERVICES.filter((s) => s.category === category)\n}\n","// Plaintext config store, backed by the `config` table.\n//\n// Companion to `secrets.ts` — same key-shaped values, but for the ones that\n// don't need confidentiality (server URLs, region slugs, project IDs). Kept\n// separate so the /config UI can show plaintext values directly without\n// extra masking logic.\n//\n// The CONFIG_KEYS allowlist (derived from the services registry) is enforced\n// here on writes — a typo or accidental misclassification can't put an API\n// key in this table.\n\nimport type { BazilionDb } from '../db/client.ts'\nimport { SERVICES } from '../services.ts'\n\n/**\n * Daemon-managed internal state keys. Not exposed in the services registry\n * because the user never edits them — they're written by background machinery\n * (polling watermarks, derived topic ids, …). Kept here so the allowlist\n * still gates accidental misuse from the generic field write endpoint.\n */\nconst INTERNAL_CONFIG_KEYS: readonly string[] = [\n // Telegram (step 2+): polling watermark + derived topic/message ids.\n 'TELEGRAM_LAST_UPDATE_ID',\n 'TELEGRAM_SERVICE_TOPIC_ID',\n 'TELEGRAM_DIRECTORY_MESSAGE_ID',\n // Phase 5: proposed new chat id after a migrate_to_chat_id event, pending\n // operator confirmation via POST /api/config/telegram/reconnect.\n 'TELEGRAM_MIGRATED_CHAT_ID',\n]\n\n/**\n * Env var names that live in the plaintext config store. Derived from the\n * services registry — any field marked `kind: 'config'` ends up here — and\n * unioned with the internal keys list above.\n */\nexport const CONFIG_KEYS: readonly string[] = [\n ...SERVICES.flatMap((s) => s.fields.filter((f) => f.kind === 'config').map((f) => f.envVar)),\n ...INTERNAL_CONFIG_KEYS,\n]\n\nconst CONFIG_KEY_SET = new Set<string>(CONFIG_KEYS)\n\nexport function isConfigKey(key: string): boolean {\n return CONFIG_KEY_SET.has(key)\n}\n\ninterface RawRow {\n key: string\n value: string\n updated_at: number\n}\n\nexport interface ConfigStore {\n get(key: string): string | undefined\n set(key: string, value: string): void\n remove(key: string): void\n list(): { key: string; value: string }[]\n getAll(): Record<string, string>\n}\n\nexport function openConfig(db: BazilionDb): ConfigStore {\n return {\n get(key) {\n const row = db.raw.query<RawRow, [string]>('SELECT * FROM config WHERE key = ?').get(key)\n return row?.value\n },\n set(key, value) {\n if (!isConfigKey(key)) {\n throw new Error(\n `config.set: \"${key}\" is not a known config key (${CONFIG_KEYS.join(', ')})`,\n )\n }\n db.raw.run(\n `INSERT INTO config (key, value, updated_at) VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n [key, value, Date.now()],\n )\n },\n remove(key) {\n db.raw.run('DELETE FROM config WHERE key = ?', [key])\n },\n list() {\n return db.raw\n .query<RawRow, []>('SELECT * FROM config ORDER BY key ASC')\n .all()\n .map((r) => ({ key: r.key, value: r.value }))\n },\n getAll() {\n const out: Record<string, string> = {}\n for (const r of db.raw.query<RawRow, []>('SELECT * FROM config').all()) {\n out[r.key] = r.value\n }\n return out\n },\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { McpServer, McpTransport } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawRow {\n id: string\n name: string\n transport: string\n command: string | null\n args: string\n url: string | null\n has_auth: number\n enabled: number\n created_at: number\n updated_at: number\n}\n\nfunction toServer(r: RawRow): McpServer {\n let args: string[] = []\n try {\n const parsed = JSON.parse(r.args)\n if (Array.isArray(parsed)) args = parsed.filter((a): a is string => typeof a === 'string')\n } catch {}\n return {\n id: r.id,\n name: r.name,\n transport: r.transport as McpTransport,\n command: r.command,\n args,\n url: r.url,\n hasAuthToken: r.has_auth === 1,\n enabled: r.enabled === 1,\n createdAt: r.created_at,\n updatedAt: r.updated_at,\n }\n}\n\nexport interface InsertMcpServerInput {\n name: string\n transport: McpTransport\n command?: string | null\n args?: string[]\n url?: string | null\n hasAuth?: boolean\n enabled?: boolean\n}\n\nexport function insert(db: BazilionDb, input: InsertMcpServerInput): McpServer {\n const id = randomUUID()\n const now = Date.now()\n db.raw.run(\n `INSERT INTO mcp_servers (id, name, transport, command, args, url, has_auth, enabled, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n id,\n input.name,\n input.transport,\n input.command ?? null,\n JSON.stringify(input.args ?? []),\n input.url ?? null,\n input.hasAuth ? 1 : 0,\n input.enabled === false ? 0 : 1,\n now,\n now,\n ],\n )\n const row = get(db, id)\n if (!row) throw new Error('mcp server insert failed')\n return row\n}\n\nexport function get(db: BazilionDb, id: string): McpServer | null {\n const row = db.raw.query<RawRow, [string]>('SELECT * FROM mcp_servers WHERE id = ?').get(id)\n return row ? toServer(row) : null\n}\n\nexport function getByName(db: BazilionDb, name: string): McpServer | null {\n const row = db.raw.query<RawRow, [string]>('SELECT * FROM mcp_servers WHERE name = ?').get(name)\n return row ? toServer(row) : null\n}\n\nexport function list(db: BazilionDb): McpServer[] {\n return db.raw\n .query<RawRow, []>('SELECT * FROM mcp_servers ORDER BY created_at ASC')\n .all()\n .map(toServer)\n}\n\nexport function listEnabled(db: BazilionDb): McpServer[] {\n return db.raw\n .query<RawRow, []>('SELECT * FROM mcp_servers WHERE enabled = 1 ORDER BY created_at ASC')\n .all()\n .map(toServer)\n}\n\nexport interface UpdateMcpServerInput {\n name?: string\n transport?: McpTransport\n command?: string | null\n args?: string[]\n url?: string | null\n enabled?: boolean\n hasAuth?: boolean\n}\n\nexport function update(db: BazilionDb, id: string, input: UpdateMcpServerInput): McpServer | null {\n const existing = get(db, id)\n if (!existing) return null\n const next = {\n name: input.name ?? existing.name,\n transport: input.transport ?? existing.transport,\n command: input.command !== undefined ? input.command : existing.command,\n args: input.args ?? existing.args,\n url: input.url !== undefined ? input.url : existing.url,\n enabled: input.enabled ?? existing.enabled,\n hasAuth: input.hasAuth ?? existing.hasAuthToken,\n }\n db.raw.run(\n `UPDATE mcp_servers\n SET name = ?, transport = ?, command = ?, args = ?, url = ?, enabled = ?, has_auth = ?, updated_at = ?\n WHERE id = ?`,\n [\n next.name,\n next.transport,\n next.command,\n JSON.stringify(next.args),\n next.url,\n next.enabled ? 1 : 0,\n next.hasAuth ? 1 : 0,\n Date.now(),\n id,\n ],\n )\n return get(db, id)\n}\n\nexport function setEnabled(db: BazilionDb, id: string, enabled: boolean): void {\n db.raw.run('UPDATE mcp_servers SET enabled = ?, updated_at = ? WHERE id = ?', [\n enabled ? 1 : 0,\n Date.now(),\n id,\n ])\n}\n\nexport function remove(db: BazilionDb, id: string): void {\n db.raw.run('DELETE FROM mcp_servers WHERE id = ?', [id])\n}\n","import { randomUUID } from 'node:crypto'\nimport type { Message } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawMessage {\n id: string\n from_agent_id: string\n to_agent_id: string\n reply_to: string | null\n causal_chain_id: string\n causal_hop: number\n payload: string\n created_at: number\n read_at: number | null\n policy_disposition: string\n policy_blocked_at: number | null\n policy_claimed_at: number | null\n policy_delivered_at: number | null\n}\n\nfunction toMessage(r: RawMessage): Message {\n return {\n id: r.id,\n fromAgentId: r.from_agent_id,\n toAgentId: r.to_agent_id,\n replyTo: r.reply_to,\n causalChainId: r.causal_chain_id,\n causalHop: r.causal_hop,\n payload: r.payload,\n createdAt: r.created_at,\n readAt: r.read_at,\n }\n}\n\nexport function send(\n db: BazilionDb,\n input: {\n from: string\n to: string\n payload: string\n replyTo?: string | null\n causalChainId?: string\n causalHop?: number\n },\n): Message {\n const id = randomUUID()\n const now = Date.now()\n const causalChainId = input.causalChainId ?? id\n const causalHop = input.causalHop ?? 0\n db.raw.run(\n `INSERT INTO messages\n (id, from_agent_id, to_agent_id, reply_to, causal_chain_id, causal_hop, payload, created_at, read_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n [id, input.from, input.to, input.replyTo ?? null, causalChainId, causalHop, input.payload, now],\n )\n return {\n id,\n fromAgentId: input.from,\n toAgentId: input.to,\n replyTo: input.replyTo ?? null,\n causalChainId,\n causalHop,\n payload: input.payload,\n createdAt: now,\n readAt: null,\n }\n}\n\nexport function get(db: BazilionDb, id: string): Message | null {\n const row = db.raw.query<RawMessage, [string]>('SELECT * FROM messages WHERE id = ?').get(id)\n return row ? toMessage(row) : null\n}\n\nexport function listInbox(\n db: BazilionDb,\n agentId: string,\n opts?: { unreadOnly?: boolean },\n): Message[] {\n const sql = opts?.unreadOnly\n ? \"SELECT * FROM messages WHERE to_agent_id = ? AND read_at IS NULL AND policy_claimed_at IS NULL AND policy_disposition = 'deliverable' ORDER BY created_at ASC\"\n : \"SELECT * FROM messages WHERE to_agent_id = ? AND policy_disposition = 'deliverable' ORDER BY created_at ASC\"\n return db.raw.query<RawMessage, [string]>(sql).all(agentId).map(toMessage)\n}\n\nexport function listInboxForOperator(\n db: BazilionDb,\n agentId: string,\n opts?: { unreadOnly?: boolean },\n): Message[] {\n const sql = opts?.unreadOnly\n ? 'SELECT * FROM messages WHERE to_agent_id = ? AND read_at IS NULL ORDER BY created_at ASC'\n : 'SELECT * FROM messages WHERE to_agent_id = ? ORDER BY created_at ASC'\n return db.raw.query<RawMessage, [string]>(sql).all(agentId).map(toMessage)\n}\n\nexport function markRead(db: BazilionDb, id: string): void {\n db.raw.run('UPDATE messages SET read_at = ? WHERE id = ? AND read_at IS NULL', [Date.now(), id])\n}\n\nexport function markPolicyBlocked(db: BazilionDb, id: string): void {\n db.raw.run(\n \"UPDATE messages SET policy_disposition = 'policy_blocked', policy_blocked_at = ? WHERE id = ? AND policy_disposition = 'deliverable'\",\n [Date.now(), id],\n )\n}\n\nexport function markPolicyClaimed(db: BazilionDb, id: string, when: number): void {\n db.raw.run(\n \"UPDATE messages SET policy_claimed_at = ? WHERE id = ? AND policy_disposition = 'deliverable' AND read_at IS NULL AND policy_claimed_at IS NULL\",\n [when, id],\n )\n}\n\nexport function markPolicyDelivered(db: BazilionDb, id: string, when: number): void {\n db.raw.run(\n \"UPDATE messages SET policy_delivered_at = ?, read_at = ? WHERE id = ? AND policy_disposition = 'deliverable' AND policy_claimed_at IS NOT NULL AND policy_delivered_at IS NULL\",\n [when, when, id],\n )\n}\n\n/**\n * Find messages that are replies to a given message id, addressed to a specific agent.\n * Used by `wait_for_reply` to poll for incoming responses.\n */\nexport function findReplies(db: BazilionDb, toAgentId: string, inReplyTo: string): Message[] {\n return db.raw\n .query<RawMessage, [string, string]>(\n `SELECT * FROM messages\n WHERE to_agent_id = ? AND reply_to = ?\n ORDER BY created_at ASC`,\n )\n .all(toAgentId, inReplyTo)\n .map(toMessage)\n}\n\n/**\n * Return the distinct `to_agent_id`s that currently have at least one unread\n * message. Archived recipients remain included so the scheduler can convert\n * their unread rows into one terminal policy-blocked disposition instead of\n * leaving an eternal retry candidate.\n * Used by the scheduler's message-wake loop so a tick can fan-out auto-\n * delivery turns without walking every agent in the DB.\n */\nexport function listRecipientsWithUnread(db: BazilionDb): string[] {\n const rows = db.raw\n .query<{ to_agent_id: string }, []>(\n `SELECT DISTINCT m.to_agent_id FROM messages m\n JOIN agents a ON a.id = m.to_agent_id\n WHERE m.read_at IS NULL AND m.policy_claimed_at IS NULL AND m.policy_disposition = 'deliverable'\n ORDER BY m.to_agent_id`,\n )\n .all()\n return rows.map((r) => r.to_agent_id)\n}\n\n/**\n * Atomically fetch + mark-read all unread messages addressed to `agentId`.\n * Runs inside a transaction so two concurrent schedulers / manual deliveries\n * can't double-dispatch the same message. Returns the fetched messages in\n * ascending `created_at` order — callers format them into the recipient's\n * wake-up prompt.\n */\nexport function drainUnreadForAgent(db: BazilionDb, agentId: string): Message[] {\n return db.raw.transaction(() => {\n const rows = db.raw\n .query<RawMessage, [string]>(\n `SELECT * FROM messages\n WHERE to_agent_id = ? AND read_at IS NULL AND policy_disposition = 'deliverable'\n ORDER BY created_at ASC`,\n )\n .all(agentId)\n if (rows.length === 0) return []\n const now = Date.now()\n db.raw.run(\n `UPDATE messages SET read_at = ?\n WHERE to_agent_id = ? AND read_at IS NULL AND policy_disposition = 'deliverable'`,\n [now, agentId],\n )\n return rows.map((r) => toMessage({ ...r, read_at: now }))\n })()\n}\n\n/** Capture the destination in the same transaction that claims inbox delivery. */\nexport function bindConversation(db: BazilionDb, id: string, conversationId: string): void {\n const result = db.raw.run(\n `UPDATE messages SET conversation_id = ? WHERE id = ? AND conversation_id IS NULL\n AND EXISTS (SELECT 1 FROM agent_conversations c WHERE c.id = ? AND c.agent_id = messages.to_agent_id)`,\n [conversationId, id, conversationId],\n )\n if (Number(result.changes) !== 1) throw new Error('inbox conversation binding failed')\n}\n","// Encrypted secrets store, backed by the `secrets` table.\n//\n// Layout: one row per env-var-shaped key (`ANTHROPIC_API_KEY`,\n// `OPENAI_CODEX_OAUTH`, …). Each value is an AES-256-GCM envelope (salt +\n// iv + tag + data, hex-encoded JSON), with the key derived from the\n// bootstrap token via PBKDF2-SHA256 (100k iterations). The crypto matches\n// the previous `secrets.enc` file format byte-for-byte — only the storage\n// medium changed.\n//\n// Why encrypt at all when the password lives in `~/.bazilion/auth.json`\n// next to the DB? Same reason as before: it's defense against accidental\n// exposure (cat'd dumps, screenshares, naive backups), not against an\n// attacker with filesystem read. Anyone who can read both files wins.\n//\n// Caching: `deriveKey` runs PBKDF2 once per (password, salt) pair and the\n// salt is per-row, so a busy daemon does ~one PBKDF2 per secret read. The\n// `secretCache` keeps decrypted values in-memory keyed by row id so repeated\n// reads inside one process don't repeat the work; mutations clear the cache\n// row.\n\nimport { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from 'node:crypto'\nimport type { BazilionDb } from '../db/client.ts'\n\nconst ALGORITHM = 'aes-256-gcm'\nconst KEY_LEN = 32\nconst ITERATIONS = 100_000\nconst DIGEST = 'sha256'\n\ninterface EncryptedEnvelope {\n salt: string\n iv: string\n tag: string\n data: string\n}\n\nfunction deriveKey(password: string, salt: Buffer): Buffer {\n return pbkdf2Sync(password, salt, ITERATIONS, KEY_LEN, DIGEST)\n}\n\nfunction encrypt(plaintext: string, password: string): EncryptedEnvelope {\n const salt = randomBytes(16)\n const key = deriveKey(password, salt)\n const iv = randomBytes(12)\n const cipher = createCipheriv(ALGORITHM, key, iv)\n let data = cipher.update(plaintext, 'utf8', 'hex')\n data += cipher.final('hex')\n const tag = cipher.getAuthTag()\n return {\n salt: salt.toString('hex'),\n iv: iv.toString('hex'),\n tag: tag.toString('hex'),\n data,\n }\n}\n\nfunction decrypt(envelope: EncryptedEnvelope, password: string): string {\n const salt = Buffer.from(envelope.salt, 'hex')\n const key = deriveKey(password, salt)\n const iv = Buffer.from(envelope.iv, 'hex')\n const tag = Buffer.from(envelope.tag, 'hex')\n const decipher = createDecipheriv(ALGORITHM, key, iv)\n decipher.setAuthTag(tag)\n let plaintext = decipher.update(envelope.data, 'hex', 'utf8')\n plaintext += decipher.final('utf8')\n return plaintext\n}\n\ninterface RawRow {\n key: string\n envelope: string\n updated_at: number\n}\n\nexport interface SecretsStore {\n get(key: string): string | undefined\n set(key: string, value: string): void\n remove(key: string): void\n has(key: string): boolean\n list(): { key: string; preview: string }[]\n getAll(): Record<string, string>\n}\n\n/**\n * Open the encrypted secrets store. `password` is the bootstrap token\n * (read from `auth.json` by the caller). Throws on individual-row decrypt\n * failures only when actively reading that row — bad rows show as\n * `undefined` from `get`, the caller can `set` to overwrite.\n */\nexport function openSecrets(db: BazilionDb, password: string): SecretsStore {\n function getRow(key: string): RawRow | null {\n return db.raw.query<RawRow, [string]>('SELECT * FROM secrets WHERE key = ?').get(key)\n }\n\n function listAll(): RawRow[] {\n return db.raw.query<RawRow, []>('SELECT * FROM secrets ORDER BY key ASC').all()\n }\n\n function tryDecrypt(row: RawRow): string | undefined {\n try {\n const envelope = JSON.parse(row.envelope) as EncryptedEnvelope\n return decrypt(envelope, password)\n } catch {\n return undefined\n }\n }\n\n return {\n get(key) {\n const row = getRow(key)\n if (!row) return undefined\n return tryDecrypt(row)\n },\n set(key, value) {\n const envelope = JSON.stringify(encrypt(value, password))\n db.raw.run(\n `INSERT INTO secrets (key, envelope, updated_at) VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET envelope = excluded.envelope, updated_at = excluded.updated_at`,\n [key, envelope, Date.now()],\n )\n },\n remove(key) {\n db.raw.run('DELETE FROM secrets WHERE key = ?', [key])\n },\n has(key) {\n return getRow(key) !== null\n },\n list() {\n return listAll().map((r) => {\n const value = tryDecrypt(r)\n return {\n key: r.key,\n preview: value ? (value.length > 8 ? `${value.slice(0, 6)}…` : '***') : '(unreadable)',\n }\n })\n },\n getAll() {\n const out: Record<string, string> = {}\n for (const r of listAll()) {\n const v = tryDecrypt(r)\n if (v !== undefined) out[r.key] = v\n }\n return out\n },\n }\n}\n","import { createHash, randomBytes, timingSafeEqual } from 'node:crypto'\nimport type { BazilionDb } from '../db/client.ts'\nimport { add } from './telegram-acl.ts'\n\nconst TTL_MS = 10 * 60_000\n\ninterface PairingRow {\n digest: string\n expires_at: number\n created_at: number\n}\n\nexport interface TelegramPairingStatus {\n paired: boolean\n challengeActive: boolean\n challengeExpiresAt: number | null\n ownerUserId: number | null\n}\n\nfunction digest(code: string): Buffer {\n return createHash('sha256').update(code, 'utf8').digest()\n}\n\nexport function create(db: BazilionDb): { code: string; expiresAt: number } {\n if (status(db).paired) throw new Error('Telegram already has a paired owner')\n const code = randomBytes(16).toString('base64url')\n const now = Date.now()\n const expiresAt = now + TTL_MS\n db.raw.run(\n `INSERT INTO telegram_pairing_challenge (singleton, digest, expires_at, created_at)\n VALUES (1, ?, ?, ?)\n ON CONFLICT(singleton) DO UPDATE SET\n digest = excluded.digest, expires_at = excluded.expires_at, created_at = excluded.created_at`,\n [digest(code).toString('hex'), expiresAt, now],\n )\n return { code, expiresAt }\n}\n\nexport function cancel(db: BazilionDb): void {\n db.raw.run('DELETE FROM telegram_pairing_challenge WHERE singleton = 1')\n}\n\nexport function reset(db: BazilionDb): void {\n db.raw.transaction(() => {\n cancel(db)\n db.raw.run('DELETE FROM telegram_allowed_users')\n })()\n}\n\nexport function status(db: BazilionDb): TelegramPairingStatus {\n const owner = db.raw\n .query<{ user_id: number }, []>(\n \"SELECT user_id FROM telegram_allowed_users WHERE role = 'owner' ORDER BY added_at LIMIT 1\",\n )\n .get()\n const row = db.raw\n .query<PairingRow, []>(\n 'SELECT digest, expires_at, created_at FROM telegram_pairing_challenge WHERE singleton = 1',\n )\n .get()\n const active = row !== null && row.expires_at > Date.now()\n if (row && !active) cancel(db)\n return {\n paired: owner !== null,\n challengeActive: active,\n challengeExpiresAt: active ? (row?.expires_at ?? null) : null,\n ownerUserId: owner?.user_id ?? null,\n }\n}\n\nexport function consume(\n db: BazilionDb,\n input: { code: string; userId: number; username?: string | null; label?: string | null },\n): boolean {\n return db.raw.transaction(() => {\n if (status(db).paired) return false\n const row = db.raw\n .query<PairingRow, []>(\n 'SELECT digest, expires_at, created_at FROM telegram_pairing_challenge WHERE singleton = 1',\n )\n .get()\n if (!row || row.expires_at <= Date.now()) {\n cancel(db)\n return false\n }\n const supplied = digest(input.code)\n const expected = Buffer.from(row.digest, 'hex')\n if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) return false\n add(db, { ...input, role: 'owner' })\n cancel(db)\n return true\n })()\n}\n","import { randomUUID } from 'node:crypto'\nimport type { TriggerDispatch, TriggerDispatchStatus } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawDispatch {\n id: string\n trigger_id: string\n agent_id: string\n conversation_id: string\n scheduled_at: number\n status: string\n attempt_count: number\n next_attempt_at: number\n lease_expires_at: number | null\n started_at: number | null\n finished_at: number | null\n last_error: string | null\n created_at: number\n updated_at: number\n}\n\nfunction toDispatch(row: RawDispatch): TriggerDispatch {\n return {\n id: row.id,\n triggerId: row.trigger_id,\n agentId: row.agent_id,\n conversationId: row.conversation_id,\n scheduledAt: row.scheduled_at,\n status: row.status as TriggerDispatchStatus,\n attemptCount: row.attempt_count,\n nextAttemptAt: row.next_attempt_at,\n leaseExpiresAt: row.lease_expires_at,\n startedAt: row.started_at,\n finishedAt: row.finished_at,\n lastError: row.last_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n }\n}\n\nexport function materialize(\n db: BazilionDb,\n input: {\n triggerId: string\n agentId: string\n conversationId: string\n scheduledAt: number\n now?: number\n },\n): TriggerDispatch {\n const now = input.now ?? Date.now()\n db.raw.run(\n `INSERT INTO trigger_dispatches\n (id, trigger_id, agent_id, conversation_id, scheduled_at, status, attempt_count, next_attempt_at,\n lease_expires_at, started_at, finished_at, last_error, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, 'pending', 0, ?, NULL, NULL, NULL, NULL, ?, ?)\n ON CONFLICT(trigger_id, scheduled_at) DO NOTHING`,\n [\n randomUUID(),\n input.triggerId,\n input.agentId,\n input.conversationId,\n input.scheduledAt,\n now,\n now,\n now,\n ],\n )\n const row = db.raw\n .query<RawDispatch, [string, number]>(\n 'SELECT * FROM trigger_dispatches WHERE trigger_id = ? AND scheduled_at = ?',\n )\n .get(input.triggerId, input.scheduledAt)\n if (!row) throw new Error('trigger dispatch materialization failed')\n return toDispatch(row)\n}\n\nexport function get(db: BazilionDb, id: string): TriggerDispatch | null {\n const row = db.raw\n .query<RawDispatch, [string]>('SELECT * FROM trigger_dispatches WHERE id = ?')\n .get(id)\n return row ? toDispatch(row) : null\n}\n\nexport function listClaimable(db: BazilionDb, now = Date.now()): TriggerDispatch[] {\n return db.raw\n .query<RawDispatch, [number, number]>(\n `SELECT * FROM trigger_dispatches\n WHERE (status IN ('pending','retrying') AND next_attempt_at <= ?)\n OR (status = 'running' AND lease_expires_at <= ?)\n ORDER BY scheduled_at ASC, created_at ASC`,\n )\n .all(now, now)\n .map(toDispatch)\n}\n\nexport function hasOpenForTrigger(db: BazilionDb, triggerId: string): boolean {\n return (\n db.raw\n .query<{ found: number }, [string]>(\n `SELECT 1 found FROM trigger_dispatches\n WHERE trigger_id = ? AND status IN ('pending','running','retrying') LIMIT 1`,\n )\n .get(triggerId) !== null\n )\n}\n\nexport function claim(\n db: BazilionDb,\n id: string,\n options: { now?: number; leaseMs?: number } = {},\n): TriggerDispatch | null {\n const now = options.now ?? Date.now()\n const leaseExpiresAt = now + (options.leaseMs ?? 5 * 60_000)\n return db.raw.transaction(() => {\n const result = db.raw.run(\n `UPDATE trigger_dispatches\n SET status = 'running', attempt_count = attempt_count + 1,\n lease_expires_at = ?, started_at = ?, finished_at = NULL, updated_at = ?\n WHERE id = ? AND (\n (status IN ('pending','retrying') AND next_attempt_at <= ?)\n OR (status = 'running' AND lease_expires_at <= ?)\n )`,\n [leaseExpiresAt, now, now, id, now, now],\n )\n if (Number(result.changes) !== 1) return null\n const row = db.raw\n .query<RawDispatch, [string]>('SELECT * FROM trigger_dispatches WHERE id = ?')\n .get(id)\n return row ? toDispatch(row) : null\n })()\n}\n\nexport function succeed(db: BazilionDb, id: string, now = Date.now()): void {\n db.raw.run(\n `UPDATE trigger_dispatches SET status = 'succeeded', lease_expires_at = NULL,\n finished_at = ?, last_error = NULL, updated_at = ? WHERE id = ? AND status = 'running'`,\n [now, now, id],\n )\n}\n\nexport function defer(db: BazilionDb, id: string, nextAttemptAt = Date.now() + 1_000): void {\n db.raw.run(\n `UPDATE trigger_dispatches SET status = 'pending',\n attempt_count = CASE WHEN attempt_count > 0 THEN attempt_count - 1 ELSE 0 END,\n next_attempt_at = ?, lease_expires_at = NULL, started_at = NULL, updated_at = ?\n WHERE id = ? AND status = 'running'`,\n [nextAttemptAt, Date.now(), id],\n )\n}\n\nexport function cancelRunning(db: BazilionDb, id: string, reason: string, now = Date.now()): void {\n db.raw.run(\n `UPDATE trigger_dispatches SET status = 'cancelled', lease_expires_at = NULL,\n finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,\n [now, reason.slice(0, 2_000), now, id],\n )\n}\n\nexport function fail(\n db: BazilionDb,\n id: string,\n error: string,\n options: { now?: number; maxAttempts?: number; retryDelayMs?: number } = {},\n): void {\n const now = options.now ?? Date.now()\n const maxAttempts = options.maxAttempts ?? 3\n const retryDelayMs = options.retryDelayMs ?? 5_000\n const current = db.raw\n .query<{ attempt_count: number }, [string]>(\n 'SELECT attempt_count FROM trigger_dispatches WHERE id = ?',\n )\n .get(id)\n if (!current) return\n const terminal = current.attempt_count >= maxAttempts\n db.raw.run(\n `UPDATE trigger_dispatches SET status = ?, next_attempt_at = ?, lease_expires_at = NULL,\n finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,\n [\n terminal ? 'failed' : 'retrying',\n terminal ? now : now + retryDelayMs * 2 ** Math.max(0, current.attempt_count - 1),\n terminal ? now : null,\n error.slice(0, 2_000),\n now,\n id,\n ],\n )\n}\n\nexport function cancelPendingForTrigger(db: BazilionDb, triggerId: string, now = Date.now()): void {\n db.raw.run(\n `UPDATE trigger_dispatches SET status = 'cancelled', finished_at = ?, updated_at = ?\n WHERE trigger_id = ? AND status IN ('pending','retrying')`,\n [now, now, triggerId],\n )\n}\n\nexport function listForTrigger(db: BazilionDb, triggerId: string, limit = 20): TriggerDispatch[] {\n return db.raw\n .query<RawDispatch, [string, number]>(\n 'SELECT * FROM trigger_dispatches WHERE trigger_id = ? ORDER BY scheduled_at DESC LIMIT ?',\n )\n .all(triggerId, limit)\n .map(toDispatch)\n}\n","import { randomUUID } from 'node:crypto'\nimport type { AgentTrigger, TriggerKind } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport { cancelPendingForTrigger } from './triggerDispatches.ts'\n\ninterface RawTrigger {\n id: string\n agent_id: string\n kind: string\n interval_sec: number | null\n cron_expr: string | null\n message: string\n enabled: number\n last_fired_at: number | null\n created_at: number\n}\n\nfunction toTrigger(r: RawTrigger): AgentTrigger {\n return {\n id: r.id,\n agentId: r.agent_id,\n kind: r.kind as TriggerKind,\n intervalSec: r.interval_sec,\n cronExpr: r.cron_expr,\n message: r.message,\n enabled: r.enabled === 1,\n lastFiredAt: r.last_fired_at,\n createdAt: r.created_at,\n }\n}\n\nexport interface InsertTriggerInput {\n agentId: string\n kind: TriggerKind\n intervalSec: number | null\n cronExpr: string | null\n message: string\n enabled?: boolean\n}\n\nexport function insert(db: BazilionDb, input: InsertTriggerInput): AgentTrigger {\n const id = randomUUID()\n const now = Date.now()\n const enabled = input.enabled === false ? 0 : 1\n db.raw.run(\n `INSERT INTO agent_triggers\n (id, agent_id, kind, interval_sec, cron_expr, message, enabled, last_fired_at, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?)`,\n [id, input.agentId, input.kind, input.intervalSec, input.cronExpr, input.message, enabled, now],\n )\n return {\n id,\n agentId: input.agentId,\n kind: input.kind,\n intervalSec: input.intervalSec,\n cronExpr: input.cronExpr,\n message: input.message,\n enabled: enabled === 1,\n lastFiredAt: null,\n createdAt: now,\n }\n}\n\nexport function get(db: BazilionDb, id: string): AgentTrigger | null {\n const row = db.raw\n .query<RawTrigger, [string]>('SELECT * FROM agent_triggers WHERE id = ?')\n .get(id)\n return row ? toTrigger(row) : null\n}\n\nexport function listForAgent(db: BazilionDb, agentId: string): AgentTrigger[] {\n return db.raw\n .query<RawTrigger, [string]>(\n 'SELECT * FROM agent_triggers WHERE agent_id = ? ORDER BY created_at ASC',\n )\n .all(agentId)\n .map(toTrigger)\n}\n\nexport function listEnabled(db: BazilionDb): AgentTrigger[] {\n return db.raw\n .query<RawTrigger, []>(\n `SELECT t.* FROM agent_triggers t\n JOIN agents a ON a.id = t.agent_id\n WHERE t.enabled = 1\n ORDER BY t.created_at ASC`,\n )\n .all()\n .map(toTrigger)\n}\n\nexport function setEnabled(db: BazilionDb, id: string, enabled: boolean): void {\n db.raw.transaction(() => {\n db.raw.run('UPDATE agent_triggers SET enabled = ? WHERE id = ?', [enabled ? 1 : 0, id])\n if (!enabled) cancelPendingForTrigger(db, id)\n })()\n}\n\nexport function markFired(db: BazilionDb, id: string, when: number = Date.now()): void {\n db.raw.run('UPDATE agent_triggers SET last_fired_at = ? WHERE id = ?', [when, id])\n}\n\nexport function remove(db: BazilionDb, id: string): void {\n db.raw.run('DELETE FROM agent_triggers WHERE id = ?', [id])\n}\n","import { createHash, randomBytes, randomUUID } from 'node:crypto'\nimport type { WebSession } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport { hashToken } from './webTokens.ts'\n\nexport const SESSION_IDLE_MS = 12 * 60 * 60 * 1000\nexport const SESSION_ABSOLUTE_MS = 7 * 24 * 60 * 60 * 1000\n\ninterface RawSession {\n id: string\n secret_hash: string\n csrf_hash: string\n device_token_id: string\n device_label: string\n created_at: number\n last_seen_at: number\n idle_expires_at: number\n absolute_expires_at: number\n revoked_at: number | null\n}\n\nexport interface CreatedSession {\n session: WebSession\n cookieValue: string\n csrfToken: string\n}\n\nexport interface AuthenticatedSession {\n id: string\n deviceTokenId: string\n deviceLabel: string\n csrfHash: string\n}\n\nfunction hash(value: string): string {\n return createHash('sha256').update(value).digest('hex')\n}\n\nfunction toSession(row: RawSession, currentId?: string): WebSession {\n return {\n id: row.id,\n deviceTokenId: row.device_token_id,\n deviceLabel: row.device_label,\n createdAt: row.created_at,\n lastSeenAt: row.last_seen_at,\n idleExpiresAt: row.idle_expires_at,\n absoluteExpiresAt: row.absolute_expires_at,\n revokedAt: row.revoked_at,\n current: row.id === currentId,\n }\n}\n\nexport function create(db: BazilionDb, deviceTokenId: string, now = Date.now()): CreatedSession {\n const token = db.raw\n .query<{ id: string; label: string }, [string, number]>(\n `SELECT id, label FROM web_tokens\n WHERE id = ? AND kind = 'device' AND revoked_at IS NULL AND expires_at > ?`,\n )\n .get(deviceTokenId, now)\n if (!token) throw new Error('active device credential required')\n const id = randomUUID()\n const secret = randomBytes(32).toString('base64url')\n const csrfToken = randomBytes(32).toString('base64url')\n const idleExpiresAt = now + SESSION_IDLE_MS\n const absoluteExpiresAt = now + SESSION_ABSOLUTE_MS\n db.raw.run(\n `INSERT INTO web_sessions\n (id, secret_hash, csrf_hash, device_token_id, created_at, last_seen_at,\n idle_expires_at, absolute_expires_at, revoked_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`,\n [id, hash(secret), hash(csrfToken), deviceTokenId, now, now, idleExpiresAt, absoluteExpiresAt],\n )\n return {\n cookieValue: `${id}.${secret}`,\n csrfToken,\n session: {\n id,\n deviceTokenId,\n deviceLabel: token.label,\n createdAt: now,\n lastSeenAt: now,\n idleExpiresAt,\n absoluteExpiresAt,\n revokedAt: null,\n current: true,\n },\n }\n}\n\nexport function authenticate(\n db: BazilionDb,\n cookieValue: string,\n now = Date.now(),\n): AuthenticatedSession | null {\n const separator = cookieValue.indexOf('.')\n if (separator <= 0) return null\n const id = cookieValue.slice(0, separator)\n const secret = cookieValue.slice(separator + 1)\n if (!secret) return null\n const row = db.raw\n .query<RawSession, [string, string, number, number, number]>(\n `SELECT s.*, t.label AS device_label\n FROM web_sessions s JOIN web_tokens t ON t.id = s.device_token_id\n WHERE s.id = ? AND s.secret_hash = ? AND s.revoked_at IS NULL\n AND s.idle_expires_at > ? AND s.absolute_expires_at > ?\n AND t.kind = 'device' AND t.revoked_at IS NULL AND t.expires_at > ?`,\n )\n .get(id, hash(secret), now, now, now)\n if (!row) return null\n const idleExpiresAt = Math.min(now + SESSION_IDLE_MS, row.absolute_expires_at)\n db.raw.run('UPDATE web_sessions SET last_seen_at = ?, idle_expires_at = ? WHERE id = ?', [\n now,\n idleExpiresAt,\n id,\n ])\n return {\n id,\n deviceTokenId: row.device_token_id,\n deviceLabel: row.device_label,\n csrfHash: row.csrf_hash,\n }\n}\n\nexport function csrfMatches(session: AuthenticatedSession, csrf: string): boolean {\n return hashToken(csrf) === session.csrfHash\n}\n\nexport function list(\n db: BazilionDb,\n opts: { includeRevoked?: boolean; currentId?: string } = {},\n): WebSession[] {\n const where = opts.includeRevoked ? '' : 'WHERE s.revoked_at IS NULL'\n return db.raw\n .query<RawSession, []>(\n `SELECT s.*, t.label AS device_label FROM web_sessions s\n JOIN web_tokens t ON t.id = s.device_token_id\n ${where} ORDER BY s.created_at DESC`,\n )\n .all()\n .map((row) => toSession(row, opts.currentId))\n}\n\nexport function revoke(db: BazilionDb, id: string, now = Date.now()): boolean {\n return (\n db.raw.run('UPDATE web_sessions SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL', [\n now,\n id,\n ]).changes > 0\n )\n}\n\nexport function revokeForDevice(db: BazilionDb, tokenId: string, now = Date.now()): number {\n return db.raw.run(\n 'UPDATE web_sessions SET revoked_at = ? WHERE device_token_id = ? AND revoked_at IS NULL',\n [now, tokenId],\n ).changes\n}\n\nexport function revokeAll(db: BazilionDb, now = Date.now()): number {\n return db.raw.run('UPDATE web_sessions SET revoked_at = ? WHERE revoked_at IS NULL', [now])\n .changes\n}\n","import { createHash, randomBytes, randomUUID } from 'node:crypto'\nimport type { WebToken } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\ninterface RawToken {\n id: string\n label: string\n kind: 'bootstrap' | 'device'\n token_hash: string\n created_at: number\n last_used_at: number | null\n expires_at: number | null\n revoked_at: number | null\n}\n\nfunction toToken(r: RawToken): WebToken {\n return {\n id: r.id,\n label: r.label,\n kind: r.kind,\n createdAt: r.created_at,\n lastUsedAt: r.last_used_at,\n expiresAt: r.expires_at,\n revokedAt: r.revoked_at,\n }\n}\n\nexport function hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex')\n}\n\nexport interface CreatedToken {\n meta: WebToken\n /** Plaintext token — shown exactly once, never re-queryable. */\n token: string\n}\n\nexport function create(\n db: BazilionDb,\n label: string,\n opts: { kind?: 'bootstrap' | 'device'; expiresAt?: number | null } = {},\n): CreatedToken {\n const id = randomUUID()\n const token = randomBytes(24).toString('hex')\n const tokenHash = hashToken(token)\n const now = Date.now()\n const kind = opts.kind ?? 'device'\n const expiresAt = kind === 'bootstrap' ? null : (opts.expiresAt ?? now + 90 * 86_400_000)\n db.raw.run(\n `INSERT INTO web_tokens\n (id, label, kind, token_hash, created_at, last_used_at, expires_at, revoked_at)\n VALUES (?, ?, ?, ?, ?, NULL, ?, NULL)`,\n [id, label, kind, tokenHash, now, expiresAt],\n )\n return {\n token,\n meta: {\n id,\n label,\n kind,\n createdAt: now,\n lastUsedAt: null,\n expiresAt,\n revokedAt: null,\n },\n }\n}\n\nexport function list(db: BazilionDb, opts?: { includeRevoked?: boolean }): WebToken[] {\n const sql = opts?.includeRevoked\n ? 'SELECT * FROM web_tokens ORDER BY created_at ASC'\n : 'SELECT * FROM web_tokens WHERE revoked_at IS NULL ORDER BY created_at ASC'\n return db.raw.query<RawToken, []>(sql).all().map(toToken)\n}\n\nexport function get(db: BazilionDb, id: string): WebToken | null {\n const row = db.raw.query<RawToken, [string]>('SELECT * FROM web_tokens WHERE id = ?').get(id)\n return row ? toToken(row) : null\n}\n\n/**\n * Returns the active token row matching the given plaintext, or null.\n * Does NOT bump last_used_at — call markUsed separately once the caller\n * has decided the request is authorized.\n */\nexport function findActiveByToken(db: BazilionDb, token: string): WebToken | null {\n const tokenHash = hashToken(token)\n const row = db.raw\n .query<RawToken, [string, number]>(\n `SELECT * FROM web_tokens\n WHERE token_hash = ? AND revoked_at IS NULL\n AND (expires_at IS NULL OR expires_at > ?)`,\n )\n .get(tokenHash, Date.now())\n return row ? toToken(row) : null\n}\n\nexport function rejectionReason(\n db: BazilionDb,\n token: string,\n now = Date.now(),\n): 'invalid' | 'expired' | 'revoked' {\n const row = db.raw\n .query<Pick<RawToken, 'expires_at' | 'revoked_at'>, [string]>(\n 'SELECT expires_at, revoked_at FROM web_tokens WHERE token_hash = ?',\n )\n .get(hashToken(token))\n if (!row) return 'invalid'\n if (row.revoked_at !== null) return 'revoked'\n if (row.expires_at !== null && row.expires_at <= now) return 'expired'\n return 'invalid'\n}\n\nexport function markUsed(db: BazilionDb, id: string, when: number = Date.now()): void {\n db.raw.run('UPDATE web_tokens SET last_used_at = ? WHERE id = ?', [when, id])\n}\n\nexport function revoke(db: BazilionDb, id: string, when: number = Date.now()): boolean {\n const res = db.raw.run(\n 'UPDATE web_tokens SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL',\n [when, id],\n )\n return res.changes > 0\n}\n","// Auth / secrets entry points keyed off the bootstrap `auth.json` file.\n//\n// `auth.json` carries one mandatory field — `token` — written by the daemon's\n// first-run bootstrap. The daemon uses it as the PBKDF2 seed for the `secrets`\n// table; the CLI uses it as the bearer for loopback HTTP. CLI-side `remote`\n// overrides (set by `bazilion login`) coexist in the same file.\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport type { BazilionDb } from './db/client.ts'\nimport { openConfig } from './repos/config.ts'\nimport { openSecrets } from './repos/secrets.ts'\n\nexport interface AuthFile {\n token: string\n remote?: { server: string; token: string } | null\n}\n\n/**\n * Read `auth.json` and return the parsed contents. Throws when missing or\n * malformed — callers should treat that as \"bazilion not initialized.\"\n */\nexport function readAuthFile(authFile: string): AuthFile {\n if (!existsSync(authFile)) {\n throw new Error(\n `${authFile} not found. Start the daemon (\\`bazilion serve\\`) — it auto-bootstraps on first run.`,\n )\n }\n const raw = readFileSync(authFile, 'utf8')\n const parsed = JSON.parse(raw) as Partial<AuthFile>\n if (typeof parsed.token !== 'string' || !parsed.token) {\n throw new Error(`${authFile} is missing the \"token\" field`)\n }\n return {\n token: parsed.token,\n remote: parsed.remote ?? null,\n }\n}\n\n/**\n * Merge plaintext config + decrypted secrets + process env into a single\n * env-shaped record. Precedence (low → high): config → secrets → env. The\n * caller supplies the bootstrap `password` (typically `readAuthFile().token`)\n * because the secrets table is encrypted with it.\n *\n * Either layer may fail to read (corrupt row, wrong key, table absent at\n * fixture-bootstrap time); failures are swallowed per-layer so a single bad\n * value never blocks the merge.\n */\nexport function mergeSecretsIntoEnv(\n db: BazilionDb,\n password: string,\n env: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n let configValues: Record<string, string> = {}\n let secretValues: Record<string, string> = {}\n try {\n configValues = openConfig(db).getAll()\n } catch {\n // table missing or unreadable — continue without the layer\n }\n try {\n secretValues = openSecrets(db, password).getAll()\n } catch {\n // table missing or wrong password — continue without the layer\n }\n return { ...configValues, ...secretValues, ...env }\n}\n","import { cpSync, existsSync, mkdtempSync, readdirSync, rmSync, statSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, join, resolve, sep } from 'node:path'\nimport type { SkillScanFinding } from '@bazilion/api-types'\nimport AdmZip from 'adm-zip'\nimport type { Paths } from '../paths.ts'\nimport { parseSkillFile } from './parse.ts'\nimport { scanSkillContent } from './scan.ts'\n\nexport interface ImportSkillsInput {\n /**\n * Absolute path to either:\n * - a single skill folder (contains SKILL.md),\n * - a parent directory containing multiple skill folders,\n * - a `.zip` file whose top level holds one-folder-per-skill (or a single\n * wrapping folder that holds them).\n */\n source: string\n /** overwrite existing target skills */\n force?: boolean\n}\n\nexport interface ImportResult {\n imported: string[]\n skipped: { name: string; reason: string; findings?: SkillScanFinding[] }[]\n findings?: Record<string, SkillScanFinding[]>\n}\n\nexport class SkillScanBlockedError extends Error {\n findings: Record<string, SkillScanFinding[]>\n\n constructor(findings: Record<string, SkillScanFinding[]>) {\n const names = Object.keys(findings).sort()\n super(\n `skill scan blocked import: ${names.join(', ')} ${names.length === 1 ? 'has' : 'have'} findings (rerun with --force to confirm)`,\n )\n this.name = 'SkillScanBlockedError'\n this.findings = findings\n }\n}\n\n/**\n * Extract a zip archive into a fresh temp dir with a zip-slip guard — every\n * entry's resolved path must stay under the extraction root. Returns both the\n * mkdtemp root (for cleanup) and the effective source path to hand to the\n * importer. When the archive is wrapped in a single top-level directory\n * (common with GitHub-style archives), we descend into it so `source` lines\n * up with the \"parent-of-skill-dirs\" shape the importer already understands.\n */\nfunction extractZipSafely(zipPath: string): { root: string; effectiveSource: string } {\n const root = mkdtempSync(join(tmpdir(), 'bazilion-skill-zip-'))\n try {\n const zip = new AdmZip(zipPath)\n for (const entry of zip.getEntries()) {\n const rawName = entry.entryName\n if (rawName.startsWith('/') || rawName.startsWith('\\\\')) {\n throw new Error(`zip entry has absolute path: ${rawName}`)\n }\n const resolved = resolve(root, rawName)\n if (resolved !== root && !resolved.startsWith(root + sep)) {\n throw new Error(`zip entry escapes extraction root: ${rawName}`)\n }\n }\n zip.extractAllTo(root, true)\n } catch (err) {\n rmSync(root, { recursive: true, force: true })\n throw err\n }\n\n // Unwrap a single top-level folder if present — the importer's\n // \"source-is-a-directory\" cases (single-skill dir vs. parent-of-skills dir)\n // both apply equally well to the unwrapped path.\n let effectiveSource = root\n const topEntries = readdirSync(root, { withFileTypes: true })\n if (topEntries.length === 1 && topEntries[0]?.isDirectory()) {\n effectiveSource = join(root, topEntries[0].name)\n }\n return { root, effectiveSource }\n}\n\nexport function importSkills(paths: Paths, input: ImportSkillsInput): ImportResult {\n const rawSource = resolve(input.source)\n if (!existsSync(rawSource)) {\n throw new Error(`source does not exist: ${rawSource}`)\n }\n\n let source = rawSource\n let tempRoot: string | null = null\n const sourceStat = statSync(rawSource)\n if (sourceStat.isFile()) {\n if (!rawSource.toLowerCase().endsWith('.zip')) {\n throw new Error(`source file must be a .zip archive: ${rawSource}`)\n }\n const { root, effectiveSource } = extractZipSafely(rawSource)\n tempRoot = root\n source = effectiveSource\n } else if (!sourceStat.isDirectory()) {\n throw new Error(`source is not a directory: ${rawSource}`)\n }\n\n try {\n return importSkillsFromDir(paths, source, input)\n } finally {\n if (tempRoot) rmSync(tempRoot, { recursive: true, force: true })\n }\n}\n\nfunction importSkillsFromDir(paths: Paths, source: string, input: ImportSkillsInput): ImportResult {\n const candidates: { name: string; dir: string }[] = []\n\n // Two shapes are accepted:\n // 1. source is itself a single skill dir (contains SKILL.md)\n // 2. source is a parent containing multiple skill dirs\n if (existsSync(join(source, 'SKILL.md'))) {\n candidates.push({ name: basename(source), dir: source })\n } else {\n const entries = readdirSync(source, { withFileTypes: true })\n for (const e of entries) {\n if (!e.isDirectory()) continue\n const skillDir = join(source, e.name)\n if (!existsSync(join(skillDir, 'SKILL.md'))) continue\n candidates.push({ name: e.name, dir: skillDir })\n }\n }\n\n if (candidates.length === 0) {\n throw new Error(`no skills found in ${source}`)\n }\n\n // Validate and scan every SKILL.md before touching the target dir.\n const findingsBySkill: Record<string, SkillScanFinding[]> = {}\n for (const c of candidates) {\n const parsed = parseSkillFile(join(c.dir, 'SKILL.md'))\n const findings = scanSkillContent(parsed.raw)\n if (findings.length > 0) findingsBySkill[c.name] = findings\n }\n if (Object.keys(findingsBySkill).length > 0 && !input.force) {\n throw new SkillScanBlockedError(findingsBySkill)\n }\n\n const imported: string[] = []\n const skipped: { name: string; reason: string; findings?: SkillScanFinding[] }[] = []\n\n for (const c of candidates) {\n const target = join(paths.skillsDir, c.name)\n if (existsSync(target) && !input.force) {\n skipped.push({\n name: c.name,\n reason: 'already exists (use --force to overwrite)',\n findings: findingsBySkill[c.name],\n })\n continue\n }\n cpSync(c.dir, target, { recursive: true, force: !!input.force })\n imported.push(c.name)\n }\n\n return {\n imported,\n skipped,\n findings: Object.keys(findingsBySkill).length > 0 ? findingsBySkill : undefined,\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { parse as parseYaml } from 'yaml'\n\n/**\n * Standard agent-skill frontmatter. Required fields are typed; the rest is open\n * (skill formats may add fields like `allowed-tools`, `homepage`, etc.).\n */\nexport interface SkillFrontmatter {\n name: string\n description: string\n [key: string]: unknown\n}\n\nexport interface ParsedSkill {\n frontmatter: SkillFrontmatter\n body: string\n raw: string\n}\n\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/\n\nexport function parseSkillContent(raw: string): ParsedSkill {\n const m = raw.match(FRONTMATTER_RE)\n if (!m) {\n throw new Error('SKILL.md missing YAML frontmatter (expected leading \"---\")')\n }\n const yamlBlock = m[1] ?? ''\n const body = m[2] ?? ''\n\n let fm: unknown\n try {\n fm = parseYaml(yamlBlock)\n } catch (err) {\n throw new Error(`SKILL.md frontmatter is not valid YAML: ${(err as Error).message}`)\n }\n if (!fm || typeof fm !== 'object' || Array.isArray(fm)) {\n throw new Error('SKILL.md frontmatter must be a YAML object')\n }\n const fmObj = fm as Record<string, unknown>\n if (typeof fmObj.name !== 'string' || fmObj.name.length === 0) {\n throw new Error('SKILL.md frontmatter missing required \"name\"')\n }\n if (typeof fmObj.description !== 'string' || fmObj.description.length === 0) {\n throw new Error('SKILL.md frontmatter missing required \"description\"')\n }\n return { frontmatter: fmObj as SkillFrontmatter, body, raw }\n}\n\nexport function parseSkillFile(path: string): ParsedSkill {\n const raw = readFileSync(path, 'utf8')\n return parseSkillContent(raw)\n}\n","import type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\n\nexport function deleteTeam(\n db: BazilionDb,\n paths: Paths,\n id: string,\n expectedTeamPolicyRevision: number,\n): void {\n const g = teamRepo.get(db, id, paths)\n if (!g) throw new Error(`team not found: ${id}`)\n const teamPolicy = teamPolicyRepo.get(db, id)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${id}`)\n if (teamPolicy.revision !== expectedTeamPolicyRevision) {\n throw new Error(\n `team_revision_conflict: expected ${expectedTeamPolicyRevision}, current ${teamPolicy.revision}`,\n )\n }\n\n // ON DELETE RESTRICT on agents.team_id enforces this at the SQL layer,\n // but we surface a friendlier error listing the blocking members.\n const members = agentRepo.list(db, { includeArchived: true }).filter((a) => a.teamId === id)\n if (members.length > 0) {\n const names = members.map((a) => `${a.name} (${a.id.slice(0, 8)})`).join(', ')\n throw new Error(\n `cannot delete team \"${id}\": ${members.length} agent(s) still belong to it: ${names}. Move or delete them first.`,\n )\n }\n const stagedPath = `${g.path}.deleting-${randomUUID()}`\n const hadSlot = existsSync(g.path)\n if (hadSlot) renameSync(g.path, stagedPath)\n try {\n db.raw.transaction(() => {\n const current = teamPolicyRepo.get(db, id)\n if (!current) throw new Error(`team_policy_missing: ${id}`)\n if (current.revision !== expectedTeamPolicyRevision) {\n throw new Error(\n `team_revision_conflict: expected ${expectedTeamPolicyRevision}, current ${current.revision}`,\n )\n }\n teamRepo.remove(db, id)\n })()\n } catch (error) {\n if (hadSlot && existsSync(stagedPath)) renameSync(stagedPath, g.path)\n throw error\n }\n if (hadSlot && existsSync(stagedPath)) {\n // For linked Teams this removes the renamed symlink only, never its target.\n rmSync(stagedPath, { recursive: true, force: true })\n }\n}\n\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, renameSync, rmSync } from 'node:fs'\n","import { randomUUID } from 'node:crypto'\nimport type { ResolvedTeamPolicy, TeamPolicyEdge, TeamPolicyPlacement } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamTemplateRepo from '../repos/teamTemplates.ts'\n\ntype RemainingPlacement = Exclude<TeamPolicyPlacement, 'template_snapshot'>\n\nexport interface AdoptTeamTemplateInput {\n teamExpectedRevision: number\n templateId: string\n templateExpectedRevision: number\n slotMappings: Array<{ slotId: string; agentId: string }>\n remainingPlacements: Array<{ agentId: string; placement: RemainingPlacement }>\n previewEdges: Omit<TeamPolicyEdge, 'teamId'>[]\n}\n\nexport function previewTeamPolicyAdoption(\n db: BazilionDb,\n teamId: string,\n input: Omit<AdoptTeamTemplateInput, 'previewEdges'>,\n): Omit<TeamPolicyEdge, 'teamId'>[] {\n return resolveAdoptionPlan(db, teamId, { ...input, previewEdges: [] }).resolved\n}\n\nfunction resolveAdoptionPlan(db: BazilionDb, teamId: string, input: AdoptTeamTemplateInput) {\n const teamPolicy = teamPolicyRepo.get(db, teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${teamId}`)\n if (teamPolicy.revision !== input.teamExpectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n const template = teamTemplateRepo.get(db, input.templateId)\n if (!template) throw new Error(`team template not found: ${input.templateId}`)\n if (template.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`)\n if (template.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${template.currentRevision}`,\n )\n }\n const snapshot = teamTemplateRepo.revision(db, template.id, input.templateExpectedRevision)\n if (!snapshot) throw new Error('template_snapshot_missing')\n const members = agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === teamId)\n const memberIds = new Set(members.map((agent) => agent.id))\n const mapping = new Map(input.slotMappings.map((item) => [item.slotId, item.agentId]))\n if (mapping.size !== input.slotMappings.length || mapping.size !== snapshot.slots.length) {\n throw new Error('adoption_mapping_invalid: every active source slot must be mapped once')\n }\n const mappedAgents = new Set(mapping.values())\n if (mappedAgents.size !== mapping.size) {\n throw new Error('adoption_mapping_invalid: mappings must be injective')\n }\n for (const slot of snapshot.slots) {\n const agentId = mapping.get(slot.slotId)\n if (!agentId || !memberIds.has(agentId)) {\n throw new Error(`adoption_mapping_invalid: slot ${slot.slotId} is not mapped to a member`)\n }\n }\n const remainingIds = new Set([...memberIds].filter((id) => !mappedAgents.has(id)))\n if (\n input.remainingPlacements.some(\n (item) => item.placement !== 'isolated' && item.placement !== 'profile_defaults',\n )\n ) {\n throw new Error('adoption_mapping_invalid: unknown placement')\n }\n const placements = new Map(\n input.remainingPlacements.map((item) => [item.agentId, item.placement]),\n )\n if (\n placements.size !== input.remainingPlacements.length ||\n placements.size !== remainingIds.size ||\n [...remainingIds].some((id) => !placements.has(id)) ||\n [...placements].some(([id]) => !remainingIds.has(id))\n ) {\n throw new Error('adoption_mapping_invalid: every remaining Agent needs one placement')\n }\n return {\n template,\n snapshot,\n mapping,\n resolved: resolveAdoptionEdges(db, snapshot.edges, mapping, placements, members),\n }\n}\n\nexport function adoptTeamTemplate(\n db: BazilionDb,\n teamId: string,\n input: AdoptTeamTemplateInput,\n): ResolvedTeamPolicy {\n return db.raw.transaction(() => {\n const { template, snapshot, mapping, resolved } = resolveAdoptionPlan(db, teamId, input)\n if (!sameEdges(resolved, input.previewEdges)) {\n throw new Error('adoption_preview_mismatch: reviewed preview does not match resolved policy')\n }\n\n db.raw.run('UPDATE team_policies SET baseline_instantiation_id = NULL WHERE team_id = ?', [\n teamId,\n ])\n db.raw.run(\n `DELETE FROM source_slot_bindings WHERE instantiation_id IN\n (SELECT id FROM template_instantiations WHERE team_id = ?)`,\n [teamId],\n )\n db.raw.run('DELETE FROM template_instantiations WHERE team_id = ?', [teamId])\n db.raw.run('DELETE FROM team_policy_edges WHERE team_id = ?', [teamId])\n const instantiationId = randomUUID()\n db.raw.run(\n `INSERT INTO template_instantiations\n (id, team_id, template_id, template_revision, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n [instantiationId, teamId, template.id, snapshot.revision, Date.now()],\n )\n for (const [slotId, agentId] of mapping) {\n db.raw.run(\n `INSERT INTO source_slot_bindings (agent_id, instantiation_id, source_slot_id)\n VALUES (?, ?, ?)`,\n [agentId, instantiationId, slotId],\n )\n }\n for (const edge of resolved) {\n db.raw.run(\n `INSERT INTO team_policy_edges\n (team_id, source_kind, source_id, target_kind, target_id, posture)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [\n teamId,\n edge.sourceKind,\n edge.sourceId ?? '',\n edge.targetKind,\n edge.targetId ?? '',\n edge.posture,\n ],\n )\n }\n db.raw.run('UPDATE team_policies SET baseline_instantiation_id = ? WHERE team_id = ?', [\n instantiationId,\n teamId,\n ])\n teamPolicyRepo.bumpRevision(db, teamId)\n const result = teamPolicyRepo.detail(db, teamId)\n if (!result) throw new Error(`team_policy_missing: ${teamId}`)\n return result\n })()\n}\n\nfunction resolveAdoptionEdges(\n db: BazilionDb,\n templateEdges: Array<{\n sourceKind: 'user' | 'outside_team' | 'slot'\n sourceId: string | null\n targetKind: 'user' | 'outside_team' | 'slot'\n targetId: string | null\n posture: 'allow' | 'approval_required'\n }>,\n mapping: ReadonlyMap<string, string>,\n placements: ReadonlyMap<string, RemainingPlacement>,\n members: ReturnType<typeof agentRepo.list>,\n): Omit<TeamPolicyEdge, 'teamId'>[] {\n const edgeMap = new Map<string, Omit<TeamPolicyEdge, 'teamId'>>()\n const add = (\n sourceKind: TeamPolicyEdge['sourceKind'],\n sourceId: string | null,\n targetKind: TeamPolicyEdge['targetKind'],\n targetId: string | null,\n posture: TeamPolicyEdge['posture'] = 'allow',\n ) => {\n const edge = { sourceKind, sourceId, targetKind, targetId, posture }\n edgeMap.set(edgeKey(edge), edge)\n }\n for (const edge of templateEdges) {\n add(\n edge.sourceKind === 'slot' ? 'agent' : edge.sourceKind,\n edge.sourceKind === 'slot' ? (mapping.get(edge.sourceId ?? '') ?? null) : null,\n edge.targetKind === 'slot' ? 'agent' : edge.targetKind,\n edge.targetKind === 'slot' ? (mapping.get(edge.targetId ?? '') ?? null) : null,\n edge.posture,\n )\n }\n const mappedIds = new Set(mapping.values())\n const memberById = new Map(members.map((agent) => [agent.id, agent]))\n const requestsPeers = new Set<string>()\n for (const [agentId, placement] of placements) {\n const agent = memberById.get(agentId)\n if (!agent) continue\n if (placement === 'profile_defaults') {\n const defaults = db.raw\n .query<\n {\n user_input: number\n user_output: number\n outside_team_input: number\n outside_team_output: number\n peer_default: string\n },\n [string]\n >('SELECT * FROM profile_communication_defaults WHERE profile_id = ?')\n .get(agent.profileId)\n if (defaults?.user_input) add('user', null, 'agent', agentId)\n if (defaults?.user_output) add('agent', agentId, 'user', null)\n if (defaults?.outside_team_input) add('outside_team', null, 'agent', agentId)\n if (defaults?.outside_team_output) add('agent', agentId, 'outside_team', null)\n if (defaults?.peer_default === 'allow_all') requestsPeers.add(agentId)\n }\n }\n for (const agentId of requestsPeers) {\n for (const mappedId of mappedIds) {\n add('agent', agentId, 'agent', mappedId)\n add('agent', mappedId, 'agent', agentId)\n }\n }\n const liveOnly = [...placements.keys()]\n for (let left = 0; left < liveOnly.length; left++) {\n for (let right = left + 1; right < liveOnly.length; right++) {\n const a = liveOnly[left]\n const b = liveOnly[right]\n if (a && b && requestsPeers.has(a) && requestsPeers.has(b)) {\n add('agent', a, 'agent', b)\n add('agent', b, 'agent', a)\n }\n }\n }\n return [...edgeMap.values()].sort((a, b) => edgeKey(a).localeCompare(edgeKey(b)))\n}\n\nfunction sameEdges(\n actual: Omit<TeamPolicyEdge, 'teamId'>[],\n preview: Omit<TeamPolicyEdge, 'teamId'>[],\n): boolean {\n const a = actual.map(edgeKey).sort()\n const b = preview.map(edgeKey).sort()\n return a.length === b.length && a.every((value, index) => value === b[index])\n}\n\nfunction edgeKey(edge: Omit<TeamPolicyEdge, 'teamId'>): string {\n return `${edge.sourceKind}:${edge.sourceId ?? ''}>${edge.targetKind}:${edge.targetId ?? ''}[${edge.posture ?? 'allow'}]`\n}\n","import { createHash, randomUUID } from 'node:crypto'\nimport type {\n CommunicationAuthorizationResult,\n CommunicationChannel,\n CommunicationComponentOutcome,\n CommunicationDecision,\n CommunicationEndpoint,\n CommunicationPolicyRef,\n} from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\n\nexport type { CommunicationChannel, CommunicationDecision, CommunicationEndpoint }\n\nexport interface AuthorizationInput {\n source: CommunicationEndpoint\n target: CommunicationEndpoint\n origin: string\n attemptKind: string\n attemptId: string\n // Boundary-only metadata ignored by policy evaluation.\n approvalPayloadKind?: string\n approvalPayload?: unknown\n requester?: string\n /** Optional source-owned deadline; can shorten, never extend, approval lifetime. */\n approvalExpiresAt?: number\n}\n\nexport type PolicyRef = CommunicationPolicyRef\nexport type ComponentOutcome = CommunicationComponentOutcome\nexport type AuthorizationResult = CommunicationAuthorizationResult\n\ninterface AgentRow {\n id: string\n team_id: string\n status: string\n}\ninterface TeamPolicyRow {\n team_id: string\n revision: number\n}\ninterface EdgeRow {\n source_kind: string\n source_id: string\n target_kind: string\n target_id: string\n posture: 'allow' | 'approval_required'\n}\n\nfunction denied(channel: CommunicationChannel, code: string, reason: string): AuthorizationResult {\n return {\n decision: 'deny',\n channel,\n reasonCode: code,\n reason,\n policyRefs: [],\n componentOutcomes: [],\n matchedEdgeIds: [],\n requiredEdgeIds: [],\n }\n}\n\nfunction edgeId(teamId: string, sk: string, sid: string, tk: string, tid: string): string {\n return `${teamId}:${sk}:${sid || '-'}->${tk}:${tid || '-'}`\n}\n\nexport function authorizeCommunication(\n db: BazilionDb,\n input: AuthorizationInput,\n): AuthorizationResult {\n return db.raw.transaction(() => authorizeInSnapshot(db, input))()\n}\n\nexport function authorizeInSnapshot(\n db: BazilionDb,\n input: AuthorizationInput,\n): AuthorizationResult {\n const channel: CommunicationChannel =\n input.source.kind === 'user' || input.target.kind === 'user'\n ? 'user'\n : input.source.kind === 'outside_team' || input.target.kind === 'outside_team'\n ? 'outside_team'\n : 'same_team'\n if (!input.origin || !input.attemptKind || !input.attemptId)\n return denied(\n channel,\n 'invalid_communication_path',\n 'origin and typed attempt identity are required',\n )\n if (input.source.kind !== 'agent' && input.target.kind !== 'agent')\n return denied(\n channel,\n 'invalid_communication_path',\n 'a communication path must include an Agent',\n )\n if (\n input.source.kind === 'agent' &&\n input.target.kind === 'agent' &&\n input.source.id === input.target.id\n )\n return denied(\n channel,\n 'invalid_communication_path',\n 'self communication is not a valid policy path',\n )\n\n const resolveAgent = (endpoint: CommunicationEndpoint): AgentRow | null =>\n endpoint.kind === 'agent'\n ? db.raw\n .query<AgentRow, [string]>('SELECT id, team_id, status FROM agents WHERE id = ?')\n .get(endpoint.id)\n : null\n const sourceAgent = resolveAgent(input.source)\n const targetAgent = resolveAgent(input.target)\n if (input.source.kind === 'agent' && !sourceAgent)\n return denied(channel, 'agent_not_found', 'source Agent was not found')\n if (input.target.kind === 'agent' && !targetAgent)\n return denied(channel, 'agent_not_found', 'target Agent was not found')\n if (sourceAgent?.status === 'archived' || targetAgent?.status === 'archived')\n return denied(channel, 'agent_archived', 'archived Agents cannot communicate')\n\n const sourceGroup =\n sourceAgent?.team_id ??\n targetAgent?.team_id ??\n ('teamId' in input.source ? input.source.teamId : '')\n const targetGroup =\n targetAgent?.team_id ??\n sourceAgent?.team_id ??\n ('teamId' in input.target ? input.target.teamId : '')\n const actualChannel: CommunicationChannel =\n sourceGroup && targetGroup && sourceGroup !== targetGroup ? 'cross_team' : channel\n if (!sourceGroup || !targetGroup)\n return denied(actualChannel, 'member_not_in_group', 'endpoint is not a Team member')\n\n const teams = [...new Set([sourceGroup, targetGroup])]\n const teamPolicyes = new Map<string, TeamPolicyRow>()\n for (const team of teams) {\n const teamPolicy = db.raw\n .query<TeamPolicyRow, [string]>(\n 'SELECT team_id, revision FROM team_policies WHERE team_id = ?',\n )\n .get(team)\n if (!teamPolicy)\n return denied(actualChannel, 'team_policy_missing', `Team policy is missing for ${team}`)\n if (!Number.isInteger(teamPolicy.revision) || teamPolicy.revision < 1)\n return denied(actualChannel, 'team_policy_invalid', `Team policy is invalid for ${team}`)\n teamPolicyes.set(team, teamPolicy)\n }\n\n const requirements: Array<{\n teamId: string\n sk: string\n sid: string\n tk: string\n tid: string\n failure: string\n }> = []\n if (\n input.source.kind === 'agent' &&\n input.target.kind === 'agent' &&\n sourceGroup !== targetGroup\n ) {\n requirements.push(\n {\n teamId: sourceGroup,\n sk: 'agent',\n sid: input.source.id,\n tk: 'outside_team',\n tid: '',\n failure: 'source_outside_output_denied',\n },\n {\n teamId: targetGroup,\n sk: 'outside_team',\n sid: '',\n tk: 'agent',\n tid: input.target.id,\n failure: 'target_outside_input_denied',\n },\n )\n } else {\n const teamId = sourceGroup\n const mapEndpoint = (endpoint: CommunicationEndpoint): [string, string] =>\n endpoint.kind === 'agent' ? ['agent', endpoint.id] : [endpoint.kind, '']\n const [sk, sid] = mapEndpoint(input.source)\n const [tk, tid] = mapEndpoint(input.target)\n requirements.push({ teamId, sk, sid, tk, tid, failure: 'no_allow_edge' })\n }\n\n const outcomes = requirements.map((requirement) => {\n const edge = db.raw\n .query<EdgeRow, [string, string, string, string, string]>(\n 'SELECT source_kind, source_id, target_kind, target_id, posture FROM team_policy_edges WHERE team_id = ? AND source_kind = ? AND source_id = ? AND target_kind = ? AND target_id = ?',\n )\n .get(requirement.teamId, requirement.sk, requirement.sid, requirement.tk, requirement.tid)\n return {\n teamId: requirement.teamId,\n edge: edgeId(\n requirement.teamId,\n requirement.sk,\n requirement.sid,\n requirement.tk,\n requirement.tid,\n ),\n matched: edge !== null,\n posture: edge?.posture ?? null,\n }\n })\n const policyRefs = teams.map((teamId) => ({\n teamId,\n revision: teamPolicyes.get(teamId)?.revision ?? 0,\n }))\n const requiredEdgeIds = outcomes.map((outcome) => outcome.edge)\n const matchedEdgeIds = outcomes\n .filter((outcome) => outcome.matched)\n .map((outcome) => outcome.edge)\n const failed = requirements.find((_, index) => !outcomes[index]?.matched)\n if (failed) {\n return {\n decision: 'deny',\n channel: actualChannel,\n reasonCode: failed.failure,\n reason: `required allow edge is absent: ${outcomes[requirements.indexOf(failed)]?.edge}`,\n policyRefs,\n componentOutcomes: outcomes,\n matchedEdgeIds,\n requiredEdgeIds,\n }\n }\n const approvalEdges = outcomes.filter((outcome) => outcome.posture === 'approval_required')\n return approvalEdges.length\n ? {\n decision: 'approval_required',\n channel: actualChannel,\n reasonCode: 'approval_required',\n reason: `approval is required by ${approvalEdges.length} policy edge${approvalEdges.length === 1 ? '' : 's'}`,\n policyRefs,\n componentOutcomes: outcomes,\n matchedEdgeIds,\n requiredEdgeIds,\n }\n : {\n decision: 'allow',\n channel: actualChannel,\n reasonCode: 'allowed',\n reason: 'all required policy edges allow communication',\n policyRefs,\n componentOutcomes: outcomes,\n matchedEdgeIds,\n requiredEdgeIds,\n }\n}\n\nexport function authorizationFingerprint(input: AuthorizationInput, operation: string): string {\n return createHash('sha256')\n .update(JSON.stringify({ operation, source: input.source, target: input.target }))\n .digest('hex')\n}\n\nexport function recordDenial(\n db: BazilionDb,\n input: AuthorizationInput,\n operation: string,\n result: AuthorizationResult,\n): AuthorizationResult {\n const fingerprint = authorizationFingerprint(input, operation)\n const loadExisting = () =>\n db.raw\n .query<\n {\n fingerprint: string\n reason_code: string\n reason: string\n channel: CommunicationChannel\n policy_refs_json: string\n component_outcomes_json: string\n matched_edge_ids_json: string\n required_edge_ids_json: string\n },\n [string, string]\n >(\n 'SELECT fingerprint, reason_code, reason, channel, policy_refs_json, component_outcomes_json, matched_edge_ids_json, required_edge_ids_json FROM team_policy_block_events WHERE attempt_kind = ? AND attempt_id = ?',\n )\n .get(input.attemptKind, input.attemptId)\n const existing = loadExisting()\n if (existing) {\n if (existing.fingerprint !== fingerprint)\n return denied(\n result.channel,\n 'attempt_key_conflict',\n 'typed attempt identity was already used for different semantics',\n )\n return {\n decision: 'deny',\n channel: existing.channel,\n reasonCode: existing.reason_code,\n reason: existing.reason,\n policyRefs: JSON.parse(existing.policy_refs_json),\n componentOutcomes: JSON.parse(existing.component_outcomes_json),\n matchedEdgeIds: JSON.parse(existing.matched_edge_ids_json),\n requiredEdgeIds: JSON.parse(existing.required_edge_ids_json),\n }\n }\n const agentGroup = (endpoint: CommunicationEndpoint) =>\n endpoint.kind === 'agent'\n ? (db.raw\n .query<{ team_id: string }, [string]>('SELECT team_id FROM agents WHERE id = ?')\n .get(endpoint.id)?.team_id ?? null)\n : null\n const sourceGroup =\n agentGroup(input.source) ??\n agentGroup(input.target) ??\n ('teamId' in input.source ? input.source.teamId : null)\n const targetGroup =\n agentGroup(input.target) ??\n agentGroup(input.source) ??\n ('teamId' in input.target ? input.target.teamId : null)\n const values = [\n randomUUID(),\n input.attemptKind,\n input.attemptId,\n operation,\n input.source.kind,\n input.source.kind === 'agent' ? input.source.id : '',\n input.target.kind,\n input.target.kind === 'agent' ? input.target.id : '',\n sourceGroup,\n targetGroup,\n result.channel,\n input.origin,\n result.reasonCode,\n result.reason,\n JSON.stringify(result.policyRefs),\n JSON.stringify(result.componentOutcomes),\n JSON.stringify(result.matchedEdgeIds),\n JSON.stringify(result.requiredEdgeIds),\n fingerprint,\n Date.now(),\n ]\n try {\n db.raw.run(\n `INSERT INTO team_policy_block_events (id, attempt_kind, attempt_id, operation, source_kind, source_id, target_kind, target_id, source_team_id, target_team_id, channel, origin, reason_code, reason, policy_refs_json, component_outcomes_json, matched_edge_ids_json, required_edge_ids_json, fingerprint, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n values,\n )\n } catch (error) {\n // Another connection may have won the typed-attempt unique race after our lookup.\n // Reloading applies the same immutable fingerprint contract; unrelated DB failures remain errors.\n const raced = loadExisting()\n if (!raced) throw error\n if (raced.fingerprint !== fingerprint)\n return denied(\n result.channel,\n 'attempt_key_conflict',\n 'typed attempt identity was already used for different semantics',\n )\n return {\n decision: 'deny',\n channel: raced.channel,\n reasonCode: raced.reason_code,\n reason: raced.reason,\n policyRefs: JSON.parse(raced.policy_refs_json),\n componentOutcomes: JSON.parse(raced.component_outcomes_json),\n matchedEdgeIds: JSON.parse(raced.matched_edge_ids_json),\n requiredEdgeIds: JSON.parse(raced.required_edge_ids_json),\n }\n }\n return result\n}\n","import { randomUUID } from 'node:crypto'\nimport type { TeamPolicyEdge, TeamTemplateDetail } from '@bazilion/api-types'\nimport type { BazilionDb } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\nimport * as teamTemplateRepo from '../repos/teamTemplates.ts'\n\nexport interface TeamPolicyDiff {\n teamId: string\n liveRevision: number\n baseline: {\n instantiationId: string\n templateId: string\n templateRevision: number\n snapshot: ReturnType<typeof teamTemplateRepo.revision>\n } | null\n currentSource: TeamTemplateDetail | null\n liveEdges: TeamPolicyEdge[]\n sourceDiverged: boolean\n comparison: {\n baselineEdges: TeamPolicyEdge[]\n addedSinceBaseline: TeamPolicyEdge[]\n removedSinceBaseline: TeamPolicyEdge[]\n currentSourceAddedSlotIds: string[]\n currentSourceRemovedSlotIds: string[]\n }\n}\n\nexport function diffTeamPolicy(db: BazilionDb, teamId: string): TeamPolicyDiff {\n const detail = teamPolicyRepo.detail(db, teamId)\n if (!detail) throw new Error(`team_policy_missing: ${teamId}`)\n const baseline = detail.baseline\n const source = baseline ? teamTemplateRepo.detail(db, baseline.templateId) : null\n const baselineSnapshot = baseline\n ? teamTemplateRepo.revision(db, baseline.templateId, baseline.templateRevision)\n : null\n const bindingBySlot = new Map(\n detail.bindings\n .filter((binding) => binding.instantiationId === baseline?.id)\n .map((binding) => [binding.sourceSlotId, binding.agentId]),\n )\n const baselineEdges: TeamPolicyEdge[] = (baselineSnapshot?.edges ?? []).flatMap((edge) => {\n const sourceId = edge.sourceKind === 'slot' ? bindingBySlot.get(edge.sourceId ?? '') : null\n const targetId = edge.targetKind === 'slot' ? bindingBySlot.get(edge.targetId ?? '') : null\n if ((edge.sourceKind === 'slot' && !sourceId) || (edge.targetKind === 'slot' && !targetId)) {\n return []\n }\n return [\n {\n teamId,\n sourceKind: edge.sourceKind === 'slot' ? 'agent' : edge.sourceKind,\n sourceId: sourceId ?? null,\n targetKind: edge.targetKind === 'slot' ? 'agent' : edge.targetKind,\n targetId: targetId ?? null,\n posture: edge.posture,\n },\n ]\n })\n const liveEdges = detail.edges\n const baselineKeys = new Set(baselineEdges.map(liveEdgeKey))\n const liveKeys = new Set(liveEdges.map(liveEdgeKey))\n const baselineSlotIds = new Set(baselineSnapshot?.slots.map((slot) => slot.slotId) ?? [])\n const currentSlotIds = new Set(source?.slots.map((slot) => slot.slotId) ?? [])\n return {\n teamId,\n liveRevision: detail.teamPolicy.revision,\n baseline: baseline\n ? {\n instantiationId: baseline.id,\n templateId: baseline.templateId,\n templateRevision: baseline.templateRevision,\n snapshot: baselineSnapshot,\n }\n : null,\n currentSource: source,\n liveEdges,\n sourceDiverged: !!baseline && source?.template.currentRevision !== baseline.templateRevision,\n comparison: {\n baselineEdges,\n addedSinceBaseline: liveEdges.filter((edge) => !baselineKeys.has(liveEdgeKey(edge))),\n removedSinceBaseline: baselineEdges.filter((edge) => !liveKeys.has(liveEdgeKey(edge))),\n currentSourceAddedSlotIds: [...currentSlotIds].filter((id) => !baselineSlotIds.has(id)),\n currentSourceRemovedSlotIds: [...baselineSlotIds].filter((id) => !currentSlotIds.has(id)),\n },\n }\n}\n\nexport function saveTeamPolicyAsTemplate(\n db: BazilionDb,\n paths: Paths,\n teamId: string,\n input: { expectedRevision: number; id: string; name: string; userMd?: string | null },\n): TeamTemplateDetail {\n return db.raw.transaction(() => {\n const teamPolicy = teamPolicyRepo.get(db, teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${teamId}`)\n if (teamPolicy.revision !== input.expectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.expectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n if (teamTemplateRepo.get(db, input.id)) {\n throw new Error(`team template already exists: ${input.id}`)\n }\n const team = teamRepo.get(db, teamId, paths)\n if (!team) throw new Error(`team not found: ${teamId}`)\n const members = agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === teamId)\n const slotByAgent = new Map(members.map((agent) => [agent.id, randomUUID()]))\n const stateByAgent = new Map(\n teamPolicyRepo.agentState(db, teamId).map((state) => [state.agentId, state]),\n )\n return teamTemplateRepo.insertCanonicalDefinition(db, {\n id: input.id,\n name: input.name,\n userMd: Object.hasOwn(input, 'userMd') ? input.userMd : team.userMd,\n slots: members.map((agent) => ({\n slotId: slotByAgent.get(agent.id),\n profileId: agent.profileId,\n agentName: agent.name,\n modelOverride: agent.modelOverride,\n reasoningLevel: agent.reasoningLevel,\n layoutPosition: stateByAgent.get(agent.id)?.position ?? null,\n display: stateByAgent.get(agent.id)?.display ?? null,\n })),\n edges: teamPolicyRepo.edges(db, teamId).map((edge) => ({\n sourceKind: edge.sourceKind === 'agent' ? 'slot' : edge.sourceKind,\n sourceId: edge.sourceKind === 'agent' ? slotByAgent.get(edge.sourceId ?? '') : null,\n targetKind: edge.targetKind === 'agent' ? 'slot' : edge.targetKind,\n targetId: edge.targetKind === 'agent' ? slotByAgent.get(edge.targetId ?? '') : null,\n posture: edge.posture,\n })),\n })\n })()\n}\n\nfunction liveEdgeKey(edge: TeamPolicyEdge): string {\n return `${edge.sourceKind}:${edge.sourceId ?? ''}>${edge.targetKind}:${edge.targetId ?? ''}[${edge.posture}]`\n}\n\nexport function updateTeamPolicySource(\n db: BazilionDb,\n teamId: string,\n input: {\n teamExpectedRevision: number\n templateExpectedRevision: number\n includeAgentIds: string[]\n },\n): {\n template: TeamTemplateDetail\n team: NonNullable<ReturnType<typeof teamPolicyRepo.detail>>\n} {\n return db.raw.transaction(() => {\n const teamPolicy = teamPolicyRepo.get(db, teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${teamId}`)\n if (teamPolicy.revision !== input.teamExpectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n if (!teamPolicy.baselineInstantiationId) throw new Error('baseline_missing: Team has no source')\n const baseline = teamPolicyRepo\n .instantiations(db, teamId)\n .find((item) => item.id === teamPolicy.baselineInstantiationId)\n if (!baseline) throw new Error('baseline_missing: retained baseline does not exist')\n const source = teamTemplateRepo.get(db, baseline.templateId)\n if (!source) throw new Error(`team template not found: ${baseline.templateId}`)\n if (source.deletedAt !== null) throw new Error(`template_deleted: ${source.id}`)\n if (source.currentRevision !== baseline.templateRevision) {\n throw new Error(\n `source_diverged: retained ${baseline.templateRevision}, current ${source.currentRevision}`,\n )\n }\n if (source.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${source.currentRevision}`,\n )\n }\n const include = new Set(input.includeAgentIds)\n if (include.size !== input.includeAgentIds.length) {\n throw new Error('source_update_invalid: duplicate included Agent')\n }\n const members = agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === teamId)\n const memberById = new Map(members.map((agent) => [agent.id, agent]))\n for (const id of include) {\n if (!memberById.has(id)) throw new Error(`member_not_in_group: ${id}`)\n }\n const bindings = teamPolicyRepo.bindings(db, teamId)\n const previous = teamTemplateRepo.revision(db, source.id, source.currentRevision)\n if (!previous) throw new Error('template_snapshot_missing')\n const slotPosition = new Map(previous.slots.map((slot) => [slot.slotId, slot.position]))\n const baselineBindings = bindings\n .filter((binding) => binding.instantiationId === baseline.id)\n .sort(\n (a, b) =>\n (slotPosition.get(a.sourceSlotId) ?? Number.MAX_SAFE_INTEGER) -\n (slotPosition.get(b.sourceSlotId) ?? Number.MAX_SAFE_INTEGER),\n )\n const selectedIds = new Set([...baselineBindings.map((binding) => binding.agentId), ...include])\n const slotByAgent = new Map(\n baselineBindings.map((binding) => [binding.agentId, binding.sourceSlotId]),\n )\n for (const id of selectedIds) {\n if (!slotByAgent.has(id)) slotByAgent.set(id, randomUUID())\n }\n const previousSlotById = new Map(previous.slots.map((slot) => [slot.slotId, slot]))\n const selectedMembers = [...selectedIds].map((id) => {\n const agent = memberById.get(id)\n if (!agent) throw new Error(`member_not_in_group: ${id}`)\n return agent\n })\n const template = teamTemplateRepo.replaceCanonicalDefinition(db, source.id, {\n expectedRevision: input.templateExpectedRevision,\n allowAllocatedSlotIds: true,\n slots: selectedMembers.map((agent) => {\n const slotId = slotByAgent.get(agent.id)\n const old = slotId ? previousSlotById.get(slotId) : undefined\n return {\n slotId,\n profileId: agent.profileId,\n agentName: agent.name,\n modelOverride: agent.modelOverride,\n reasoningLevel: agent.reasoningLevel,\n layoutPosition: old?.layoutPosition ?? null,\n display: old?.display ?? null,\n }\n }),\n edges: teamPolicyRepo\n .edges(db, teamId)\n .filter(\n (edge) =>\n (edge.sourceKind !== 'agent' || selectedIds.has(edge.sourceId ?? '')) &&\n (edge.targetKind !== 'agent' || selectedIds.has(edge.targetId ?? '')),\n )\n .map((edge) => ({\n sourceKind: edge.sourceKind === 'agent' ? 'slot' : edge.sourceKind,\n sourceId: edge.sourceKind === 'agent' ? slotByAgent.get(edge.sourceId ?? '') : null,\n targetKind: edge.targetKind === 'agent' ? 'slot' : edge.targetKind,\n targetId: edge.targetKind === 'agent' ? slotByAgent.get(edge.targetId ?? '') : null,\n })),\n })\n db.raw.run('UPDATE template_instantiations SET template_revision = ? WHERE id = ?', [\n template.template.currentRevision,\n baseline.id,\n ])\n for (const id of include) {\n const oldBinding = bindings.find((binding) => binding.agentId === id)\n if (oldBinding?.instantiationId === baseline.id) continue\n if (oldBinding) {\n db.raw.run('DELETE FROM source_slot_bindings WHERE agent_id = ?', [id])\n }\n db.raw.run(\n `INSERT INTO source_slot_bindings (agent_id, instantiation_id, source_slot_id)\n VALUES (?, ?, ?)`,\n [id, baseline.id, slotByAgent.get(id)],\n )\n if (oldBinding)\n pruneEmptyNonbaselineInstantiation(db, oldBinding.instantiationId, baseline.id)\n }\n teamPolicyRepo.bumpRevision(db, teamId)\n const team = teamPolicyRepo.detail(db, teamId)\n if (!team) throw new Error(`team_policy_missing: ${teamId}`)\n return { template, team }\n })()\n}\n\nfunction pruneEmptyNonbaselineInstantiation(\n db: BazilionDb,\n instantiationId: string,\n baselineId: string,\n): void {\n if (instantiationId === baselineId) return\n const remaining =\n db.raw\n .query<{ count: number }, [string]>(\n 'SELECT COUNT(*) AS count FROM source_slot_bindings WHERE instantiation_id = ?',\n )\n .get(instantiationId)?.count ?? 0\n if (remaining === 0) {\n db.raw.run('DELETE FROM template_instantiations WHERE id = ?', [instantiationId])\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport { readdirSync, rmSync } from 'node:fs'\nimport type { Agent, ResolvedTeamPolicy } from '@bazilion/api-types'\nimport { spawnAgent } from '../agent/spawn.ts'\nimport type { BazilionDb } from '../db/client.ts'\nimport { inTx } from '../db/client.ts'\nimport type { Paths } from '../paths.ts'\nimport * as agentRepo from '../repos/agents.ts'\nimport * as profileRepo from '../repos/profiles.ts'\nimport * as teamPolicyRepo from '../repos/teamPolicies.ts'\nimport * as teamRepo from '../repos/teams.ts'\nimport * as teamTemplateRepo from '../repos/teamTemplates.ts'\nimport { registerTeam } from '../team/register.ts'\nimport { rmWithRetry } from '../team-policy/rm-with-retry.ts'\n\nexport interface SpawnTeamTemplateInput {\n templateId: string\n templateExpectedRevision: number\n teamId: string\n teamExpectedRevision?: number\n mode: 'initialize' | 'append'\n userMd?: string\n}\n\nexport interface SpawnTeamTemplateResult {\n agents: Agent[]\n team: ResolvedTeamPolicy\n}\n\nexport interface SpawnTeamTemplatePreview {\n mode: 'initialize' | 'append'\n teamId: string\n currentRevision: number | null\n resultingRevision: number\n newMembers: Array<{ slotId: string; agentName: string; profileId: string }>\n edges: Array<{\n sourceKind: 'user' | 'outside_team' | 'agent'\n sourceId: string | null\n targetKind: 'user' | 'outside_team' | 'agent'\n targetId: string | null\n posture: 'allow' | 'approval_required'\n }>\n}\n\nexport function previewTeamTemplateSpawn(\n db: BazilionDb,\n paths: Paths,\n input: SpawnTeamTemplateInput,\n): SpawnTeamTemplatePreview {\n const source = teamTemplateRepo.get(db, input.templateId)\n if (!source) throw new Error(`team template not found: ${input.templateId}`)\n if (source.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`)\n if (source.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${source.currentRevision}`,\n )\n }\n const snapshot = teamTemplateRepo.revision(db, source.id, input.templateExpectedRevision)\n if (!snapshot)\n throw new Error(`template_snapshot_missing: ${source.id}@${source.currentRevision}`)\n const existingGroup = teamRepo.get(db, input.teamId, paths)\n const teamPolicy = existingGroup ? teamPolicyRepo.get(db, input.teamId) : null\n if (existingGroup && !input.teamExpectedRevision) {\n throw new Error('team_revision_required: teamExpectedRevision is required')\n }\n if (teamPolicy && teamPolicy.revision !== input.teamExpectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n const members = existingGroup\n ? agentRepo.list(db, { includeArchived: true }).filter((agent) => agent.teamId === input.teamId)\n : []\n if (!existingGroup && input.mode !== 'initialize') {\n throw new Error('initialize_required: a new Team must establish a baseline')\n }\n if (\n existingGroup &&\n input.mode === 'append' &&\n members.length === 0 &&\n !teamPolicy?.baselineInstantiationId\n ) {\n throw new Error('initialize_required: empty uninitialized Team must establish a baseline')\n }\n if (input.mode === 'initialize') {\n if (members.length > 0) throw new Error('team_not_empty: initialize requires an empty Team')\n if (teamPolicy?.baselineInstantiationId) {\n throw new Error('baseline_replacement_required: empty Team retains a baseline')\n }\n }\n const slotEndpoint = (slotId: string | null) => (slotId ? `new:${slotId}` : null)\n const newEdges = snapshot.edges.map((edge) => ({\n sourceKind: edge.sourceKind === 'slot' ? ('agent' as const) : edge.sourceKind,\n sourceId: edge.sourceKind === 'slot' ? slotEndpoint(edge.sourceId) : null,\n targetKind: edge.targetKind === 'slot' ? ('agent' as const) : edge.targetKind,\n targetId: edge.targetKind === 'slot' ? slotEndpoint(edge.targetId) : null,\n posture: edge.posture,\n }))\n return {\n mode: input.mode,\n teamId: input.teamId,\n currentRevision: teamPolicy?.revision ?? null,\n resultingRevision: teamPolicy ? teamPolicy.revision + 1 : 1,\n newMembers: snapshot.slots.map((slot) => ({\n slotId: slot.slotId,\n agentName: slot.agentName,\n profileId: slot.profileId,\n })),\n edges: [...(existingGroup ? teamPolicyRepo.edges(db, input.teamId) : []), ...newEdges],\n }\n}\n\nexport class SpawnTeamTemplateError extends Error {\n override name = 'SpawnTeamTemplateError'\n readonly orphanAgentIds: string[]\n override readonly cause: unknown\n constructor(message: string, orphanAgentIds: string[], cause: unknown) {\n super(message)\n this.orphanAgentIds = orphanAgentIds\n this.cause = cause\n }\n}\n\nexport async function spawnTeamTemplate(\n db: BazilionDb,\n paths: Paths,\n input: SpawnTeamTemplateInput,\n): Promise<SpawnTeamTemplateResult> {\n const source = teamTemplateRepo.get(db, input.templateId)\n if (!source) throw new Error(`team template not found: ${input.templateId}`)\n if (source.deletedAt !== null) throw new Error(`template_deleted: ${input.templateId}`)\n if (source.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${source.currentRevision}`,\n )\n }\n const snapshot = teamTemplateRepo.revision(db, source.id, input.templateExpectedRevision)\n if (!snapshot)\n throw new Error(`template_snapshot_missing: ${source.id}@${source.currentRevision}`)\n for (const slot of snapshot.slots) {\n if (!profileRepo.get(db, slot.profileId)) {\n throw new Error(`profile not found: ${slot.profileId}`)\n }\n }\n const existingGroup = teamRepo.get(db, input.teamId, paths)\n if (existingGroup && !input.teamExpectedRevision) {\n throw new Error('team_revision_required: teamExpectedRevision is required')\n }\n const beforeAgentDirs = new Set(safeReaddir(paths.agentsDir))\n const beforeGroupDirs = new Set(safeReaddir(paths.teamsDir))\n const created: Agent[] = []\n try {\n inTx(db, () => {\n const transactionalSource = teamTemplateRepo.get(db, input.templateId)\n if (!transactionalSource || transactionalSource.deletedAt !== null) {\n throw new Error(`template_deleted: ${input.templateId}`)\n }\n if (transactionalSource.currentRevision !== input.templateExpectedRevision) {\n throw new Error(\n `template_revision_conflict: expected ${input.templateExpectedRevision}, current ${transactionalSource.currentRevision}`,\n )\n }\n const teamCreated = !existingGroup\n if (teamCreated) registerTeam(db, { id: input.teamId, name: input.teamId }, paths)\n const teamPolicy = teamPolicyRepo.get(db, input.teamId)\n if (!teamPolicy) throw new Error(`team_policy_missing: ${input.teamId}`)\n if (!teamCreated && teamPolicy.revision !== input.teamExpectedRevision) {\n throw new Error(\n `team_revision_conflict: expected ${input.teamExpectedRevision}, current ${teamPolicy.revision}`,\n )\n }\n const memberCount = agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === input.teamId).length\n if (teamCreated && input.mode !== 'initialize') {\n throw new Error('initialize_required: a new Team must establish a baseline')\n }\n if (\n !teamCreated &&\n input.mode === 'append' &&\n memberCount === 0 &&\n teamPolicy.baselineInstantiationId === null\n ) {\n throw new Error('initialize_required: empty uninitialized Team must establish a baseline')\n }\n if (input.mode === 'initialize') {\n if (memberCount > 0) throw new Error('team_not_empty: initialize requires an empty Team')\n if (teamPolicy.baselineInstantiationId !== null) {\n throw new Error('baseline_replacement_required: empty Team retains a baseline')\n }\n }\n if (teamCreated && (input.userMd ?? snapshot.userMd)) {\n teamRepo.setUserMd(db, input.teamId, input.userMd ?? snapshot.userMd ?? '')\n }\n const existingNames = new Set(\n agentRepo\n .list(db, { includeArchived: true })\n .filter((agent) => agent.teamId === input.teamId)\n .map((agent) => agent.name),\n )\n for (const slot of snapshot.slots) {\n const name = uniqueName(slot.agentName, existingNames)\n existingNames.add(name)\n created.push(\n spawnAgent(db, paths, {\n profileId: slot.profileId,\n name,\n modelOverride: slot.modelOverride,\n reasoningLevel: slot.reasoningLevel ?? 'medium',\n teamId: input.teamId,\n deferTeamPolicyUpdate: true,\n }),\n )\n }\n const instantiationId = randomUUID()\n db.raw.run(\n `INSERT INTO template_instantiations\n (id, team_id, template_id, template_revision, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n [instantiationId, input.teamId, source.id, snapshot.revision, Date.now()],\n )\n const agentBySlot = new Map<string, Agent>()\n for (let index = 0; index < snapshot.slots.length; index++) {\n const slot = snapshot.slots[index]\n const agent = created[index]\n if (!slot || !agent) throw new Error('template_snapshot_invalid: slot mapping missing')\n agentBySlot.set(slot.slotId, agent)\n db.raw.run(\n `INSERT INTO source_slot_bindings (agent_id, instantiation_id, source_slot_id)\n VALUES (?, ?, ?)`,\n [agent.id, instantiationId, slot.slotId],\n )\n teamPolicyRepo.insertAgentState(db, input.teamId, agent.id)\n }\n for (const edge of snapshot.edges) {\n const sourceId =\n edge.sourceKind === 'slot' ? agentBySlot.get(edge.sourceId ?? '')?.id : null\n const targetId =\n edge.targetKind === 'slot' ? agentBySlot.get(edge.targetId ?? '')?.id : null\n if (\n (edge.sourceKind === 'slot' && !sourceId) ||\n (edge.targetKind === 'slot' && !targetId)\n ) {\n throw new Error('template_snapshot_invalid: edge slot mapping missing')\n }\n db.raw.run(\n `INSERT INTO team_policy_edges\n (team_id, source_kind, source_id, target_kind, target_id, posture)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [\n input.teamId,\n edge.sourceKind === 'slot' ? 'agent' : edge.sourceKind,\n sourceId ?? '',\n edge.targetKind === 'slot' ? 'agent' : edge.targetKind,\n targetId ?? '',\n edge.posture,\n ],\n )\n }\n if (input.mode === 'initialize') {\n db.raw.run('UPDATE team_policies SET baseline_instantiation_id = ? WHERE team_id = ?', [\n instantiationId,\n input.teamId,\n ])\n }\n if (teamCreated) {\n db.raw.run('UPDATE team_policies SET updated_at = ? WHERE team_id = ?', [\n Date.now(),\n input.teamId,\n ])\n } else {\n teamPolicyRepo.bumpRevision(db, input.teamId)\n }\n })\n const team = teamPolicyRepo.detail(db, input.teamId)\n if (!team) throw new Error(`team_policy_missing: ${input.teamId}`)\n return { agents: created, team }\n } catch (error) {\n const orphans: string[] = []\n for (const dir of safeReaddir(paths.agentsDir).filter((name) => !beforeAgentDirs.has(name))) {\n if (!(await rmWithRetry(paths.agentDir(dir)))) orphans.push(dir)\n }\n for (const slug of safeReaddir(paths.teamsDir).filter((name) => !beforeGroupDirs.has(name))) {\n try {\n rmSync(paths.teamDir(slug), { recursive: true, force: true })\n } catch {}\n }\n throw new SpawnTeamTemplateError((error as Error).message, orphans, error)\n }\n}\n\nfunction uniqueName(base: string, existing: ReadonlySet<string>): string {\n if (!existing.has(base)) return base\n let suffix = 2\n while (existing.has(`${base}-${suffix}`)) suffix++\n return `${base}-${suffix}`\n}\n\nfunction safeReaddir(path: string): string[] {\n try {\n return readdirSync(path)\n } catch {\n return []\n }\n}\n","import { rmSync } from 'node:fs'\n\nexport const DEFAULT_RM_RETRY_DELAYS_MS = [100, 500, 2000] as const\n\nexport interface RmRetryOptions {\n /** Injectable rm fn for tests. Defaults to `rmSync(target, { recursive, force })`. */\n rm?: (target: string) => void\n /** Backoff between attempts. Defaults to [100, 500, 2000]. */\n delays?: readonly number[]\n /** Injectable sleep for tests. Defaults to `setTimeout`-backed promise. */\n sleep?: (ms: number) => Promise<void>\n}\n\n/**\n * Retry filesystem removal with backoff. Returns true if the target is\n * gone after some attempt, false if every attempt failed. Used during\n * profile-team spawn rollback to clean up agent dirs created by\n * successful `spawnAgent` calls before the failing slot.\n *\n * The default rm is `rmSync(target, { recursive: true, force: true })` —\n * `force: true` swallows ENOENT, so a missing target counts as success.\n */\nexport async function rmWithRetry(target: string, opts: RmRetryOptions = {}): Promise<boolean> {\n const rm = opts.rm ?? ((p: string) => rmSync(p, { recursive: true, force: true }))\n const delays = opts.delays ?? DEFAULT_RM_RETRY_DELAYS_MS\n const sleep = opts.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)))\n for (let attempt = 0; attempt <= delays.length; attempt++) {\n try {\n rm(target)\n return true\n } catch {\n if (attempt === delays.length) return false\n const delay = delays[attempt] ?? 0\n await sleep(delay)\n }\n }\n return false\n}\n","import type { Stats } from 'node:fs'\nimport {\n closeSync,\n constants,\n fstatSync,\n lstatSync,\n mkdirSync,\n openSync,\n readdirSync,\n readFileSync,\n realpathSync,\n rmSync,\n writeFileSync,\n} from 'node:fs'\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport type { MemoryEntry, MemoryHit } from '@bazilion/api-types'\nimport { createStore, extractSnippet, type QMDStore } from '@tobilu/qmd'\nimport { withNativeModuleErrorBoundary } from '../../lib/native-module-error.ts'\nimport type { MemoryBackend } from './types.ts'\n\nconst INDEX_FILENAME = '.qmd-index.sqlite'\nconst COLLECTION_NAME = 'memory'\nconst PATTERN = '**/*.md'\nconst QMD_NATIVE_ERROR_OPTIONS = { subject: 'Bazilion memory' } as const\n\nexport interface QmdBackendOptions {\n /** Test seam for exercising qmd initialization failures without replacing a native binary. */\n createStore?: typeof createStore\n}\n\n// One store per memory directory per process. createStore opens a SQLite\n// handle; we dedupe so concurrent chat requests for the same agent reuse it.\nconst storeCache = new Map<string, Promise<QMDStore>>()\n\nfunction getStore(dir: string, storeFactory: typeof createStore): Promise<QMDStore> {\n let p = storeCache.get(dir)\n if (!p) {\n p = withNativeModuleErrorBoundary(\n () =>\n storeFactory({\n dbPath: join(dir, INDEX_FILENAME),\n config: {\n collections: {\n [COLLECTION_NAME]: { path: dir, pattern: PATTERN },\n },\n },\n }),\n QMD_NATIVE_ERROR_OPTIONS,\n ).catch((error) => {\n // A failed initialization must be retryable after the operator rebuilds\n // native dependencies without restarting the daemon.\n storeCache.delete(dir)\n throw error\n })\n storeCache.set(dir, p)\n }\n return p\n}\n\nfunction unsafeMemoryPath(path: string, reason: string): Error {\n return new Error(`unsafe memory path \"${path}\": ${reason}`)\n}\n\nfunction lstatIfPresent(path: string): Stats | undefined {\n try {\n return lstatSync(path)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined\n throw error\n }\n}\n\nfunction isContained(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (!isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`))\n}\n\nfunction assertContained(root: string, candidate: string): void {\n if (!isContained(root, candidate)) {\n throw unsafeMemoryPath(candidate, `resolved outside memory root \"${root}\"`)\n }\n}\n\n/**\n * Create and canonicalize the memory root without accepting a final symlink.\n * A Team itself may intentionally be registered through a symlink, so only\n * the memory root and descendants are treated as the capability boundary.\n */\nfunction initializeSafeRoot(root: string): string {\n mkdirSync(root, { recursive: true })\n const info = lstatSync(root)\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(root, 'memory root must not be a symbolic link')\n }\n if (!info.isDirectory()) {\n throw unsafeMemoryPath(root, 'memory root is not a directory')\n }\n return realpathSync(root)\n}\n\nfunction assertRootUnchanged(root: string, canonicalRoot: string): void {\n const info = lstatIfPresent(root)\n if (!info) throw unsafeMemoryPath(root, 'memory root disappeared')\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(root, 'memory root must not be a symbolic link')\n }\n if (!info.isDirectory()) {\n throw unsafeMemoryPath(root, 'memory root is not a directory')\n }\n const current = realpathSync(root)\n if (relative(canonicalRoot, current) !== '' || relative(current, canonicalRoot) !== '') {\n throw unsafeMemoryPath(root, 'memory root changed after initialization')\n }\n}\n\nfunction safeKey(root: string, key: string): string {\n const segments = key.split(/[\\\\/]/)\n if (\n key.length === 0 ||\n key.includes('\\0') ||\n isAbsolute(key) ||\n /^[A-Za-z]:[\\\\/]/.test(key) ||\n key.startsWith('\\\\\\\\') ||\n segments.includes('..')\n ) {\n throw new Error(`unsafe memory key: ${key}`)\n }\n\n const candidate = resolve(root, key)\n if (candidate === root) throw new Error(`unsafe memory key: ${key}`)\n assertContained(root, candidate)\n return candidate\n}\n\n/**\n * Inspect every existing component of a key without following symlinks.\n * Returns the final lstat, or undefined when the final path does not exist.\n */\nfunction inspectSafePath(root: string, path: string): Stats | undefined {\n assertContained(root, path)\n const rel = relative(root, path)\n let current = root\n const components = rel.split(sep).filter(Boolean)\n\n for (let index = 0; index < components.length; index += 1) {\n current = join(current, components[index] as string)\n const info = lstatIfPresent(current)\n if (!info) return undefined\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(current, 'symbolic links are not allowed')\n }\n if (index < components.length - 1 && !info.isDirectory()) {\n throw unsafeMemoryPath(current, 'path ancestor is not a directory')\n }\n assertContained(root, realpathSync(current))\n }\n\n return lstatIfPresent(path)\n}\n\nfunction ensureSafeParentDirectories(root: string, path: string): void {\n const parent = dirname(path)\n assertContained(root, parent)\n const rel = relative(root, parent)\n let current = root\n\n for (const component of rel.split(sep).filter(Boolean)) {\n current = join(current, component)\n let info = lstatIfPresent(current)\n if (!info) {\n mkdirSync(current)\n info = lstatSync(current)\n }\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(current, 'symbolic-link ancestors are not allowed')\n }\n if (!info.isDirectory()) {\n throw unsafeMemoryPath(current, 'path ancestor is not a directory')\n }\n assertContained(root, realpathSync(current))\n }\n}\n\nfunction readSafeFile(root: string, path: string): { content: string; stats: Stats } {\n const info = inspectSafePath(root, path)\n if (!info) throw new Error(`memory entry not found: ${relative(root, path)}`)\n if (!info.isFile()) throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n\n let fd: number | undefined\n try {\n fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW)\n const stats = fstatSync(fd)\n if (!stats.isFile()) throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n return { content: readFileSync(fd, 'utf8'), stats }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ELOOP') {\n throw unsafeMemoryPath(path, 'symbolic links are not allowed')\n }\n throw error\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n}\n\nfunction writeSafeFile(root: string, path: string, content: string): Stats {\n ensureSafeParentDirectories(root, path)\n const existing = inspectSafePath(root, path)\n if (existing?.isSymbolicLink()) {\n throw unsafeMemoryPath(path, 'symbolic links are not allowed')\n }\n if (existing && !existing.isFile()) {\n throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n }\n\n let fd: number | undefined\n try {\n fd = openSync(\n path,\n constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,\n 0o666,\n )\n const stats = fstatSync(fd)\n if (!stats.isFile()) throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n writeFileSync(fd, content)\n return fstatSync(fd)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ELOOP') {\n throw unsafeMemoryPath(path, 'symbolic links are not allowed')\n }\n throw error\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n}\n\nfunction assertSafeTree(root: string, dir = root): void {\n assertContained(root, realpathSync(dir))\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name)\n const info = lstatSync(full)\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(full, 'symbolic links are not allowed in memory storage')\n }\n if (info.isDirectory()) {\n assertSafeTree(root, full)\n } else if (!info.isFile()) {\n throw unsafeMemoryPath(full, 'special files are not allowed in memory storage')\n }\n }\n}\n\nfunction walkMd(root: string, dir: string, prefix: string, out: MemoryEntry[]): void {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name)\n const info = lstatSync(full)\n if (info.isSymbolicLink()) {\n throw unsafeMemoryPath(full, 'symbolic links are not allowed in memory storage')\n }\n if (entry.name.startsWith('.')) continue // skip .qmd-index.sqlite and friends\n\n const key = prefix ? `${prefix}/${entry.name}` : entry.name\n if (info.isDirectory()) {\n walkMd(root, full, key, out)\n } else if (info.isFile() && entry.name.endsWith('.md')) {\n const { content, stats } = readSafeFile(root, full)\n out.push({ key, content, updatedAt: stats.mtimeMs })\n } else if (!info.isFile()) {\n throw unsafeMemoryPath(full, 'special files are not allowed in memory storage')\n }\n }\n}\n\n/**\n * Memory backend backed by @tobilu/qmd — BM25 keyword search over markdown\n * files under `root`. Writes markdown to disk, then asks qmd to reindex.\n *\n * Uses `searchLex` only; no embeddings, no LLM rerank, no model download.\n * The hybrid `search()` / `searchVector()` paths exist in the qmd SDK and\n * can be wired in later if we want semantic search — they'd add a dependency\n * on `node-llama-cpp` and several GB of GGUF models.\n */\nexport function qmdBackend(root: string, options: QmdBackendOptions = {}): MemoryBackend {\n let canonicalRoot: string | undefined\n const storeFactory = options.createStore ?? createStore\n\n const safeRoot = (): string => {\n if (!canonicalRoot) canonicalRoot = initializeSafeRoot(root)\n else assertRootUnchanged(root, canonicalRoot)\n return canonicalRoot\n }\n\n return {\n async init() {\n const safe = safeRoot()\n assertSafeTree(safe)\n // Opening the store + initial scan. update() is idempotent.\n const store = await getStore(safe, storeFactory)\n await withNativeModuleErrorBoundary(() => store.update(), QMD_NATIVE_ERROR_OPTIONS)\n },\n\n async read(key) {\n const safe = safeRoot()\n const path = safeKey(safe, key)\n const { content, stats } = readSafeFile(safe, path)\n return {\n key,\n content,\n updatedAt: stats.mtimeMs,\n }\n },\n\n async write(key, content) {\n const safe = safeRoot()\n assertSafeTree(safe)\n const path = safeKey(safe, key)\n const stats = writeSafeFile(safe, path, content)\n // Reindex so the newly-written file is searchable on the next search().\n // update() re-scans the collection; for typical memory sizes (tens of\n // files) this is sub-millisecond.\n assertSafeTree(safe)\n const store = await getStore(safe, storeFactory)\n await withNativeModuleErrorBoundary(() => store.update(), QMD_NATIVE_ERROR_OPTIONS)\n return { key, content, updatedAt: stats.mtimeMs }\n },\n\n async search(query, opts) {\n const limit = opts?.limit ?? 10\n const safe = safeRoot()\n assertSafeTree(safe)\n const store = await getStore(safe, storeFactory)\n const results = await withNativeModuleErrorBoundary(\n () =>\n store.searchLex(query, {\n limit,\n collection: COLLECTION_NAME,\n }),\n QMD_NATIVE_ERROR_OPTIONS,\n )\n const hits: MemoryHit[] = []\n for (const r of results) {\n // qmd's filepath is a synthetic URI (`qmd://<collection>/<path>`);\n // displayPath is collection-prefixed (`<collection>/<path>`). Strip\n // the leading `<collection>/` to get the key the caller wrote.\n const prefix = `${COLLECTION_NAME}/`\n const key = r.displayPath.startsWith(prefix)\n ? r.displayPath.slice(prefix.length)\n : r.displayPath\n const path = safeKey(safe, key)\n const info = inspectSafePath(safe, path)\n if (!info) continue\n if (!info.isFile()) throw unsafeMemoryPath(path, 'search result is not a regular file')\n let content = r.body ?? ''\n if (!content) {\n content = readSafeFile(safe, path).content\n }\n const snippet = extractSnippet(content, query).snippet\n hits.push({ key, snippet, score: r.score })\n }\n return hits\n },\n\n async list() {\n const safe = safeRoot()\n assertSafeTree(safe)\n const out: MemoryEntry[] = []\n walkMd(safe, safe, '', out)\n return out.sort((a, b) => a.key.localeCompare(b.key))\n },\n\n async remove(key) {\n const safe = safeRoot()\n assertSafeTree(safe)\n const path = safeKey(safe, key)\n const info = inspectSafePath(safe, path)\n if (info) {\n if (!info.isFile()) throw unsafeMemoryPath(path, 'memory entry is not a regular file')\n rmSync(path)\n }\n assertSafeTree(safe)\n const store = await getStore(safe, storeFactory)\n await withNativeModuleErrorBoundary(() => store.update(), QMD_NATIVE_ERROR_OPTIONS)\n },\n }\n}\n","const NODE_MODULE_ABI_MISMATCH =\n /was compiled against a different Node\\.js version using\\s+NODE_MODULE_VERSION\\s+(\\d+)[\\s\\S]*?requires\\s+NODE_MODULE_VERSION\\s+(\\d+)/i\n\nexport interface NativeModuleErrorOptions {\n subject?: string\n}\n\n/**\n * Return a stable, user-safe recovery message for Node native-addon ABI\n * mismatches. The original diagnostic can contain absolute checkout paths, so\n * callers must only retain it in an explicitly internal diagnostic sink.\n */\nexport function formatNativeModuleAbiMismatch(\n diagnostic: string,\n options: NativeModuleErrorOptions = {},\n): string | undefined {\n const mismatch = NODE_MODULE_ABI_MISMATCH.exec(diagnostic)\n if (!mismatch) return undefined\n\n const builtAbi = mismatch[1]\n const requiredAbi = mismatch[2]\n const rebuildCommand = /better_sqlite3\\.node|better-sqlite3/i.test(diagnostic)\n ? '`pnpm rebuild better-sqlite3`'\n : '`pnpm rebuild`'\n const subject = options.subject ?? 'Bazilion'\n return (\n `${subject} could not load a native dependency built for Node module ABI ${builtAbi}; ` +\n `the current ${process.version} runtime requires ABI ${requiredAbi}. ` +\n 'Reinstall Bazilion with this same Node.js version, then restart it. ' +\n `From the repository root of a source checkout, run ${rebuildCommand} ` +\n '(or `pnpm install --force`).'\n )\n}\n\nexport class NativeModuleAbiMismatchError extends Error {\n readonly code = 'native_module_abi_mismatch'\n\n constructor(message: string) {\n super(message)\n this.name = 'NativeModuleAbiMismatchError'\n }\n}\n\n/**\n * Replace an ABI-mismatch error with a safe public error. Raw diagnostics are\n * intentionally not attached as a cause: callers such as the worker launcher\n * may send them to an explicit internal sink before using this helper, but an\n * ordinary logged error must not reveal a checkout path. Non-ABI errors\n * preserve their original identity and message.\n */\nexport function sanitizeNativeModuleError(\n error: unknown,\n options: NativeModuleErrorOptions = {},\n): unknown {\n const diagnostic = error instanceof Error ? error.message : String(error)\n const formatted = formatNativeModuleAbiMismatch(diagnostic, options)\n if (!formatted) return error\n return new NativeModuleAbiMismatchError(formatted)\n}\n\nexport async function withNativeModuleErrorBoundary<T>(\n operation: () => T | Promise<T>,\n options: NativeModuleErrorOptions = {},\n): Promise<T> {\n try {\n return await operation()\n } catch (error) {\n throw sanitizeNativeModuleError(error, options)\n }\n}\n","// Translator: pi `AgentSessionEvent` → Bazilion `SessionEvent[]`.\n//\n// The worker's NDJSON wire format (`ChatFrame`) is still the same discrete\n// event stream CLI / browser clients know how to render. This module is the\n// adapter at the single choke-point — pi event in, zero-or-more Bazilion\n// events out.\n//\n// Coverage:\n// - user message_start → one `user_message`\n// - assistant text_delta → one `assistant_delta` per chunk\n// - assistant message_end:\n// - if the message carries text → one `assistant_message`\n// - for every tool-call content → one `tool_call`\n// - tool_execution_end → `tool_result` or `tool_error` based on isError\n// - message_end with aborted/error stopReason → one `error`\n//\n// Not surfaced (intentional):\n// - agent_start / agent_end — run-row lifecycle, not chat events\n// - turn_start / turn_end — too chatty, nothing to render\n// - message_start (assistant) — text will come via updates + end\n// - message_update (non-text) — thinking deltas etc; left for a later\n// pass when UIs render thinking blocks\n// - queue_update / compaction_* — session meta, not assistant output\n// - auto_retry_* — silently retried, user sees only the\n// eventual success or failure\n\nimport type { ProviderMessage, SessionEvent, ToolCall, ToolResultImage } from '@bazilion/api-types'\nimport type { AgentMessage, AgentToolResult } from '@earendil-works/pi-agent-core'\nimport type { AssistantMessage } from '@earendil-works/pi-ai'\nimport type { AgentSessionEvent } from '@earendil-works/pi-coding-agent'\n\nexport function translatePiEvent(e: AgentSessionEvent): SessionEvent[] {\n switch (e.type) {\n case 'message_start': {\n if (e.message.role === 'user') {\n return [{ type: 'user_message', text: stringifyContent(e.message.content) }]\n }\n return []\n }\n\n case 'message_update': {\n const inner = e.assistantMessageEvent\n if (inner.type === 'text_delta') {\n return [{ type: 'assistant_delta', delta: inner.delta }]\n }\n return []\n }\n\n case 'message_end': {\n const m = e.message\n if (m.role !== 'assistant') return []\n const out: SessionEvent[] = []\n const text = extractAssistantText(m as AssistantMessage)\n if (text) out.push({ type: 'assistant_message', text })\n for (const block of (m as AssistantMessage).content ?? []) {\n if (block.type === 'toolCall') {\n out.push({\n type: 'tool_call',\n id: block.id,\n name: block.name,\n arguments: block.name === 'ask_user' ? '{}' : JSON.stringify(block.arguments ?? {}),\n })\n }\n }\n const stopReason = (m as AssistantMessage).stopReason\n if (stopReason === 'aborted' || stopReason === 'error') {\n const errText = (m as AssistantMessage).errorMessage ?? stopReason\n out.push({ type: 'error', error: errText })\n }\n return out\n }\n\n case 'tool_execution_end': {\n if (e.toolName === 'ask_user')\n return [\n {\n type: 'tool_result',\n id: e.toolCallId,\n name: e.toolName,\n result:\n 'Question outcome is available through the question card or authorized conversation history.',\n },\n ]\n const text = extractToolResultText(e.result)\n if (e.isError) {\n return [{ type: 'tool_error', id: e.toolCallId, name: e.toolName, error: text }]\n }\n const images = extractToolResultImages(e.result)\n return [\n {\n type: 'tool_result',\n id: e.toolCallId,\n name: e.toolName,\n result: text,\n resultReference: extractResultReference(e.result),\n ...(images.length > 0 ? { images } : {}),\n },\n ]\n }\n\n default:\n return []\n }\n}\n\nexport function extractAssistantText(m: AssistantMessage): string {\n let out = ''\n for (const block of m.content ?? []) {\n if (block.type === 'text') out += block.text\n }\n return out\n}\n\nexport function extractAssistantToolCalls(m: AssistantMessage): ToolCall[] {\n const out: ToolCall[] = []\n for (const block of m.content ?? []) {\n if (block.type === 'toolCall') {\n out.push({\n id: block.id,\n name: block.name,\n arguments: block.name === 'ask_user' ? '{}' : JSON.stringify(block.arguments ?? {}),\n })\n }\n }\n return out\n}\n\n/** Flatten `AgentToolResult.content` blocks into a single string — matches the\n * shape Bazilion tools always returned before pi adoption. */\nexport function extractToolResultText(result: unknown): string {\n const r = result as AgentToolResult<unknown> | undefined\n if (!r?.content) return ''\n let out = ''\n for (const block of r.content) {\n if (block.type === 'text') out += block.text\n }\n return out\n}\n\n/** Pull image blocks out of a tool result (browser screenshots, MCP images). */\nexport function extractToolResultImages(result: unknown): ToolResultImage[] {\n const r = result as AgentToolResult<unknown> | undefined\n if (!r?.content) return []\n const out: ToolResultImage[] = []\n for (const block of r.content) {\n if (block.type === 'image') {\n const b = block as { data?: string; mimeType?: string }\n if (typeof b.data === 'string' && typeof b.mimeType === 'string') {\n out.push({ data: b.data, mimeType: b.mimeType })\n }\n }\n }\n return out\n}\n\n/** Stringify a pi user-message content array. */\nfunction stringifyContent(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n let out = ''\n for (const block of content as { type: string; text?: string }[]) {\n if (block.type === 'text' && typeof block.text === 'string') out += block.text\n }\n return out\n}\n\n/**\n * Convert pi's authoritative `session.state.messages` (AgentMessage[]) into\n * Bazilion's ProviderMessage[] shape, so the `done` ChatFrame stays\n * compatible with browser + CLI clients that rehydrate from it.\n *\n * Notes on role mapping: pi uses `\"toolResult\"` for tool-response messages;\n * Bazilion uses `\"tool\"`. AssistantMessage content arrays become either\n * `content` string (text blocks joined) or a `toolCalls` array (toolCall\n * blocks converted).\n */\nexport function piMessagesToProviderView(\n messages: AgentMessage[],\n questionResultVisible?: (message: AgentMessage) => boolean,\n): ProviderMessage[] {\n const out: ProviderMessage[] = []\n for (const m of messages) {\n // Custom Bazilion message types would land in this switch too, but we\n // don't register any via CustomAgentMessages declaration merging yet.\n switch (m.role) {\n case 'user': {\n const content = (m as { content: unknown }).content\n const images = extractToolResultImages({ content })\n out.push({\n role: 'user',\n content: stringifyContent(content),\n ...(images.length > 0 ? { images } : {}),\n })\n break\n }\n case 'assistant': {\n const am = m as AssistantMessage\n const text = extractAssistantText(am)\n const toolCalls = extractAssistantToolCalls(am)\n const msg: ProviderMessage = { role: 'assistant', content: text }\n if (toolCalls.length > 0) msg.toolCalls = toolCalls\n out.push(msg)\n break\n }\n case 'toolResult': {\n const tr = m as { content: unknown; toolCallId?: string; toolName?: string }\n // A Pi tool transcript is private execution evidence, not proof that its transport\n // frame was released. Existing HTTP/Telegram approvals own captured publication.\n // Do not reconstruct a new repository snapshot publication from history or done frames.\n if (\n ['repository_context', 'coding_environment', 'coding_command', 'coding_receipt'].includes(\n tr.toolName ?? '',\n )\n ) {\n out.push({\n role: 'tool',\n toolCallId: tr.toolCallId,\n toolName: tr.toolName,\n content:\n 'Coding context or command evidence retained privately. Captured transport output follows its communication approval; ask the Agent for the relevant result.',\n })\n break\n }\n if (tr.toolName === 'ask_user' && !questionResultVisible?.(m)) {\n out.push({\n role: 'tool',\n toolCallId: tr.toolCallId,\n toolName: 'ask_user',\n content: 'Question content has not been released for this view.',\n })\n break\n }\n const images = extractToolResultImages({ content: tr.content })\n out.push({\n role: 'tool',\n content: stringifyContent(tr.content),\n toolCallId: tr.toolCallId,\n toolName: tr.toolName,\n result: extractResultReference(m),\n ...(images.length > 0 ? { images } : {}),\n })\n break\n }\n default:\n // System and other unknown roles are skipped — the runtime's system\n // prompt is already wired via pi's settingsManager + our buildSystemPrompt.\n break\n }\n }\n return out\n}\n\n/** Structured tool details are opaque references, never filesystem paths or inline bytes. */\nfunction extractResultReference(\n value: unknown,\n): import('@bazilion/api-types').ResultReference | undefined {\n if (!value || typeof value !== 'object') return undefined\n const details = (value as { details?: { result?: { resultId?: unknown } } }).details\n const id = details?.result?.resultId\n return typeof id === 'string' && /^[a-f0-9-]{36}$/.test(id) ? { resultId: id } : undefined\n}\n","import { closeSync, constants, fstatSync, openSync, readSync, realpathSync } from 'node:fs'\nimport { basename, join } from 'node:path'\nimport type { FileEntry } from '@earendil-works/pi-coding-agent'\nimport type { Paths } from '../core/paths.ts'\n\n/** Bounded descriptor read shared by publication provenance and source-conversation display. */\nexport function readResultSession(\n paths: Paths,\n agentId: string,\n filename: string,\n sessionId: string,\n minimumEntryOffset = 0,\n): FileEntry[] {\n if (basename(filename) !== filename) throw new Error('Invalid result session filename')\n const directory = join(realpathSync(paths.agentsDir), agentId, 'sessions')\n return readCanonicalSessionFile(directory, filename, sessionId, minimumEntryOffset)\n}\n\n/** Bounded canonical reader for an already Agent-bound session directory. */\nexport function readCanonicalSessionFile(\n directory: string,\n filename: string,\n sessionId: string,\n minimumEntryOffset = 0,\n): FileEntry[] {\n if (basename(filename) !== filename) throw new Error('Invalid session filename')\n if (realpathSync(directory) !== directory) throw new Error('Result session escaped its owner')\n const fd = openSync(\n join(directory, filename),\n constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,\n )\n try {\n const stat = fstatSync(fd)\n if (!stat.isFile() || stat.size > 64 * 1024 * 1024)\n throw new Error('Result source session is unavailable or too large')\n const buffer = Buffer.alloc(stat.size + 1)\n let count = 0\n while (count < buffer.length) {\n const n = readSync(fd, buffer, count, buffer.length - count, null)\n if (n === 0) break\n count += n\n }\n if (count !== stat.size) throw new Error('Result source session changed while reading')\n const entries: Array<{ entry: FileEntry; offset: number }> = []\n let position = 0\n for (const line of buffer.subarray(0, count).toString('utf8').split('\\n')) {\n if (line.trim()) entries.push({ entry: JSON.parse(line), offset: position })\n position += Buffer.byteLength(line) + 1\n }\n const header = entries[0]?.entry\n if (header?.type !== 'session' || header.id !== sessionId)\n throw new Error('Result source session does not match the active Agent session')\n return entries\n .filter((row, index) => index === 0 || row.offset >= minimumEntryOffset)\n .map((row) => row.entry)\n } finally {\n closeSync(fd)\n }\n}\n","import type { CodingCommandOutcome, CodingCommandReceipt } from '@bazilion/api-types'\nimport {\n type BashOperations,\n createLocalBashOperations,\n type ToolDefinition,\n} from '@earendil-works/pi-coding-agent'\nimport { Type } from 'typebox'\nimport { validateCodingCommand } from '../../core/coding-environment/config.ts'\nimport { CodingDiagnostics } from '../../lib/coding-environment/diagnostics.ts'\nimport { resolveCodingDirectory } from '../coding-directory.ts'\nimport {\n BashApprovalDeniedError,\n type BashApprovalHost,\n requireBashApproval,\n} from '../shell/approval.ts'\nimport {\n createPreparedDockerBashOperations,\n type DockerResourceLifecycle,\n type ProtectedDockerRuntime,\n} from '../shell/docker.ts'\nimport type { CodingHost } from './coding-contract.ts'\nimport type { RepositoryContextHost } from './repository-context.ts'\n\n/** Uses the admitted shell backend and lifecycle; never a daemon probe or another workspace lease. */\nexport function codingTools(input: {\n host: CodingHost\n root: string\n context: RepositoryContextHost\n docker?: ProtectedDockerRuntime\n lifecycle?: DockerResourceLifecycle\n approval: boolean\n approvalHost?: BashApprovalHost\n secrets?: readonly string[]\n}): ToolDefinition[] {\n const hostOperations = input.docker ? null : createLocalBashOperations()\n return [\n {\n name: 'coding_environment',\n label: 'Inspect coding environment',\n description:\n 'Read the actual environment admitted to this turn for a Team-relative directory. No commands run. Discover prerequisites during the task; no Team checklist is required. Missing network/tooling is a specific blocker, never permission to change posture.',\n parameters: Type.Object(\n { target: Type.String({ default: '.', maxLength: 4096 }) },\n { additionalProperties: false },\n ),\n executionMode: 'sequential',\n async execute(_id, params) {\n const value = await input.host.invoke({\n action: 'environment',\n target: (params as { target: string }).target,\n })\n return {\n content: [{ type: 'text', text: JSON.stringify(value) }],\n details: { codingEnvironment: value },\n }\n },\n },\n {\n name: 'coding_command',\n label: 'Run coding command',\n description:\n 'Execute an ad hoc finite runtime/dependency/preparation/build/test command in this turn’s actual environment. Use discovered repository commands, not saved operator checks. Resolve prerequisites and use available offline artifacts within current authority. Share coding-receipt:<id> through send_message if a teammate needs evidence; never wait holding its workspace lease. Success is an observed exit, not proof about later code.',\n parameters: Type.Object(\n {\n command: Type.String({ minLength: 1, maxLength: 4096 }),\n cwd: Type.String({ maxLength: 4096 }),\n purpose: Type.Union(\n ['runtime', 'dependency', 'prepare', 'build', 'test'].map((value) =>\n Type.Literal(value),\n ),\n ),\n timeoutSeconds: Type.Integer({ minimum: 1, maximum: 300 }),\n },\n { additionalProperties: false },\n ),\n executionMode: 'sequential',\n async execute(toolCallId, params, signal) {\n const command = validateCodingCommand(params)\n // Refresh Pi's effective instruction set as well as the daemon's receipt evidence.\n const context = await input.context(command.cwd)\n if (context.instructions.state !== 'complete')\n throw new Error('Coding scope instructions are incomplete')\n const started = (await input.host.invoke({\n action: 'start',\n toolCallId,\n input: command,\n })) as CodingCommandReceipt\n const output = new CodingDiagnostics(input.secrets ?? [])\n let outcome: CodingCommandOutcome\n try {\n if (input.approval)\n await requireBashApproval(command.command, toolCallId, signal, input.approvalHost)\n signal?.throwIfAborted()\n const cwd = resolveCodingDirectory(input.root, command.cwd)\n const operations: BashOperations = input.docker\n ? createPreparedDockerBashOperations(\n {\n ...input.docker,\n coding: {\n revision: input.docker.coding?.revision ?? 0,\n cwd: command.cwd,\n env: input.docker.coding?.env ?? {},\n },\n },\n input.lifecycle,\n )\n : hostOperations!\n const result = await operations.exec(\n command.command,\n input.docker ? input.root : cwd.path,\n { onData: (bytes) => output.append(bytes), signal, timeout: command.timeoutSeconds },\n )\n outcome = {\n state:\n result.exitCode === 0\n ? 'succeeded'\n : result.exitCode === null\n ? 'interrupted'\n : 'failed',\n exitCode: result.exitCode,\n ...output.finish(),\n reason: result.exitCode === null ? 'exit_unavailable' : null,\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Command execution unavailable'\n const state =\n signal?.aborted || message === 'aborted'\n ? 'cancelled'\n : message.startsWith('timeout:')\n ? 'timed_out'\n : error instanceof BashApprovalDeniedError\n ? 'blocked'\n : /cleanup|teardown/i.test(message)\n ? 'interrupted'\n : 'blocked'\n outcome = { state, exitCode: null, ...output.finish(), reason: message }\n }\n const receipt = (await input.host.invoke({\n action: 'finish',\n id: started.id,\n outcome,\n })) as CodingCommandReceipt\n return {\n content: [{ type: 'text', text: JSON.stringify(receipt) }],\n details: { codingReceipt: receipt },\n }\n },\n },\n {\n name: 'coding_receipt',\n label: 'Read coding result',\n description:\n 'Read a retained command result with current applicability. Your own receipts are private. For a teammate’s receipt, provide the id of its policy-authorized message containing coding-receipt:<id>. Old results are not current verification.',\n parameters: Type.Object(\n {\n id: Type.String({ maxLength: 64 }),\n messageId: Type.Optional(Type.String({ maxLength: 64 })),\n },\n { additionalProperties: false },\n ),\n executionMode: 'sequential',\n async execute(_id, params) {\n const value = await input.host.invoke({\n action: 'read',\n ...(params as { id: string; messageId?: string }),\n })\n return {\n content: [{ type: 'text', text: JSON.stringify(value) }],\n details: { codingEvidence: value },\n }\n },\n },\n ]\n}\n","import type { CodingEnvironmentConfig } from '@bazilion/api-types'\n\nexport class CodingEnvironmentValidationError extends Error {\n readonly status = 400\n}\n\nfunction invalid(field: string): never {\n // Never echo configuration values: rejected values could contain credentials.\n throw new CodingEnvironmentValidationError(`Invalid coding environment ${field}`)\n}\n\nfunction object(value: unknown, keys: string[], field: string): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) invalid(field)\n const record = value as Record<string, unknown>\n if (Object.keys(record).some((key) => !keys.includes(key))) invalid(field)\n return record\n}\n\nfunction text(value: unknown, max: number, field: string): string {\n if (\n typeof value !== 'string' ||\n !value.trim() ||\n Buffer.byteLength(value) > max ||\n Array.from(value).some((char) => {\n const code = char.charCodeAt(0)\n return code === 127 || (code < 32 && code !== 9 && code !== 10)\n })\n )\n invalid(field)\n return value\n}\n\n/** Lexical validation only; admission separately pins existing non-symlinked directories. */\nexport function codingRelativePath(value: unknown, field = 'cwd'): string {\n const path = text(value, 4096, field)\n if (path === '.') return path\n const parts = path.split('/')\n if (\n parts.length > 16 ||\n path.includes('\\\\') ||\n Array.from(path).some((char) => char.charCodeAt(0) < 32) ||\n parts.some((part) => !part || part === '.' || part === '..' || part === '.git')\n )\n invalid(field)\n return path\n}\n\nexport function validateCodingEnvironmentConfig(input: unknown): CodingEnvironmentConfig {\n const config = object(input, ['image', 'cwd', 'env'], 'configuration')\n const image = text(config.image, 512, 'image')\n if (!/^[A-Za-z0-9][A-Za-z0-9._/:@-]*$/.test(image)) invalid('image')\n const cwd = codingRelativePath(config.cwd)\n const env = object(config.env, ['CI', 'NO_COLOR', 'TZ'], 'environment values')\n for (const [key, value] of Object.entries(env)) {\n const allowed = key === 'CI' ? ['true', 'false'] : key === 'NO_COLOR' ? ['0', '1'] : ['UTC']\n if (typeof value !== 'string' || !allowed.includes(value)) invalid('environment values')\n }\n return { image, cwd, env } as CodingEnvironmentConfig\n}\nexport function validateCodingCommand(\n input: unknown,\n): import('@bazilion/api-types').CodingCommandInput {\n const value = object(input, ['command', 'cwd', 'purpose', 'timeoutSeconds'], 'command')\n const command = text(value.command, 4096, 'command')\n if (command.includes('\\0')) invalid('command')\n const cwd = codingRelativePath(value.cwd)\n if (!['runtime', 'dependency', 'prepare', 'build', 'test'].includes(String(value.purpose)))\n invalid('purpose')\n if (\n !Number.isInteger(value.timeoutSeconds) ||\n Number(value.timeoutSeconds) < 1 ||\n Number(value.timeoutSeconds) > 300\n )\n invalid('timeout')\n return {\n command,\n cwd,\n purpose: value.purpose,\n timeoutSeconds: value.timeoutSeconds,\n } as import('@bazilion/api-types').CodingCommandInput\n}\n","export const CODING_OUTPUT_BYTES = 64 * 1024\n\n/** A UTF-8 tail with no partial leading codepoint. */\nexport function diagnosticTail(value: string, limit: number): { text: string; truncated: boolean } {\n const bytes = Buffer.from(value)\n if (bytes.length <= limit) return { text: value, truncated: false }\n let offset = bytes.length - limit\n while (offset < bytes.length && ((bytes[offset] ?? 0) & 0xc0) === 0x80) offset++\n return { text: bytes.subarray(offset).toString('utf8'), truncated: true }\n}\n\n/** Redacts across arbitrary output chunks before a byte reaches retained diagnostics. */\nexport class CodingDiagnostics {\n private readonly decoder = new TextDecoder()\n private readonly secrets: string[]\n private readonly lookbehind: number\n private pending = ''\n private text = ''\n private truncated = false\n private finished = false\n\n constructor(secrets: readonly string[]) {\n this.secrets = [...new Set(secrets.filter(Boolean))].sort((a, b) => b.length - a.length)\n this.lookbehind = Math.max(1, ...this.secrets.map((secret) => secret.length)) - 1\n }\n\n append(bytes: Uint8Array): void {\n if (this.finished) throw new Error('Coding diagnostics already finalized')\n // Bound temporary decoded strings even if a producer supplies one enormous chunk.\n for (let offset = 0; offset < bytes.length; offset += 8192) {\n this.pending += this.decoder.decode(bytes.subarray(offset, offset + 8192), { stream: true })\n this.flush(false)\n }\n }\n\n finish(): { diagnostic: string; truncated: boolean } {\n if (!this.finished) {\n this.pending += this.decoder.decode()\n this.flush(true)\n this.finished = true\n }\n return { diagnostic: this.text, truncated: this.truncated }\n }\n\n private flush(final: boolean): void {\n let end = final ? this.pending.length : Math.max(0, this.pending.length - this.lookbehind)\n // Keep a UTF-16 pair together before converting the retained tail back to UTF-8.\n const last = this.pending.charCodeAt(end - 1)\n if (!final && last >= 0xd800 && last <= 0xdbff) end--\n let offset = 0\n let safe = ''\n while (offset < end) {\n const secret = this.secrets.find((value) => this.pending.startsWith(value, offset))\n if (secret) {\n safe += '[redacted]'\n offset += secret.length\n } else {\n const code = this.pending.charCodeAt(offset)\n const char = this.pending[offset] ?? ''\n safe +=\n (code < 32 && code !== 9 && code !== 10) || (code >= 127 && code <= 159)\n ? `\\\\u${code.toString(16).padStart(4, '0')}`\n : char\n offset++\n }\n }\n this.pending = this.pending.slice(offset)\n const tail = diagnosticTail(this.text + safe, CODING_OUTPUT_BYTES)\n this.text = tail.text\n this.truncated ||= tail.truncated\n }\n}\n\n/** Include individual values in stored structured credentials, without retaining their keys. */\nexport function codingSecrets(env: NodeJS.ProcessEnv): string[] {\n const values = new Set<string>()\n function collect(value: unknown, depth = 0) {\n if (depth > 8) return\n if (typeof value === 'string' && value.length >= 4) values.add(value)\n else if (value && typeof value === 'object')\n for (const entry of Object.values(value)) collect(entry, depth + 1)\n }\n for (const [key, value] of Object.entries(env)) {\n if (!value || !/KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH/i.test(key)) continue\n values.add(value)\n try {\n collect(JSON.parse(value))\n } catch {\n /* Ordinary string credential. */\n }\n }\n return [...values]\n}\n","import { realpathSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { ContextDirectory, relativeParts } from '../lib/repository-context/files.ts'\n\n/** Require every cwd component to exist and be a real directory, including intermediate components. */\nexport function resolveCodingDirectory(\n root: string,\n cwd: string,\n): { path: string; identity: string } {\n const directory = new ContextDirectory(root)\n try {\n let current = directory\n for (const part of relativeParts(cwd)) current = current.directory(part)\n const path = realpathSync(join(root, cwd))\n directory.validate()\n return { path, identity: current.identity }\n } finally {\n directory.close()\n }\n}\n","import { createHash } from 'node:crypto'\nimport {\n closeSync,\n constants,\n fstatSync,\n lstatSync,\n opendirSync,\n openSync,\n readSync,\n realpathSync,\n type Stats,\n} from 'node:fs'\nimport { isAbsolute, join } from 'node:path'\n\nexport const hash = (value: string | Buffer): string =>\n createHash('sha256').update(value).digest('hex')\n\nexport class ContextReadError extends Error {\n readonly code: string\n constructor(code: string) {\n super(code)\n this.code = code\n }\n}\n\nfunction identity(stat: Stats): string {\n return `${stat.dev}:${stat.ino}`\n}\nfunction stamp(stat: Stats): string {\n return `${identity(stat)}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`\n}\n\nexport function relativeParts(target: string): string[] {\n if (\n target.length > 4096 ||\n isAbsolute(target) ||\n [...target].some(\n (char) => char === '\\\\' || char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127,\n ) ||\n target.split('/').some((part) => part === '..' || part === '.git')\n )\n throw new ContextReadError('unsafe_target')\n const parts = target.split('/').filter((part) => part && part !== '.')\n if (parts.length > 17) throw new ContextReadError('depth_limit')\n return parts\n}\n\n/**\n * Directory descriptors pin each ancestry component. On Linux /proc/self/fd provides openat-like\n * access with O_NOFOLLOW, including during renames. Unsupported platforms fail closed instead of\n * promising containment from a check-then-read path. The registered root may deliberately be linked.\n */\nexport class ContextDirectory {\n readonly fd: number\n readonly identity: string\n private readonly originalStamp: string\n private readonly children: ContextDirectory[] = []\n private listing: string | undefined\n private readonly observedFiles = new Map<string, string | null>()\n\n readonly registeredPath: string\n readonly label: string\n private readonly parent?: ContextDirectory\n\n constructor(registeredPath: string, label = '.', parent?: ContextDirectory) {\n this.registeredPath = registeredPath\n this.label = label\n this.parent = parent\n if (process.platform !== 'linux') throw new ContextReadError('safe_reads_unavailable')\n const path = parent ? join(parent.path, registeredPath) : realpathSync(registeredPath)\n this.fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW)\n const stat = fstatSync(this.fd)\n this.identity = hash(identity(stat))\n this.originalStamp = identity(stat)\n }\n\n get path(): string {\n return `/proc/self/fd/${this.fd}`\n }\n\n directory(name: string): ContextDirectory {\n const child = new ContextDirectory(\n name,\n this.label === '.' ? name : `${this.label}/${name}`,\n this,\n )\n this.children.push(child)\n return child\n }\n\n entry(name: string): Stats | null {\n try {\n return lstatSync(join(this.path, name))\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw new ContextReadError('unreadable')\n }\n }\n\n list(): string[] {\n const names = this.listNames()\n this.listing = JSON.stringify(names)\n return names\n }\n\n private listNames(): string[] {\n const handle = opendirSync(this.path)\n const names: string[] = []\n try {\n let entry = handle.readSync()\n while (entry) {\n if (names.length >= 16_384) throw new ContextReadError('directory_entry_limit')\n names.push(entry.name)\n entry = handle.readSync()\n }\n return names.sort()\n } finally {\n handle.closeSync()\n }\n }\n\n read(name: string, maxBytes: number): Buffer | null {\n const entry = this.entry(name)\n if (!entry) {\n this.observedFiles.set(name, null)\n return null\n }\n if (!entry.isFile() || entry.isSymbolicLink()) throw new ContextReadError('unsafe_file')\n if (entry.size > maxBytes) throw new ContextReadError('byte_limit')\n let fd: number | undefined\n try {\n fd = openSync(\n join(this.path, name),\n constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,\n )\n const before = fstatSync(fd)\n if (!before.isFile() || stamp(entry) !== stamp(before))\n throw new ContextReadError('source_changed')\n const buffer = Buffer.alloc(Math.min(maxBytes + 1, before.size + 1))\n let size = 0\n while (size < buffer.length) {\n const count = readSync(fd, buffer, size, buffer.length - size, null)\n if (!count) break\n size += count\n }\n if (size > maxBytes || size !== before.size || stamp(fstatSync(fd)) !== stamp(before)) {\n throw new ContextReadError('source_changed')\n }\n this.observedFiles.set(name, stamp(before))\n return buffer.subarray(0, size)\n } catch (error) {\n if (error instanceof ContextReadError) throw error\n throw new ContextReadError('unreadable')\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n }\n\n validate(skipGit = false, names?: ReadonlySet<string>): void {\n const currentPath = this.parent\n ? join(this.parent.path, this.registeredPath)\n : realpathSync(this.registeredPath)\n const stat = lstatSync(currentPath)\n if (!stat.isDirectory() || identity(stat) !== this.originalStamp)\n throw new ContextReadError('root_changed')\n if (this.listing !== undefined && JSON.stringify(this.listNames()) !== this.listing) {\n throw new ContextReadError('source_changed')\n }\n for (const [name, previous] of this.observedFiles) {\n if (names && !names.has(name)) continue\n const current = this.entry(name)\n if ((current ? stamp(current) : null) !== previous)\n throw new ContextReadError('source_changed')\n }\n for (const child of this.children) {\n if (!skipGit || !child.label.split('/').includes('.git')) child.validate(skipGit, names)\n }\n }\n\n close(): void {\n for (const child of this.children) child.close()\n closeSync(this.fd)\n }\n}\n","import { type createBashToolDefinition, defineTool } from '@earendil-works/pi-coding-agent'\nimport { type CommandRisk, classifyBashCommand } from './security.ts'\n\nexport type BashApprovalDecision = 'approved' | 'denied'\n\nexport interface BashApprovalRequest {\n toolCallId: string\n command: string\n risks: readonly CommandRisk[]\n signal: AbortSignal | undefined\n}\n\n/**\n * Turn-scoped bridge used only for dangerous bash command approval.\n *\n * The transport lives outside the shell layer. Omitting the host is a valid\n * non-interactive posture: risky commands fail closed while safe commands\n * continue to the selected local or Docker execution backend.\n */\nexport interface BashApprovalHost {\n requestApproval(request: BashApprovalRequest): Promise<BashApprovalDecision>\n}\n\nexport class BashApprovalDeniedError extends Error {\n readonly risks: readonly CommandRisk[]\n\n constructor(risks: readonly CommandRisk[]) {\n const codes = [...new Set(risks.map((risk) => risk.code))]\n super(`Dangerous bash command denied (${codes.join(', ')})`)\n this.name = 'BashApprovalDeniedError'\n this.risks = risks\n }\n}\n\ntype BashToolDefinition = ReturnType<typeof createBashToolDefinition>\n\nexport async function requireBashApproval(\n command: string,\n toolCallId: string,\n signal?: AbortSignal,\n host?: BashApprovalHost,\n): Promise<void> {\n const risks = classifyBashCommand(command)\n if (!risks.length) return\n const decision = host\n ? await host.requestApproval({ toolCallId, command, risks, signal })\n : 'denied'\n if (decision !== 'approved') throw new BashApprovalDeniedError(risks)\n}\n\n/**\n * Wrap Pi's bash ToolDefinition at the tool-call boundary, where the stable\n * toolCallId is available. BashOperations is deliberately left untouched so\n * approval always happens before either the host or Docker backend executes.\n */\nexport function createApprovalGatedBashTool(base: BashToolDefinition, host?: BashApprovalHost) {\n return defineTool({\n ...base,\n description: `${base.description} Commands classified as dangerous require operator approval before execution.`,\n executionMode: 'sequential',\n async execute(toolCallId, params, signal, onUpdate, ctx) {\n await requireBashApproval(params.command, toolCallId, signal, host)\n\n return base.execute(toolCallId, params, signal, onUpdate, ctx)\n },\n })\n}\n","import type { CommandRisk, CommandRiskCode, CommandRiskSeverity } from '@bazilion/api-types'\n\nexport type { CommandRisk, CommandRiskCode, CommandRiskSeverity } from '@bazilion/api-types'\n\nexport type CommandRiskSpan = CommandRisk['span']\n\nexport type BashSandboxMode = 'off' | 'docker'\n\nexport type BashApprovalPolicy = 'off' | 'dangerous'\n\nexport interface ShellSecurityConfig {\n sandboxMode: BashSandboxMode\n approvalMode: BashApprovalPolicy\n sandboxImage: string\n envAllowlist: readonly string[]\n}\n\nexport const DEFAULT_BASH_SANDBOX_IMAGE = 'debian:bookworm-slim'\n\nexport const SAFE_SHELL_ENV_KEYS = [\n 'PATH',\n 'HOME',\n 'LANG',\n 'LANGUAGE',\n 'LC_ALL',\n 'LC_CTYPE',\n 'TERM',\n 'SHELL',\n 'TMPDIR',\n 'TZ',\n] as const\n\nconst ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/\nconst RESERVED_OBJECT_KEYS = new Set(['__proto__', 'prototype', 'constructor'])\nconst DOCKER_IMAGE_RE = /^[A-Za-z0-9][A-Za-z0-9._/:@-]*$/\nconst DOCKER_CLIENT_CONTROL_ENV = new Set([\n 'DOCKER_HOST',\n 'DOCKER_CONTEXT',\n 'DOCKER_CONFIG',\n 'DOCKER_CERT_PATH',\n 'DOCKER_TLS_VERIFY',\n 'DOCKER_API_VERSION',\n 'XDG_CONFIG_HOME',\n 'XDG_RUNTIME_DIR',\n])\nconst SANDBOX_CONTROL_ENV = new Set(['BASH_ENV'])\n\nconst SENSITIVE_HOME_PATH_RE =\n /(?:~(?:[A-Za-z0-9_-]+)?|\\$(?:HOME|\\{HOME\\})|\\/(?:home|Users)\\/[^\\s'\"`;|&/]+|\\/root)\\/\\.(?:ssh|aws|gnupg)(?:\\/[^\\s'\"`;|&]*)?/gi\nconst SENSITIVE_FILE_RE =\n /(?:\\bauth\\.json\\b|\\bbazilion\\.db\\b|(?:^|[\\s/'\"=])\\.env(?:\\.[A-Za-z0-9_-]+)?(?=$|[\\s/'\"`;|&])|\\/etc\\/(?:shadow|sudoers)(?:\\.d\\/[^\\s'\"`;|&]+)?)/gim\nconst SENSITIVE_ENV_RE =\n /\\b(?:OPENAI_CODEX_OAUTH|AWS_ACCESS_KEY_ID|AWS_PROFILE|GOOGLE_APPLICATION_CREDENTIALS|BAZILION_TOKEN|(?:[A-Z][A-Z0-9_]*_)?(?:API_KEY|ACCESS_TOKEN|AUTH_TOKEN|OAUTH_TOKEN|CLIENT_SECRET|SECRET_ACCESS_KEY|PRIVATE_KEY))\\b/g\n\nconst REMOTE_PIPE_RE =\n /(?:^|[;&|(\\n])\\s*(?:command\\s+)?(?:curl|wget)\\b[^;&\\n]*(?:\\|\\s*(?:(?:sudo|env)\\s+)*(?:[^\\s;&|()]+\\/)?(?:sh|bash|dash|zsh|fish|node|python(?:3)?|perl|ruby)\\b)/gim\n\nconst COMMAND_BOUNDARY_RE = /[;&|)\\n]/\n\nexport class ShellSecurityConfigError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ShellSecurityConfigError'\n }\n}\n\nfunction isValidEnvName(name: string): boolean {\n return ENV_NAME_RE.test(name) && !RESERVED_OBJECT_KEYS.has(name)\n}\n\nfunction addRisk(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n input: {\n code: CommandRiskCode\n severity?: CommandRiskSeverity\n message: string\n start: number\n end: number\n },\n): void {\n const start = Math.max(0, input.start)\n const end = Math.min(command.length, Math.max(start, input.end))\n const key = `${input.code}:${start}:${end}`\n if (seen.has(key)) return\n seen.add(key)\n risks.push({\n code: input.code,\n severity: input.severity ?? 'danger',\n message: input.message,\n matchedText: command.slice(start, end),\n span: { start, end },\n })\n}\n\nfunction addRegexRisks(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n pattern: RegExp,\n code: CommandRiskCode,\n message: string,\n): void {\n pattern.lastIndex = 0\n for (const match of command.matchAll(pattern)) {\n if (match.index === undefined) continue\n let start = match.index\n let matchedText = match[0]\n\n // Some patterns consume a shell delimiter to express a command boundary.\n // Keep the reported span focused on the risky command itself.\n const leading = matchedText.match(/^[;&|(\\n\\s]+/)\n if (leading) {\n start += leading[0].length\n matchedText = matchedText.slice(leading[0].length)\n }\n\n addRisk(command, risks, seen, {\n code,\n message,\n start,\n end: start + matchedText.length,\n })\n }\n}\n\nfunction shellTokens(fragment: string): string[] {\n return Array.from(fragment.matchAll(/\"(?:\\\\.|[^\"\\\\])*\"|'[^']*'|[^\\s]+/g), (match) =>\n match[0].replace(/^(?:\"|')|(?:\"|')$/g, ''),\n )\n}\n\nfunction isBroadFilesystemTarget(rawTarget: string): boolean {\n const target = rawTarget.replace(/[;,]+$/, '')\n // An absolute target can point outside the Team regardless of its top-level\n // directory (/mnt, /srv, /data, custom mounts, ...). Relative project-local\n // targets such as ./dist remain intentionally below the approval threshold.\n if (target.startsWith('/')) return true\n return /^(?:\\.|\\.\\/\\*+|\\*+|~(?:\\/.*)?|\\$(?:HOME|\\{HOME\\})(?:\\/.*)?|\\.\\.(?:\\/.*)?)$/.test(target)\n}\n\nfunction classifyBroadFilesystemOperations(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n): void {\n const commandRe = /(?:^|[;&|(\\n])\\s*(?:command\\s+)?(rm|find|chmod|chown)\\b[^;&|)\\n]*/gim\n for (const match of command.matchAll(commandRe)) {\n if (match.index === undefined || !match[1]) continue\n const nameOffset = match[0].toLowerCase().indexOf(match[1].toLowerCase())\n const start = match.index + nameOffset\n const text = command.slice(start, match.index + match[0].length).trimEnd()\n const tokens = shellTokens(text)\n const name = tokens[0]?.toLowerCase()\n const args = tokens.slice(1)\n let risky = false\n\n if (name === 'rm') {\n const recursive = args.some(\n (arg) => arg === '--recursive' || (/^-[^-]+$/.test(arg) && /[rR]/.test(arg)),\n )\n risky = recursive && args.some((arg) => !arg.startsWith('-') && isBroadFilesystemTarget(arg))\n } else if (name === 'find') {\n const target = args.find((arg) => !arg.startsWith('-'))\n risky = args.includes('-delete') && target !== undefined && isBroadFilesystemTarget(target)\n } else if (name === 'chmod' || name === 'chown') {\n const recursive = args.some(\n (arg) => arg === '--recursive' || (/^-[^-]+$/.test(arg) && /R/.test(arg)),\n )\n risky = recursive && args.some((arg) => !arg.startsWith('-') && isBroadFilesystemTarget(arg))\n }\n\n if (!risky) continue\n addRisk(command, risks, seen, {\n code: 'broad-destructive-operation',\n message: 'Recursively changes or deletes a broad filesystem target.',\n start,\n end: start + text.length,\n })\n }\n}\n\nfunction isLocalHostname(rawHostname: string): boolean {\n const hostname = rawHostname\n .trim()\n .replace(/^.*@/, '')\n .replace(/^\\[|\\]$/g, '')\n .replace(/\\.$/, '')\n .toLowerCase()\n return (\n hostname === 'localhost' ||\n hostname.endsWith('.localhost') ||\n hostname === '::1' ||\n hostname === '0.0.0.0' ||\n /^127(?:\\.\\d{1,3}){3}$/.test(hostname)\n )\n}\n\nfunction hasExternalHttpDestination(segment: string): boolean {\n const urls = Array.from(segment.matchAll(/https?:\\/\\/[^\\s'\"`]+/gi), (match) => match[0])\n if (urls.length === 0) return true\n return urls.some((rawUrl) => {\n try {\n return !isLocalHostname(new URL(rawUrl).hostname)\n } catch {\n return true\n }\n })\n}\n\nfunction remoteTransferHosts(segment: string): string[] {\n const hosts: string[] = []\n const uriRe = /(?:sftp|ssh|rsync):\\/\\/(?:[^@\\s/]+@)?(\\[[^\\]]+\\]|[^\\s/:]+)(?::\\d+)?/gi\n for (const match of segment.matchAll(uriRe)) {\n if (match[1]) hosts.push(match[1])\n }\n\n const scpRe = /(?:^|\\s)(?:[^@\\s:]+@)?(\\[[^\\]]+\\]|[A-Za-z0-9._-]+):[^\\s]+/g\n for (const match of segment.matchAll(scpRe)) {\n if (match[1]) hosts.push(match[1])\n }\n return hosts\n}\n\nfunction invocationSegment(\n command: string,\n match: RegExpMatchArray,\n commandName: string,\n): {\n start: number\n text: string\n} | null {\n if (match.index === undefined) return null\n const nameOffset = match[0].toLowerCase().indexOf(commandName.toLowerCase())\n if (nameOffset < 0) return null\n const start = match.index + nameOffset\n const boundaryOffset = command.slice(start).search(COMMAND_BOUNDARY_RE)\n const end = boundaryOffset < 0 ? command.length : start + boundaryOffset\n return { start, text: command.slice(start, end).trimEnd() }\n}\n\nfunction classifyOutboundExfiltration(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n): void {\n const invocationRe = /(?:^|[;&|(\\n])\\s*(?:command\\s+)?(curl|wget|scp|rsync|nc|netcat|sftp)\\b/gim\n for (const match of command.matchAll(invocationRe)) {\n const name = match[1]?.toLowerCase()\n if (!name) continue\n const segment = invocationSegment(command, match, name)\n if (!segment) continue\n let risky = false\n\n if (name === 'curl') {\n const uploads =\n /(?:^|\\s)(?:-X\\s*POST\\b|-[dFT](?:\\s|[^\\s]))/.test(segment.text) ||\n /(?:^|\\s)(?:--request(?:=|\\s+)POST\\b|--data(?:-[a-z-]+)?(?:=|\\s)|--form(?:-string)?(?:=|\\s)|--upload-file(?:=|\\s))/i.test(\n segment.text,\n )\n risky = uploads && hasExternalHttpDestination(segment.text)\n } else if (name === 'wget') {\n const uploads =\n /(?:^|\\s)(?:--method(?:=|\\s+)POST\\b|--post-data(?:=|\\s)|--post-file(?:=|\\s)|--body-data(?:=|\\s)|--body-file(?:=|\\s))/i.test(\n segment.text,\n )\n risky = uploads && hasExternalHttpDestination(segment.text)\n } else if (name === 'scp' || name === 'rsync') {\n const hosts = remoteTransferHosts(segment.text)\n risky =\n hosts.some((host) => !isLocalHostname(host)) ||\n (hosts.length === 0 && /\\$/.test(segment.text))\n } else if (name === 'sftp') {\n const hosts = remoteTransferHosts(segment.text)\n if (hosts.length > 0) {\n risky = hosts.some((host) => !isLocalHostname(host))\n } else {\n const destination = shellTokens(segment.text).at(-1)\n risky = destination !== undefined && destination !== name && !isLocalHostname(destination)\n }\n } else {\n const tokens = shellTokens(segment.text)\n const host = tokens.find(\n (token, index) =>\n index > 0 &&\n !token.startsWith('-') &&\n !/^\\d+$/.test(token) &&\n !['tcp', 'udp'].includes(token.toLowerCase()),\n )\n risky = host !== undefined && !isLocalHostname(host)\n }\n\n if (!risky) continue\n addRisk(command, risks, seen, {\n code: 'outbound-exfiltration',\n message: 'Uploads data or opens a transfer connection to a non-local destination.',\n start: segment.start,\n end: segment.start + segment.text.length,\n })\n }\n}\n\nfunction classifyPrivilegeAndCredentialAccess(\n command: string,\n risks: CommandRisk[],\n seen: Set<string>,\n): void {\n const patterns = [\n /(?:^|[;&|(\\n])\\s*(?:sudo|su|doas|pkexec)\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*security\\s+(?:find-(?:generic|internet)-password|dump-keychain)\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*gcloud\\s+auth\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*aws\\s+(?:configure|sso\\s+login)\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*git\\s+credential(?:-[A-Za-z0-9_-]+|\\s+(?:fill|approve|reject))?\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*(?:docker|npm|gh)\\s+(?:login|auth\\s+token)\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*(?:pass\\s+(?:show|ls)|secret-tool\\s+lookup|ssh-add\\s+-[Ll])\\b[^;&|)\\n]*/gim,\n /(?:^|[;&|(\\n])\\s*(?:env|printenv|set|export\\s+-p)\\s*(?=$|[;&|)\\n])/gim,\n ]\n\n for (const pattern of patterns) {\n addRegexRisks(\n command,\n risks,\n seen,\n pattern,\n 'privilege-or-credential-access',\n 'Requests elevated privileges or accesses a credential store.',\n )\n }\n}\n\n/**\n * Conservatively identify shell commands that should hit the dangerous-command\n * approval tripwire. This is a classifier, not a shell parser or sandbox.\n */\nexport function classifyBashCommand(command: string): CommandRisk[] {\n const risks: CommandRisk[] = []\n const seen = new Set<string>()\n\n addRegexRisks(\n command,\n risks,\n seen,\n SENSITIVE_HOME_PATH_RE,\n 'sensitive-path-read',\n 'Accesses a path that commonly contains credentials.',\n )\n addRegexRisks(\n command,\n risks,\n seen,\n SENSITIVE_FILE_RE,\n 'sensitive-path-read',\n 'Accesses a sensitive environment, authentication, or database file.',\n )\n addRegexRisks(\n command,\n risks,\n seen,\n SENSITIVE_ENV_RE,\n 'sensitive-path-read',\n 'References an environment variable name commonly used for credentials.',\n )\n classifyBroadFilesystemOperations(command, risks, seen)\n addRegexRisks(\n command,\n risks,\n seen,\n REMOTE_PIPE_RE,\n 'remote-pipe-execution',\n 'Pipes downloaded content directly into an interpreter.',\n )\n classifyOutboundExfiltration(command, risks, seen)\n classifyPrivilegeAndCredentialAccess(command, risks, seen)\n\n return risks.sort((a, b) => a.span.start - b.span.start || a.code.localeCompare(b.code))\n}\n\nfunction parseEnvAllowlist(raw: string | undefined): string[] {\n if (raw === undefined || raw.trim() === '') return []\n const values = raw.split(',').map((value) => value.trim())\n if (values.some((value) => value === '')) {\n throw new ShellSecurityConfigError(\n 'BAZILION_BASH_SANDBOX_ENV_ALLOWLIST contains an empty environment variable name',\n )\n }\n\n const unique: string[] = []\n const seen = new Set<string>()\n for (const value of values) {\n if (!isValidEnvName(value)) {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST contains invalid environment variable name: ${value}`,\n )\n }\n if (DOCKER_CLIENT_CONTROL_ENV.has(value)) {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST cannot include Docker client control variable: ${value}`,\n )\n }\n if (SANDBOX_CONTROL_ENV.has(value)) {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST cannot include shell startup control variable: ${value}`,\n )\n }\n if (!seen.has(value)) {\n seen.add(value)\n unique.push(value)\n }\n }\n return unique\n}\n\n/** Resolve shell sandbox settings without silently accepting unsafe typos. */\nexport function resolveShellSecurityConfig(\n env: Readonly<NodeJS.ProcessEnv> = process.env,\n): ShellSecurityConfig {\n const rawMode = env.BAZILION_BASH_SANDBOX?.trim() ?? ''\n if (rawMode !== '' && rawMode !== 'off' && rawMode !== 'docker') {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX must be \"off\" or \"docker\", received: ${rawMode}`,\n )\n }\n\n const rawApproval = env.BAZILION_BASH_APPROVAL?.trim() ?? ''\n if (rawApproval !== '' && rawApproval !== 'off' && rawApproval !== 'dangerous') {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_APPROVAL must be \"off\" or \"dangerous\", received: ${rawApproval}`,\n )\n }\n\n const rawImage = env.BAZILION_BASH_SANDBOX_IMAGE?.trim() ?? ''\n if (rawImage !== '' && !DOCKER_IMAGE_RE.test(rawImage)) {\n throw new ShellSecurityConfigError(\n `BAZILION_BASH_SANDBOX_IMAGE is not a valid Docker image reference: ${rawImage}`,\n )\n }\n\n return {\n sandboxMode: rawMode === 'docker' ? 'docker' : 'off',\n approvalMode: rawApproval === 'dangerous' ? 'dangerous' : 'off',\n sandboxImage: rawImage || DEFAULT_BASH_SANDBOX_IMAGE,\n envAllowlist: parseEnvAllowlist(env.BAZILION_BASH_SANDBOX_ENV_ALLOWLIST),\n }\n}\n\nfunction assertEnvName(name: string): void {\n if (!isValidEnvName(name)) {\n throw new ShellSecurityConfigError(`invalid shell environment variable name: ${name}`)\n }\n if (DOCKER_CLIENT_CONTROL_ENV.has(name) || SANDBOX_CONTROL_ENV.has(name)) {\n throw new ShellSecurityConfigError(`unsafe shell environment variable name: ${name}`)\n }\n}\n\n/**\n * Copy only fixed shell basics and operator-approved keys into a fresh object.\n * Explicit keys are intentionally authoritative: an operator may opt a secret\n * into the sandbox, but it is never inherited accidentally.\n */\nexport function buildScrubbedShellEnv(\n env: Readonly<NodeJS.ProcessEnv>,\n allowedKeys: readonly string[] = [],\n): NodeJS.ProcessEnv {\n const scrubbed: NodeJS.ProcessEnv = {}\n const keys = new Set<string>(SAFE_SHELL_ENV_KEYS)\n for (const key of allowedKeys) {\n assertEnvName(key)\n keys.add(key)\n }\n\n for (const key of keys) {\n const value = env[key]\n if (value !== undefined) scrubbed[key] = value\n }\n return scrubbed\n}\n","import { spawn } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport { closeSync, constants, mkdtempSync, openSync, rmSync, writeFileSync } from 'node:fs'\nimport { access, lstat, realpath, stat } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { delimiter, isAbsolute, join, relative, sep } from 'node:path'\nimport type { BashOperations } from '@earendil-works/pi-coding-agent'\nimport { resolveCodingDirectory } from '../coding-directory.ts'\nimport {\n codingContainerCwd,\n type DockerCodingSelection,\n validateDockerCodingSelection,\n} from './coding.ts'\n\nconst CONTAINER_WORKDIR = '/workspace'\nconst MAX_TIMEOUT_MS = 2_147_483_647\nconst MAX_STDERR_CHARS = 16 * 1024\nconst CLEANUP_TIMEOUT_MS = 650\nconst CLEANUP_RETRY_DELAY_MS = 100\nconst CLEANUP_ATTEMPTS = 3\nconst DOCKER_CONTEXT_TIMEOUT_MS = 2_000\nconst DOCKER_CREATE_TIMEOUT_MS = 5_000\nconst DOCKER_PROBE_START_TIMEOUT_MS = 2_000\nconst ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/\nconst CONTAINER_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]+$/\nconst DOCKER_CLIENT_ENV_KEYS = [\n 'PATH',\n 'HOME',\n 'DOCKER_HOST',\n 'DOCKER_CONTEXT',\n 'DOCKER_CONFIG',\n 'DOCKER_CERT_PATH',\n 'DOCKER_TLS_VERIFY',\n 'DOCKER_API_VERSION',\n 'XDG_CONFIG_HOME',\n 'XDG_RUNTIME_DIR',\n] as const\nconst DOCKER_CLIENT_CONTROL_ENV = new Set<string>(DOCKER_CLIENT_ENV_KEYS)\nconst SANDBOX_CONTROL_ENV = new Set(['BASH_ENV'])\nconst CONTAINER_LITERAL_ENV_KEYS = new Set([\n 'PATH',\n 'HOME',\n 'LANG',\n 'LANGUAGE',\n 'LC_ALL',\n 'LC_CTYPE',\n 'TERM',\n 'SHELL',\n 'TMPDIR',\n 'TZ',\n])\n\nexport interface DockerResourceLifecycle {\n name(kind: 'bash' | 'preflight'): string\n beforeCreate(identity: DockerContainerIdentity): Promise<void>\n afterCreate(containerName: string): Promise<void>\n afterRemove(containerName: string): Promise<void>\n}\n\nexport interface DockerBashOptions {\n lifecycle?: DockerResourceLifecycle\n image: string\n env: NodeJS.ProcessEnv\n dockerPath?: string\n /** Host environment used to resolve the active Docker context. Defaults to process.env. */\n hostEnv?: Readonly<NodeJS.ProcessEnv>\n /** Optional, narrowly scoped host directories exposed read-only. */\n readOnlyMounts?: readonly DockerReadOnlyMount[]\n}\n\nexport interface DockerReadOnlyMount {\n source: string\n target: string\n /** Canonical host root the resolved source must remain within. */\n sourceRoot?: string\n}\n\nexport interface ProtectedContainerEnvironment {\n PATH: string\n HOME: '/tmp'\n LANG: 'C.UTF-8'\n LC_ALL: 'C.UTF-8'\n SHELL: '/bin/bash'\n TMPDIR: '/tmp'\n}\n\nexport interface ProtectedDockerMount {\n source: string\n target: string\n sourceRoot: string\n}\n\nexport interface ProtectedDockerExecutableIdentity {\n device: string\n inode: string\n mode: string\n size: string\n modifiedTimeNs: string\n changedTimeNs: string\n}\n\n/** Fully inspected, serializable Docker facts carried by a protected worker. */\nexport interface ProtectedDockerRuntime {\n dockerPath: string\n executableIdentity: ProtectedDockerExecutableIdentity\n endpoint: string\n image: string\n imageId: string\n uid: number\n gid: number\n workspace: ProtectedDockerMount\n readOnlyMounts: ProtectedDockerMount[]\n containerEnv: ProtectedContainerEnvironment\n coding?: DockerCodingSelection\n}\n\nexport interface ProtectedDockerPreflightInput {\n signal?: AbortSignal\n /** Internal admitted engine identity; avoids resolving a mutable tag again for an async probe. */\n engine?: ProtectedDockerEngineRuntime\n lifecycle?: DockerResourceLifecycle\n coding?: DockerCodingSelection\n image: string\n workspaceDir: string\n /** Canonical boundary the registered Team workspace must remain within. */\n workspaceRoot?: string\n readOnlyMounts?: readonly Required<DockerReadOnlyMount>[]\n /** Absolute path or executable name resolved once in the daemon. */\n dockerPath?: string\n /** Daemon environment used only during preflight discovery. */\n hostEnv?: Readonly<NodeJS.ProcessEnv>\n}\n\nexport type ProtectedDockerReadiness =\n | { ready: true; image: string }\n | {\n ready: false\n image: string\n reason:\n | 'unsupported platform'\n | 'Docker executable is unavailable'\n | 'Docker must use a local Unix socket'\n | 'Docker socket is unavailable'\n | 'Docker image is unavailable'\n | 'Docker image declares writable volumes'\n | 'Docker preflight failed'\n }\n\nexport interface ProtectedDockerEngineRuntime {\n dockerPath: string\n executableIdentity: ProtectedDockerExecutableIdentity\n endpoint: string\n image: string\n imageId: string\n uid: number\n gid: number\n}\n\nconst PROTECTED_CONTAINER_ENV: ProtectedContainerEnvironment = {\n PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',\n HOME: '/tmp',\n LANG: 'C.UTF-8',\n LC_ALL: 'C.UTF-8',\n SHELL: '/bin/bash',\n TMPDIR: '/tmp',\n}\n\nexport interface DockerRunSpecInput extends DockerBashOptions {\n /** Validated Team-relative default working directory; mount remains the entire Team. */\n codingCwd?: string\n command: string\n mountSource: string\n containerName: string\n uid: number\n gid: number\n /** Immutable local image id resolved after policy inspection. */\n resolvedImage?: string\n}\n\nexport interface DockerRunSpec {\n executable: string\n args: string[]\n containerName: string\n image: string\n mountSource: string\n /** Minimal environment for the Docker CLI; never the worker's merged env wholesale. */\n processEnv: NodeJS.ProcessEnv\n /** Docker env-file contents passed through an inherited anonymous descriptor. */\n envFileContent?: string\n}\n\n/** Build the complete, argument-array Docker invocation without touching the filesystem. */\nexport function buildDockerRunSpec(input: DockerRunSpecInput): DockerRunSpec {\n const executable = input.dockerPath ?? 'docker'\n validateDockerPath(executable)\n validateImage(input.image)\n if (input.resolvedImage !== undefined) validateImage(input.resolvedImage)\n validateContainerName(input.containerName)\n validateMountArgument(input.mountSource)\n const readOnlyMounts = input.readOnlyMounts ?? []\n validateReadOnlyMounts(readOnlyMounts, input.mountSource)\n validateHostIdentity(input.uid, input.gid)\n\n if (input.command.includes('\\0')) {\n throw new Error('Docker sandbox command cannot contain a NUL byte')\n }\n\n const args = [\n 'run',\n '--rm',\n '--pull',\n 'never',\n '--name',\n input.containerName,\n '--network',\n 'none',\n '--read-only',\n '--tmpfs',\n '/tmp:rw,nosuid,nodev,size=64m,mode=1777',\n '--user',\n `${input.uid}:${input.gid}`,\n '--cap-drop',\n 'ALL',\n '--security-opt',\n 'no-new-privileges=true',\n '--pids-limit',\n '256',\n '--entrypoint',\n '/bin/bash',\n '--workdir',\n codingContainerCwd(input.codingCwd ?? '.'),\n // A bind mount is read/write unless `readonly` is present. No other mount is added.\n '--mount',\n `type=bind,source=${input.mountSource},target=${CONTAINER_WORKDIR},bind-recursive=disabled`,\n ]\n\n for (const mount of readOnlyMounts) {\n args.push(\n '--mount',\n `type=bind,source=${mount.source},target=${mount.target},readonly,bind-recursive=disabled`,\n )\n }\n\n const processEnv = buildDockerClientEnv(input.hostEnv ?? process.env)\n const envFileLines: string[] = []\n const containerEnvNames: string[] = []\n for (const [name, value] of sortedEnvironment(input.env)) {\n containerEnvNames.push(name)\n if (CONTAINER_LITERAL_ENV_KEYS.has(name)) {\n // These are shell mechanics, not operator-approved secret values. They\n // may differ from the host values needed by the Docker client itself.\n args.push('--env', `${name}=${value}`)\n } else {\n // Keep operator-approved values out of process argv and out of the\n // Docker client's own environment. The CLI reads these lines from its\n // stdin before asking the daemon to create the container.\n envFileLines.push(`${name}=${value}`)\n }\n }\n if (envFileLines.length > 0) args.push('--env-file', '/dev/fd/3')\n // Prevent an image-provided BASH_ENV from executing before the clean-env\n // wrapper. The final command environment below omits it entirely.\n args.push('--env', 'BASH_ENV=')\n\n const cleanAssignments = containerEnvNames.map((name) => `${name}=\"$${name}\"`).join(' ')\n const cleanEnvironmentCommand =\n `exec /usr/bin/env -i${cleanAssignments ? ` ${cleanAssignments}` : ''} ` +\n '/bin/bash --noprofile --norc -c \"$1\"'\n args.push(\n input.resolvedImage ?? input.image,\n '--noprofile',\n '--norc',\n '-c',\n cleanEnvironmentCommand,\n 'bazilion-sandbox',\n input.command,\n )\n\n return {\n executable,\n args,\n containerName: input.containerName,\n image: input.image,\n mountSource: input.mountSource,\n processEnv,\n ...(envFileLines.length > 0 ? { envFileContent: `${envFileLines.join('\\n')}\\n` } : {}),\n }\n}\n\n/**\n * Secret-free readiness projection for health/config surfaces. It verifies the\n * absolute executable, local Unix endpoint, socket access, immutable image id,\n * and image VOLUME policy without returning any host path or endpoint.\n */\nexport async function checkProtectedDockerReadiness(\n input: Pick<ProtectedDockerPreflightInput, 'image' | 'dockerPath' | 'hostEnv'>,\n): Promise<ProtectedDockerReadiness> {\n try {\n const engine = await preflightProtectedDockerEngine(input)\n await proveProtectedImageReadiness(engine)\n return { ready: true, image: input.image }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n let reason: Exclude<ProtectedDockerReadiness, { ready: true }>['reason']\n if (/numeric uid\\/gid|supported only on POSIX/i.test(message)) reason = 'unsupported platform'\n else if (/required \\/bin\\/bash and \\/usr\\/bin\\/env probe/i.test(message)) {\n reason = 'Docker preflight failed'\n } else if (/executable|ENOENT|not found|spawn/i.test(message)) {\n reason = 'Docker executable is unavailable'\n } else if (/local Unix-socket context/i.test(message)) {\n reason = 'Docker must use a local Unix socket'\n } else if (/Unix socket/i.test(message)) reason = 'Docker socket is unavailable'\n else if (/declares writable volumes/i.test(message)) {\n reason = 'Docker image declares writable volumes'\n } else if (\n /image.*unavailable|no such image|unable to find image|manifest unknown/i.test(message)\n ) {\n reason = 'Docker image is unavailable'\n } else reason = 'Docker preflight failed'\n return { ready: false, image: input.image, reason }\n }\n}\n\nasync function proveProtectedImageReadiness(engine: ProtectedDockerEngineRuntime): Promise<void> {\n const scratchDir = mkdtempSync(join(tmpdir(), 'bazilion-docker-readiness-'))\n try {\n const source = await resolveMountSource(scratchDir)\n await proveProtectedContainerCreation(\n engine,\n { source, sourceRoot: source, target: CONTAINER_WORKDIR },\n [],\n )\n } finally {\n rmSync(scratchDir, { recursive: true, force: true })\n }\n}\n\n/** Resolve every protected Docker fact before a worker is spawned. */\nexport async function preflightProtectedDockerRuntime(\n input: ProtectedDockerPreflightInput,\n): Promise<ProtectedDockerRuntime> {\n if (input.signal?.aborted) throw new Error('aborted')\n const coding = input.coding ? validateDockerCodingSelection(input.coding) : undefined\n if (coding) resolveCodingDirectory(input.workspaceDir, coding.cwd)\n const engine = input.engine ?? (await preflightProtectedDockerEngine(input))\n if (engine.image !== input.image) throw new Error('Docker engine image selection changed')\n const workspaceSource = await resolveMountSource(input.workspaceDir)\n const workspaceRoot = await resolveRequiredRoot(input.workspaceRoot ?? input.workspaceDir)\n if (!isWithin(workspaceRoot, workspaceSource)) {\n throw new Error('Docker sandbox workspace escaped its configured root')\n }\n const workspace: ProtectedDockerMount = {\n source: workspaceSource,\n target: CONTAINER_WORKDIR,\n sourceRoot: workspaceRoot,\n }\n\n const readOnlyMounts = await Promise.all(\n (input.readOnlyMounts ?? []).map((mount) => prepareProtectedReadOnlyMount(mount)),\n )\n validateReadOnlyMounts(readOnlyMounts, workspace.source)\n\n await proveProtectedContainerCreation(\n engine,\n workspace,\n readOnlyMounts,\n input.lifecycle,\n input.signal,\n )\n\n return {\n ...engine,\n workspace,\n readOnlyMounts,\n containerEnv: { ...PROTECTED_CONTAINER_ENV },\n ...(coding ? { coding } : {}),\n }\n}\n\n/**\n * Execute against daemon-preflighted facts. Mutable paths, socket access, tag\n * identity, and VOLUME policy are rechecked immediately before every command.\n */\nexport function createPreparedDockerBashOperations(\n runtime: ProtectedDockerRuntime,\n lifecycle?: DockerResourceLifecycle,\n): BashOperations {\n validatePreparedDockerRuntime(runtime)\n return {\n async exec(command, cwd, { onData, signal, timeout }) {\n const timeoutMs = resolveTimeoutMs(timeout)\n if (signal?.aborted) throw new Error('aborted')\n\n const workspace = await recheckProtectedMount(runtime.workspace)\n const requestedCwd = await resolveMountSource(cwd)\n if (requestedCwd !== workspace.source) {\n throw new Error('Docker sandbox execution workspace no longer matches preflight')\n }\n const readOnlyMounts = await Promise.all(\n runtime.readOnlyMounts.map((mount) => recheckProtectedMount(mount)),\n )\n await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity)\n await assertLocalDockerSocket(runtime.endpoint)\n const dockerClientEnv: NodeJS.ProcessEnv = { DOCKER_HOST: runtime.endpoint }\n if (runtime.coding) resolveCodingDirectory(workspace.source, runtime.coding.cwd)\n const currentImageId = await inspectSandboxImage(\n runtime.dockerPath,\n runtime.coding ? runtime.imageId : runtime.image,\n dockerClientEnv,\n )\n if (currentImageId !== runtime.imageId) {\n throw new Error('Docker sandbox image no longer matches its preflighted immutable id')\n }\n await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity)\n if (signal?.aborted) throw new Error('aborted')\n\n const containerName =\n lifecycle?.name('bash') ??\n `bazilion-bash-${process.pid}-${randomUUID().replaceAll('-', '')}`\n const spec = buildDockerRunSpec({\n image: runtime.image,\n resolvedImage: runtime.imageId,\n env: { ...runtime.containerEnv, ...runtime.coding?.env },\n codingCwd: runtime.coding?.cwd,\n dockerPath: runtime.dockerPath,\n hostEnv: dockerClientEnv,\n command,\n mountSource: workspace.source,\n readOnlyMounts,\n containerName,\n uid: runtime.uid,\n gid: runtime.gid,\n })\n await lifecycle?.beforeCreate({\n dockerPath: runtime.dockerPath,\n executableIdentity: runtime.executableIdentity,\n endpoint: runtime.endpoint,\n containerName,\n })\n try {\n return await executeRegisteredDockerRun(\n spec,\n { onData, signal, timeout, timeoutMs },\n lifecycle,\n )\n } finally {\n await lifecycle?.afterRemove(containerName)\n }\n },\n }\n}\n\nexport async function preflightProtectedDockerEngine(\n input: Pick<ProtectedDockerPreflightInput, 'image' | 'dockerPath' | 'hostEnv'>,\n): Promise<ProtectedDockerEngineRuntime> {\n validateImage(input.image)\n const { uid, gid } = hostIdentity()\n const hostEnv = input.hostEnv ?? process.env\n const dockerPath = await resolveAbsoluteDockerPath(input.dockerPath ?? 'docker', hostEnv)\n const executableIdentity = await inspectDockerExecutableIdentity(dockerPath)\n const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv)\n const endpoint = dockerClientEnv.DOCKER_HOST\n if (!endpoint) throw new Error('Docker sandbox local Unix socket is unavailable')\n await assertLocalDockerSocket(endpoint)\n const imageId = await inspectSandboxImage(dockerPath, input.image, dockerClientEnv)\n await assertDockerExecutableIdentity(dockerPath, executableIdentity)\n return { dockerPath, executableIdentity, endpoint, image: input.image, imageId, uid, gid }\n}\n\nasync function proveProtectedContainerCreation(\n engine: ProtectedDockerEngineRuntime,\n workspace: ProtectedDockerMount,\n readOnlyMounts: ProtectedDockerMount[],\n lifecycle?: DockerResourceLifecycle,\n signal?: AbortSignal,\n): Promise<void> {\n if (signal?.aborted) throw new Error('aborted')\n const containerName =\n lifecycle?.name('preflight') ??\n `bazilion-preflight-${process.pid}-${randomUUID().replaceAll('-', '')}`\n const dockerClientEnv: NodeJS.ProcessEnv = { DOCKER_HOST: engine.endpoint }\n const spec = buildDockerRunSpec({\n image: engine.image,\n resolvedImage: engine.imageId,\n env: { ...PROTECTED_CONTAINER_ENV },\n dockerPath: engine.dockerPath,\n hostEnv: dockerClientEnv,\n command: 'true',\n mountSource: workspace.source,\n readOnlyMounts,\n containerName,\n uid: engine.uid,\n gid: engine.gid,\n })\n\n await assertDockerExecutableIdentity(engine.dockerPath, engine.executableIdentity)\n await lifecycle?.beforeCreate({\n dockerPath: engine.dockerPath,\n executableIdentity: engine.executableIdentity,\n endpoint: engine.endpoint,\n containerName,\n })\n let preflightError: unknown\n try {\n await runProtectedContainerCreate(spec, { signal })\n await lifecycle?.afterCreate(containerName)\n // `docker container create` validates the pinned image and mount/runtime\n // configuration, but it does not resolve or execute the image entrypoint.\n // Start the exact container as well so readiness proves both required\n // executables (`/bin/bash` and `/usr/bin/env`) before a provider can be\n // prompted. The known `true` command still runs with the same no-network,\n // read-only, clean-environment policy used by protected model commands.\n await assertDockerExecutableIdentity(engine.dockerPath, engine.executableIdentity)\n await runProtectedContainerProbe(spec, signal)\n } catch (error) {\n preflightError = error\n }\n\n try {\n await assertDockerExecutableIdentity(engine.dockerPath, engine.executableIdentity)\n if (lifecycle) await lifecycle.afterRemove(containerName)\n else await removePreflightContainer(engine.dockerPath, containerName, dockerClientEnv)\n await assertDockerExecutableIdentity(engine.dockerPath, engine.executableIdentity)\n } catch (cleanupError) {\n if (preflightError) {\n throw new Error(\n 'Docker sandbox preflight failed and probe container cleanup could not be confirmed',\n { cause: cleanupError },\n )\n }\n throw cleanupError\n }\n\n if (preflightError) throw preflightError\n}\n\nfunction runProtectedContainerCreate(\n spec: DockerRunSpec,\n options?: Pick<DockerExecutionOptions, 'signal' | 'timeout' | 'timeoutMs'>,\n): Promise<void> {\n if (options?.signal?.aborted) return Promise.reject(new Error('aborted'))\n let envFileFd: number | undefined\n try {\n if (spec.envFileContent !== undefined) {\n envFileFd = openAnonymousEnvFile(spec.envFileContent)\n }\n } catch (error) {\n return Promise.reject(\n new Error(\n `Docker sandbox could not prepare its preflight environment: ${error instanceof Error ? error.message : String(error)}`,\n ),\n )\n }\n\n return new Promise((resolve, reject) => {\n let child: ReturnType<typeof spawn>\n try {\n child = spawn(spec.executable, ['container', 'create', ...spec.args.slice(1)], {\n stdio: ['ignore', 'ignore', 'pipe', envFileFd ?? 'ignore'],\n windowsHide: true,\n env: spec.processEnv,\n })\n } catch (error) {\n if (envFileFd !== undefined) closeSync(envFileFd)\n reject(error instanceof Error ? formatSpawnError(spec.executable, error) : error)\n return\n }\n if (envFileFd !== undefined) closeSync(envFileFd)\n\n let settled = false\n let stderr = ''\n let timeoutHandle: NodeJS.Timeout | undefined\n let killSettleHandle: NodeJS.Timeout | undefined\n let termination: Error | undefined\n const finish = (error?: Error) => {\n if (settled) return\n settled = true\n if (timeoutHandle) clearTimeout(timeoutHandle)\n if (killSettleHandle) clearTimeout(killSettleHandle)\n options?.signal?.removeEventListener('abort', onAbort)\n if (error) reject(error)\n else resolve()\n }\n\n const commandTimeout =\n options?.timeoutMs !== undefined && options.timeoutMs <= DOCKER_CREATE_TIMEOUT_MS\n const timeoutError = () =>\n commandTimeout\n ? new Error(`timeout:${options?.timeout}`)\n : new Error(\n `Docker sandbox could not create its protected preflight container within ${DOCKER_CREATE_TIMEOUT_MS}ms`,\n )\n\n child.stderr?.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-MAX_STDERR_CHARS)\n })\n child.once('error', (error) => finish(formatSpawnError(spec.executable, error)))\n child.once('close', (code) => {\n if (termination) finish(termination)\n else if (code === 0) finish()\n else finish(formatDockerCreateError(spec, stderr, code))\n })\n const terminate = (error: Error) => {\n if (settled || termination) return\n termination = error\n child.kill('SIGKILL')\n // SIGKILL should close promptly. Keep a separate bound so a pathological\n // child cannot prevent the exact-name cleanup phase from running.\n killSettleHandle = setTimeout(() => finish(error), CLEANUP_TIMEOUT_MS)\n }\n const onAbort = () => terminate(new Error('aborted'))\n timeoutHandle = setTimeout(\n () => terminate(timeoutError()),\n Math.min(options?.timeoutMs ?? DOCKER_CREATE_TIMEOUT_MS, DOCKER_CREATE_TIMEOUT_MS),\n )\n options?.signal?.addEventListener('abort', onAbort, { once: true })\n if (options?.signal?.aborted) onAbort()\n })\n}\n\nfunction runProtectedContainerProbe(spec: DockerRunSpec, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) return Promise.reject(new Error('aborted'))\n return new Promise((resolve, reject) => {\n let child: ReturnType<typeof spawn>\n try {\n child = spawn(spec.executable, ['container', 'start', '--attach', spec.containerName], {\n stdio: ['ignore', 'ignore', 'pipe'],\n windowsHide: true,\n env: spec.processEnv,\n })\n } catch (error) {\n reject(error instanceof Error ? formatSpawnError(spec.executable, error) : error)\n return\n }\n\n let settled = false\n let stderr = ''\n let timeoutHandle: NodeJS.Timeout | undefined\n let killSettleHandle: NodeJS.Timeout | undefined\n let termination: Error | undefined\n const finish = (error?: Error) => {\n if (settled) return\n settled = true\n if (timeoutHandle) clearTimeout(timeoutHandle)\n if (killSettleHandle) clearTimeout(killSettleHandle)\n signal?.removeEventListener('abort', onAbort)\n if (error) reject(error)\n else resolve()\n }\n\n const timeoutError = () =>\n new Error(\n `Docker sandbox could not start its protected preflight container within ${DOCKER_PROBE_START_TIMEOUT_MS}ms`,\n )\n\n child.stderr?.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-MAX_STDERR_CHARS)\n })\n child.once('error', (error) => finish(formatSpawnError(spec.executable, error)))\n child.once('close', (code) => {\n if (termination) finish(termination)\n else if (code === 0) finish()\n else finish(formatDockerProbeError(spec, stderr, code))\n })\n const terminate = (error: Error) => {\n if (settled || termination) return\n termination = error\n child.kill('SIGKILL')\n // Bound the handoff to exact-name force cleanup even if the Docker CLI\n // itself does not report close promptly after termination.\n killSettleHandle = setTimeout(() => finish(error), CLEANUP_TIMEOUT_MS)\n }\n const onAbort = () => terminate(new Error('aborted'))\n timeoutHandle = setTimeout(() => terminate(timeoutError()), DOCKER_PROBE_START_TIMEOUT_MS)\n signal?.addEventListener('abort', onAbort, { once: true })\n if (signal?.aborted) onAbort()\n })\n}\n\nasync function removePreflightContainer(\n dockerPath: string,\n containerName: string,\n processEnv: NodeJS.ProcessEnv,\n creationAcknowledged = true,\n): Promise<void> {\n const args = ['container', 'rm', '--force', containerName]\n let last: CleanupResult | undefined\n for (let attempt = 0; attempt < CLEANUP_ATTEMPTS; attempt += 1) {\n last = await runCleanupCommand(dockerPath, args, processEnv)\n if (last.ok && !last.missing) return\n if (attempt < CLEANUP_ATTEMPTS - 1) {\n await new Promise<void>((resolve) => setTimeout(resolve, CLEANUP_RETRY_DELAY_MS))\n }\n }\n // A miss only proves absence after acknowledged creation. An unacknowledged\n // create may still materialize after an arbitrarily long Docker-side delay.\n if (last?.missing && creationAcknowledged) return\n throw new Error('Docker sandbox could not confirm cleanup of its preflight container')\n}\n\nasync function prepareProtectedReadOnlyMount(\n mount: Required<DockerReadOnlyMount>,\n): Promise<ProtectedDockerMount> {\n const sourceInfo = await lstat(mount.source)\n if (sourceInfo.isSymbolicLink() || !sourceInfo.isDirectory()) {\n throw new Error(`Docker sandbox read-only mount must be a real directory: ${mount.source}`)\n }\n const sourceRoot = await resolveRequiredRoot(mount.sourceRoot)\n const source = await resolveMountSource(mount.source)\n if (!isWithin(sourceRoot, source)) {\n throw new Error(`Docker sandbox read-only mount escaped its configured root: ${mount.source}`)\n }\n return { source, target: mount.target, sourceRoot }\n}\n\nasync function recheckProtectedMount(mount: ProtectedDockerMount): Promise<ProtectedDockerMount> {\n const sourceInfo = await lstat(mount.source)\n if (sourceInfo.isSymbolicLink() || !sourceInfo.isDirectory()) {\n throw new Error('Docker sandbox mount no longer matches preflight')\n }\n const sourceRoot = await resolveRequiredRoot(mount.sourceRoot)\n const source = await resolveMountSource(mount.source)\n if (sourceRoot !== mount.sourceRoot || source !== mount.source || !isWithin(sourceRoot, source)) {\n throw new Error('Docker sandbox mount no longer matches preflight')\n }\n return { ...mount, source, sourceRoot }\n}\n\nasync function resolveRequiredRoot(root: string): Promise<string> {\n if (!isAbsolute(root)) throw new Error('Docker sandbox mount root must be absolute')\n return resolveMountSource(root)\n}\n\nasync function resolveAbsoluteDockerPath(\n requested: string,\n hostEnv: Readonly<NodeJS.ProcessEnv>,\n): Promise<string> {\n validateDockerPath(requested)\n const candidates = isAbsolute(requested)\n ? [requested]\n : (hostEnv.PATH ?? '')\n .split(delimiter)\n .filter(Boolean)\n .map((dir) => join(dir, requested))\n for (const candidate of candidates) {\n try {\n await access(candidate, constants.X_OK)\n const info = await stat(candidate)\n if (!info.isFile()) continue\n const canonical = await realpath(candidate)\n if (!isAbsolute(canonical)) continue\n return canonical\n } catch {\n // Keep looking through the daemon's PATH; protected workers never inherit it.\n }\n }\n throw new Error(`Docker sandbox executable is unavailable: ${requested}`)\n}\n\nasync function inspectDockerExecutableIdentity(\n dockerPath: string,\n): Promise<ProtectedDockerExecutableIdentity> {\n try {\n await access(dockerPath, constants.X_OK)\n const info = await stat(dockerPath, { bigint: true })\n if (!info.isFile()) throw new Error('not a regular file')\n return {\n device: info.dev.toString(),\n inode: info.ino.toString(),\n mode: info.mode.toString(),\n size: info.size.toString(),\n modifiedTimeNs: info.mtimeNs.toString(),\n changedTimeNs: info.ctimeNs.toString(),\n }\n } catch {\n throw new Error(`Docker sandbox executable is unavailable: ${dockerPath}`)\n }\n}\n\nasync function assertDockerExecutableIdentity(\n dockerPath: string,\n expected: ProtectedDockerExecutableIdentity,\n): Promise<void> {\n let current: ProtectedDockerExecutableIdentity\n try {\n current = await inspectDockerExecutableIdentity(dockerPath)\n } catch {\n throw new Error('Docker sandbox executable no longer matches its preflighted identity')\n }\n if (JSON.stringify(current) !== JSON.stringify(expected)) {\n throw new Error('Docker sandbox executable no longer matches its preflighted identity')\n }\n}\n\nasync function assertLocalDockerSocket(endpoint: string): Promise<void> {\n const prefix = 'unix://'\n if (!endpoint.startsWith(prefix) || !isAbsolute(endpoint.slice(prefix.length))) {\n throw new Error('Docker sandbox requires a local Unix-socket context')\n }\n const socketPath = endpoint.slice(prefix.length)\n try {\n const info = await stat(socketPath)\n if (!info.isSocket()) throw new Error('not a Unix socket')\n await access(socketPath, constants.R_OK | constants.W_OK)\n } catch {\n throw new Error('Docker sandbox local Unix socket is unavailable')\n }\n}\n\nfunction validatePreparedDockerRuntime(runtime: ProtectedDockerRuntime): void {\n if (runtime.coding) validateDockerCodingSelection(runtime.coding)\n if (!isAbsolute(runtime.dockerPath)) {\n throw new Error('protected Docker executable must be absolute')\n }\n validateDockerPath(runtime.dockerPath)\n validateDockerExecutableIdentity(runtime.executableIdentity)\n validateImage(runtime.image)\n validateImage(runtime.imageId)\n validateHostIdentity(runtime.uid, runtime.gid)\n if (!runtime.endpoint.startsWith('unix://')) {\n throw new Error('protected Docker endpoint must be a local Unix socket')\n }\n validateReadOnlyMounts(runtime.readOnlyMounts, runtime.workspace.source)\n const expectedEnvironment = JSON.stringify(PROTECTED_CONTAINER_ENV)\n if (JSON.stringify(runtime.containerEnv) !== expectedEnvironment) {\n throw new Error('protected Docker container environment does not match policy')\n }\n}\n\nfunction validateDockerExecutableIdentity(identity: ProtectedDockerExecutableIdentity): void {\n if (typeof identity !== 'object' || identity === null) {\n throw new Error('protected Docker executable identity is invalid')\n }\n const fields = [\n identity.device,\n identity.inode,\n identity.mode,\n identity.size,\n identity.modifiedTimeNs,\n identity.changedTimeNs,\n ]\n if (fields.some((field) => typeof field !== 'string' || !/^\\d+$/.test(field))) {\n throw new Error('protected Docker executable identity is invalid')\n }\n}\n\n/**\n * Execute each bash command in a fresh, exact-name Docker container.\n *\n * The `env` supplied to `exec` by pi is intentionally ignored. The only values\n * passed into the container are the environment captured in `options.env`.\n */\nexport function createDockerBashOperations(options: DockerBashOptions): BashOperations {\n const image = options.image\n const env = { ...options.env }\n const dockerPath = options.dockerPath ?? 'docker'\n const configuredReadOnlyMounts = options.readOnlyMounts ?? []\n const hostEnv = { ...(options.hostEnv ?? process.env) }\n\n validateDockerPath(dockerPath)\n validateImage(image)\n\n return {\n async exec(command, cwd, { onData, signal, timeout }) {\n const timeoutMs = resolveTimeoutMs(timeout)\n if (signal?.aborted) throw new Error('aborted')\n\n const mountSource = await resolveMountSource(cwd)\n const readOnlyMounts = await Promise.all(\n configuredReadOnlyMounts.map(async (mount) => {\n const source = await resolveMountSource(mount.source)\n if (mount.sourceRoot && !isWithin(mount.sourceRoot, source)) {\n throw new Error(\n `Docker sandbox read-only mount escaped its configured root: ${mount.source}`,\n )\n }\n return { source, target: mount.target }\n }),\n )\n if (signal?.aborted) throw new Error('aborted')\n\n const { uid, gid } = hostIdentity()\n const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv)\n const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv)\n if (signal?.aborted) throw new Error('aborted')\n const containerName =\n options.lifecycle?.name('bash') ??\n `bazilion-bash-${process.pid}-${randomUUID().replaceAll('-', '')}`\n const spec = buildDockerRunSpec({\n image,\n env,\n dockerPath,\n command,\n mountSource,\n containerName,\n uid,\n gid,\n resolvedImage,\n readOnlyMounts,\n hostEnv: dockerClientEnv,\n })\n\n if (options.lifecycle) {\n const absolute = await resolveAbsoluteDockerPath(dockerPath, hostEnv)\n await options.lifecycle.beforeCreate({\n dockerPath: absolute,\n executableIdentity: await inspectDockerExecutableIdentity(absolute),\n endpoint: dockerClientEnv.DOCKER_HOST as string,\n containerName,\n })\n }\n try {\n return await executeRegisteredDockerRun(\n spec,\n { onData, signal, timeout, timeoutMs },\n options.lifecycle,\n )\n } finally {\n await options.lifecycle?.afterRemove(containerName)\n }\n },\n }\n}\n\ninterface DockerExecutionOptions {\n onData: (data: Buffer) => void\n signal?: AbortSignal\n timeout?: number\n timeoutMs?: number\n}\n\nasync function executeRegisteredDockerRun(\n spec: DockerRunSpec,\n options: DockerExecutionOptions,\n lifecycle?: DockerResourceLifecycle,\n): Promise<{ exitCode: number | null }> {\n if (!lifecycle) return executeDockerRun(spec, options)\n const deadline = options.timeoutMs === undefined ? undefined : Date.now() + options.timeoutMs\n // No project code starts until the daemon durably acknowledges a successful create.\n await runProtectedContainerCreate(spec, options)\n await lifecycle.afterCreate(spec.containerName)\n if (options.signal?.aborted) throw new Error('aborted')\n const remaining = deadline === undefined ? undefined : deadline - Date.now()\n if (remaining !== undefined && remaining <= 0) throw new Error(`timeout:${options.timeout}`)\n return executeDockerRun(\n {\n ...spec,\n args: ['container', 'start', '--attach', spec.containerName],\n envFileContent: undefined,\n },\n { ...options, timeoutMs: remaining },\n )\n}\n\nfunction executeDockerRun(\n spec: DockerRunSpec,\n options: DockerExecutionOptions,\n): Promise<{ exitCode: number | null }> {\n let envFileFd: number | undefined\n try {\n if (spec.envFileContent !== undefined) {\n envFileFd = openAnonymousEnvFile(spec.envFileContent)\n }\n } catch (error) {\n return Promise.reject(\n new Error(\n `Docker sandbox could not prepare its environment: ${error instanceof Error ? error.message : String(error)}`,\n ),\n )\n }\n\n return new Promise((resolve, reject) => {\n const child = spawn(spec.executable, spec.args, {\n stdio: ['ignore', 'pipe', 'pipe', envFileFd ?? 'ignore'],\n windowsHide: true,\n env: spec.processEnv,\n })\n if (envFileFd !== undefined) closeSync(envFileFd)\n\n let settled = false\n let termination: 'aborted' | 'timeout' | undefined\n let timeoutHandle: NodeJS.Timeout | undefined\n let stderr = ''\n\n const requestTermination = (reason: 'aborted' | 'timeout') => {\n termination ??= reason\n child.kill('SIGKILL')\n }\n\n const onAbort = () => requestTermination('aborted')\n\n const finish = async (exitCode: number | null, spawnError?: Error) => {\n if (settled) return\n settled = true\n\n if (timeoutHandle) clearTimeout(timeoutHandle)\n options.signal?.removeEventListener('abort', onAbort)\n\n if (!termination && options.signal?.aborted) termination = 'aborted'\n\n if (termination) {\n await removeContainer(spec.executable, spec.containerName, spec.processEnv)\n reject(\n termination === 'aborted'\n ? new Error('aborted')\n : new Error(`timeout:${options.timeout}`),\n )\n return\n }\n\n if (spawnError) {\n reject(formatSpawnError(spec.executable, spawnError))\n return\n }\n\n if (exitCode === 125) {\n await removeContainer(spec.executable, spec.containerName, spec.processEnv)\n reject(formatDockerRunError(spec, stderr))\n return\n }\n\n if (exitCode === null) {\n await removeContainer(spec.executable, spec.containerName, spec.processEnv)\n reject(new Error(`Docker sandbox CLI for container \"${spec.containerName}\" was killed`))\n return\n }\n\n resolve({ exitCode })\n }\n\n child.stdout?.on('data', options.onData)\n child.stderr?.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-MAX_STDERR_CHARS)\n options.onData(data)\n })\n child.once('error', (error) => void finish(null, error))\n child.once('close', (exitCode) => void finish(exitCode))\n\n if (options.timeoutMs !== undefined) {\n timeoutHandle = setTimeout(() => requestTermination('timeout'), options.timeoutMs)\n }\n\n if (options.signal) {\n options.signal.addEventListener('abort', onAbort, { once: true })\n if (options.signal.aborted) onAbort()\n }\n })\n}\n\nfunction openAnonymousEnvFile(content: string): number {\n const dir = mkdtempSync(join(tmpdir(), 'bazilion-docker-env-'))\n const path = join(dir, 'environment')\n let fd: number | undefined\n try {\n writeFileSync(path, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 })\n fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW)\n // The descriptor remains valid after unlink. Docker inherits it as fd 3,\n // while no pathname or secret-bearing file survives the spawn boundary.\n rmSync(dir, { recursive: true, force: true })\n return fd\n } catch (error) {\n if (fd !== undefined) closeSync(fd)\n rmSync(dir, { recursive: true, force: true })\n throw error\n }\n}\n\nfunction resolveTimeoutMs(timeout: number | undefined): number | undefined {\n if (timeout === undefined) return undefined\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new Error('Invalid timeout: must be a finite number of seconds')\n }\n\n const timeoutMs = timeout * 1_000\n if (timeoutMs > MAX_TIMEOUT_MS) {\n throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_MS / 1_000} seconds`)\n }\n return timeoutMs\n}\n\nasync function resolveMountSource(cwd: string): Promise<string> {\n try {\n await access(cwd, constants.F_OK)\n const info = await stat(cwd)\n if (!info.isDirectory()) {\n throw new Error(`Docker sandbox mount is not a directory: ${cwd}`)\n }\n return await realpath(cwd)\n } catch (error) {\n if (error instanceof Error && error.message.startsWith('Docker sandbox mount is not')) {\n throw error\n }\n\n const code = (error as NodeJS.ErrnoException).code\n if (code === 'ENOENT') {\n throw new Error(`Docker sandbox mount does not exist: ${cwd}`)\n }\n throw new Error(\n `Docker sandbox cannot access mount \"${cwd}\": ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n}\n\nfunction hostIdentity(): { uid: number; gid: number } {\n if (typeof process.getuid !== 'function' || typeof process.getgid !== 'function') {\n throw new Error('Docker sandbox requires a host platform with numeric uid/gid support')\n }\n return { uid: process.getuid(), gid: process.getgid() }\n}\n\nfunction sortedEnvironment(env: NodeJS.ProcessEnv): Array<[string, string]> {\n const entries: Array<[string, string]> = []\n for (const [name, value] of Object.entries(env)) {\n if (value === undefined) continue\n if (!ENV_NAME.test(name)) {\n throw new Error(\n `Docker sandbox environment variable name is invalid: ${JSON.stringify(name)}`,\n )\n }\n if (DOCKER_CLIENT_CONTROL_ENV.has(name) && !CONTAINER_LITERAL_ENV_KEYS.has(name)) {\n throw new Error(`Docker sandbox cannot pass Docker client control variable: ${name}`)\n }\n if (SANDBOX_CONTROL_ENV.has(name)) {\n throw new Error(`Docker sandbox cannot pass shell startup control variable: ${name}`)\n }\n if (value.includes('\\0')) {\n throw new Error(`Docker sandbox environment variable ${name} contains a NUL byte`)\n }\n if (value.includes('\\n') || value.includes('\\r')) {\n throw new Error(`Docker sandbox environment variable ${name} cannot contain a line break`)\n }\n entries.push([name, value])\n }\n return entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n}\n\nasync function resolveLocalDockerClientEnv(\n dockerPath: string,\n hostEnv: Readonly<NodeJS.ProcessEnv>,\n): Promise<NodeJS.ProcessEnv> {\n const discoveryEnv = buildDockerClientEnv(hostEnv)\n const endpoint = await inspectDockerEndpoint(dockerPath, discoveryEnv)\n const prefix = 'unix://'\n if (!endpoint.startsWith(prefix) || !isAbsolute(endpoint.slice(prefix.length))) {\n throw new Error(\n `Docker sandbox requires a local Unix-socket context; active endpoint is ${JSON.stringify(endpoint)}`,\n )\n }\n\n // Pin all run/cleanup calls to the inspected endpoint. This prevents an\n // ambient remote context or a later context-file change from redirecting\n // bind mounts to another machine.\n const pinned: NodeJS.ProcessEnv = { DOCKER_HOST: endpoint }\n if (hostEnv.PATH !== undefined) pinned.PATH = hostEnv.PATH\n if (hostEnv.HOME !== undefined) pinned.HOME = hostEnv.HOME\n return pinned\n}\n\nasync function inspectDockerEndpoint(\n dockerPath: string,\n processEnv: NodeJS.ProcessEnv,\n): Promise<string> {\n const raw = await runDockerInspection(\n dockerPath,\n ['context', 'inspect', '--format', '{{json .Endpoints.docker.Host}}'],\n processEnv,\n 'the active context',\n )\n try {\n const parsed = JSON.parse(raw) as unknown\n if (typeof parsed !== 'string' || parsed.length === 0) throw new Error('empty endpoint')\n return parsed\n } catch {\n throw new Error(\n `Docker sandbox could not resolve the active Docker endpoint: ${raw || 'empty context response'}`,\n )\n }\n}\n\nasync function inspectSandboxImage(\n dockerPath: string,\n image: string,\n processEnv: NodeJS.ProcessEnv,\n): Promise<string> {\n let raw: string\n try {\n raw = await runDockerInspection(\n dockerPath,\n ['image', 'inspect', '--format', '{{json .Id}}\\t{{json (index .Config \"Volumes\")}}', image],\n processEnv,\n `local image ${JSON.stringify(image)}`,\n )\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error)\n if (/no such image|unable to find image|manifest unknown/i.test(detail)) {\n throw new Error(`Docker sandbox image \"${image}\" is unavailable: ${detail}`)\n }\n throw error\n }\n\n const separator = raw.indexOf('\\t')\n if (separator < 0) {\n throw new Error(`Docker sandbox received an invalid inspection result for image \"${image}\"`)\n }\n let id: unknown\n let volumes: unknown\n try {\n id = JSON.parse(raw.slice(0, separator))\n volumes = JSON.parse(raw.slice(separator + 1))\n } catch {\n throw new Error(`Docker sandbox received an invalid inspection result for image \"${image}\"`)\n }\n if (typeof id !== 'string' || !id.startsWith('sha256:')) {\n throw new Error(`Docker sandbox received an invalid image id for \"${image}\"`)\n }\n if (volumes !== null && typeof volumes === 'object' && Object.keys(volumes).length > 0) {\n throw new Error(\n `Docker sandbox image \"${image}\" declares writable volumes (${Object.keys(volumes).sort().join(', ')}); use an image without VOLUME declarations`,\n )\n }\n if (volumes !== null && (typeof volumes !== 'object' || Array.isArray(volumes))) {\n throw new Error(`Docker sandbox received invalid volume metadata for image \"${image}\"`)\n }\n return id\n}\n\nfunction runDockerInspection(\n dockerPath: string,\n args: string[],\n processEnv: NodeJS.ProcessEnv,\n subject: string,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(dockerPath, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n windowsHide: true,\n env: processEnv,\n })\n let settled = false\n let stdout = ''\n let stderr = ''\n const finish = (error?: Error) => {\n if (settled) return\n settled = true\n clearTimeout(timeoutHandle)\n if (error) {\n reject(error)\n return\n }\n resolve(stdout.trim())\n }\n const timeoutHandle = setTimeout(() => {\n child.kill('SIGKILL')\n finish(\n new Error(\n `Docker sandbox could not inspect ${subject} within ${DOCKER_CONTEXT_TIMEOUT_MS}ms`,\n ),\n )\n }, DOCKER_CONTEXT_TIMEOUT_MS)\n\n child.stdout.on('data', (data: Buffer) => {\n stdout = `${stdout}${data.toString('utf8')}`.slice(-4_096)\n })\n child.stderr.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-4_096)\n })\n child.once('error', (error) => finish(formatSpawnError(dockerPath, error)))\n child.once('close', (code) => {\n if (code === 0) finish()\n else\n finish(\n new Error(\n `Docker sandbox could not inspect ${subject}: ${stderr.trim() || `Docker exited with status ${code}`}`,\n ),\n )\n })\n })\n}\n\nfunction buildDockerClientEnv(hostEnv: Readonly<NodeJS.ProcessEnv>): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {}\n for (const key of DOCKER_CLIENT_ENV_KEYS) {\n const value = hostEnv[key]\n if (value !== undefined) env[key] = value\n }\n return env\n}\n\nfunction validateDockerPath(dockerPath: string): void {\n if (!dockerPath.trim()) throw new Error('Docker sandbox executable path is required')\n if (dockerPath.includes('\\0'))\n throw new Error('Docker sandbox executable path contains a NUL byte')\n}\n\nfunction validateImage(image: string): void {\n if (!image.trim()) throw new Error('Docker sandbox image is required')\n if (image.startsWith('-')) {\n throw new Error(`Docker sandbox image cannot start with \"-\": ${JSON.stringify(image)}`)\n }\n if (image.includes('\\0')) throw new Error('Docker sandbox image contains a NUL byte')\n}\n\nfunction validateContainerName(containerName: string): void {\n if (!CONTAINER_NAME.test(containerName)) {\n throw new Error(`Docker sandbox container name is invalid: ${JSON.stringify(containerName)}`)\n }\n}\n\nfunction validateMountArgument(mountSource: string): void {\n if (!mountSource) throw new Error('Docker sandbox mount path is required')\n if (mountSource.includes('\\0') || mountSource.includes(',')) {\n throw new Error(\n `Docker sandbox mount path cannot be represented safely: ${JSON.stringify(mountSource)}`,\n )\n }\n}\n\nfunction validateReadOnlyMounts(\n mounts: readonly DockerReadOnlyMount[],\n workspaceSource: string,\n): void {\n const targets = new Set<string>([CONTAINER_WORKDIR, '/tmp'])\n for (const mount of mounts) {\n validateMountArgument(mount.source)\n if (\n mount.sourceRoot !== undefined &&\n (!isAbsolute(mount.sourceRoot) ||\n mount.sourceRoot.includes('\\0') ||\n mount.sourceRoot.includes(','))\n ) {\n throw new Error(\n `Docker sandbox read-only mount root is invalid: ${JSON.stringify(mount.sourceRoot)}`,\n )\n }\n if (\n !mount.target.startsWith('/') ||\n mount.target.includes('\\0') ||\n mount.target.includes(',') ||\n mount.target.split('/').includes('..')\n ) {\n throw new Error(\n `Docker sandbox read-only mount target is invalid: ${JSON.stringify(mount.target)}`,\n )\n }\n if (mount.source === workspaceSource) {\n throw new Error('Docker sandbox cannot mount the writable workspace again as an input')\n }\n if (targets.has(mount.target)) {\n throw new Error(`Docker sandbox mount target is duplicated or reserved: ${mount.target}`)\n }\n targets.add(mount.target)\n }\n}\n\nfunction isWithin(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n}\n\nfunction validateHostIdentity(uid: number, gid: number): void {\n if (!Number.isSafeInteger(uid) || uid < 0 || !Number.isSafeInteger(gid) || gid < 0) {\n throw new Error(`Docker sandbox received an invalid host uid/gid: ${uid}:${gid}`)\n }\n}\n\nfunction formatSpawnError(dockerPath: string, error: Error): Error {\n const code = (error as NodeJS.ErrnoException).code\n if (code === 'ENOENT') {\n return new Error(`Docker sandbox unavailable: executable \"${dockerPath}\" was not found`)\n }\n if (code === 'EACCES') {\n return new Error(`Docker sandbox unavailable: executable \"${dockerPath}\" is not executable`)\n }\n return new Error(`Docker sandbox could not start \"${dockerPath}\": ${error.message}`)\n}\n\nfunction formatDockerRunError(spec: DockerRunSpec, stderr: string): Error {\n const detail = stderr.trim() || 'Docker exited with status 125 without an error message'\n if (/no such image|unable to find image|manifest unknown|pull access denied/i.test(detail)) {\n return new Error(`Docker sandbox image \"${spec.image}\" is unavailable: ${detail}`)\n }\n if (\n /mounts denied|invalid mount config|bind source path does not exist|mount.*failed/i.test(detail)\n ) {\n return new Error(`Docker sandbox mount failed for \"${spec.mountSource}\": ${detail}`)\n }\n if (/cannot connect to the docker daemon|is the docker daemon running/i.test(detail)) {\n return new Error(`Docker sandbox unavailable: ${detail}`)\n }\n return new Error(`Docker sandbox failed to start container \"${spec.containerName}\": ${detail}`)\n}\n\nfunction formatDockerCreateError(\n spec: DockerRunSpec,\n stderr: string,\n exitCode: number | null,\n): Error {\n const detail =\n stderr.trim() ||\n `Docker exited with ${exitCode === null ? 'no status' : `status ${exitCode}`} without an error message`\n if (\n /mounts denied|invalid mount config|bind source path does not exist|mount.*failed/i.test(detail)\n ) {\n return new Error(`Docker sandbox preflight mount failed for \"${spec.mountSource}\": ${detail}`)\n }\n if (/cannot connect to the docker daemon|is the docker daemon running/i.test(detail)) {\n return new Error(`Docker sandbox preflight could not reach the local Docker daemon: ${detail}`)\n }\n return new Error(`Docker sandbox preflight could not create the required container: ${detail}`)\n}\n\nfunction formatDockerProbeError(\n spec: DockerRunSpec,\n stderr: string,\n exitCode: number | null,\n): Error {\n const detail =\n stderr.trim() ||\n `Docker exited with ${exitCode === null ? 'no status' : `status ${exitCode}`} without an error message`\n return new Error(\n `Docker sandbox image \"${spec.image}\" could not run the required /bin/bash and /usr/bin/env probe: ${detail}`,\n )\n}\n\nasync function removeContainer(\n dockerPath: string,\n containerName: string,\n processEnv: NodeJS.ProcessEnv,\n): Promise<void> {\n const args = ['container', 'rm', '--force', containerName]\n let last: CleanupResult | undefined\n for (let attempt = 0; attempt < CLEANUP_ATTEMPTS; attempt += 1) {\n last = await runCleanupCommand(dockerPath, args, processEnv)\n if (last.ok) return\n if (attempt < CLEANUP_ATTEMPTS - 1) {\n await new Promise<void>((resolve) => setTimeout(resolve, CLEANUP_RETRY_DELAY_MS))\n }\n }\n\n if (last && !last.missing) {\n console.warn(\n `Docker sandbox could not confirm cleanup of container \"${containerName}\": ${last.error || 'cleanup command failed'}`,\n )\n }\n}\n\ninterface CleanupResult {\n ok: boolean\n missing: boolean\n error: string\n}\n\nfunction runCleanupCommand(\n dockerPath: string,\n args: string[],\n processEnv: NodeJS.ProcessEnv,\n): Promise<CleanupResult> {\n return new Promise((resolve) => {\n let done = false\n let stderr = ''\n const child = spawn(dockerPath, args, {\n stdio: ['ignore', 'ignore', 'pipe'],\n windowsHide: true,\n env: processEnv,\n })\n const finish = (ok: boolean, error = '') => {\n if (done) return\n done = true\n clearTimeout(timeoutHandle)\n const detail = error || stderr.trim()\n resolve({\n ok,\n missing: /no such container/i.test(detail),\n error: detail,\n })\n }\n const timeoutHandle = setTimeout(() => {\n child.kill('SIGKILL')\n finish(false, `cleanup timed out after ${CLEANUP_TIMEOUT_MS}ms`)\n }, CLEANUP_TIMEOUT_MS)\n\n child.stderr.on('data', (data: Buffer) => {\n stderr = `${stderr}${data.toString('utf8')}`.slice(-2_048)\n })\n child.once('error', (error) => finish(false, error.message))\n child.once('close', (code) => finish(code === 0))\n })\n}\n\n/** Captured by the daemon before creation; never assembled from an operator-supplied cleanup path. */\nexport interface DockerContainerIdentity {\n dockerPath: string\n executableIdentity: ProtectedDockerExecutableIdentity\n endpoint: string\n containerName: string\n}\n\nexport async function terminateRecordedContainer(\n identity: DockerContainerIdentity,\n creationAcknowledged = false,\n): Promise<boolean> {\n try {\n validateContainerName(identity.containerName)\n if (!identity.containerName.startsWith('bazilion-')) return false\n await assertDockerExecutableIdentity(identity.dockerPath, identity.executableIdentity)\n await assertLocalDockerSocket(identity.endpoint)\n let removalIdentity = identity.containerName\n if (!creationAcknowledged) {\n // Docker can silently return success for `rm --force` of a nonexistent name.\n // Observe a real immutable ID first; otherwise a late create remains possible.\n const observed = (\n await runDockerInspection(\n identity.dockerPath,\n ['container', 'inspect', '--format', '{{.Id}}', identity.containerName],\n { DOCKER_HOST: identity.endpoint },\n 'container recovery identity',\n )\n ).trim()\n if (!/^[a-f0-9]{64}$/.test(observed)) return false\n removalIdentity = observed\n }\n await removePreflightContainer(\n identity.dockerPath,\n removalIdentity,\n {\n DOCKER_HOST: identity.endpoint,\n },\n true,\n )\n return true\n } catch {\n return false\n }\n}\n","import type { CodingEnvironmentValues } from '@bazilion/api-types'\nimport { codingRelativePath } from '../../core/coding-environment/config.ts'\n\n/** Closed, serializable addition to preflighted Docker facts; never contains host paths. */\nexport interface DockerCodingSelection {\n revision: number\n cwd: string\n env: CodingEnvironmentValues\n}\n\nexport function validateDockerCodingSelection(input: unknown): DockerCodingSelection {\n if (!input || typeof input !== 'object' || Array.isArray(input))\n throw new Error('Invalid Docker coding selection')\n const value = input as Record<string, unknown>\n if (\n Object.keys(value).some((key) => !['revision', 'cwd', 'env'].includes(key)) ||\n !Number.isSafeInteger(value.revision) ||\n (value.revision as number) < 0\n )\n throw new Error('Invalid Docker coding revision')\n const cwd = codingRelativePath(value.cwd)\n if (!value.env || typeof value.env !== 'object' || Array.isArray(value.env))\n throw new Error('Invalid Docker coding environment')\n const env: CodingEnvironmentValues = {}\n for (const [key, entry] of Object.entries(value.env)) {\n if (key === 'CI' && (entry === 'true' || entry === 'false')) env.CI = entry\n else if (key === 'NO_COLOR' && (entry === '0' || entry === '1')) env.NO_COLOR = entry\n else if (key === 'TZ' && entry === 'UTC') env.TZ = entry\n else throw new Error('Invalid Docker coding environment')\n }\n return { revision: value.revision as number, cwd, env }\n}\n\nexport function codingContainerCwd(cwd: string): string {\n return cwd === '.' ? '/workspace' : `/workspace/${codingRelativePath(cwd)}`\n}\n","import type { RepositoryContextReport } from '@bazilion/api-types'\nimport type { AgentSession, ToolDefinition } from '@earendil-works/pi-coding-agent'\nimport { Type } from 'typebox'\nimport { assertRepositoryContext } from './repository-context-contract.ts'\n\nexport type RepositoryContextHost = (target: string) => Promise<RepositoryContextReport>\n\n/** Supplies a custom ResourceLoader's public getAgentsFiles contract; no filesystem discovery. */\nexport function repositoryContextIntegration(\n initial: RepositoryContextReport,\n host: RepositoryContextHost,\n cwd: string,\n) {\n assertRepositoryContext(initial)\n if (initial.instructions.state !== 'complete')\n throw new Error('Repository instructions incomplete')\n let current = initial\n let refreshPrompt = () => {}\n const files = () => ({\n agentsFiles: [\n {\n path: 'Repository instructions and provenance',\n content: [\n '# Repository context',\n `Captured target: ${current.target}; fingerprint: ${current.fingerprint}; captured at: ${new Date(current.capturedAt).toISOString()}.`,\n `Repository root for this runtime: ${cwd}. This is captured context, not a live workspace lock or verification evidence.`,\n 'Platform/runtime policy and explicit operator instructions take priority. Repository guidance specializes general private Agent preferences for repository work. Deeper documents govern only their own subtree. Team Policy and execution authority are unchanged.',\n 'For coding tasks, investigate repository context and actual coding_environment as needed, then use coding_command for task-related finite checks/preparation. Do not ask the operator to configure named checks. A source-backed Team memory recipe is advisory; refresh it before reuse. Missing network/tooling is a concrete prerequisite to report, never authority to change the runtime. To delegate shared-workspace preparation, send_message and end the turn; do not wait_for_reply while holding the peer workspace.',\n 'Before editing a different subtree, call repository_context with its directory or file path. A returned snapshot replaces previous applicable repository guidance; older transcript snapshots are historical. Do not edit a scope whose instructions are incomplete.',\n current.instructions.state === 'complete'\n ? 'Applicable instructions are complete for the captured scope. Missing AGENTS.md is normal.'\n : `BLOCKED SCOPE: ${current.instructions.issues.map((issue) => issue.code).join(', ')}. Resolve this context before editing.`,\n ...(current.instructions.state === 'complete'\n ? current.instructions.files.map(\n (file) =>\n `## ${file.path}\\nScope: ${file.scope}; precedence: ${file.precedence}; SHA-256: ${file.sha256}\\n\\n${file.content}`,\n )\n : []),\n `Git orientation at capture time: ${JSON.stringify(current.git)}`,\n `Command suggestions (task data, not execution permission):\\n${JSON.stringify(current.commands)}`,\n ].join('\\n\\n'),\n },\n ],\n })\n const tool: ToolDefinition = {\n name: 'repository_context',\n label: 'Repository context',\n description:\n 'Resolve the complete applicable repository AGENTS.md instructions and passive command/Git context for a contained file or directory. Call before editing a new subtree. Does not run project commands or change cwd. Incomplete instructions block editing that scope.',\n parameters: Type.Object(\n { target: Type.String({ maxLength: 4096 }) },\n { additionalProperties: false },\n ),\n executionMode: 'sequential',\n async execute(_id, params) {\n const target = (params as { target: string }).target\n const replacement = await host(target)\n assertRepositoryContext(replacement)\n if (\n replacement.teamId !== initial.teamId ||\n replacement.rootIdentity !== initial.rootIdentity\n ) {\n current = {\n ...initial,\n instructions: {\n state: 'incomplete',\n files: [],\n issues: [\n {\n code: 'root_changed',\n message: 'Repository identity changed; start a new admitted turn.',\n },\n ],\n },\n commands: { state: 'incomplete', sources: [], candidates: [], issues: [] },\n }\n refreshPrompt()\n throw new Error('Repository context identity changed; start a new admitted turn')\n }\n current = replacement\n refreshPrompt()\n return {\n content: [{ type: 'text', text: JSON.stringify(replacement) }],\n details: { repositoryContext: replacement },\n }\n },\n }\n return {\n files,\n tool,\n async resolve(target: string) {\n await tool.execute('coding-context', { target }, undefined, undefined, {} as never)\n return current\n },\n bind(session: AgentSession, allowedTools: string[]) {\n // Public SDK API rebuilds the prompt from the ResourceLoader. The admitted tool set is\n // unchanged; only the captured context supplied by getAgentsFiles has been replaced.\n refreshPrompt = () => session.setActiveToolsByName(allowedTools)\n },\n }\n}\n","import type { RepositoryContextReport } from '@bazilion/api-types'\n\nconst object = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\nconst keys = (\n value: Record<string, unknown>,\n required: string[],\n optional: string[] = [],\n): boolean =>\n required.every((key) => Object.hasOwn(value, key)) &&\n Object.keys(value).every((key) => required.includes(key) || optional.includes(key))\nconst string = (value: unknown, limit = 4096): value is string =>\n typeof value === 'string' && value.length <= limit\nconst digest = (value: unknown): boolean =>\n typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)\nconst count = (value: unknown): boolean => Number.isSafeInteger(value) && (value as number) >= 0\nconst list = (value: unknown, limit: number, check: (entry: unknown) => boolean): boolean =>\n Array.isArray(value) && value.length <= limit && value.every(check)\nconst issue = (value: unknown): boolean =>\n object(value) &&\n keys(value, ['code', 'message'], ['path']) &&\n string(value.code, 100) &&\n string(value.message, 1024) &&\n (value.path === undefined || string(value.path))\n\n/** Validate the bounded stdin/IPC report before it can become model instructions. */\nexport function assertRepositoryContext(\n value: unknown,\n teamId?: string,\n): asserts value is RepositoryContextReport {\n if (\n !object(value) ||\n !keys(value, [\n 'version',\n 'teamId',\n 'rootIdentity',\n 'target',\n 'capturedAt',\n 'fingerprint',\n 'instructions',\n 'commands',\n 'git',\n ]) ||\n value.version !== 1 ||\n !string(value.teamId, 256) ||\n (teamId !== undefined && value.teamId !== teamId) ||\n (value.rootIdentity !== null && !digest(value.rootIdentity)) ||\n !string(value.target) ||\n !count(value.capturedAt) ||\n !digest(value.fingerprint) ||\n !object(value.instructions) ||\n !object(value.commands) ||\n !object(value.git) ||\n Buffer.byteLength(JSON.stringify(value)) > 256 * 1024\n )\n throw new Error('Invalid repository context report')\n const instructions = value.instructions\n const commands = value.commands\n const git = value.git\n if (\n !keys(instructions, ['state', 'issues', 'files']) ||\n !keys(commands, ['state', 'issues', 'sources', 'candidates']) ||\n !keys(git, [\n 'state',\n 'root',\n 'branch',\n 'head',\n 'headState',\n 'staged',\n 'unstaged',\n 'untracked',\n 'conflicted',\n 'issues',\n ]) ||\n !['complete', 'incomplete'].includes(String(instructions.state)) ||\n !list(instructions.issues, 128, issue) ||\n !list(\n instructions.files,\n 17,\n (file) =>\n object(file) &&\n keys(file, ['path', 'scope', 'precedence', 'sha256', 'content']) &&\n string(file.path) &&\n string(file.scope) &&\n count(file.precedence) &&\n digest(file.sha256) &&\n string(file.content, 64 * 1024) &&\n Buffer.byteLength(file.content) <= 64 * 1024,\n ) ||\n !['complete', 'incomplete'].includes(String(commands.state)) ||\n !list(commands.issues, 128, issue) ||\n !list(\n commands.sources,\n 32,\n (source) =>\n object(source) &&\n keys(source, ['path', 'sha256']) &&\n string(source.path) &&\n digest(source.sha256),\n ) ||\n !list(\n commands.candidates,\n 32,\n (candidate) =>\n object(candidate) &&\n keys(candidate, [\n 'source',\n 'sha256',\n 'location',\n 'cwd',\n 'command',\n 'kind',\n 'packageManager',\n ]) &&\n string(candidate.source) &&\n digest(candidate.sha256) &&\n string(candidate.location) &&\n string(candidate.cwd) &&\n string(candidate.command) &&\n ['script', 'excerpt'].includes(String(candidate.kind)) &&\n [null, 'npm', 'pnpm', 'yarn'].includes(candidate.packageManager as string | null),\n ) ||\n !['available', 'unavailable', 'not_repository'].includes(String(git.state)) ||\n ![null, 'branch', 'detached', 'unborn'].includes(git.headState as string | null) ||\n ![git.root, git.branch, git.head].every((value) => value === null || string(value)) ||\n ![git.staged, git.unstaged, git.untracked, git.conflicted].every(count) ||\n !list(git.issues, 128, issue)\n )\n throw new Error('Invalid repository context report')\n const report = value as unknown as RepositoryContextReport\n if (\n report.instructions.files.reduce((bytes, file) => bytes + Buffer.byteLength(file.content), 0) >\n 128 * 1024 ||\n (report.instructions.state === 'complete' &&\n (report.rootIdentity === null || report.instructions.issues.length > 0))\n )\n throw new Error('Invalid repository instruction completeness')\n}\n","import { readCanonicalSessionFile } from '../../lib/result-source.ts'\nimport type { AskUser } from '../tools/ask-user.ts'\nimport { codingTools } from './coding.ts'\nimport type { CodingHost } from './coding-contract.ts'\nimport { type RepositoryContextHost, repositoryContextIntegration } from './repository-context.ts'\n// Bazilion → pi-coding-agent session bridge.\n//\n// `createBazilionSession` returns a fully-wired `AgentSession` suitable for\n// calling `session.prompt(text)` / `session.compact(instructions)` / etc.\n//\n// What we take ownership of (and hand to pi):\n// - cwd: the agent's team workspace path. With shell security off, Pi's\n// built-in coding tools use it as their working directory. With Docker\n// isolation on, Bazilion exposes only a same-name containerized `bash`\n// replacement and hides every host-backed coding file tool.\n// - agentDir: `<bazilion-home>/pi` — pi writes transient state here\n// (settings overrides, resource caches). We don't share it with the\n// user's global `~/.pi/agent` so a Bazilion install never clobbers an\n// independent pi CLI install.\n// - authStorage: `InMemoryAuthStorageBackend` pre-seeded with the resolved\n// API key for the agent's current provider. We never let pi read/write\n// its own auth file — secrets live in the daemon-owned `secrets` table\n// and reach us via `opts.apiKey` (initial) + `opts.refreshApiKey`\n// (OAuth refresher for long turns).\n// - modelRegistry: in-memory. Native providers (anthropic/openai/google/…)\n// come from pi's bundled catalog. For Bazilion-only providers\n// (`lmstudio`, `ollama`) we call `registerProvider(name, {baseUrl,\n// api: 'openai-completions', authHeader: false})` — matches the\n// openai-completions shim our pi-adapter has been using.\n// - sessionManager: `SessionManager.create(cwd, <agentDir>/sessions)`\n// writing JSONL to `~/.bazilion/agents/<id>/sessions/<sessionId>.jsonl`.\n// Crash-survival, append-only, branching, compaction entries — all owned\n// by pi now. Replaces our `agents.chat_messages` blob.\n// - settingsManager: in-memory. Bazilion controls auto-compaction\n// (disabled — we compact manually on user request via /compact) and\n// retry (enabled with Bazilion-tuned caps).\n//\n// What stays outside pi's purview:\n// - spawning agents / profiles / skills discovery (core/)\n// - workspaces registry & mount tracking (core/)\n// - inter-agent messaging, triggers, scheduler (core + apps/web)\n// - memory backend (we wrap it as a pi customTool via `createBazilionCustomTools`)\n\nimport { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\nimport type { ResolvedAgent } from '@bazilion/api-types'\nimport type { AgentMessage, ThinkingLevel } from '@earendil-works/pi-agent-core'\nimport {\n type AssistantMessage,\n type AssistantMessageEvent,\n createAssistantMessageEventStream,\n} from '@earendil-works/pi-ai'\nimport {\n type AgentSession,\n buildSessionContext,\n createAgentSession,\n DefaultResourceLoader,\n SessionManager,\n SettingsManager,\n type ToolDefinition,\n} from '@earendil-works/pi-coding-agent'\nimport type { BazilionDb, Paths } from '../../core/index.ts'\nimport { providerStateRepo } from '../../core/index.ts'\nimport type { MemoryBackend } from '../memory/types.ts'\nimport {\n createBazilionPiRuntime,\n createProtectedPiRuntime,\n piProviderName,\n providerApiKey,\n providerBaseUrl,\n resolvePiModel,\n} from '../providers/pi-runtime.ts'\nimport { createProviderRegistry, loadProviderConfigFromEnv } from '../providers/registry.ts'\nimport { buildSystemPrompt, loadPromptSkills } from '../session/prompt.ts'\nimport type { BashApprovalHost } from '../shell/approval.ts'\nimport { codingContainerCwd } from '../shell/coding.ts'\nimport type { ProtectedDockerRuntime } from '../shell/docker.ts'\nimport { createProtectedSessionShellTools, createSessionShellTools } from '../shell/tooling.ts'\nimport type {\n BrowserHost,\n InjectedMcpTool,\n McpHost,\n MessagingHost,\n UserMdHost,\n} from '../worker/ipc-protocol.ts'\nimport type {\n MinimalWorkerScratch,\n ProtectedProviderWorkerRuntime,\n ProtectedWorkerPaths,\n} from '../worker/runtime.ts'\nimport { protectedRuntimeSecrets, redactJsonValue } from '../worker/runtime.ts'\nimport { createBazilionCustomTools, createProtectedBazilionCustomTools } from './tools.ts'\n\nexport interface CreateBazilionSessionOptions {\n dockerLifecycle?: import('../shell/docker.ts').DockerResourceLifecycle\n preparedDocker?: ProtectedDockerRuntime\n repositoryContext?: import('@bazilion/api-types').RepositoryContextReport\n codingHost?: CodingHost\n repositoryContextHost?: RepositoryContextHost\n agent: ResolvedAgent\n paths: Paths\n /** Merged env (process.env + secrets) — produced via `mergeSecretsIntoEnv`. */\n env: NodeJS.ProcessEnv\n memory: MemoryBackend\n /**\n * Names of providers the user has explicitly enabled in /config.\n * Empty set means \"no per-provider gating configured\" — all providers pass.\n * Pre-computed by the daemon and handed in so the session never has to\n * touch the SQLite `provider_state` table itself.\n */\n enabledProviders: Set<string>\n /**\n * Optional host for inter-agent messaging. Wired from the worker's IPC\n * channel back to the daemon — workers no longer hold a SQLite handle of\n * their own. Omit to disable the messaging tools entirely (e.g. unit\n * tests that don't exercise inbox flows).\n */\n messagingHost?: MessagingHost\n /**\n * Optional host for the team-shared USER.md append tool. Like\n * `messagingHost`, this is wired via the worker's IPC channel. Omit to\n * disable the `user_md_append` tool.\n */\n userMdHost?: UserMdHost\n /**\n * Optional browser pool surface for the `browser_*` tools. Wired from the\n * worker's IPC channel back to the daemon (the Playwright session lives in\n * the daemon, persistent across turns). Omit to disable browser automation.\n */\n browserHost?: BrowserHost\n /**\n * Optional MCP connection-pool surface for proxied MCP tools, paired with\n * `mcpTools`. Wired via the worker's IPC channel. Omit when no MCP servers\n * are enabled.\n */\n mcpHost?: McpHost\n /** MCP tools discovered daemon-side, exposed as proxy tools alongside `mcpHost`. */\n mcpTools?: InjectedMcpTool[]\n /** If provided, enables the `deliver_file` tool — the agent's outbound file channel. */\n askUser?: AskUser\n fileSink?: import('../tools/deliver-file.ts').FileSink\n /** Turn-scoped bridge for dangerous bash commands. Omit to fail closed. */\n bashApprovalHost?: BashApprovalHost\n /**\n * Optional explicit API key for the agent's provider. Wins over any value\n * derived from `env`. Required for OAuth-backed providers (`openai-codex`)\n * since their credentials live in the daemon-owned `secrets` table, not\n * in env vars.\n */\n apiKey?: string\n /**\n * Optional callback for OAuth-backed providers whose access tokens may\n * expire mid-turn. When provided, pi calls it to refresh the JWT during\n * long tool-execution loops. Daemon-side callers (compact/context/truncate)\n * wire this directly against the secrets repo; worker turns call the same\n * daemon-owned refresher through their private, turn-scoped IPC channel.\n */\n refreshApiKey?: (providerName: string) => Promise<string>\n conversation?: import('@bazilion/api-types').ConversationTarget\n /** Internal restricted mode used by the learning reviewer. */\n restricted?: {\n systemPrompt: string\n tools: ToolDefinition[]\n sessionDir: string\n reasoningLevel: ResolvedAgent['reasoningLevel']\n }\n}\n\nexport interface BazilionSessionHandle {\n session: AgentSession\n /** Call when done — disposes listeners + closes the pi session. */\n dispose(): void\n}\n\nexport interface CreateProtectedBazilionSessionOptions {\n dockerLifecycle?: import('../shell/docker.ts').DockerResourceLifecycle\n repositoryContext?: import('@bazilion/api-types').RepositoryContextReport\n codingHost?: CodingHost\n repositoryContextHost?: RepositoryContextHost\n conversation: import('@bazilion/api-types').ConversationTarget\n agent: ResolvedAgent\n runtime: ProtectedProviderWorkerRuntime\n paths: ProtectedWorkerPaths\n scratch: MinimalWorkerScratch\n docker: ProtectedDockerRuntime\n memory: MemoryBackend\n messagingHost: MessagingHost\n userMdHost: UserMdHost\n askUser?: AskUser\n fileSink: import('../tools/deliver-file.ts').FileSink\n bashApprovalHost: BashApprovalHost\n refreshApiKey: (providerName: string) => Promise<string>\n}\n\nexport interface CreateRestrictedReviewSessionOptions {\n runtime: ProtectedProviderWorkerRuntime\n scratch: MinimalWorkerScratch\n systemPrompt: string\n tools: ToolDefinition[]\n refreshApiKey: (providerName: string) => Promise<string>\n}\n\nconst PROTECTED_MODEL_CWD = '/workspace'\nconst REVIEW_MODEL_CWD = '/review'\nconst PROTECTED_BASE_SYSTEM_PROMPT = `You are an assistant operating inside Bazilion's protected\nAgent runtime. Use only the tools exposed for this turn. Coding commands execute in an ephemeral,\nnetwork-disabled Docker container whose Team workspace is /workspace. Host paths and host tools are\nnot available. Be concise and show container paths clearly when working with files.`\n\n/**\n * Build a pi `AgentSession` using Bazilion's resolved agent + provider state.\n * The returned session is ready for `prompt()` / `compact()` / `reset()`.\n */\nexport async function createBazilionSession(\n opts: CreateBazilionSessionOptions,\n): Promise<BazilionSessionHandle> {\n const {\n agent,\n paths,\n env,\n memory,\n enabledProviders,\n messagingHost,\n userMdHost,\n browserHost,\n mcpHost,\n mcpTools,\n fileSink,\n askUser,\n bashApprovalHost,\n refreshApiKey,\n restricted,\n } = opts\n\n const { providerName, modelId } = splitModelString(agent.model)\n\n // Enabled-set gate — mirrors createProviderRegistry's check. We keep the\n // Bazilion-side enabled/disabled /config toggles authoritative even though\n // pi does its own provider resolution: we simply refuse to build a session\n // for a disabled provider. The set is pre-computed by the daemon (the\n // worker has no SQLite handle of its own).\n if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {\n throw new Error(`${providerName} provider is disabled — enable it on the /config page`)\n }\n\n // One public Pi runtime owns provider aliases, credentials, catalog lookup,\n // local-provider registration and arbitrary-model fallback for both direct\n // Provider.chat calls and full coding-agent sessions.\n const apiKey = opts.apiKey ?? providerApiKey(providerName, env)\n const baseUrl = providerBaseUrl(providerName, env)\n const modelRuntime = await createBazilionPiRuntime({\n providerName,\n env,\n ...(apiKey ? { apiKey } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n modelId,\n })\n const model = resolvePiModel(modelRuntime, providerName, modelId, baseUrl)\n\n // cwd is the agent's team directory. Every agent belongs to exactly one\n // team; the team's filesystem root is where work product lives. In host\n // mode Pi uses this as the coding tools' working directory (not a security\n // boundary). In Docker mode only the containerized bash is exposed and the\n // directory is its sole read/write bind mount. Private identity/soul files\n // live in `agent.dir` and are reached through scoped `home_*` tools.\n const cwd = agent.team.path\n if (!existsSync(cwd)) mkdirSync(cwd, { recursive: true })\n\n const promptSkills = restricted ? [] : loadPromptSkills(paths.skillsDir, agent.skills)\n const uploadsDir = join(agent.agent.dir, 'uploads')\n const shellTools = restricted\n ? null\n : createSessionShellTools(cwd, env, {\n preparedDocker: opts.preparedDocker,\n dockerLifecycle: opts.dockerLifecycle,\n ...(existsSync(uploadsDir) ? { inputsDir: uploadsDir } : {}),\n skillMounts: promptSkills.map((skill) => ({\n source: skill.hostDir,\n target: skill.sandboxDir,\n })),\n approvalHost: bashApprovalHost,\n })\n\n // Session file under the agent's own directory. Keeping it under\n // `agents/<id>/sessions/` makes `bazilion uninstall` (reset tier) already\n // clean them up without changes.\n //\n // Normal sessions use the exact daemon-admitted target. Reviews remain private.\n const sessionDir = restricted?.sessionDir ?? join(paths.agentDir(agent.agent.id), 'sessions')\n mkdirSync(sessionDir, { recursive: true })\n const existing = restricted ? null : explicitConversationFile(sessionDir, opts.conversation)\n const sessionManager = existing\n ? SessionManager.open(existing, sessionDir, cwd)\n : SessionManager.create(cwd, sessionDir)\n\n // In-memory settings: auto-compaction off (we trigger compaction manually\n // via /compact), retry on with Bazilion-tuned caps matching what withRetry\n // used to apply before pi-adoption.\n const settingsManager = SettingsManager.inMemory({\n compaction: { enabled: false },\n retry: {\n enabled: true,\n maxRetries: 2,\n baseDelayMs: 500,\n provider: { maxRetryDelayMs: 8_000 },\n },\n })\n\n // Bazilion-authored system prompt becomes an `appendSystemPrompt` entry.\n // Pi keeps its default base (which lists built-in tools + guidelines), our\n // profile content (SOUL.md / IDENTITY.md / workspaces / memory hint) is\n // concatenated after it. This is the same injection hook pi extensions use.\n const bazilionPrompt =\n restricted?.systemPrompt ??\n buildSystemPrompt(agent, {\n skills: promptSkills,\n sandboxMode: shellTools?.config.sandboxMode ?? 'off',\n })\n const repository =\n !restricted && opts.repositoryContext && opts.repositoryContextHost\n ? repositoryContextIntegration(\n opts.repositoryContext,\n opts.repositoryContextHost,\n shellTools?.config.sandboxMode === 'docker' ? '/workspace' : cwd,\n )\n : undefined\n const resourceLoader = new DefaultResourceLoader({\n cwd,\n agentDir: join(paths.home, 'pi'),\n settingsManager,\n noExtensions: true,\n noSkills: true,\n noPromptTemplates: true,\n noThemes: true,\n noContextFiles: true,\n appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : undefined,\n })\n if (repository) resourceLoader.getAgentsFiles = repository.files\n await resourceLoader.reload()\n\n // Tool allowlist: pi's `tools` option is exclusive when provided — only\n // the listed names are enabled, regardless of what's in `customTools`.\n // So we enumerate the host-backed Pi tools allowed by the shell policy and\n // every Bazilion custom tool we want the LLM to see. Docker mode contributes\n // a custom same-name `bash` and zero host tools; missing its name (or any\n // memory/messaging/web/bootstrap name) would silently drop it.\n const bazilionTools = restricted\n ? restricted.tools\n : createBazilionCustomTools({\n agent,\n memory,\n messagingHost,\n userMdHost,\n browserHost,\n mcpHost,\n mcpTools,\n fileSink,\n askUser,\n sessionId: sessionManager.getSessionId(),\n env,\n })\n if (repository) bazilionTools.push(repository.tool)\n if (\n repository &&\n opts.codingHost &&\n shellTools &&\n shellTools.config.sandboxMode === 'docker' &&\n !opts.preparedDocker\n )\n throw new Error('Coding commands require the admitted Docker runtime')\n if (repository && opts.codingHost && shellTools)\n bazilionTools.push(\n ...codingTools({\n host: opts.codingHost,\n root: cwd,\n context: repository.resolve,\n docker: opts.preparedDocker,\n lifecycle: opts.dockerLifecycle,\n approval: shellTools.config.approvalMode === 'dangerous',\n approvalHost: bashApprovalHost,\n secrets: Object.entries(env)\n .filter(([key]) => /KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH/i.test(key))\n .map(([, value]) => value ?? ''),\n }),\n )\n const customTools = shellTools?.customBash\n ? [...bazilionTools, shellTools.customBash]\n : bazilionTools\n const allowedTools = [...(shellTools?.hostToolNames ?? []), ...customTools.map((t) => t.name)]\n\n const { session } = await createAgentSession({\n cwd,\n agentDir: join(paths.home, 'pi'),\n model,\n thinkingLevel: toPiThinkingLevel(restricted?.reasoningLevel ?? agent.reasoningLevel),\n tools: allowedTools,\n customTools,\n sessionManager,\n settingsManager,\n modelRuntime,\n resourceLoader,\n })\n\n repository?.bind(session, allowedTools)\n\n // OAuth providers: wire pi's per-request `getApiKey` callback so the JWT\n // gets refreshed *during* a long tool-execution loop, not just at the\n // start of the turn. This is exactly the use case pi-agent-core's doc\n // calls out for this hook (\"short-lived OAuth tokens that may expire\n // during long-running tool execution phases\"). Caller supplies the\n // refresher because only they have access to the secrets table.\n if (refreshApiKey) {\n session.agent.getApiKey = async (requestedProvider) => {\n if (requestedProvider !== piProviderName(providerName)) return undefined\n try {\n return await refreshApiKey(providerName)\n } catch {\n // Stale/removed credentials mid-session → return undefined so pi\n // surfaces a \"no auth\" error cleanly instead of us throwing out of\n // the provider callback (which would drag down the whole turn).\n return undefined\n }\n }\n }\n\n return {\n session,\n dispose() {\n session.dispose()\n },\n }\n}\n\n/** Build the closed Docker-only normal session used by protected invocations. */\nexport async function createProtectedBazilionSession(\n opts: CreateProtectedBazilionSessionOptions,\n): Promise<BazilionSessionHandle> {\n const { providerName, modelId } = splitModelString(opts.agent.model)\n if (providerName !== opts.runtime.providerName || modelId !== opts.runtime.modelId) {\n throw new Error('protected Agent model does not match the prepared provider runtime')\n }\n if (opts.agent.agent.dir !== opts.paths.agentDir || opts.agent.team.path !== opts.paths.teamDir) {\n throw new Error('protected session paths do not match the resolved Agent')\n }\n if (!existsSync(opts.paths.teamDir)) {\n throw new Error('protected Team workspace is unavailable')\n }\n\n const modelRuntime = await createProtectedPiRuntime({\n providerName: opts.runtime.providerName,\n modelId: opts.runtime.modelId,\n apiKey: opts.runtime.apiKey,\n baseUrl: opts.runtime.baseUrl,\n credentialEnv: materializeProtectedCredentialFile(opts.runtime, opts.scratch),\n })\n const model = resolvePiModel(\n modelRuntime,\n opts.runtime.providerName,\n opts.runtime.modelId,\n opts.runtime.baseUrl,\n )\n const shellTools = createProtectedSessionShellTools(\n opts.paths.teamDir,\n opts.docker,\n opts.bashApprovalHost,\n opts.dockerLifecycle,\n )\n const modelCwd = codingContainerCwd(opts.docker.coding?.cwd ?? '.')\n\n mkdirSync(opts.paths.sessionDir, { recursive: true })\n const existing = explicitConversationFile(opts.paths.sessionDir, opts.conversation)\n const sessionManager = existing\n ? SessionManager.open(existing, opts.paths.sessionDir, opts.paths.teamDir)\n : SessionManager.create(opts.paths.teamDir, opts.paths.sessionDir)\n const settingsManager = createInMemorySettingsManager()\n const bazilionPrompt = redactJsonValue(\n buildSystemPrompt(opts.agent, {\n skills: opts.paths.skills,\n sandboxMode: 'docker',\n homeDocuments: opts.paths.homeDocuments,\n }),\n protectedRuntimeSecrets(opts.runtime),\n )\n const repository =\n opts.repositoryContext && opts.repositoryContextHost\n ? repositoryContextIntegration(\n redactJsonValue(opts.repositoryContext, protectedRuntimeSecrets(opts.runtime)),\n opts.repositoryContextHost,\n PROTECTED_MODEL_CWD,\n )\n : undefined\n const resourceLoader = new DefaultResourceLoader({\n cwd: modelCwd,\n agentDir: opts.scratch.piAgentDir,\n settingsManager,\n noExtensions: true,\n noSkills: true,\n noPromptTemplates: true,\n noThemes: true,\n noContextFiles: true,\n systemPrompt: PROTECTED_BASE_SYSTEM_PROMPT,\n appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : undefined,\n })\n if (repository) resourceLoader.getAgentsFiles = repository.files\n await resourceLoader.reload()\n\n const bazilionTools = createProtectedBazilionCustomTools({\n agent: opts.agent,\n memory: opts.memory,\n messagingHost: opts.messagingHost,\n userMdHost: opts.userMdHost,\n fileSink: opts.fileSink,\n askUser: opts.askUser,\n sessionId: sessionManager.getSessionId(),\n })\n if (repository) bazilionTools.push(repository.tool)\n if (repository && opts.codingHost)\n bazilionTools.push(\n ...codingTools({\n host: opts.codingHost,\n root: opts.paths.teamDir,\n context: repository.resolve,\n docker: opts.docker,\n lifecycle: opts.dockerLifecycle,\n approval: true,\n approvalHost: opts.bashApprovalHost,\n secrets: protectedRuntimeSecrets(opts.runtime),\n }),\n )\n if (!shellTools.customBash) throw new Error('protected Docker bash tool is unavailable')\n const customTools = [...bazilionTools, shellTools.customBash]\n const { session } = await createAgentSession({\n cwd: modelCwd,\n agentDir: opts.scratch.piAgentDir,\n model,\n thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),\n tools: customTools.map((tool) => tool.name),\n customTools,\n sessionManager,\n settingsManager,\n modelRuntime,\n resourceLoader,\n })\n repository?.bind(\n session,\n customTools.map((tool) => tool.name),\n )\n installProtectedCredentialBoundary(\n session,\n opts.runtime.providerName,\n protectedRuntimeSecrets(opts.runtime),\n opts.refreshApiKey,\n )\n return sessionHandle(session)\n}\n\n/** Build a scratch-only reviewer with exactly the caller-supplied proposal tool. */\nexport async function createRestrictedReviewSession(\n opts: CreateRestrictedReviewSessionOptions,\n): Promise<BazilionSessionHandle> {\n const modelRuntime = await createProtectedPiRuntime({\n providerName: opts.runtime.providerName,\n modelId: opts.runtime.modelId,\n apiKey: opts.runtime.apiKey,\n baseUrl: opts.runtime.baseUrl,\n credentialEnv: materializeProtectedCredentialFile(opts.runtime, opts.scratch),\n })\n const model = resolvePiModel(\n modelRuntime,\n opts.runtime.providerName,\n opts.runtime.modelId,\n opts.runtime.baseUrl,\n )\n const settingsManager = createInMemorySettingsManager()\n const sessionManager = SessionManager.create(\n opts.scratch.reviewCwd,\n opts.scratch.reviewSessionDir,\n )\n const resourceLoader = new DefaultResourceLoader({\n cwd: REVIEW_MODEL_CWD,\n agentDir: opts.scratch.piAgentDir,\n settingsManager,\n noExtensions: true,\n noSkills: true,\n noPromptTemplates: true,\n noThemes: true,\n noContextFiles: true,\n systemPrompt: opts.systemPrompt,\n })\n await resourceLoader.reload()\n\n const { session } = await createAgentSession({\n cwd: REVIEW_MODEL_CWD,\n agentDir: opts.scratch.piAgentDir,\n model,\n thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),\n tools: opts.tools.map((tool) => tool.name),\n customTools: opts.tools,\n sessionManager,\n settingsManager,\n modelRuntime,\n resourceLoader,\n })\n installProtectedCredentialBoundary(\n session,\n opts.runtime.providerName,\n protectedRuntimeSecrets(opts.runtime),\n opts.refreshApiKey,\n )\n return sessionHandle(session)\n}\n\nfunction materializeProtectedCredentialFile(\n runtime: ProtectedProviderWorkerRuntime,\n scratch: MinimalWorkerScratch,\n): Array<{ name: string; value: string }> | undefined {\n const fields = [...(runtime.credentialEnv ?? [])]\n if (runtime.credentialFile) {\n const path = join(scratch.tempDir, 'provider-credential.json')\n writeFileSync(path, runtime.credentialFile.content, {\n encoding: 'utf8',\n mode: 0o600,\n flag: 'wx',\n })\n fields.push({ name: runtime.credentialFile.envName, value: path })\n }\n return fields.length > 0 ? fields : undefined\n}\n\n// --- helpers ---\n\nfunction createInMemorySettingsManager(): SettingsManager {\n return SettingsManager.inMemory({\n compaction: { enabled: false },\n retry: {\n enabled: true,\n maxRetries: 2,\n baseDelayMs: 500,\n provider: { maxRetryDelayMs: 8_000 },\n },\n })\n}\n\nexport function installProtectedCredentialBoundary(\n session: AgentSession,\n providerName: string,\n initialSecrets: string[],\n refreshApiKey: (providerName: string) => Promise<string>,\n): void {\n const activeTokens = [...new Set(initialSecrets.filter(Boolean))]\n session.agent.state.messages = redactJsonValue(session.agent.state.messages, activeTokens)\n const originalStream = session.agent.streamFunction\n session.agent.streamFunction = async (model, context, options) => {\n let upstream: Awaited<ReturnType<typeof originalStream>>\n try {\n upstream = await originalStream(model, context, options)\n } catch (error) {\n throw credentialSafeError(error, activeTokens)\n }\n const sanitized = createAssistantMessageEventStream()\n void (async () => {\n let latestPartial: AssistantMessage | undefined\n try {\n for await (const event of upstream) {\n const safeEvent = redactJsonValue(event, activeTokens) as AssistantMessageEvent\n if ('partial' in safeEvent) latestPartial = safeEvent.partial\n sanitized.push(safeEvent)\n }\n } catch (error) {\n const safeError = credentialSafeError(error, activeTokens)\n sanitized.push({\n type: 'error',\n reason: 'error',\n error: credentialSafeAssistantError(model, latestPartial, safeError, activeTokens),\n })\n } finally {\n sanitized.end()\n }\n })()\n return sanitized\n }\n\n const originalAfterToolCall = session.agent.afterToolCall\n session.agent.afterToolCall = async (context, signal) => {\n let prior: Awaited<ReturnType<NonNullable<typeof originalAfterToolCall>>> | undefined\n try {\n prior = await originalAfterToolCall?.(context, signal)\n } catch (error) {\n throw credentialSafeError(error, activeTokens)\n }\n return {\n ...prior,\n content: redactJsonValue(prior?.content ?? context.result.content, activeTokens),\n details: redactJsonValue(prior?.details ?? context.result.details, activeTokens),\n }\n }\n\n session.agent.getApiKey = async (requestedProvider) => {\n if (requestedProvider !== providerName) return undefined\n try {\n const token = await refreshApiKey(providerName)\n if (token && !activeTokens.includes(token)) activeTokens.push(token)\n return token || undefined\n } catch {\n return undefined\n }\n }\n}\n\nfunction credentialSafeError(error: unknown, activeTokens: readonly string[]): Error {\n const message = error instanceof Error ? error.message : String(error)\n return new Error(redactJsonValue(message, activeTokens))\n}\n\nfunction credentialSafeAssistantError(\n model: {\n api: AssistantMessage['api']\n provider: AssistantMessage['provider']\n id: string\n },\n latestPartial: AssistantMessage | undefined,\n error: Error,\n activeTokens: readonly string[],\n): AssistantMessage {\n const safePartial = latestPartial\n ? redactJsonValue(latestPartial, activeTokens)\n : {\n role: 'assistant' as const,\n content: [],\n api: model.api,\n provider: model.provider,\n model: model.id,\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n timestamp: Date.now(),\n }\n return {\n ...safePartial,\n stopReason: 'error',\n errorMessage: error.message,\n timestamp: Date.now(),\n }\n}\n\nfunction sessionHandle(session: AgentSession): BazilionSessionHandle {\n return {\n session,\n dispose() {\n session.dispose()\n },\n }\n}\n\nfunction splitModelString(s: string): { providerName: string; modelId: string } {\n const idx = s.indexOf(':')\n if (idx === -1) {\n throw new Error(`invalid model string \"${s}\": expected \"provider:model\"`)\n }\n return { providerName: s.slice(0, idx), modelId: s.slice(idx + 1) }\n}\n\nfunction toPiThinkingLevel(level: string): ThinkingLevel {\n switch (level) {\n case 'off':\n case 'minimal':\n case 'low':\n case 'medium':\n case 'high':\n case 'xhigh':\n return level\n default:\n return 'medium'\n }\n}\n\n/**\n * Re-exported for callers that want to check whether a provider is\n * Bazilion-enabled before even trying to spawn a session (e.g. /context\n * endpoint which builds a session just to enumerate tools).\n */\nexport function isProviderEnabled(db: BazilionDb, providerName: string): boolean {\n const enabled = providerStateRepo.listEnabled(db)\n return enabled.size === 0 || enabled.has(providerName)\n}\n\n/**\n * Escape hatch for callers that need the raw provider registry (e.g. the\n * current /api/providers/test endpoint). Keeps that one endpoint on our\n * existing non-pi path until we migrate it in a follow-up.\n */\nexport function loadEnabledRegistry(db: BazilionDb, authToken: string, env: NodeJS.ProcessEnv) {\n return createProviderRegistry(loadProviderConfigFromEnv(env, { db, authToken }), {\n enabledSet: providerStateRepo.listEnabled(db),\n })\n}\n\n/**\n * Read the most recent session file for an agent *without* spawning a full\n * AgentSession, and return the resolved provider-message view. Used for SSR\n * page loads that only need to render the canonical transcript — no need\n * to boot pi just to inspect the transcript.\n *\n * Returns an empty array when the agent has no prior session (fresh spawn,\n * or post-/reset).\n */\nexport function loadInitialMessages(\n agent: ResolvedAgent,\n paths: Paths,\n target?: import('@bazilion/api-types').ConversationTarget | null,\n): AgentMessage[] {\n if (!target) return []\n const entries = readCanonicalSessionFile(\n join(paths.agentDir(agent.agent.id), 'sessions'),\n target.filename,\n target.id,\n )\n return buildSessionContext(entries.filter((entry) => entry.type !== 'session')).messages\n}\n\n/** Read one named canonical session for evidence display without creating a live session. */\nexport function loadSessionMessages(\n agent: ResolvedAgent,\n paths: Paths,\n sessionId: string,\n): AgentMessage[] {\n if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) return []\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n const file = join(sessionDir, `${sessionId}.jsonl`)\n if (!existsSync(file) || !existsSync(agent.team.path)) return []\n try {\n return SessionManager.open(file, sessionDir).buildSessionContext().messages\n } catch {\n return []\n }\n}\n\n/**\n * Cheap \"has the session changed?\" probe for polling clients (the web chat\n * stale-tab banner). Returns the most recent session file's basename plus\n * byte size — append-only JSONL, so either value moving means new activity.\n * Returns `{ file: null, size: 0 }` for agents that have never had a turn.\n */\nexport function loadSessionHead(\n agent: ResolvedAgent,\n paths: Paths,\n target?: import('@bazilion/api-types').ConversationTarget | null,\n): { file: string | null; size: number } {\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n if (!existsSync(sessionDir)) return { file: null, size: 0 }\n const recent = target ? explicitConversationFile(sessionDir, target) : null\n if (!recent) return { file: null, size: 0 }\n try {\n const s = statSync(recent)\n return { file: basename(recent), size: s.size }\n } catch {\n return { file: null, size: 0 }\n }\n}\n\n/**\n * Test helper: seed a pi session file for an agent with `n` synthetic\n * user/assistant message pairs. Writes a real JSONL entry tree via\n * SessionManager so round-tripping through pi's own reader stays honest.\n * Exported from runtime rather than lived in tests because tests in apps/cli\n * can't directly import pi packages (not a direct dep).\n */\nexport function seedSessionForTest(\n agent: ResolvedAgent,\n paths: Paths,\n messages: Array<{ role: 'user' | 'assistant'; text: string }>,\n conversation: import('@bazilion/api-types').ConversationTarget,\n): void {\n const cwd = agent.team.path\n if (!existsSync(cwd)) mkdirSync(cwd, { recursive: true })\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n mkdirSync(sessionDir, { recursive: true })\n const sm = SessionManager.open(\n explicitConversationFile(sessionDir, conversation),\n sessionDir,\n cwd,\n )\n const now = Date.now()\n messages.forEach((m, i) => {\n if (m.role === 'user') {\n sm.appendMessage({\n role: 'user',\n content: [{ type: 'text', text: m.text }],\n timestamp: now + i,\n })\n } else {\n sm.appendMessage({\n role: 'assistant',\n content: [{ type: 'text', text: m.text }],\n api: 'openai-completions',\n provider: 'lmstudio',\n model: 'test-model',\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason: 'stop',\n timestamp: now + i,\n })\n }\n })\n}\n\n/**\n * Test helper: count message entries on the current leaf's branch of the\n * agent's explicitly selected session file. Returns 0 when no session file exists.\n */\nexport function countSessionMessagesForTest(\n agent: ResolvedAgent,\n paths: Paths,\n conversation: import('@bazilion/api-types').ConversationTarget | null | undefined,\n): number {\n const sessionDir = join(paths.agentDir(agent.agent.id), 'sessions')\n if (!conversation) return 0\n const recent = explicitConversationFile(sessionDir, conversation)\n const cwd = agent.team.path\n try {\n const sm = SessionManager.open(recent, sessionDir, cwd)\n return sm.getBranch().filter((e) => e.type === 'message').length\n } catch {\n return 0\n }\n}\n\n/** Never fall back to directory activity when an admitted session is unavailable. */\nfunction explicitConversationFile(\n directory: string,\n target: import('@bazilion/api-types').ConversationTarget | undefined,\n): string {\n if (!target || !/^[0-9a-f-]{36}$/i.test(target.id) || target.filename !== `${target.id}.jsonl`)\n throw new Error('Explicit conversation target is required')\n const file = join(directory, target.filename)\n // Validate bounded bytes before Pi can repair or recreate a session.\n readCanonicalSessionFile(directory, target.filename, target.id)\n return file\n}\n","import type { Api, Model } from '@earendil-works/pi-ai'\nimport { InMemoryCredentialStore } from '@earendil-works/pi-ai'\nimport { ModelRuntime } from '@earendil-works/pi-coding-agent'\n\nconst PROVIDER_ALIASES: Record<string, string> = {\n bedrock: 'amazon-bedrock',\n 'azure-openai': 'azure-openai-responses',\n}\n\nexport const PROTECTED_PROVIDER_NAMES = [\n 'anthropic',\n 'openai',\n 'openai-codex',\n 'google',\n 'google-vertex',\n 'azure-openai',\n 'bedrock',\n 'mistral',\n 'groq',\n 'cerebras',\n 'xai',\n 'zai',\n 'huggingface',\n 'openrouter',\n 'vercel-ai-gateway',\n 'deepseek',\n 'fireworks',\n 'together',\n 'baseten',\n 'moonshotai',\n 'moonshotai-cn',\n 'kimi-coding',\n 'minimax',\n 'minimax-cn',\n 'qwen-token-plan',\n 'qwen-token-plan-cn',\n 'qwen-token-plan-individual',\n 'xiaomi',\n 'xiaomi-token-plan-ams',\n 'xiaomi-token-plan-cn',\n 'xiaomi-token-plan-sgp',\n 'ant-ling',\n 'nvidia',\n 'opencode',\n 'opencode-go',\n 'zai-coding-cn',\n 'github-copilot',\n 'cloudflare-ai-gateway',\n 'cloudflare-workers-ai',\n 'lmstudio',\n 'ollama',\n 'llamacpp',\n] as const\n\nexport type ProtectedProviderName = (typeof PROTECTED_PROVIDER_NAMES)[number]\n\nexport const LOCAL_PROVIDERS: Record<\n string,\n { baseUrl: string; apiKey: string; authHeader: boolean }\n> = {\n lmstudio: {\n baseUrl: 'http://127.0.0.1:1234/v1',\n apiKey: 'lm-studio',\n authHeader: false,\n },\n ollama: { baseUrl: 'http://127.0.0.1:11434/v1', apiKey: 'ollama', authHeader: false },\n llamacpp: { baseUrl: 'http://127.0.0.1:8080/v1', apiKey: 'no-key', authHeader: true },\n}\n\nexport const PROVIDER_API_KEY_ENV: Record<string, readonly string[]> = {\n anthropic: ['ANTHROPIC_OAUTH_TOKEN', 'ANTHROPIC_API_KEY'],\n openai: ['OPENAI_API_KEY'],\n google: ['GEMINI_API_KEY'],\n 'azure-openai': ['AZURE_OPENAI_API_KEY'],\n mistral: ['MISTRAL_API_KEY'],\n groq: ['GROQ_API_KEY'],\n cerebras: ['CEREBRAS_API_KEY'],\n xai: ['XAI_API_KEY'],\n zai: ['ZAI_API_KEY'],\n huggingface: ['HF_TOKEN'],\n openrouter: ['OPENROUTER_API_KEY'],\n 'vercel-ai-gateway': ['AI_GATEWAY_API_KEY'],\n deepseek: ['DEEPSEEK_API_KEY'],\n fireworks: ['FIREWORKS_API_KEY'],\n together: ['TOGETHER_API_KEY'],\n baseten: ['BASETEN_API_KEY'],\n moonshotai: ['MOONSHOT_API_KEY'],\n 'moonshotai-cn': ['MOONSHOT_CN_API_KEY'],\n 'kimi-coding': ['KIMI_API_KEY'],\n minimax: ['MINIMAX_API_KEY'],\n 'minimax-cn': ['MINIMAX_CN_API_KEY'],\n 'qwen-token-plan': ['QWEN_TOKEN_PLAN_API_KEY'],\n 'qwen-token-plan-cn': ['QWEN_TOKEN_PLAN_CN_API_KEY'],\n 'qwen-token-plan-individual': ['QWEN_TOKEN_PLAN_API_KEY'],\n xiaomi: ['XIAOMI_API_KEY'],\n 'xiaomi-token-plan-ams': ['XIAOMI_TOKEN_PLAN_AMS_API_KEY'],\n 'xiaomi-token-plan-cn': ['XIAOMI_TOKEN_PLAN_CN_API_KEY'],\n 'xiaomi-token-plan-sgp': ['XIAOMI_TOKEN_PLAN_SGP_API_KEY'],\n 'ant-ling': ['ANT_LING_API_KEY'],\n nvidia: ['NVIDIA_API_KEY'],\n opencode: ['OPENCODE_API_KEY'],\n 'opencode-go': ['OPENCODE_GO_API_KEY'],\n 'zai-coding-cn': ['ZAI_CODING_CN_API_KEY'],\n 'github-copilot': ['COPILOT_GITHUB_TOKEN'],\n 'cloudflare-ai-gateway': ['CLOUDFLARE_API_KEY'],\n 'cloudflare-workers-ai': ['CLOUDFLARE_API_KEY'],\n lmstudio: ['LMSTUDIO_API_KEY'],\n ollama: ['OLLAMA_API_KEY'],\n llamacpp: ['LLAMACPP_API_KEY'],\n}\n\nexport const PROVIDER_CREDENTIAL_ENV: Record<string, readonly string[]> = {\n bedrock: [\n 'AWS_PROFILE',\n 'AWS_BEARER_TOKEN_BEDROCK',\n 'AWS_ACCESS_KEY_ID',\n 'AWS_SECRET_ACCESS_KEY',\n 'AWS_SESSION_TOKEN',\n 'AWS_REGION',\n 'AWS_DEFAULT_REGION',\n ],\n 'google-vertex': [\n 'GOOGLE_CLOUD_PROJECT',\n 'GOOGLE_CLOUD_LOCATION',\n 'GOOGLE_APPLICATION_CREDENTIALS',\n ],\n 'cloudflare-ai-gateway': ['CLOUDFLARE_API_KEY', 'CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_GATEWAY_ID'],\n 'cloudflare-workers-ai': ['CLOUDFLARE_API_KEY', 'CLOUDFLARE_ACCOUNT_ID'],\n}\n\nexport interface BazilionPiRuntimeOptions {\n providerName: string\n env: NodeJS.ProcessEnv\n apiKey?: string\n baseUrl?: string\n modelId?: string\n}\n\nexport interface OpenAICodexPiRuntimeOptions {\n providerName: 'openai-codex'\n modelId: string\n accessToken: string\n}\n\nexport interface ProtectedPiRuntimeOptions {\n providerName: ProtectedProviderName\n modelId: string\n apiKey?: string\n baseUrl?: string\n credentialEnv?: Array<{ name: string; value: string }>\n}\n\nexport function piProviderName(providerName: string): string {\n return PROVIDER_ALIASES[providerName] ?? providerName\n}\n\nexport function providerApiKey(providerName: string, env: NodeJS.ProcessEnv): string | undefined {\n for (const name of PROVIDER_API_KEY_ENV[providerName] ?? []) {\n const value = env[name]\n if (value) return value\n }\n return LOCAL_PROVIDERS[providerName]?.apiKey\n}\n\nexport function providerBaseUrl(providerName: string, env: NodeJS.ProcessEnv): string | undefined {\n switch (providerName) {\n case 'lmstudio':\n return env.LMSTUDIO_URL ?? LOCAL_PROVIDERS.lmstudio?.baseUrl\n case 'ollama':\n return env.OLLAMA_URL ?? LOCAL_PROVIDERS.ollama?.baseUrl\n case 'llamacpp':\n return env.LLAMACPP_URL ?? LOCAL_PROVIDERS.llamacpp?.baseUrl\n case 'vercel-ai-gateway':\n return env.AI_GATEWAY_BASE_URL\n default:\n return undefined\n }\n}\n\nexport function providerCredentialEnv(\n providerName: string,\n env: NodeJS.ProcessEnv,\n): Record<string, string> | undefined {\n const result: Record<string, string> = {}\n for (const name of PROVIDER_CREDENTIAL_ENV[providerName] ?? []) {\n const value = env[name]\n if (value !== undefined) result[name] = value\n }\n return Object.keys(result).length > 0 ? result : undefined\n}\n\nfunction fallbackApi(providerName: string): Api {\n switch (providerName) {\n case 'anthropic':\n return 'anthropic-messages'\n case 'google':\n return 'google-generative-ai'\n case 'google-vertex':\n return 'google-vertex'\n case 'azure-openai':\n return 'azure-openai-responses'\n case 'bedrock':\n return 'bedrock-converse-stream'\n case 'openai-codex':\n return 'openai-codex-responses'\n default:\n return 'openai-completions'\n }\n}\n\nexport function fallbackPiModel(\n providerName: string,\n modelId: string,\n baseUrl?: string,\n): Model<Api> {\n return {\n id: modelId,\n name: modelId,\n api: fallbackApi(providerName),\n provider: piProviderName(providerName),\n baseUrl: baseUrl ?? '',\n reasoning: false,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 32_768,\n maxTokens: 4_096,\n }\n}\n\nexport async function createBazilionPiRuntime(\n options: BazilionPiRuntimeOptions,\n): Promise<ModelRuntime> {\n const providerName = options.providerName\n const canonicalName = piProviderName(providerName)\n const credentials = new InMemoryCredentialStore()\n const apiKey = options.apiKey ?? providerApiKey(providerName, options.env)\n const credentialEnv = providerCredentialEnv(providerName, options.env)\n\n if (apiKey || credentialEnv) {\n await credentials.modify(canonicalName, async () =>\n canonicalName === 'openai-codex' && apiKey\n ? {\n type: 'oauth',\n access: apiKey,\n // Bazilion owns refresh outside Pi. This deliberately empty value\n // satisfies Pi's canonical OAuth shape but is never used because\n // the token is long-lived only inside this one-shot runtime and\n // mid-turn replacement is supplied through Agent.getApiKey.\n refresh: '',\n expires: Number.MAX_SAFE_INTEGER,\n }\n : {\n type: 'api_key',\n ...(apiKey ? { key: apiKey } : {}),\n ...(credentialEnv ? { env: credentialEnv } : {}),\n },\n )\n }\n\n const runtime = await ModelRuntime.create({ credentials, modelsPath: null })\n const local = LOCAL_PROVIDERS[providerName]\n const baseUrl = options.baseUrl ?? providerBaseUrl(providerName, options.env)\n\n if (local) {\n const modelId = options.modelId ?? 'model-name'\n const model = fallbackPiModel(providerName, modelId, baseUrl ?? local.baseUrl)\n runtime.registerProvider(canonicalName, {\n baseUrl: baseUrl ?? local.baseUrl,\n api: 'openai-completions',\n authHeader: local.authHeader,\n apiKey: apiKey ?? local.apiKey,\n models: [model],\n })\n } else if (baseUrl) {\n runtime.registerProvider(canonicalName, { baseUrl })\n }\n\n return runtime\n}\n\n/**\n * Closed provider runtime for protected/review workers. It cannot consult an\n * environment record or register a non-Codex provider.\n */\nexport async function createOpenAICodexPiRuntime(\n options: OpenAICodexPiRuntimeOptions,\n): Promise<ModelRuntime> {\n if (options.providerName !== 'openai-codex') {\n throw new Error('protected execution supports only openai-codex')\n }\n if (!options.modelId.trim()) throw new Error('openai-codex model id is required')\n if (!options.accessToken.trim()) throw new Error('openai-codex access token is required')\n\n const credentials = new InMemoryCredentialStore()\n await credentials.modify('openai-codex', async () => ({\n type: 'oauth',\n access: options.accessToken,\n // The daemon-owned refresh credential never enters this process. Pi only\n // needs a non-expiring in-memory access-token projection for its startup\n // auth gate; Agent.getApiKey performs every real mid-turn refresh over IPC.\n refresh: '',\n expires: Number.MAX_SAFE_INTEGER,\n }))\n return ModelRuntime.create({ credentials, modelsPath: null })\n}\n\n/** Build a protected runtime only from the daemon's explicit wire projection. */\nexport async function createProtectedPiRuntime(\n options: ProtectedPiRuntimeOptions,\n): Promise<ModelRuntime> {\n const env: NodeJS.ProcessEnv = {}\n for (const field of options.credentialEnv ?? []) env[field.name] = field.value\n return createBazilionPiRuntime({\n providerName: options.providerName,\n modelId: options.modelId,\n env,\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n })\n}\n\nexport function resolvePiModel(\n runtime: ModelRuntime,\n providerName: string,\n modelId: string,\n baseUrl?: string,\n): Model<Api> {\n const known = runtime.getModel(piProviderName(providerName), modelId)\n if (known) return baseUrl ? { ...known, baseUrl } : known\n return fallbackPiModel(providerName, modelId, baseUrl)\n}\n","// OpenAI ChatGPT / Codex OAuth — token storage + refresh on top of pi-ai.\n//\n// Pi-ai ships a complete OAuth flow for the ChatGPT backend through its\n// `openai-codex` provider, so this module is\n// thin: it adapts the credential I/O to Bazilion's encrypted secrets store\n// and exposes a single `loadAccessToken(db, authToken)` call that refreshes\n// when the token is about to expire.\n//\n// Storage: Pi's complete OAuth credential blob lives under the\n// secrets key `OPENAI_CODEX_OAUTH` in the `secrets` table. The blob is\n// never copied into the env (unlike plain-API-key providers) — refresh is\n// stateful, so every call reads and writes through the live secrets store.\n\nimport type { OpenAICodexStatus } from '@bazilion/api-types'\nimport type { AuthInteraction, OAuthCredential, OAuthCredentials } from '@earendil-works/pi-ai'\nimport { openaiCodexProvider } from '@earendil-works/pi-ai/providers/openai-codex'\nimport { type BazilionDb, openSecrets } from '../../core/index.ts'\n\nexport const OPENAI_CODEX_SECRET_KEY = 'OPENAI_CODEX_OAUTH'\n\n/** Refresh when the access token has less than this much life left. */\nconst REFRESH_MARGIN_MS = 60_000\n\n/**\n * One refresh flight per daemon DB/auth context. Multiple Agents can reach the\n * expiry margin at the same time; without single-flight they would all submit\n * the same (potentially rotating) refresh token and race the credentials write.\n * The WeakMap lets closed DB handles and their nested maps be collected.\n */\nconst refreshFlights = new WeakMap<BazilionDb, Map<string, Promise<string>>>()\n\nfunction openAICodexOAuth() {\n const oauth = openaiCodexProvider().auth.oauth\n if (!oauth) throw new Error('Pi openai-codex provider does not expose OAuth')\n return oauth\n}\n\nexport async function loginOpenAICodex(interaction: AuthInteraction): Promise<OAuthCredential> {\n return openAICodexOAuth().login({\n ...interaction,\n signal: interaction.signal ?? new AbortController().signal,\n })\n}\n\nexport async function refreshOpenAICodexToken(\n credential: OAuthCredential,\n): Promise<OAuthCredential> {\n return openAICodexOAuth().refresh(credential, new AbortController().signal)\n}\n\nexport type StoredCredentials = OAuthCredential\n\nfunction readCredentials(db: BazilionDb, authToken: string): StoredCredentials | null {\n const raw = openSecrets(db, authToken).get(OPENAI_CODEX_SECRET_KEY)\n if (!raw) return null\n try {\n const parsed = JSON.parse(raw) as Partial<StoredCredentials>\n if (\n typeof parsed.refresh === 'string' &&\n parsed.refresh.length > 0 &&\n typeof parsed.access === 'string' &&\n parsed.access.length > 0 &&\n typeof parsed.expires === 'number'\n ) {\n // Older Bazilion rows predate Pi's type discriminator. Normalizing here\n // keeps those rows readable while preserving all provider-specific fields.\n return { ...parsed, type: 'oauth' } as StoredCredentials\n }\n return null\n } catch {\n return null\n }\n}\n\nfunction writeCredentials(db: BazilionDb, authToken: string, creds: StoredCredentials): void {\n openSecrets(db, authToken).set(OPENAI_CODEX_SECRET_KEY, JSON.stringify(creds))\n}\n\nexport function clearCredentials(db: BazilionDb, authToken: string): void {\n openSecrets(db, authToken).remove(OPENAI_CODEX_SECRET_KEY)\n}\n\nexport function hasCredentials(db: BazilionDb, authToken: string): boolean {\n return readCredentials(db, authToken) !== null\n}\n\nfunction decodeAccountId(accessToken: string): string | null {\n const parts = accessToken.split('.')\n if (parts.length !== 3) return null\n try {\n const payload = JSON.parse(Buffer.from(parts[1] as string, 'base64').toString('utf8')) as {\n 'https://api.openai.com/auth'?: { chatgpt_account_id?: string }\n }\n return payload['https://api.openai.com/auth']?.chatgpt_account_id ?? null\n } catch {\n return null\n }\n}\n\nexport function getStatus(db: BazilionDb, authToken: string): OpenAICodexStatus {\n const creds = readCredentials(db, authToken)\n if (!creds) return { connected: false, expiresAt: null, accountId: null }\n return {\n connected: true,\n expiresAt: creds.expires,\n accountId: decodeAccountId(creds.access),\n }\n}\n\n/**\n * Returns a valid access token, refreshing via pi-ai if the stored one is\n * within `REFRESH_MARGIN_MS` of expiry. Throws when no credentials are stored\n * so callers can surface an actionable \"run `bazilion auth openai login`\"\n * error rather than a 401 from the upstream API.\n */\nexport async function loadAccessToken(db: BazilionDb, authToken: string): Promise<string> {\n const creds = readCredentials(db, authToken)\n if (!creds) {\n throw credentialsMissingError()\n }\n if (creds.expires > Date.now() + REFRESH_MARGIN_MS) return creds.access\n\n let dbFlights = refreshFlights.get(db)\n if (!dbFlights) {\n dbFlights = new Map()\n refreshFlights.set(db, dbFlights)\n }\n\n const existing = dbFlights.get(authToken)\n if (existing) return existing\n\n // Publish the flight before starting it. The microtask boundary makes the\n // credential re-read below happen after publication, so every concurrent\n // caller observes this same Promise instead of starting a second refresh.\n const pending = Promise.resolve().then(() => refreshExpiredAccessToken(db, authToken))\n dbFlights.set(authToken, pending)\n try {\n return await pending\n } finally {\n if (dbFlights.get(authToken) === pending) dbFlights.delete(authToken)\n if (dbFlights.size === 0) refreshFlights.delete(db)\n }\n}\n\nasync function refreshExpiredAccessToken(db: BazilionDb, authToken: string): Promise<string> {\n // Credentials may have been refreshed or replaced between the caller's\n // initial expiry check and this flight acquiring ownership. Re-read after\n // publishing the flight and skip the network call when another path won.\n const creds = readCredentials(db, authToken)\n if (!creds) throw credentialsMissingError()\n if (creds.expires > Date.now() + REFRESH_MARGIN_MS) return creds.access\n\n const next = await refreshOpenAICodexToken(creds)\n\n // A logout or a new login can happen while the network request is pending.\n // Never resurrect cleared credentials or overwrite a newer credential set.\n const current = readCredentials(db, authToken)\n if (!current) throw credentialsMissingError()\n if (!sameCredentials(current, creds)) {\n if (current.expires > Date.now() + REFRESH_MARGIN_MS) return current.access\n throw new Error('OpenAI ChatGPT OAuth credentials changed while refresh was in progress')\n }\n\n writeCredentials(db, authToken, next)\n return next.access\n}\n\nfunction credentialsMissingError(): Error {\n return new Error(\n 'OpenAI ChatGPT OAuth not configured — run `bazilion auth openai login` (or use the Connect button on /config)',\n )\n}\n\nfunction sameCredentials(left: StoredCredentials, right: StoredCredentials): boolean {\n return (\n left.refresh === right.refresh && left.access === right.access && left.expires === right.expires\n )\n}\n\n/** Persist credentials fetched by pi-ai's `loginOpenAICodex`. */\nexport function saveLoginCredentials(\n db: BazilionDb,\n authToken: string,\n creds: OAuthCredentials,\n): void {\n writeCredentials(db, authToken, { ...creds, type: 'oauth' })\n}\n","// Adapter from Bazilion's Provider interface → pi-ai's streamSimple.\n//\n// Pi-ai (`@earendil-works/pi-ai`) is Mario Zechner's unified LLM SDK — 15+\n// providers behind one event-stream API. This adapter is the boundary where\n// Pi messages become Bazilion's provider contract; everything downstream\n// (`runTurnStream`, `persistRun`, the worker entry, CLI, web) sees the same\n// `Provider.chat(ProviderRequest): Promise<ProviderResponse>` contract it\n// always has. That keeps the wire format, DB schema, and chat UI stable while\n// giving us cost/usage, thinking levels, prompt caching, and every provider\n// pi supports — for free.\n//\n// Model/auth/stream resolution is delegated to Bazilion's Pi runtime factory,\n// which uses Pi's public ModelRuntime API and preserves arbitrary model ids.\n\nimport type { ProviderMessage, ReasoningLevel, ToolCall, ToolDef } from '@bazilion/api-types'\nimport {\n type AssistantMessage,\n type Message as PiMessage,\n type Tool as PiTool,\n type ToolCall as PiToolCall,\n type TextContent,\n Type,\n} from '@earendil-works/pi-ai'\nimport type { ModelRuntime } from '@earendil-works/pi-coding-agent'\nimport { createBazilionPiRuntime, resolvePiModel } from './pi-runtime.ts'\nimport type { Provider, ProviderRequest, ProviderResponse, StopReason } from './types.ts'\n\nexport interface PiProviderConfig {\n /** Display name on the returned Provider; also the registry key (e.g. 'bedrock', 'azure-openai'). */\n providerName: string\n /** Override baseUrl for openai-compat endpoints (lmstudio, ollama, custom). */\n baseUrl?: string\n /** Merged daemon environment used for provider-specific ambient configuration. */\n env?: NodeJS.ProcessEnv\n /**\n * Static key or an async supplier. Suppliers are called at the top of each\n * chat() so OAuth-backed providers can refresh expiring tokens without\n * rebuilding the Provider instance (which the registry caches).\n */\n apiKey?: string | (() => string | Promise<string>)\n /** Test seam for a public-API ModelRuntime with a native faux provider. */\n runtimeFactory?: () => Promise<ModelRuntime>\n}\n\nfunction convertMessages(messages: ProviderMessage[]): PiMessage[] {\n const out: PiMessage[] = []\n const now = Date.now()\n for (const m of messages) {\n if (m.role === 'system') continue // pi takes system prompt separately\n if (m.role === 'user') {\n out.push({ role: 'user', content: m.content, timestamp: now })\n continue\n }\n if (m.role === 'assistant') {\n const content: AssistantMessage['content'] = []\n if (m.content) content.push({ type: 'text', text: m.content } satisfies TextContent)\n if (m.toolCalls) {\n for (const tc of m.toolCalls) {\n let parsed: Record<string, unknown> = {}\n try {\n parsed = JSON.parse(tc.arguments) as Record<string, unknown>\n } catch {\n // leave empty\n }\n content.push({\n type: 'toolCall',\n id: tc.id,\n name: tc.name,\n arguments: parsed,\n } satisfies PiToolCall)\n }\n }\n // Synthesize the AssistantMessage fields pi expects on replays — these\n // are only load-bearing for the LLM that gets the transcript; since we\n // don't persist usage/stopReason in Bazilion's message store, defaults\n // are fine.\n out.push({\n role: 'assistant',\n content,\n api: 'anthropic-messages',\n provider: 'anthropic',\n model: 'unknown',\n usage: {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason: 'stop',\n timestamp: now,\n })\n continue\n }\n if (m.role === 'tool') {\n out.push({\n role: 'toolResult',\n toolCallId: m.toolCallId ?? '',\n toolName: m.toolName ?? 'tool',\n content: [{ type: 'text', text: m.content }],\n isError: false,\n timestamp: now,\n })\n }\n }\n return out\n}\n\nfunction convertTools(tools: ToolDef[] | undefined): PiTool[] | undefined {\n if (!tools || tools.length === 0) return undefined\n return tools.map((t) => ({\n name: t.name,\n description: t.description,\n // Type.Unsafe lets us pass raw JSON Schema through without re-authoring in\n // typebox. Providers validate against the schema, not typebox's TSchema.\n parameters: Type.Unsafe<unknown>(t.parameters as Record<string, unknown>),\n }))\n}\n\nfunction toBazilionStopReason(reason: string): StopReason {\n switch (reason) {\n case 'stop':\n return 'stop'\n case 'length':\n return 'length'\n case 'toolUse':\n return 'tool_use'\n default:\n return 'error'\n }\n}\n\nfunction extractFinalResponse(msg: AssistantMessage): ProviderResponse {\n let text = ''\n const toolCalls: ToolCall[] = []\n for (const block of msg.content) {\n if (block.type === 'text') {\n text += block.text\n } else if (block.type === 'toolCall') {\n toolCalls.push({\n id: block.id,\n name: block.name,\n arguments: JSON.stringify(block.arguments ?? {}),\n })\n }\n }\n const res: ProviderResponse = {\n content: text,\n toolCalls,\n stopReason: toBazilionStopReason(msg.stopReason),\n }\n if (msg.usage) {\n res.usage = {\n promptTokens: msg.usage.input,\n completionTokens: msg.usage.output,\n }\n }\n return res\n}\n\nfunction mapReasoning(\n r: ReasoningLevel | undefined,\n): 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | undefined {\n if (!r || r === 'off') return undefined\n return r\n}\n\nasync function resolveApiKey(cfg: PiProviderConfig): Promise<string | undefined> {\n if (typeof cfg.apiKey === 'function') return await cfg.apiKey()\n return cfg.apiKey\n}\n\nexport function piProvider(cfg: PiProviderConfig): Provider {\n return {\n name: cfg.providerName,\n async chat(req: ProviderRequest): Promise<ProviderResponse> {\n const apiKey = await resolveApiKey(cfg)\n const runtime = cfg.runtimeFactory\n ? await cfg.runtimeFactory()\n : await createBazilionPiRuntime({\n providerName: cfg.providerName,\n env: cfg.env ?? {},\n ...(apiKey ? { apiKey } : {}),\n ...(cfg.baseUrl ? { baseUrl: cfg.baseUrl } : {}),\n modelId: req.model,\n })\n const model = resolvePiModel(runtime, cfg.providerName, req.model, cfg.baseUrl)\n\n const stream = runtime.streamSimple(\n model,\n {\n systemPrompt: req.system ?? '',\n messages: convertMessages(req.messages),\n tools: convertTools(req.tools),\n },\n {\n signal: req.signal,\n apiKey,\n reasoning: mapReasoning(req.reasoning),\n maxTokens: req.maxTokens,\n temperature: req.temperature,\n },\n )\n\n let finalMessage: AssistantMessage | null = null\n for await (const event of stream) {\n if (event.type === 'text_delta' && req.onDelta) {\n req.onDelta(event.delta)\n } else if (event.type === 'done') {\n finalMessage = event.message\n } else if (event.type === 'error') {\n finalMessage = event.error\n }\n }\n if (!finalMessage) {\n throw new Error(`pi provider ${cfg.providerName} returned no terminal event`)\n }\n if (finalMessage.stopReason === 'aborted' || finalMessage.stopReason === 'error') {\n const msg = finalMessage.errorMessage ?? 'provider error'\n throw new Error(msg)\n }\n return extractFinalResponse(finalMessage)\n },\n }\n}\n","// Bazilion system-prompt builder. Feeds pi's `getAppendSystemPrompt()` hook\n// so the agent sees its persona + skills + workspaces + memory guidance\n// stacked on top of pi's built-in base prompt (which lists coding tools and\n// general guidelines). Pure filesystem read — no LLM, no DB.\n\nimport { existsSync, readdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { ResolvedAgent } from '@bazilion/api-types'\nimport { parseSkillContent } from '../../core/skills/parse.ts'\nimport { readRegularFileNoFollow, resolveRealDirectory } from '../safe-files.ts'\nimport type { BashSandboxMode } from '../shell/security.ts'\n\nexport const SANDBOX_SKILLS_DIR = '/skills'\n\nexport interface PromptSkill {\n name: string\n description: string\n body: string\n hostDir: string\n sandboxDir: string\n}\n\nexport interface BuildSystemPromptOptions {\n skills?: readonly PromptSkill[]\n sandboxMode?: BashSandboxMode\n /** Daemon-prepared fixed document bytes; protected workers must supply this. */\n homeDocuments?: ProtectedHomeDocuments\n}\n\nexport interface ProtectedHomeDocuments {\n 'AGENTS.md': string | null\n 'SOUL.md': string | null\n 'TOOLS.md': string | null\n 'IDENTITY.md': string | null\n 'BOOTSTRAP.md': string | null\n}\n\n/** Load only direct, installed skill directories attached to this agent. */\nexport function loadPromptSkills(\n skillsDir: string,\n attachedNames: readonly string[],\n): PromptSkill[] {\n if (!existsSync(skillsDir) || attachedNames.length === 0) return []\n const attached = new Set(attachedNames)\n const discovered = readdirSync(skillsDir, { withFileTypes: true })\n .filter((entry) => entry.isDirectory() && attached.has(entry.name))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n const loaded: PromptSkill[] = []\n for (const entry of discovered) {\n const hostDir = join(skillsDir, entry.name)\n try {\n const parsed = parseSkillContent(readRegularFileNoFollow(join(hostDir, 'SKILL.md')))\n const safeLabel = entry.name.replace(/[^A-Za-z0-9._-]/g, '_') || 'skill'\n loaded.push({\n name: entry.name,\n description: parsed.frontmatter.description,\n body: parsed.body.trim(),\n hostDir,\n sandboxDir: `${SANDBOX_SKILLS_DIR}/${loaded.length}-${safeLabel}`,\n })\n } catch {\n // The attachment surface already reports missing/parse-broken skills.\n // Keep turn creation resilient and name the unavailable attachment below.\n }\n }\n return loaded\n}\n\n// Prompt order: peers first (who else is around), then persona, then tooling\n// hints, then self-knowledge, then the wake-up playbook.\n//\n// BOOTSTRAP.md is intentionally NOT in this generic-context list — it gets\n// its own dedicated \"First-Run Ritual\" section below with explicit\n// anti-checklist framing, so models treat it as multi-turn Q&A guidance\n// rather than a one-shot script to execute. The system prompt regenerates\n// per turn, so the section auto-vanishes when `bootstrap_done` removes the\n// file (vs. wrapping the user message, which would persist in pi's session\n// JSONL forever and replay on every future turn).\nconst CONTEXT_FILE_ORDER = ['AGENTS.md', 'SOUL.md', 'TOOLS.md', 'IDENTITY.md'] as const\n\n/** Snapshot the exact Agent-home prompt inputs without following fixed-name links. */\nexport function loadProtectedHomeDocuments(agentDir: string): ProtectedHomeDocuments {\n const root = resolveRealDirectory(agentDir)\n const read = (file: keyof ProtectedHomeDocuments): string | null => {\n try {\n return readRegularFileNoFollow(join(root, file))\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw new Error(`private Agent home is unsafe at ${file}`)\n }\n }\n return {\n 'AGENTS.md': read('AGENTS.md'),\n 'SOUL.md': read('SOUL.md'),\n 'TOOLS.md': read('TOOLS.md'),\n 'IDENTITY.md': read('IDENTITY.md'),\n 'BOOTSTRAP.md': read('BOOTSTRAP.md'),\n }\n}\n\nexport function buildSystemPrompt(\n agent: ResolvedAgent,\n options: BuildSystemPromptOptions = {},\n): string {\n const parts: string[] = []\n const homeDocuments = options.homeDocuments ?? loadProtectedHomeDocuments(agent.agent.dir)\n\n const contextBlocks: string[] = []\n for (const file of CONTEXT_FILE_ORDER) {\n const content = homeDocuments[file]?.trimEnd()\n if (!content) continue\n contextBlocks.push(`## ${file}\\n\\n${content}`)\n }\n if (contextBlocks.length > 0) {\n parts.push(`# Agent instructions\\n\\n${contextBlocks.join('\\n\\n')}`)\n }\n\n // First-Run Ritual block — only emitted while BOOTSTRAP.md exists on disk.\n // The wording deliberately frames it as \"multi-turn Q&A\" not \"checklist\"\n // and lists hard rules at the top so tool-eager models still notice them\n // even if they skim the body.\n const bootstrap = homeDocuments['BOOTSTRAP.md']?.trimEnd()\n if (bootstrap) {\n parts.push(\n [\n '# First-Run Ritual',\n '',\n 'This is your first session. The document below is **conversational guidance**, not a checklist to execute in one shot. It describes a multi-turn Q&A you should have with the human, one question per turn.',\n '',\n '## Hard rules',\n '- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.',\n '- Each subsequent turn: at most one new question. Wait between turns.',\n '- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.',\n '',\n '## BOOTSTRAP.md',\n '',\n bootstrap,\n ].join('\\n'),\n )\n }\n\n parts.push(\n [\n '# Agent Home',\n '',\n 'Your private home holds who you are — identity, soul, and behaviour rules. It is not shared with other agents and cannot be overwritten by them. The files above (IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md) live in this home, plus BOOTSTRAP.md when you are still in your first-run ritual.',\n '',\n '- To change who you are (name, vibe, personality, how you behave): use `home_write`.',\n '- To inspect exact wording of your own files: use `home_read` or `home_list`.',\n '- To remember facts the user told you or things you learned: use `memory_write` — NOT `home_write`.',\n '- To produce work output (code, docs, artefacts): use the workspace tools currently exposed to you — those operate on your team workspace, not your private home.',\n ].join('\\n'),\n )\n\n if (agent.skills.length > 0) {\n const loaded = options.skills ?? []\n const loadedNames = new Set(loaded.map((skill) => skill.name))\n const unavailable = agent.skills.filter((name) => !loadedNames.has(name))\n const skillBlocks = loaded.map((skill) => {\n const runtimeDir = options.sandboxMode === 'docker' ? skill.sandboxDir : skill.hostDir\n return [\n `## ${skill.name}`,\n '',\n skill.description,\n '',\n `Runtime directory: \\`${runtimeDir}\\`. Resolve any relative scripts or assets mentioned by this skill from that directory.`,\n '',\n skill.body,\n ]\n .filter((line, index, lines) => line !== '' || lines[index - 1] !== '')\n .join('\\n')\n .trimEnd()\n })\n const unavailableNote =\n unavailable.length > 0\n ? `Attached but unavailable or invalid: ${unavailable.join(', ')}.`\n : ''\n parts.push(\n [\n '# Available Skills',\n '',\n 'Use these attached skill instructions when they are relevant to the task.',\n '',\n ...skillBlocks,\n ...(unavailableNote ? ['', unavailableNote] : []),\n ].join('\\n'),\n )\n }\n\n const teamLines = [\n '# Team',\n '',\n `- ${agent.team.id} (${agent.team.name}): ${options.sandboxMode === 'docker' ? '/workspace' : agent.team.path}`,\n '',\n 'Your team is where work product lives — code, docs, artefacts, shared scratch. It may be shared with other agents in the same team. Use the workspace tools currently exposed to work there. Never use workspace tools to edit your identity/soul/behaviour files — those live in your private home and are reached via `home_write` / `home_read`.',\n ]\n parts.push(teamLines.join('\\n'))\n\n if (agent.team.userMd.trim()) {\n parts.push(\n `# About the User\\n\\nShared context about the human you're working with in this team. Both you and the human curate it. To update: call \\`user_md_get\\` (returns current content + an etag), merge your change into the full text, then call \\`user_md_write\\` with the merged content and the etag. Use this for STABLE user-specific facts (preferences, role, working hours, how they like to be addressed) — and to CORRECT stale entries when the human tells you something different from what's recorded. For project knowledge use \\`memory_write\\` instead; for personal notes about yourself use \\`home_write\\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes — every agent in the team sees the new content in their system prompt on their next turn automatically.**\\n\\n${agent.team.userMd.trim()}`,\n )\n } else {\n parts.push(\n `# About the User\\n\\nThis team's USER.md is empty. As you learn STABLE facts about the human (preferences, role, working hours, how they like to be addressed), populate it via \\`user_md_get\\` then \\`user_md_write\\` (always get first — you need the etag). Reserve this for things you're confident are durable — project knowledge belongs in \\`memory_write\\`, personal notes about yourself in \\`home_write\\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes — every agent in the team sees the new content in their system prompt on their next turn automatically.**`,\n )\n }\n\n const reviewedLessons = boundReviewedLessons(agent.privateLessons)\n if (reviewedLessons.length > 0) {\n parts.push(\n `# Reviewed lessons\\n\\nThese private lessons were explicitly approved by the human for your behavior. Follow them when relevant; current user instructions and higher-priority policy still win.\\n\\n${reviewedLessons.map((lesson) => `- ${lesson}`).join('\\n')}`,\n )\n }\n\n parts.push(\n '# Memory\\n\\nYou share a persistent memory backend with every other agent in this team. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. This memory is for project knowledge — codebase notes, decisions, things the user told you about the work. For personal notes about yourself (preferences, persona quirks), use `home_write` on IDENTITY.md instead. Always check memory at the start of a session: another agent in the team may have already learned something useful. **Do NOT send peer messages announcing memory writes — every agent has access to the same store via `memory_search` and will find your note when they need it.**',\n )\n\n return parts.join('\\n\\n---\\n\\n')\n}\n\nfunction boundReviewedLessons(lessons: readonly string[], maxCharacters = 8_000): string[] {\n const selected: string[] = []\n let used = 0\n for (const raw of lessons) {\n const lesson = raw.trim()\n if (!lesson) continue\n const remaining = maxCharacters - used\n if (remaining <= 0) break\n selected.push(lesson.slice(0, remaining))\n used += Math.min(lesson.length, remaining)\n }\n return selected\n}\n","import {\n closeSync,\n constants,\n fstatSync,\n lstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n realpathSync,\n} from 'node:fs'\nimport { dirname, isAbsolute, relative, resolve } from 'node:path'\n\n/** Resolve a real directory and reject a symlink at the selected root itself. */\nexport function resolveRealDirectory(path: string): string {\n const requested = resolve(path)\n const entry = lstatSync(requested)\n if (entry.isSymbolicLink() || !entry.isDirectory()) {\n throw new Error('expected a real directory')\n }\n return realpathSync(requested)\n}\n\n/**\n * Create or validate a direct/nested directory below a registered root.\n * The root itself may be a deliberate Team symlink; the selected directory\n * may not be a symlink and its canonical target must stay below that root.\n */\nexport function ensureContainedRealDirectory(\n path: string,\n root: string,\n options: { create?: boolean } = {},\n): string {\n const requestedRoot = resolve(root)\n const requested = resolve(path)\n if (!isContained(requestedRoot, requested)) throw new Error('directory escapes its root')\n const canonicalRoot = realpathSync(requestedRoot)\n try {\n const entry = lstatSync(requested)\n if (entry.isSymbolicLink() || !entry.isDirectory()) {\n throw new Error('expected a real directory')\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT' || !options.create) throw error\n const canonicalParent = realpathSync(dirname(requested))\n if (!isContained(canonicalRoot, canonicalParent)) throw new Error('directory escapes its root')\n mkdirSync(requested, { recursive: true, mode: 0o700 })\n const entry = lstatSync(requested)\n if (entry.isSymbolicLink() || !entry.isDirectory()) {\n throw new Error('expected a real directory')\n }\n }\n const canonical = realpathSync(requested)\n if (!isContained(canonicalRoot, canonical)) throw new Error('directory escapes its root')\n return canonical\n}\n\n/** Read one regular file while refusing a final-component symbolic link. */\nexport function readRegularFileNoFollow(path: string): string {\n const entry = lstatSync(path)\n if (entry.isSymbolicLink()) throw new Error('symbolic links are not allowed')\n if (!entry.isFile()) throw new Error('target is not a regular file')\n let fd: number | undefined\n try {\n fd = openSync(path, constants.O_RDONLY | noFollowFlag())\n if (!fstatSync(fd).isFile()) throw new Error('target is not a regular file')\n return readFileSync(fd, 'utf8')\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n}\n\nexport function noFollowFlag(): number {\n return typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0\n}\n\nfunction isContained(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n}\n","import { existsSync, lstatSync, realpathSync } from 'node:fs'\nimport { dirname, isAbsolute, join, relative, sep } from 'node:path'\nimport {\n createBashToolDefinition,\n createLocalBashOperations,\n defineTool,\n type ToolDefinition,\n} from '@earendil-works/pi-coding-agent'\nimport { type BashApprovalHost, createApprovalGatedBashTool } from './approval.ts'\nimport { codingContainerCwd } from './coding.ts'\nimport {\n createDockerBashOperations,\n createPreparedDockerBashOperations,\n type DockerReadOnlyMount,\n type DockerResourceLifecycle,\n type ProtectedDockerRuntime,\n} from './docker.ts'\nimport {\n buildScrubbedShellEnv,\n resolveShellSecurityConfig,\n type ShellSecurityConfig,\n} from './security.ts'\n\nconst HOST_CODING_TOOL_NAMES = ['read', 'bash', 'edit', 'write', 'grep', 'find', 'ls'] as const\nconst HOST_FILE_TOOL_NAMES = ['read', 'edit', 'write', 'grep', 'find', 'ls'] as const\n\nconst CONTAINER_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\nexport const SANDBOX_INPUTS_DIR = '/inputs'\nexport const SANDBOX_MEMORY_DIR = '/workspace/memory'\n\nexport interface SessionShellToolOptions {\n dockerLifecycle?: DockerResourceLifecycle\n preparedDocker?: ProtectedDockerRuntime\n /** Agent-private uploaded documents, exposed read-only at /inputs when present. */\n inputsDir?: string\n /** Attached skill directories, each mapped to a stable read-only container path. */\n skillMounts?: readonly DockerReadOnlyMount[]\n /** Turn-scoped dangerous-command approval bridge. Omit to deny risky commands. */\n approvalHost?: BashApprovalHost\n}\n\nexport interface SessionShellTools {\n config: ShellSecurityConfig\n /** Pi built-ins that remain host-backed and may be enabled for this session. */\n hostToolNames: readonly string[]\n /** Same-name replacement for Pi's built-in bash when isolation or approval is active. */\n customBash?: ToolDefinition\n}\n\n/** Build the mandatory Docker-only coding surface for a protected turn. */\nexport function createProtectedSessionShellTools(\n cwd: string,\n runtime: ProtectedDockerRuntime,\n approvalHost: BashApprovalHost,\n lifecycle?: DockerResourceLifecycle,\n): SessionShellTools {\n const operations = createPreparedDockerBashOperations(runtime, lifecycle)\n const modelCwd = codingContainerCwd(runtime.coding?.cwd ?? '.')\n const base = createBashToolDefinition(cwd, {\n operations: {\n exec(command, requestedCwd, options) {\n // Pi supplies its model-facing session cwd to operations. Keep that virtual\n // container path separate from the daemon-preflighted host mount identity.\n if (requestedCwd !== modelCwd && requestedCwd !== cwd)\n throw new Error('Protected command working directory differs from the admitted scope')\n return operations.exec(command, cwd, options)\n },\n },\n })\n const dockerBash = defineTool({\n ...base,\n label: 'bash (protected Docker sandbox)',\n description: `${base.description} Default working directory: ${codingContainerCwd(runtime.coding?.cwd ?? '.')}. Runs in an ephemeral, network-disabled Docker container with the Team workspace mounted read/write plus only the preflighted read-only memory, skill, and attachment mounts. Host files, host tools, and host credentials are unavailable.`,\n })\n return {\n config: {\n sandboxMode: 'docker',\n approvalMode: 'dangerous',\n sandboxImage: runtime.image,\n envAllowlist: [],\n },\n hostToolNames: [],\n customBash: createApprovalGatedBashTool(dockerBash, approvalHost),\n }\n}\n\n/**\n * Build the only environment visible to a sandboxed command.\n *\n * Operator-allowlisted values are copied first. Container-specific basics are\n * then pinned so host paths (especially HOME) cannot leak into the sandbox or\n * point programs back at an unavailable host directory.\n */\nexport function buildSandboxContainerEnv(\n env: NodeJS.ProcessEnv,\n allowlist: readonly string[],\n): NodeJS.ProcessEnv {\n return {\n ...buildScrubbedShellEnv(env, allowlist),\n PATH: CONTAINER_PATH,\n HOME: '/tmp',\n LANG: 'C.UTF-8',\n LC_ALL: 'C.UTF-8',\n SHELL: '/bin/bash',\n TMPDIR: '/tmp',\n }\n}\n\n/**\n * Resolve the session's host-vs-container coding-tool surface.\n *\n * Docker mode deliberately enables no host-backed Pi coding tools. The\n * returned custom bash is still allowlisted by name by its caller, while\n * read/edit/write/grep/find/ls stay inactive so absolute host paths cannot\n * bypass the container boundary.\n */\nexport function createSessionShellTools(\n cwd: string,\n env: NodeJS.ProcessEnv,\n options: SessionShellToolOptions = {},\n): SessionShellTools {\n const config = resolveShellSecurityConfig(env)\n if (options.preparedDocker) {\n if (config.sandboxMode !== 'docker')\n throw new Error('Prepared coding environment cannot execute on the host')\n const runtime = options.preparedDocker\n const base = createBashToolDefinition(cwd, {\n operations: createPreparedDockerBashOperations(runtime, options.dockerLifecycle),\n })\n const tool = defineTool({\n ...base,\n label: 'bash (prepared Docker sandbox)',\n description: `${base.description} Default working directory: ${codingContainerCwd(runtime.coding?.cwd ?? '.')}. Fresh network-disabled Docker container with the Team workspace and preflighted read-only mounts.`,\n })\n return {\n config: { ...config, sandboxImage: runtime.image, envAllowlist: [] },\n hostToolNames: [],\n customBash:\n config.approvalMode === 'dangerous'\n ? createApprovalGatedBashTool(tool, options.approvalHost)\n : tool,\n }\n }\n if (config.sandboxMode === 'off' && config.approvalMode === 'off') {\n return { config, hostToolNames: HOST_CODING_TOOL_NAMES }\n }\n\n if (config.sandboxMode === 'off') {\n const base = createBashToolDefinition(cwd, {\n operations: createLocalBashOperations(),\n })\n const customBash = createApprovalGatedBashTool(base, options.approvalHost)\n return { config, hostToolNames: HOST_FILE_TOOL_NAMES, customBash }\n }\n\n const operations = createDockerBashOperations({\n lifecycle: options.dockerLifecycle,\n image: config.sandboxImage,\n env: buildSandboxContainerEnv(env, config.envAllowlist),\n readOnlyMounts: [\n ...(existsSync(join(cwd, 'memory'))\n ? [safeReadOnlyMount(join(cwd, 'memory'), SANDBOX_MEMORY_DIR, cwd)]\n : []),\n ...(options.inputsDir\n ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname(options.inputsDir))]\n : []),\n ...(options.skillMounts ?? []).map((mount) =>\n safeReadOnlyMount(mount.source, mount.target, dirname(mount.source)),\n ),\n ],\n })\n const base = createBashToolDefinition(cwd, { operations })\n const dockerBash = defineTool({\n ...base,\n label: 'bash (Docker sandbox)',\n description: `${base.description} Runs in an ephemeral, network-disabled Docker container with the team workspace mounted read/write plus only bounded read-only memory, skill, and attachment mounts. Other host files and host credentials are unavailable.`,\n })\n const customBash =\n config.approvalMode === 'dangerous'\n ? createApprovalGatedBashTool(dockerBash, options.approvalHost)\n : dockerBash\n\n return { config, hostToolNames: [], customBash }\n}\n\nfunction safeReadOnlyMount(\n source: string,\n target: string,\n expectedRoot: string,\n): DockerReadOnlyMount {\n const info = lstatSync(source)\n if (info.isSymbolicLink() || !info.isDirectory()) {\n throw new Error(`Docker sandbox read-only mount must be a real directory: ${source}`)\n }\n\n const sourceRoot = realpathSync(expectedRoot)\n const canonicalSource = realpathSync(source)\n const rel = relative(sourceRoot, canonicalSource)\n const contained = rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n if (!contained) {\n throw new Error(`Docker sandbox read-only mount escaped its configured root: ${source}`)\n }\n\n return { source: canonicalSource, target, sourceRoot }\n}\n","import {\n constants,\n existsSync,\n lstatSync,\n mkdirSync,\n mkdtempSync,\n realpathSync,\n rmSync,\n statSync,\n} from 'node:fs'\nimport { access } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { isAbsolute, join, relative, sep } from 'node:path'\nimport type {\n Attachment,\n BashApprovalMode,\n ReasoningLevel,\n ResolvedAgent,\n} from '@bazilion/api-types'\nimport { assertRepositoryContext } from '../pi/repository-context-contract.ts'\nimport {\n PROTECTED_PROVIDER_NAMES,\n PROVIDER_CREDENTIAL_ENV,\n type ProtectedProviderName,\n} from '../providers/pi-runtime.ts'\nimport type { PromptSkill, ProtectedHomeDocuments } from '../session/prompt.ts'\nimport { validateDockerCodingSelection } from '../shell/coding.ts'\nimport type { ProtectedDockerRuntime } from '../shell/docker.ts'\nimport type { InjectedMcpTool } from './ipc-protocol.ts'\n\nexport const REDACTED_VALUE = '[REDACTED]'\n\nexport interface ProtectedProviderWorkerRuntime {\n providerName: ProtectedProviderName\n modelId: string\n reasoningLevel: ReasoningLevel\n /** Selected provider credential only; unrelated daemon secrets never cross this wire. */\n apiKey?: string\n baseUrl?: string\n /** Closed provider-specific fields installed in Pi's in-memory credential store, not process.env. */\n credentialEnv?: Array<{ name: string; value: string }>\n /** Explicit Google service-account/ADC JSON materialized only inside this turn's scratch tree. */\n credentialFile?: { envName: 'GOOGLE_APPLICATION_CREDENTIALS'; content: string }\n}\n\nexport interface ProtectedWorkerPaths {\n agentDir: string\n teamDir: string\n memoryDir: string\n sessionDir: string\n uploadsDir?: string\n /** Prompt bodies and exact, daemon-resolved read-only Docker mount paths. */\n skills: PromptSkill[]\n /** Daemon-prepared fixed-file snapshot; protected workers never follow home-file symlinks. */\n homeDocuments: ProtectedHomeDocuments\n}\n\nexport interface ConfiguredOperatorHttpWorkerSpec {\n containerNamespace?: string\n configuredDocker?: { paths: ProtectedWorkerPaths; docker: ProtectedDockerRuntime }\n repositoryContext: import('@bazilion/api-types').RepositoryContextReport\n questionEnabled?: boolean\n conversation: import('@bazilion/api-types').ConversationTarget\n kind: 'configured_operator_http'\n /** Pre-resolved agent record — the worker never queries the DB itself. */\n agent: ResolvedAgent\n message: string\n enabledProviders: string[]\n apiKey?: string\n browserEnabled?: boolean\n mcpTools?: InjectedMcpTool[]\n images?: Attachment[]\n turnId: string\n bashApprovalMode: BashApprovalMode\n}\n\nexport interface ProtectedWorkerSpec {\n containerNamespace?: string\n repositoryContext: import('@bazilion/api-types').RepositoryContextReport\n questionEnabled?: boolean\n conversation: import('@bazilion/api-types').ConversationTarget\n kind: 'protected'\n agent: ResolvedAgent\n message: string\n images?: Attachment[]\n turnId: string\n bashApprovalMode: 'auto_deny'\n runtime: ProtectedProviderWorkerRuntime\n paths: ProtectedWorkerPaths\n docker: ProtectedDockerRuntime\n /** The only general web capability in the protected normal surface. */\n webFetchEnabled: true\n}\n\nexport interface RestrictedReviewWorkerSpec {\n kind: 'restricted_review'\n agentId: string\n message: string\n turnId: string\n runtime: ProtectedProviderWorkerRuntime\n review: {\n reviewId: string\n evidence: Array<{ sessionId: string; entryOrdinal: number }>\n }\n}\n\nexport type WorkerTurnSpec = ConfiguredOperatorHttpWorkerSpec | ProtectedWorkerSpec\n\nexport interface MinimalWorkerScratch {\n root: string\n homeDir: string\n tempDir: string\n piAgentDir: string\n reviewCwd: string\n reviewSessionDir: string\n}\n\nexport type WorkerInput =\n | (ConfiguredOperatorHttpWorkerSpec & { apiKeyRefreshEnabled: boolean })\n | (ProtectedWorkerSpec & {\n apiKeyRefreshEnabled: true\n scratch: MinimalWorkerScratch\n })\n | (RestrictedReviewWorkerSpec & {\n apiKeyRefreshEnabled: true\n scratch: MinimalWorkerScratch\n })\n\nconst REASONING_LEVELS = new Set<ReasoningLevel>([\n 'off',\n 'minimal',\n 'low',\n 'medium',\n 'high',\n 'xhigh',\n])\n\nconst CONFIGURED_KEYS = new Set([\n 'containerNamespace',\n 'configuredDocker',\n 'repositoryContext',\n 'questionEnabled',\n 'conversation',\n 'kind',\n 'agent',\n 'message',\n 'enabledProviders',\n 'apiKey',\n 'apiKeyRefreshEnabled',\n 'browserEnabled',\n 'mcpTools',\n 'images',\n 'turnId',\n 'bashApprovalMode',\n])\nconst CONFIGURED_REQUIRED_KEYS = new Set([\n 'repositoryContext',\n 'conversation',\n 'kind',\n 'agent',\n 'message',\n 'enabledProviders',\n 'apiKeyRefreshEnabled',\n 'turnId',\n 'bashApprovalMode',\n])\nconst PROTECTED_KEYS = new Set([\n 'containerNamespace',\n 'repositoryContext',\n 'questionEnabled',\n 'conversation',\n 'kind',\n 'agent',\n 'message',\n 'images',\n 'turnId',\n 'bashApprovalMode',\n 'runtime',\n 'paths',\n 'docker',\n 'webFetchEnabled',\n 'apiKeyRefreshEnabled',\n 'scratch',\n])\nconst PROTECTED_REQUIRED_KEYS = new Set(\n [...PROTECTED_KEYS].filter(\n (key) => key !== 'images' && key !== 'questionEnabled' && key !== 'containerNamespace',\n ),\n)\nconst REVIEW_KEYS = new Set([\n 'kind',\n 'agentId',\n 'message',\n 'turnId',\n 'runtime',\n 'review',\n 'apiKeyRefreshEnabled',\n 'scratch',\n])\n\n/** Create the complete scratch tree used by one protected/review child. */\nexport function createMinimalWorkerScratch(parentDir = tmpdir()): MinimalWorkerScratch {\n const root = mkdtempSync(join(parentDir, 'bazilion-worker-'))\n const scratch: MinimalWorkerScratch = {\n root,\n homeDir: join(root, 'home'),\n tempDir: join(root, 'tmp'),\n piAgentDir: join(root, 'pi'),\n reviewCwd: join(root, 'review-work'),\n reviewSessionDir: join(root, 'review-session'),\n }\n try {\n for (const path of Object.values(scratch).slice(1)) {\n mkdirSync(path, { recursive: true, mode: 0o700 })\n }\n return scratch\n } catch (error) {\n rmSync(root, { recursive: true, force: true })\n throw error\n }\n}\n\nexport function cleanupMinimalWorkerScratch(scratch: MinimalWorkerScratch): void {\n assertMinimalWorkerScratch(scratch)\n rmSync(scratch.root, { recursive: true, force: true })\n}\n\n/**\n * Construct the exact process environment for a protected/review child.\n * Docker/provider/tool configuration is deliberately not represented here.\n */\nexport function minimalWorkerProcessEnv(\n scratch: MinimalWorkerScratch,\n platform: NodeJS.Platform = process.platform,\n hostEnv: Readonly<NodeJS.ProcessEnv> = process.env,\n): NodeJS.ProcessEnv {\n assertMinimalWorkerScratch(scratch)\n if (platform === 'win32') {\n const systemRoot = requireCanonicalWindowsDirectory(\n hostEnv.SystemRoot ?? hostEnv.WINDIR,\n 'SystemRoot',\n )\n const windir = requireCanonicalWindowsDirectory(hostEnv.WINDIR ?? systemRoot, 'WINDIR')\n const comSpec = requireCanonicalWindowsFile(hostEnv.ComSpec, 'ComSpec')\n const pathExt = hostEnv.PATHEXT\n if (!pathExt || pathExt.includes('\\0') || pathExt.includes('\\n') || pathExt.includes('\\r')) {\n throw new Error('minimal worker bootstrap requires a valid PATHEXT on Windows')\n }\n return {\n USERPROFILE: scratch.homeDir,\n TMPDIR: scratch.tempDir,\n TMP: scratch.tempDir,\n TEMP: scratch.tempDir,\n SystemRoot: systemRoot,\n WINDIR: windir,\n ComSpec: comSpec,\n PATHEXT: pathExt,\n }\n }\n return {\n HOME: scratch.homeDir,\n TMPDIR: scratch.tempDir,\n TMP: scratch.tempDir,\n TEMP: scratch.tempDir,\n LANG: 'C',\n LC_ALL: 'C',\n }\n}\n\nexport function validateMinimalWorkerProcessEnv(\n env: Readonly<NodeJS.ProcessEnv>,\n scratch: MinimalWorkerScratch,\n platform: NodeJS.Platform = process.platform,\n): void {\n const expected = minimalWorkerProcessEnv(scratch, platform, env)\n const actualKeys = Object.keys(env).sort()\n const expectedKeys = Object.keys(expected).sort()\n if (\n actualKeys.length !== expectedKeys.length ||\n actualKeys.some((key, index) => key !== expectedKeys[index])\n ) {\n throw new Error(`minimal worker environment has unexpected keys: ${actualKeys.join(', ')}`)\n }\n for (const key of expectedKeys) {\n if (env[key] !== expected[key]) {\n throw new Error(`minimal worker environment has an invalid ${key} value`)\n }\n }\n}\n\nexport function parseWorkerInput(value: unknown): WorkerInput {\n const input = objectRecord(value, 'worker input')\n const kind = input.kind\n if (\n input.containerNamespace !== undefined &&\n (typeof input.containerNamespace !== 'string' ||\n !/^[a-f0-9-]{36}$/.test(input.containerNamespace))\n )\n throw new Error('Invalid container namespace')\n if (input.questionEnabled !== undefined && typeof input.questionEnabled !== 'boolean')\n throw new Error('Invalid worker question capability')\n if (kind === 'configured_operator_http') {\n assertExactKeys(input, CONFIGURED_KEYS, 'configured worker input', CONFIGURED_REQUIRED_KEYS)\n if (!isResolvedAgent(input.agent)) throw new Error('worker: configured input requires an agent')\n if (input.configuredDocker !== undefined) {\n const prepared = objectRecord(input.configuredDocker, 'configured Docker inputs')\n const keys = new Set(['paths', 'docker'])\n assertExactKeys(prepared, keys, 'configured Docker inputs', keys)\n assertProtectedWorkerPaths(prepared.paths, input.agent)\n assertProtectedDockerRuntime(prepared.docker, prepared.paths as ProtectedWorkerPaths)\n }\n assertRepositoryContext(input.repositoryContext, input.agent.team.id)\n requireString(input.message, 'message')\n if (!Array.isArray(input.enabledProviders) || !input.enabledProviders.every(isString)) {\n throw new Error('worker: configured input requires enabledProviders')\n }\n requireString(input.turnId, 'turnId')\n assertConversationTarget(input.conversation)\n if (input.bashApprovalMode !== 'interactive' && input.bashApprovalMode !== 'auto_deny') {\n throw new Error('worker: configured input requires a valid bashApprovalMode')\n }\n if (typeof input.apiKeyRefreshEnabled !== 'boolean') {\n throw new Error('worker: configured input requires apiKeyRefreshEnabled')\n }\n if (input.apiKeyRefreshEnabled) {\n const providerName = input.agent.model.split(':', 1)[0] ?? ''\n if (providerName !== 'openai-codex' || !nonEmptyString(input.apiKey)) {\n throw new Error(\n 'worker: API key refresh requires an openai-codex turn with an initial token',\n )\n }\n }\n return value as WorkerInput\n }\n\n if (kind === 'protected') {\n assertExactKeys(input, PROTECTED_KEYS, 'protected worker input', PROTECTED_REQUIRED_KEYS)\n if (!isResolvedAgent(input.agent)) throw new Error('worker: protected input requires an agent')\n assertRepositoryContext(input.repositoryContext, input.agent.team.id)\n requireString(input.message, 'message')\n requireString(input.turnId, 'turnId')\n assertConversationTarget(input.conversation)\n if (input.bashApprovalMode !== 'auto_deny') {\n throw new Error('worker: protected input requires auto-deny shell approval')\n }\n if (input.webFetchEnabled !== true) {\n throw new Error('worker: protected input requires guarded web_fetch')\n }\n assertProtectedProviderRuntime(input.runtime)\n const runtime = input.runtime as ProtectedProviderWorkerRuntime\n const separator = (input.agent as ResolvedAgent).model.indexOf(':')\n if (\n runtime.providerName !== (input.agent as ResolvedAgent).model.slice(0, separator) ||\n runtime.modelId !== (input.agent as ResolvedAgent).model.slice(separator + 1)\n ) {\n throw new Error('worker: protected runtime does not match the resolved Agent model')\n }\n assertProtectedWorkerPaths(input.paths, input.agent)\n assertProtectedDockerRuntime(input.docker, input.paths as ProtectedWorkerPaths)\n if (input.apiKeyRefreshEnabled !== true) {\n throw new Error('worker: protected input requires bound API key refresh')\n }\n assertMinimalWorkerScratch(input.scratch)\n return value as WorkerInput\n }\n\n if (kind === 'restricted_review') {\n assertExactKeys(input, REVIEW_KEYS, 'restricted review worker input', REVIEW_KEYS)\n requireString(input.agentId, 'agentId')\n requireString(input.message, 'message')\n requireString(input.turnId, 'turnId')\n assertProtectedProviderRuntime(input.runtime)\n if (input.apiKeyRefreshEnabled !== true) {\n throw new Error('worker: restricted review requires bound API key refresh')\n }\n assertMinimalWorkerScratch(input.scratch)\n const review = objectRecord(input.review, 'review metadata')\n assertExactKeys(\n review,\n new Set(['reviewId', 'evidence']),\n 'review metadata',\n new Set(['reviewId', 'evidence']),\n )\n requireString(review.reviewId, 'reviewId')\n if (!Array.isArray(review.evidence)) throw new Error('worker: review evidence must be an array')\n for (const item of review.evidence) {\n const evidence = objectRecord(item, 'review evidence')\n assertExactKeys(\n evidence,\n new Set(['sessionId', 'entryOrdinal']),\n 'review evidence',\n new Set(['sessionId', 'entryOrdinal']),\n )\n requireString(evidence.sessionId, 'sessionId')\n if (!Number.isInteger(evidence.entryOrdinal) || Number(evidence.entryOrdinal) < 0) {\n throw new Error('worker: review evidence entryOrdinal must be a non-negative integer')\n }\n }\n return value as WorkerInput\n }\n\n throw new Error('worker: unknown or missing runtime kind')\n}\n\nexport function assertProtectedProviderRuntime(\n value: unknown,\n): asserts value is ProtectedProviderWorkerRuntime {\n const runtime = objectRecord(value, 'protected provider runtime')\n assertExactKeys(\n runtime,\n new Set([\n 'providerName',\n 'modelId',\n 'reasoningLevel',\n 'apiKey',\n 'baseUrl',\n 'credentialEnv',\n 'credentialFile',\n ]),\n 'protected provider runtime',\n new Set(['providerName', 'modelId', 'reasoningLevel']),\n )\n if (!PROTECTED_PROVIDER_NAMES.includes(runtime.providerName as ProtectedProviderName)) {\n throw new Error('protected execution requires a registered provider')\n }\n if (!nonEmptyString(runtime.modelId))\n throw new Error('protected provider runtime requires a model id')\n if (!REASONING_LEVELS.has(runtime.reasoningLevel as ReasoningLevel)) {\n throw new Error('protected provider runtime requires a valid reasoning level')\n }\n if (runtime.apiKey !== undefined && !nonEmptyString(runtime.apiKey)) {\n throw new Error('protected provider runtime contains an invalid selected credential')\n }\n if (runtime.baseUrl !== undefined) requireString(runtime.baseUrl, 'baseUrl')\n if (runtime.credentialEnv !== undefined) {\n if (!Array.isArray(runtime.credentialEnv)) {\n throw new Error('protected provider credential fields must be an array')\n }\n const allowed = new Set(PROVIDER_CREDENTIAL_ENV[String(runtime.providerName)] ?? [])\n const seen = new Set<string>()\n for (const rawField of runtime.credentialEnv) {\n const field = objectRecord(rawField, 'protected provider credential field')\n assertExactKeys(\n field,\n new Set(['name', 'value']),\n 'protected provider credential field',\n new Set(['name', 'value']),\n )\n requireString(field.name, 'credential field name')\n requireString(field.value, 'credential field value')\n if (!allowed.has(field.name as string) || seen.has(field.name as string)) {\n throw new Error('protected provider runtime contains an unexpected credential field')\n }\n seen.add(field.name as string)\n }\n }\n if (runtime.credentialFile !== undefined) {\n const file = objectRecord(runtime.credentialFile, 'protected provider credential file')\n assertExactKeys(\n file,\n new Set(['envName', 'content']),\n 'protected provider credential file',\n new Set(['envName', 'content']),\n )\n if (\n runtime.providerName !== 'google-vertex' ||\n file.envName !== 'GOOGLE_APPLICATION_CREDENTIALS' ||\n !nonEmptyString(file.content) ||\n Buffer.byteLength(file.content as string, 'utf8') > 64 * 1024\n ) {\n throw new Error('protected provider runtime contains an invalid credential file')\n }\n }\n if (\n !nonEmptyString(runtime.apiKey) &&\n !Array.isArray(runtime.credentialEnv) &&\n runtime.credentialFile === undefined\n ) {\n throw new Error('protected provider runtime requires selected credentials')\n }\n}\n\nexport function protectedRuntimeSecrets(runtime: ProtectedProviderWorkerRuntime): string[] {\n return [\n runtime.apiKey,\n ...(runtime.credentialEnv ?? []).map((field) => field.value),\n runtime.credentialFile?.content,\n ].filter((value): value is string => Boolean(value))\n}\n\nexport function redactExactValue(text: string, value: string | undefined): string {\n return value ? text.replaceAll(value, REDACTED_VALUE) : text\n}\n\nexport function redactExactValues(text: string, values: readonly string[]): string {\n let redacted = text\n for (const value of [...new Set(values)].filter(Boolean).sort((a, b) => b.length - a.length)) {\n redacted = redacted.replaceAll(value, REDACTED_VALUE)\n }\n return redacted\n}\n\n/** Exact-value redactor that safely handles matches split across stream chunks. */\nexport class ExactValueStreamRedactor {\n readonly #values: string[] = []\n #pending = ''\n\n constructor(value: string | readonly string[]) {\n const values = typeof value === 'string' ? [value] : value\n for (const item of values) this.add(item)\n }\n\n add(value: string): void {\n if (!value) throw new Error('stream redactor requires a non-empty value')\n if (!this.#values.includes(value)) this.#values.push(value)\n }\n\n push(chunk: string): string {\n const data = this.#pending + chunk\n const maxLength = Math.max(...this.#values.map((value) => value.length))\n const cutoff = Math.max(0, data.length - (maxLength - 1))\n let output = ''\n let cursor = 0\n while (cursor < cutoff) {\n const match = this.#nextMatch(data, cursor)\n if (!match || match.index >= cutoff) {\n output += data.slice(cursor, cutoff)\n cursor = cutoff\n break\n }\n output += data.slice(cursor, match.index) + REDACTED_VALUE\n cursor = match.index + match.value.length\n }\n this.#pending = data.slice(cursor)\n return output\n }\n\n flush(): string {\n const output = redactExactValues(this.#pending, this.#values)\n this.#pending = ''\n return output\n }\n\n #nextMatch(data: string, cursor: number): { index: number; value: string } | null {\n let next: { index: number; value: string } | null = null\n for (const value of this.#values) {\n const index = data.indexOf(value, cursor)\n if (index < 0) continue\n if (\n !next ||\n index < next.index ||\n (index === next.index && value.length > next.value.length)\n ) {\n next = { index, value }\n }\n }\n return next\n }\n}\n\nexport function redactJsonValue<T>(value: T, secrets: string | readonly string[] | undefined): T {\n if (!secrets || (Array.isArray(secrets) && secrets.length === 0)) return value\n const values = typeof secrets === 'string' ? [secrets] : secrets\n if (typeof value === 'string') return redactExactValues(value, values) as T\n if (Array.isArray(value)) return value.map((item) => redactJsonValue(item, values)) as T\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, redactJsonValue(item, values)]),\n ) as T\n }\n return value\n}\n\nfunction assertProtectedWorkerPaths(value: unknown, agent: ResolvedAgent): void {\n const paths = objectRecord(value, 'protected worker paths')\n assertExactKeys(\n paths,\n new Set([\n 'agentDir',\n 'teamDir',\n 'memoryDir',\n 'sessionDir',\n 'uploadsDir',\n 'skills',\n 'homeDocuments',\n ]),\n 'protected worker paths',\n new Set(['agentDir', 'teamDir', 'memoryDir', 'sessionDir', 'skills', 'homeDocuments']),\n )\n for (const key of ['agentDir', 'teamDir', 'memoryDir', 'sessionDir'] as const) {\n requireAbsolutePath(paths[key], key)\n }\n if (paths.uploadsDir !== undefined) requireAbsolutePath(paths.uploadsDir, 'uploadsDir')\n if (paths.agentDir !== agent.agent.dir || paths.teamDir !== agent.team.path) {\n throw new Error('worker: protected paths do not match the resolved Agent')\n }\n const canonicalAgentDir = requireCanonicalRealDirectory(\n paths.agentDir as string,\n 'Agent directory',\n )\n const canonicalTeamDir = resolveCanonicalDirectory(paths.teamDir as string, 'Team directory')\n requireCanonicalContainedDirectory(\n canonicalAgentDir,\n paths.sessionDir as string,\n 'session directory',\n )\n if (paths.uploadsDir !== undefined) {\n requireCanonicalContainedDirectory(\n canonicalAgentDir,\n paths.uploadsDir as string,\n 'uploads directory',\n )\n }\n requireCanonicalContainedDirectory(\n canonicalTeamDir,\n paths.memoryDir as string,\n 'memory directory',\n )\n if (!Array.isArray(paths.skills)) throw new Error('worker: protected skills must be an array')\n for (const item of paths.skills) {\n const skill = objectRecord(item, 'protected skill')\n assertExactKeys(\n skill,\n new Set(['name', 'description', 'body', 'hostDir', 'sandboxDir']),\n 'protected skill',\n )\n requireString(skill.name, 'skill name')\n requireString(skill.description, 'skill description')\n requireString(skill.body, 'skill body')\n requireAbsolutePath(skill.hostDir, 'skill hostDir')\n requireCanonicalRealDirectory(skill.hostDir, 'skill hostDir')\n if (typeof skill.sandboxDir !== 'string' || !skill.sandboxDir.startsWith('/skills/')) {\n throw new Error('worker: protected skill sandbox path must be under /skills')\n }\n }\n const homeDocuments = objectRecord(paths.homeDocuments, 'protected home documents')\n const homeDocumentKeys = new Set([\n 'AGENTS.md',\n 'SOUL.md',\n 'TOOLS.md',\n 'IDENTITY.md',\n 'BOOTSTRAP.md',\n ])\n assertExactKeys(homeDocuments, homeDocumentKeys, 'protected home documents', homeDocumentKeys)\n for (const [name, contents] of Object.entries(homeDocuments)) {\n if (contents !== null && typeof contents !== 'string') {\n throw new Error(`worker: protected home document ${name} must be text or null`)\n }\n }\n}\n\nfunction assertProtectedDockerRuntime(\n value: unknown,\n paths: ProtectedWorkerPaths,\n): asserts value is ProtectedDockerRuntime {\n const runtime = objectRecord(value, 'protected Docker runtime')\n assertExactKeys(\n runtime,\n new Set([\n 'dockerPath',\n 'executableIdentity',\n 'endpoint',\n 'image',\n 'imageId',\n 'uid',\n 'gid',\n 'workspace',\n 'readOnlyMounts',\n 'containerEnv',\n 'coding',\n ]),\n 'protected Docker runtime',\n new Set([\n 'dockerPath',\n 'executableIdentity',\n 'endpoint',\n 'image',\n 'imageId',\n 'uid',\n 'gid',\n 'workspace',\n 'readOnlyMounts',\n 'containerEnv',\n ]),\n )\n requireAbsolutePath(runtime.dockerPath, 'Docker executable')\n const executableIdentity = objectRecord(\n runtime.executableIdentity,\n 'protected Docker executable identity',\n )\n const executableIdentityKeys = new Set([\n 'device',\n 'inode',\n 'mode',\n 'size',\n 'modifiedTimeNs',\n 'changedTimeNs',\n ])\n assertExactKeys(\n executableIdentity,\n executableIdentityKeys,\n 'protected Docker executable identity',\n executableIdentityKeys,\n )\n for (const [name, field] of Object.entries(executableIdentity)) {\n if (typeof field !== 'string' || !/^\\d+$/.test(field)) {\n throw new Error(`worker: protected Docker executable identity has invalid ${name}`)\n }\n }\n if (\n typeof runtime.endpoint !== 'string' ||\n !runtime.endpoint.startsWith('unix://') ||\n !isAbsolute(runtime.endpoint.slice('unix://'.length)) ||\n containsUnsafeDockerArgument(runtime.endpoint)\n ) {\n throw new Error('worker: protected Docker endpoint must be a local Unix socket')\n }\n if (\n !nonEmptyString(runtime.image) ||\n runtime.image.startsWith('-') ||\n runtime.image.includes('\\0') ||\n runtime.image.includes('\\n') ||\n runtime.image.includes('\\r')\n ) {\n throw new Error('worker: protected Docker image is required')\n }\n if (typeof runtime.imageId !== 'string' || !/^sha256:[a-fA-F0-9]{64}$/.test(runtime.imageId)) {\n throw new Error('worker: protected Docker runtime requires an immutable image id')\n }\n if (!Number.isSafeInteger(runtime.uid) || !Number.isSafeInteger(runtime.gid)) {\n throw new Error('worker: protected Docker runtime requires a numeric host identity')\n }\n if (Number(runtime.uid) < 0 || Number(runtime.gid) < 0) {\n throw new Error('worker: protected Docker runtime requires a non-negative host identity')\n }\n const workspace = objectRecord(runtime.workspace, 'protected Docker workspace')\n assertExactKeys(\n workspace,\n new Set(['source', 'target', 'sourceRoot']),\n 'protected Docker workspace',\n new Set(['source', 'target', 'sourceRoot']),\n )\n requireAbsolutePath(workspace.source, 'Docker workspace source')\n requireAbsolutePath(workspace.sourceRoot, 'Docker workspace root')\n assertSafeDockerHostPath(workspace.source, 'Docker workspace source')\n assertSafeDockerHostPath(workspace.sourceRoot, 'Docker workspace root')\n if (workspace.target !== '/workspace') {\n throw new Error('worker: protected Docker workspace target must be /workspace')\n }\n const canonicalTeamDir = resolveCanonicalDirectory(paths.teamDir, 'Team directory')\n if (workspace.source !== canonicalTeamDir || workspace.sourceRoot !== canonicalTeamDir) {\n throw new Error('worker: protected Docker workspace does not match the canonical Team root')\n }\n if (!Array.isArray(runtime.readOnlyMounts)) {\n throw new Error('worker: protected Docker read-only mounts must be an array')\n }\n const mountTargets = new Set(['/workspace', '/tmp'])\n for (const item of runtime.readOnlyMounts) {\n const mount = objectRecord(item, 'protected Docker read-only mount')\n assertExactKeys(\n mount,\n new Set(['source', 'target', 'sourceRoot']),\n 'protected Docker read-only mount',\n new Set(['source', 'target', 'sourceRoot']),\n )\n requireAbsolutePath(mount.source, 'Docker read-only mount source')\n requireAbsolutePath(mount.sourceRoot, 'Docker read-only mount root')\n assertSafeDockerHostPath(mount.source, 'Docker read-only mount source')\n assertSafeDockerHostPath(mount.sourceRoot, 'Docker read-only mount root')\n if (\n typeof mount.target !== 'string' ||\n !mount.target.startsWith('/') ||\n containsUnsafeDockerArgument(mount.target) ||\n mount.target.split('/').includes('..') ||\n (!mount.target.startsWith('/skills/') &&\n mount.target !== '/inputs' &&\n mount.target !== '/workspace/memory')\n ) {\n throw new Error('worker: protected Docker read-only mount target is not permitted')\n }\n if (!lexicallyWithin(mount.sourceRoot, mount.source)) {\n throw new Error('worker: protected Docker read-only mount escaped its root')\n }\n if (mountTargets.has(mount.target)) {\n throw new Error('worker: protected Docker mount target is duplicated')\n }\n mountTargets.add(mount.target)\n }\n assertExactProtectedDockerMounts(\n runtime.readOnlyMounts as ProtectedDockerRuntime['readOnlyMounts'],\n paths,\n )\n if (runtime.coding !== undefined) validateDockerCodingSelection(runtime.coding)\n const containerEnv = objectRecord(runtime.containerEnv, 'protected Docker container environment')\n const expectedContainerEnv = {\n PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',\n HOME: '/tmp',\n LANG: 'C.UTF-8',\n LC_ALL: 'C.UTF-8',\n SHELL: '/bin/bash',\n TMPDIR: '/tmp',\n }\n const expectedKeys = new Set(Object.keys(expectedContainerEnv))\n assertExactKeys(\n containerEnv,\n expectedKeys,\n 'protected Docker container environment',\n expectedKeys,\n )\n for (const [key, expected] of Object.entries(expectedContainerEnv)) {\n if (containerEnv[key] !== expected) {\n throw new Error(`worker: protected Docker container environment has invalid ${key}`)\n }\n }\n}\n\nfunction assertExactProtectedDockerMounts(\n mounts: ProtectedDockerRuntime['readOnlyMounts'],\n paths: ProtectedWorkerPaths,\n): void {\n const expected = new Map<string, string>([\n ['/workspace/memory', paths.memoryDir],\n ...((paths.uploadsDir ? [['/inputs', paths.uploadsDir]] : []) as Array<[string, string]>),\n ...paths.skills.map((skill) => [skill.sandboxDir, skill.hostDir] as [string, string]),\n ])\n if (mounts.length !== expected.size) {\n throw new Error('worker: protected Docker read-only mount set is incomplete')\n }\n for (const mount of mounts) {\n const source = expected.get(mount.target)\n if (!source || mount.source !== source) {\n throw new Error('worker: protected Docker read-only mount set does not match protected paths')\n }\n }\n}\n\nfunction assertSafeDockerHostPath(value: unknown, name: string): asserts value is string {\n if (typeof value !== 'string' || containsUnsafeDockerArgument(value)) {\n throw new Error(`worker: ${name} cannot be represented safely`)\n }\n}\n\nfunction containsUnsafeDockerArgument(value: string): boolean {\n return value.includes('\\0') || value.includes(',') || value.includes('\\n') || value.includes('\\r')\n}\n\nfunction requireCanonicalRealDirectory(value: unknown, name: string): string {\n if (typeof value !== 'string') throw new Error(`worker: ${name} must be a path`)\n try {\n const entry = lstatSync(value)\n if (entry.isSymbolicLink() || !entry.isDirectory()) {\n throw new Error('unsafe entry')\n }\n const canonical = realpathSync(value)\n if (canonical !== value) throw new Error('non-canonical entry')\n return canonical\n } catch {\n throw new Error(`worker: ${name} must be a real directory`)\n }\n}\n\nfunction resolveCanonicalDirectory(value: string, name: string): string {\n try {\n const entry = statSync(value)\n if (!entry.isDirectory()) throw new Error('not a directory')\n return realpathSync(value)\n } catch {\n throw new Error(`worker: ${name} is unavailable or unsafe`)\n }\n}\n\nfunction requireCanonicalContainedDirectory(root: string, value: string, name: string): string {\n const canonical = requireCanonicalRealDirectory(value, name)\n if (!lexicallyWithin(root, canonical)) throw new Error(`worker: ${name} escaped its root`)\n return canonical\n}\n\nfunction assertMinimalWorkerScratch(value: unknown): asserts value is MinimalWorkerScratch {\n const scratch = objectRecord(value, 'minimal worker scratch')\n assertExactKeys(\n scratch,\n new Set(['root', 'homeDir', 'tempDir', 'piAgentDir', 'reviewCwd', 'reviewSessionDir']),\n 'minimal worker scratch',\n new Set(['root', 'homeDir', 'tempDir', 'piAgentDir', 'reviewCwd', 'reviewSessionDir']),\n )\n requireAbsolutePath(scratch.root, 'scratch root')\n for (const key of [\n 'homeDir',\n 'tempDir',\n 'piAgentDir',\n 'reviewCwd',\n 'reviewSessionDir',\n ] as const) {\n requireAbsolutePath(scratch[key], key)\n if (!lexicallyWithin(scratch.root as string, scratch[key] as string)) {\n throw new Error(`worker: ${key} escaped the scratch root`)\n }\n }\n}\n\nfunction requireCanonicalWindowsDirectory(value: string | undefined, name: string): string {\n if (!value || !isAbsolute(value) || !existsSync(value) || !statSync(value).isDirectory()) {\n throw new Error(`minimal worker bootstrap requires a valid ${name} directory`)\n }\n return realpathSync(value)\n}\n\nfunction requireCanonicalWindowsFile(value: string | undefined, name: string): string {\n if (!value || !isAbsolute(value) || !existsSync(value) || !statSync(value).isFile()) {\n throw new Error(`minimal worker bootstrap requires a valid ${name} executable`)\n }\n return realpathSync(value)\n}\n\nfunction objectRecord(value: unknown, label: string): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`worker: ${label} must be an object`)\n }\n return value as Record<string, unknown>\n}\n\nfunction assertExactKeys(\n value: Record<string, unknown>,\n allowed: ReadonlySet<string>,\n label: string,\n required: ReadonlySet<string> = allowed,\n) {\n const unexpected = Object.keys(value).filter((key) => !allowed.has(key))\n if (unexpected.length > 0) {\n throw new Error(`worker: ${label} contains unexpected fields: ${unexpected.sort().join(', ')}`)\n }\n const missing = [...required].filter((key) => !(key in value))\n if (missing.length > 0) {\n throw new Error(`worker: ${label} is missing required fields: ${missing.sort().join(', ')}`)\n }\n}\n\nfunction isResolvedAgent(value: unknown): value is ResolvedAgent {\n if (!value || typeof value !== 'object') return false\n const resolved = value as Partial<ResolvedAgent>\n return Boolean(\n resolved.agent &&\n typeof resolved.agent.id === 'string' &&\n typeof resolved.agent.dir === 'string' &&\n resolved.team &&\n typeof resolved.team.id === 'string' &&\n typeof resolved.team.path === 'string' &&\n typeof resolved.model === 'string',\n )\n}\n\nfunction requireString(value: unknown, name: string): asserts value is string {\n if (typeof value !== 'string') throw new Error(`worker: ${name} must be a string`)\n}\n\nfunction nonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === 'string'\n}\n\nfunction requireAbsolutePath(value: unknown, name: string): asserts value is string {\n if (typeof value !== 'string' || !isAbsolute(value) || value.includes('\\0')) {\n throw new Error(`worker: ${name} must be an absolute path`)\n }\n}\n\nfunction lexicallyWithin(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n}\n\n/** Test/preflight helper for checking that every scratch directory is accessible. */\nexport async function checkMinimalWorkerScratch(scratch: MinimalWorkerScratch): Promise<void> {\n assertMinimalWorkerScratch(scratch)\n for (const path of Object.values(scratch)) await access(path, constants.R_OK | constants.W_OK)\n}\n\nfunction assertConversationTarget(value: unknown): void {\n const target = objectRecord(value, 'conversation target')\n assertExactKeys(target, new Set(['id', 'filename']), 'conversation target')\n if (\n typeof target.id !== 'string' ||\n !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target.id) ||\n target.filename !== `${target.id}.jsonl`\n )\n throw new Error('Invalid worker conversation target')\n}\n","import type { AgentQuestionInput, AgentQuestionToolResult } from '@bazilion/api-types'\nimport type { ToolHandler } from './types.ts'\n\nexport type AskUser = (\n toolCallId: string,\n question: AgentQuestionInput,\n) => Promise<AgentQuestionToolResult>\nexport function askUserTool(ask: AskUser): ToolHandler {\n return {\n def: {\n name: 'ask_user',\n description:\n 'Ask the operator one clarification question and wait for an explicit choice, free-text answer, Skip, or a typed no-answer outcome. An answer is information only: it never grants shell or communication permission. Do not ask for credentials. Do not invent a response when no answer is returned.',\n parameters: {\n type: 'object',\n additionalProperties: false,\n required: ['prompt', 'choices'],\n properties: {\n prompt: { type: 'string', minLength: 1, maxLength: 4096 },\n choices: {\n type: 'array',\n minItems: 2,\n maxItems: 4,\n items: {\n type: 'object',\n additionalProperties: false,\n required: ['label'],\n properties: {\n label: { type: 'string', minLength: 1, maxLength: 120 },\n description: { type: 'string', minLength: 1, maxLength: 500 },\n },\n },\n },\n recommendedIndex: { type: 'integer', minimum: 0, maximum: 3 },\n },\n },\n },\n async invoke(args, context) {\n if (!context?.toolCallId) throw new Error('Question requires its actual tool call identity')\n const { receipt, ...result } = await ask(\n context.toolCallId,\n args as unknown as AgentQuestionInput,\n )\n const text = JSON.stringify(result)\n return receipt ? { content: [{ type: 'text', text }], questionReceipt: receipt } : text\n },\n }\n}\n","import { type AskUser, askUserTool } from '../tools/ask-user.ts'\n// Adapter: Bazilion ToolHandler → pi-coding-agent ToolDefinition.\n//\n// Pi expects tools to return `AgentToolResult<TDetails>` =\n// `{ content: (TextContent | ImageContent)[]; details: TDetails; terminate?: boolean }`.\n// Our handlers return plain strings. The adapter wraps the string into a single\n// text content block with empty details — the same fidelity we had before.\n//\n// Pi's tool execute contract: throw on failure. The agent loop wraps thrown\n// errors as tool-result messages with `isError: true`. We preserve this shape\n// directly because our handlers already throw on bad args / runtime failures.\n//\n// What's in this module:\n// - `ourToolToPiTool` — single-handler wrapper.\n// - `createBazilionCustomTools` — composed suite of the Bazilion-specific\n// tools (memory_*, messaging, bootstrap_done, web_search/fetch). File I/O\n// tools are *not* here — pi's createCodingTools(cwd, …) replaces them.\n\nimport type { ResolvedAgent } from '@bazilion/api-types'\nimport type { ToolDefinition } from '@earendil-works/pi-coding-agent'\nimport { Type } from 'typebox'\nimport type { MemoryBackend } from '../memory/types.ts'\nimport { bootstrapTool } from '../tools/bootstrap.ts'\nimport { browserTools } from '../tools/browser.ts'\nimport { deliverFileTool, type FileSink } from '../tools/deliver-file.ts'\nimport { homeTools } from '../tools/home.ts'\nimport { mcpProxyTools } from '../tools/mcp.ts'\nimport { memoryTools } from '../tools/memory.ts'\nimport { messagingTools } from '../tools/messaging.ts'\nimport type { ToolHandler, ToolOutput } from '../tools/types.ts'\nimport { userMdTools } from '../tools/user-md.ts'\nimport { protectedWebFetchTool, webTools } from '../tools/web.ts'\nimport type {\n BrowserHost,\n InjectedMcpTool,\n McpHost,\n MessagingHost,\n UserMdHost,\n} from '../worker/ipc-protocol.ts'\n\n/**\n * Wrap a Bazilion `ToolHandler` as a pi `ToolDefinition` so it can be passed\n * through `customTools` to `createAgentSession`.\n *\n * Design note: we keep the same tool name + description + parameter JSON schema\n * that the handler already declares. Pi wants a `TypeBox` schema; we pass the\n * JSONSchema through `Type.Unsafe` so the LLM validation happens on pi's side\n * without us having to re-author schemas in typebox syntax.\n */\nexport function ourToolToPiTool(h: ToolHandler): ToolDefinition {\n return {\n name: h.def.name,\n label: h.def.name,\n description: h.def.description,\n parameters: Type.Unsafe<Record<string, unknown>>(h.def.parameters as Record<string, unknown>),\n async execute(toolCallId, params) {\n const out = await h.invoke(params as Record<string, unknown>, { toolCallId })\n return {\n content: toPiContent(out),\n details:\n typeof out === 'object' && !Array.isArray(out)\n ? 'result' in out\n ? { result: out.result }\n : { questionReceipt: out.questionReceipt }\n : {},\n }\n },\n }\n}\n\n/**\n * Map a Bazilion `ToolOutput` to pi's `(TextContent | ImageContent)[]`. A bare\n * string becomes a single text block (the legacy shape); a part array maps\n * 1:1 — pi's content blocks have the same field names.\n */\nfunction toPiContent(\n out: ToolOutput,\n): Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }> {\n if (typeof out === 'string') return [{ type: 'text', text: out }]\n return (Array.isArray(out) ? out : out.content).map((p) =>\n p.type === 'text'\n ? { type: 'text', text: p.text }\n : { type: 'image', data: p.data, mimeType: p.mimeType },\n )\n}\n\nexport interface BazilionCustomToolsOpts {\n askUser?: AskUser\n agent: ResolvedAgent\n memory: MemoryBackend\n /** If provided, enables inter-agent messaging tools. */\n messagingHost?: MessagingHost\n /** If provided, enables the `user_md_append` tool. */\n userMdHost?: UserMdHost\n /** If provided, enables the `browser_*` tools (proxied to the daemon pool). */\n browserHost?: BrowserHost\n /** If provided alongside `mcpTools`, enables the discovered MCP proxy tools. */\n mcpHost?: McpHost\n /** MCP tools discovered daemon-side, exposed as proxy tools. */\n mcpTools?: InjectedMcpTool[]\n /** If provided, enables the `deliver_file` tool (emits a `file` event). */\n sessionId?: string\n fileSink?: FileSink\n /** Merged env (process.env + secrets). */\n env?: NodeJS.ProcessEnv\n}\n\nexport interface ProtectedBazilionCustomToolsOpts {\n askUser?: AskUser\n agent: ResolvedAgent\n memory: MemoryBackend\n messagingHost: MessagingHost\n userMdHost: UserMdHost\n sessionId?: string\n fileSink: FileSink\n}\n\n/**\n * Build the list of Bazilion-specific custom tools in the shape pi expects.\n *\n * Excludes file-I/O tools (`read`/`bash`/`edit`/`write`/`grep`/`find`/`ls`) —\n * those come from pi's `createCodingTools(cwd, …)` now that we've adopted the\n * richer toolset. Also excludes the legacy `workspace_list/read/write`\n * triumvirate: pi's tools work against a single cwd (the agent's default\n * workspace), and mounted non-default workspaces are reachable via absolute\n * paths through pi's `bash`/`read`/`edit`.\n */\nexport function createBazilionCustomTools(opts: BazilionCustomToolsOpts): ToolDefinition[] {\n const handlers: ToolHandler[] = [\n ...memoryTools(opts.memory),\n ...homeTools(opts.agent.agent.dir),\n bootstrapTool(opts.agent.agent.dir),\n ...webTools({ env: opts.env }),\n ]\n if (opts.messagingHost) {\n handlers.push(...messagingTools(opts.messagingHost, opts.agent.agent.id))\n }\n if (opts.userMdHost) {\n handlers.push(...userMdTools(opts.userMdHost, opts.agent.team.id))\n }\n if (opts.browserHost) {\n handlers.push(...browserTools(opts.browserHost, opts.agent.agent.id))\n }\n if (opts.mcpHost && opts.mcpTools && opts.mcpTools.length > 0) {\n handlers.push(...mcpProxyTools(opts.mcpHost, opts.mcpTools))\n }\n if (opts.fileSink) {\n handlers.push(deliverFileTool(opts.agent.team.path, opts.fileSink, opts.sessionId))\n }\n if (opts.askUser) handlers.push(askUserTool(opts.askUser))\n return handlers.map(ourToolToPiTool)\n}\n\n/**\n * Closed normal-turn capability set. No environment is accepted, so search,\n * Firecrawl, browser, MCP, and provider/tool credentials cannot be restored by\n * configuration inside a protected worker.\n */\nexport function createProtectedBazilionCustomTools(\n opts: ProtectedBazilionCustomToolsOpts,\n): ToolDefinition[] {\n const handlers: ToolHandler[] = [\n ...memoryTools(opts.memory),\n ...homeTools(opts.agent.agent.dir),\n bootstrapTool(opts.agent.agent.dir),\n protectedWebFetchTool(),\n ...messagingTools(opts.messagingHost, opts.agent.agent.id),\n ...userMdTools(opts.userMdHost, opts.agent.team.id),\n deliverFileTool(opts.agent.team.path, opts.fileSink, opts.sessionId),\n ]\n if (opts.askUser) handlers.push(askUserTool(opts.askUser))\n return handlers.map(ourToolToPiTool)\n}\n","import { existsSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { ToolHandler } from './types.ts'\n\nexport function bootstrapTool(agentDir: string): ToolHandler {\n return {\n def: {\n name: 'bootstrap_done',\n description:\n 'Call this once you have finished your bootstrap conversation (introduced yourself, learned the user, updated IDENTITY.md). Removes BOOTSTRAP.md so it does not appear in future sessions.',\n parameters: { type: 'object', properties: {} },\n },\n async invoke() {\n const path = join(agentDir, 'BOOTSTRAP.md')\n if (existsSync(path)) {\n rmSync(path)\n return 'BOOTSTRAP.md removed. Bootstrap is complete.'\n }\n return 'BOOTSTRAP.md was already removed.'\n },\n }\n}\n","// Browser automation tools (worker-side definitions).\n//\n// These are thin proxies: each tool's `invoke` forwards to the daemon-side\n// Playwright pool over IPC via the injected `BrowserHost`. The browser session\n// (stateful, persistent across turns) lives entirely in the daemon — see\n// `lib/browser/pool.ts`. Results are multimodal: snapshots/console/network come\n// back as text, `browser_take_screenshot` as a base64 image.\n//\n// Perception is accessibility-tree-first. `browser_snapshot` returns an aria\n// tree with `[ref=eN]` element refs; every interaction targets an element by\n// that `ref`. No vision model is needed for routine automation — screenshots\n// are the exception, not the rule.\n\nimport type { BrowserHost } from '../worker/ipc-protocol.ts'\nimport type { ToolHandler } from './types.ts'\n\nconst REF_DESC = 'Element ref from the latest browser_snapshot, e.g. \"e5\".'\n\n/**\n * Build the `browser_*` tool suite bound to one agent's session. Each tool\n * forwards to `host.invoke(agentId, <action>, args)`.\n */\nexport function browserTools(host: BrowserHost, agentId: string): ToolHandler[] {\n const proxy = (\n name: string,\n action: string,\n description: string,\n properties: Record<string, unknown>,\n required: string[] = [],\n ): ToolHandler => ({\n def: {\n name,\n description,\n parameters: { type: 'object', properties, required, additionalProperties: false },\n },\n invoke: (args) => host.invoke(agentId, action, args),\n })\n\n return [\n proxy(\n 'browser_navigate',\n 'navigate',\n 'Open a URL in the browser and return an accessibility snapshot of the loaded page. Starts a persistent browser session that survives across turns (cookies/logins carry over).',\n { url: { type: 'string', description: 'Absolute URL to navigate to.' } },\n ['url'],\n ),\n proxy(\n 'browser_snapshot',\n 'snapshot',\n 'Capture the current page as an accessibility tree (YAML) with [ref=eN] element references. This is the primary way to \"see\" the page — prefer it over screenshots. Use the refs with browser_click/type/etc.',\n {},\n ),\n proxy(\n 'browser_click',\n 'click',\n 'Click an element identified by its snapshot ref. Returns a fresh snapshot.',\n { ref: { type: 'string', description: REF_DESC } },\n ['ref'],\n ),\n proxy(\n 'browser_type',\n 'type',\n 'Type text into an input/textarea identified by its ref. Set submit=true to press Enter afterwards. Returns a fresh snapshot.',\n {\n ref: { type: 'string', description: REF_DESC },\n text: { type: 'string', description: 'Text to type into the field.' },\n submit: { type: 'boolean', description: 'Press Enter after typing.' },\n },\n ['ref', 'text'],\n ),\n proxy(\n 'browser_hover',\n 'hover',\n 'Hover the pointer over an element identified by its ref. Returns a fresh snapshot.',\n { ref: { type: 'string', description: REF_DESC } },\n ['ref'],\n ),\n proxy(\n 'browser_select',\n 'select',\n 'Select option(s) in a <select> element identified by its ref.',\n {\n ref: { type: 'string', description: REF_DESC },\n values: {\n type: 'array',\n items: { type: 'string' },\n description: 'Option values to select.',\n },\n },\n ['ref', 'values'],\n ),\n proxy(\n 'browser_fill_form',\n 'fill_form',\n 'Fill multiple form fields in one call. Each field is { ref, value }.',\n {\n fields: {\n type: 'array',\n description: 'Fields to fill.',\n items: {\n type: 'object',\n properties: {\n ref: { type: 'string', description: REF_DESC },\n value: { type: 'string' },\n },\n required: ['ref', 'value'],\n },\n },\n },\n ['fields'],\n ),\n proxy(\n 'browser_press_key',\n 'press_key',\n 'Press a keyboard key (e.g. \"Enter\", \"Escape\", \"ArrowDown\", \"Control+A\").',\n { key: { type: 'string', description: 'Key or chord to press.' } },\n ['key'],\n ),\n proxy(\n 'browser_go_back',\n 'go_back',\n 'Navigate back to the previous page in history. Returns a fresh snapshot.',\n {},\n ),\n proxy(\n 'browser_tabs',\n 'tabs',\n 'Manage tabs. op=\"list\" lists open tabs; \"new\" opens a tab (optional url); \"select\" focuses tab at index; \"close\" closes tab at index (defaults to active).',\n {\n op: { type: 'string', enum: ['list', 'new', 'select', 'close'] },\n index: { type: 'number', description: 'Tab index for select/close.' },\n url: { type: 'string', description: 'URL to open for op=new.' },\n },\n ['op'],\n ),\n proxy(\n 'browser_take_screenshot',\n 'take_screenshot',\n 'Take a PNG screenshot of the current page (returned as an image). Use only for visual verification or canvas/pixel content the accessibility snapshot cannot represent — prefer browser_snapshot for interaction.',\n { full_page: { type: 'boolean', description: 'Capture the full scrollable page.' } },\n ),\n proxy(\n 'browser_console',\n 'console',\n 'Return recent browser console messages (logs, warnings, errors).',\n {},\n ),\n proxy(\n 'browser_network',\n 'network',\n 'Return recent network requests issued by the page (method, status, URL).',\n {},\n ),\n ]\n}\n","// `deliver_file` — the agent's outbound document channel.\n//\n// The tool reads a confined workspace file and awaits daemon-owned publication.\n// The sink returns an opaque receipt only after bytes and provenance commit;\n// worker events and canonical Pi tool-result details carry that same reference.\n// Shared Agent-to-user authorization still owns release to operator surfaces.\n\nimport {\n closeSync,\n constants,\n fstatSync,\n openSync,\n readSync,\n realpathSync,\n statSync,\n} from 'node:fs'\nimport { basename, extname, isAbsolute, relative, resolve, sep } from 'node:path'\nimport type { ResultReference } from '@bazilion/api-types'\nimport type { ToolHandler } from './types.ts'\n\nconst MAX_DELIVER_BYTES = 25 * 1024 * 1024\n\nconst MIME: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.txt': 'text/plain',\n '.md': 'text/markdown',\n '.csv': 'text/csv',\n '.json': 'application/json',\n '.html': 'text/html',\n '.zip': 'application/zip',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n}\n\nexport interface DeliveredFile {\n name: string\n mimeType: string\n data: string\n}\nexport interface DeliverySource {\n sessionId: string\n toolCallId: string\n}\nexport type FileSink = (file: DeliveredFile, source: DeliverySource) => Promise<ResultReference>\n\nfunction isWithin(root: string, candidate: string): boolean {\n const rel = relative(root, candidate)\n return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))\n}\n\nexport function deliverFileTool(cwd: string, sink: FileSink, sessionId = ''): ToolHandler {\n const workspacePath = resolve(cwd)\n let workspaceRealPath: string | null = null\n try {\n workspaceRealPath = realpathSync(workspacePath)\n } catch {\n // Keep tool construction side-effect free. Invocation reports the broken\n // workspace only if the agent actually tries to deliver a file.\n }\n\n return {\n def: {\n name: 'deliver_file',\n description:\n 'Send a file from your workspace to the user so they can download it (web), receive it as a document (Telegram), or save it (CLI). Use this to deliver reports, exports, or any artifact you produced. Max 25 MiB. Saved delivery remains subject to Team Policy.',\n parameters: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Path to a file, relative to your workspace.',\n },\n },\n required: ['path'],\n additionalProperties: false,\n },\n },\n async invoke(args, context) {\n const p = String(args.path ?? '')\n if (!p) throw new Error('deliver_file: path is required')\n if (isAbsolute(p)) {\n throw new Error(`deliver_file: path must stay within the workspace: ${p}`)\n }\n\n const abs = resolve(workspacePath, p)\n if (!isWithin(workspacePath, abs)) {\n throw new Error(`deliver_file: path must stay within the workspace: ${p}`)\n }\n\n if (!workspaceRealPath) {\n throw new Error('deliver_file: workspace is unavailable')\n }\n\n let real: string\n let expected: ReturnType<typeof statSync>\n try {\n real = realpathSync(abs)\n expected = statSync(real)\n } catch {\n throw new Error(`deliver_file: no such file: ${p}`)\n }\n\n if (!isWithin(workspaceRealPath, real)) {\n throw new Error(`deliver_file: path must stay within the workspace: ${p}`)\n }\n\n // Use a no-follow descriptor after canonical validation. Besides making\n // the regular-file check explicit, this closes the common final-symlink\n // swap window between realpath/stat/read. Non-blocking mode prevents a\n // non-regular path such as a FIFO from hanging before fstat can reject it.\n let fd: number\n try {\n fd = openSync(real, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK)\n } catch {\n throw new Error(`deliver_file: no such file: ${p}`)\n }\n\n let data: string\n try {\n const stat = fstatSync(fd)\n if (!stat.isFile()) {\n throw new Error(`deliver_file: not a regular file: ${p}`)\n }\n if (stat.dev !== expected.dev || stat.ino !== expected.ino) {\n throw new Error(`deliver_file: path changed during validation: ${p}`)\n }\n let confirmedReal: string\n try {\n confirmedReal = realpathSync(abs)\n } catch {\n throw new Error(`deliver_file: path changed during validation: ${p}`)\n }\n if (confirmedReal !== real) {\n throw new Error(`deliver_file: path changed during validation: ${p}`)\n }\n if (stat.size > MAX_DELIVER_BYTES) {\n throw new Error(\n `deliver_file: \"${basename(abs)}\" is too large (${(stat.size / 1024 / 1024).toFixed(1)} MB > 25 MB)`,\n )\n }\n // Bound the read itself, including growth after fstat. Never allocate more\n // than the limit plus one sentinel byte, even for a rapidly growing source.\n const buffer = Buffer.alloc(MAX_DELIVER_BYTES + 1)\n let length = 0\n while (length < buffer.length) {\n const count = readSync(fd, buffer, length, buffer.length - length, null)\n if (count === 0) break\n length += count\n }\n if (length > MAX_DELIVER_BYTES) throw new Error('deliver_file: file exceeds 25 MiB')\n data = buffer.subarray(0, length).toString('base64')\n } finally {\n closeSync(fd)\n }\n\n const name = basename(abs)\n const mimeType = MIME[extname(abs).toLowerCase()] ?? 'application/octet-stream'\n if (!context?.toolCallId || !sessionId)\n throw new Error('deliver_file: missing source operation')\n const result = await sink(\n { name, mimeType, data },\n { sessionId, toolCallId: context.toolCallId },\n )\n return {\n content: [\n {\n type: 'text',\n text: `Saved \"${name}\" (${mimeType}) as result ${result.resultId}. Delivery is subject to Team Policy.`,\n },\n ],\n result,\n }\n },\n }\n}\n","import {\n closeSync,\n constants,\n fstatSync,\n lstatSync,\n openSync,\n readdirSync,\n writeFileSync,\n} from 'node:fs'\nimport { join } from 'node:path'\nimport { noFollowFlag, readRegularFileNoFollow, resolveRealDirectory } from '../safe-files.ts'\nimport type { ToolHandler } from './types.ts'\n\n// Files the agent may read/write in its private home directory.\n// BOOTSTRAP.md is readable but not writable — its lifecycle belongs to\n// the `bootstrap_done` tool, not `home_write`.\nconst HOME_FILES_READABLE = [\n 'IDENTITY.md',\n 'SOUL.md',\n 'BOOTSTRAP.md',\n 'AGENTS.md',\n 'TOOLS.md',\n] as const\n\nconst HOME_FILES_WRITABLE = ['IDENTITY.md', 'SOUL.md', 'AGENTS.md', 'TOOLS.md'] as const\n\nexport type PrivateHomeFile = (typeof HOME_FILES_READABLE)[number]\nexport type PrivateHomeSnapshot = Partial<Record<PrivateHomeFile, string>>\n\n/**\n * Read the complete fixed Agent-document surface without following links.\n * Protected preparation calls this before spawn; prompt construction uses the\n * returned bytes so a planted fixed-name symlink can never inject host data.\n */\nexport function loadPrivateHomeSnapshot(agentDir: string): PrivateHomeSnapshot {\n const homeRoot = resolvePrivateHomeRoot(agentDir)\n const snapshot: PrivateHomeSnapshot = {}\n for (const file of HOME_FILES_READABLE) {\n const path = privateHomeFile(homeRoot, file)\n try {\n snapshot[file] = readRegularFileNoFollow(path)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') continue\n throw new Error(`private Agent home is unsafe at ${file}`)\n }\n }\n return snapshot\n}\n\nexport function homeTools(agentDir: string): ToolHandler[] {\n const homeRoot = resolvePrivateHomeRoot(agentDir)\n return [\n {\n def: {\n name: 'home_read',\n description:\n 'Read one of your own home files — your identity, soul, or behaviour rules. These files are private to you and are also injected into your system prompt; read them when you need to quote exact wording or check current state.',\n parameters: {\n type: 'object',\n properties: {\n file: { type: 'string', enum: [...HOME_FILES_READABLE] },\n },\n required: ['file'],\n },\n },\n async invoke(args) {\n const file = String(args.file ?? '')\n if (!HOME_FILES_READABLE.includes(file as (typeof HOME_FILES_READABLE)[number])) {\n throw new Error(\n `home_read: \"file\" must be one of ${HOME_FILES_READABLE.join(', ')}; got \"${file}\"`,\n )\n }\n const path = privateHomeFile(homeRoot, file)\n try {\n return readRegularFileNoFollow(path)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`home_read: could not read ${file}: ${msg}`)\n }\n },\n },\n {\n def: {\n name: 'home_write',\n description:\n \"Overwrite one of your own home files. Use this to update your name, personality, or persistent self-definition. Do NOT use this for work output (use `write` / `edit` — those land in your team's shared directory) or for facts you want to remember (use `memory_write`). BOOTSTRAP.md is not writable here; call `bootstrap_done` to retire it.\",\n parameters: {\n type: 'object',\n properties: {\n file: { type: 'string', enum: [...HOME_FILES_WRITABLE] },\n content: { type: 'string', description: 'new full file content' },\n },\n required: ['file', 'content'],\n },\n },\n async invoke(args) {\n const file = String(args.file ?? '')\n if (!HOME_FILES_WRITABLE.includes(file as (typeof HOME_FILES_WRITABLE)[number])) {\n throw new Error(\n `home_write: \"file\" must be one of ${HOME_FILES_WRITABLE.join(', ')}; got \"${file}\"`,\n )\n }\n const content = typeof args.content === 'string' ? args.content : ''\n const path = privateHomeFile(homeRoot, file)\n let fd: number | undefined\n try {\n rejectExistingSymlinkOrNonFile(path)\n fd = openSync(\n path,\n constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | noFollowFlag(),\n 0o600,\n )\n if (!fstatSync(fd).isFile()) throw new Error('target is not a regular file')\n writeFileSync(fd, content, 'utf8')\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw new Error(`home_write: could not write ${file}: ${msg}`)\n } finally {\n if (fd !== undefined) closeSync(fd)\n }\n return `wrote ${file} (${Buffer.byteLength(content, 'utf8')} bytes)`\n },\n },\n {\n def: {\n name: 'home_list',\n description: 'List your home files with their sizes.',\n parameters: { type: 'object', properties: {} },\n },\n async invoke() {\n const entries: string[] = []\n for (const file of HOME_FILES_READABLE) {\n const path = privateHomeFile(homeRoot, file)\n try {\n const s = lstatSync(path)\n if (s.isSymbolicLink() || !s.isFile()) continue\n entries.push(`${file} (${s.size}b)`)\n } catch {\n // file not present — skip\n }\n }\n if (entries.length === 0) {\n const dirEntries = (() => {\n try {\n return readdirSync(homeRoot)\n } catch {\n return []\n }\n })()\n return `(no home files found; agent dir contains: ${dirEntries.join(', ') || 'nothing'})`\n }\n return entries.join('\\n')\n },\n },\n ]\n}\n\nfunction resolvePrivateHomeRoot(agentDir: string): string {\n try {\n return resolveRealDirectory(agentDir)\n } catch {\n throw new Error('home tools require a real private Agent directory')\n }\n}\n\nfunction privateHomeFile(homeRoot: string, file: string): string {\n // `file` has already passed a closed enum check. Keeping path construction\n // here makes the fixed-root boundary explicit for every filesystem call.\n return join(homeRoot, file)\n}\n\nfunction rejectSymlinkOrNonFile(path: string): void {\n const entry = lstatSync(path)\n if (entry.isSymbolicLink()) throw new Error('symbolic links are not allowed')\n if (!entry.isFile()) throw new Error('target is not a regular file')\n}\n\nfunction rejectExistingSymlinkOrNonFile(path: string): void {\n try {\n rejectSymlinkOrNonFile(path)\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code\n if (code === 'ENOENT') return\n throw error\n }\n}\n","// MCP proxy tools (worker-side definitions).\n//\n// The daemon discovers each enabled MCP server's tools (`tools/list`) before\n// the worker spawns and ships them in `WorkerInput.mcpTools`. Here we turn each\n// into a Bazilion `ToolHandler` whose `invoke` forwards to the daemon-side\n// connection pool over IPC via the injected `McpHost`. The LLM sees the\n// namespaced name (`mcp__<server>__<tool>`); the proxy carries the original\n// `serverId` + `rawName` so the daemon routes the `tools/call` correctly.\n\nimport type { InjectedMcpTool, McpHost } from '../worker/ipc-protocol.ts'\nimport type { ToolHandler } from './types.ts'\n\n/** Build proxy tools for every discovered MCP tool. */\nexport function mcpProxyTools(host: McpHost, tools: InjectedMcpTool[]): ToolHandler[] {\n return tools.map((t) => ({\n def: {\n name: t.toolName,\n description: t.description,\n parameters: t.inputSchema,\n },\n invoke: (args) => host.invoke(t.serverId, t.rawName, args),\n }))\n}\n","import type { MemoryBackend } from '../memory/types.ts'\nimport type { ToolHandler } from './types.ts'\n\nexport function memoryTools(memory: MemoryBackend): ToolHandler[] {\n return [\n {\n def: {\n name: 'memory_write',\n description:\n 'Write or update a memory note in the GROUP-SHARED memory. All agents in this team can read what you write. Use it for project knowledge, codebase notes, decisions, and findings — anything other agents in the team should benefit from. For personal notes about yourself (preferences, persona) use `home_write` on IDENTITY.md. For STABLE facts about the human you\\'re working with (their preferences, role, working hours, how they like to be addressed) use `user_md_get` then `user_md_write` — those land in every agent\\'s system prompt directly. Key is a path-like string with a markdown extension, e.g. \"auth-flow.md\" or \"decisions/2026-05-migration.md\".',\n parameters: {\n type: 'object',\n properties: {\n key: { type: 'string', description: 'memory key (relative path)' },\n content: { type: 'string', description: 'note content (plain text or markdown)' },\n },\n required: ['key', 'content'],\n },\n },\n async invoke(args) {\n const key = String(args.key ?? '')\n const content = String(args.content ?? '')\n if (!key) throw new Error('memory_write: \"key\" is required')\n const entry = await memory.write(key, content)\n return `wrote ${entry.key} (${entry.content.length} bytes)`\n },\n },\n {\n def: {\n name: 'memory_search',\n description:\n 'Search the team-shared memory by substring. Returns matching entry keys with short snippets around the match.',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string' },\n limit: { type: 'number', description: 'max results (default 10)' },\n },\n required: ['query'],\n },\n },\n async invoke(args) {\n const query = String(args.query ?? '')\n if (!query) throw new Error('memory_search: \"query\" is required')\n const limit = typeof args.limit === 'number' ? args.limit : 10\n const hits = await memory.search(query, { limit })\n if (hits.length === 0) return 'no matches'\n return hits.map((h) => `${h.key}: ${h.snippet.replaceAll('\\n', ' ')}`).join('\\n')\n },\n },\n {\n def: {\n name: 'memory_read',\n description: 'Read a single entry from the team-shared memory by key.',\n parameters: {\n type: 'object',\n properties: { key: { type: 'string' } },\n required: ['key'],\n },\n },\n async invoke(args) {\n const key = String(args.key ?? '')\n if (!key) throw new Error('memory_read: \"key\" is required')\n const entry = await memory.read(key)\n return entry.content\n },\n },\n {\n def: {\n name: 'memory_list',\n description: 'List every entry in the team-shared memory with its byte size.',\n parameters: { type: 'object', properties: {} },\n },\n async invoke() {\n const all = await memory.list()\n if (all.length === 0) return '(empty)'\n return all.map((e) => `${e.key} (${e.content.length}b)`).join('\\n')\n },\n },\n ]\n}\n","import type { MessagingHost } from '../worker/ipc-protocol.ts'\nimport type { ToolHandler } from './types.ts'\n\ninterface MessagePayload {\n text: string\n [key: string]: unknown\n}\n\nfunction decodeText(payload: string): string {\n try {\n const parsed = JSON.parse(payload) as MessagePayload\n if (parsed && typeof parsed.text === 'string') return parsed.text\n } catch {\n // not JSON; return raw payload\n }\n return payload\n}\n\nexport function messagingTools(host: MessagingHost, fromAgentId: string): ToolHandler[] {\n return [\n {\n def: {\n name: 'send_message',\n description:\n 'Send a message to another agent. Use the recipient\\'s agent id (UUID). Use this ONLY for things the recipient needs to ACT on: delegating a task, asking a peer for information you cannot get yourself, escalating a decision. Do NOT use it for status updates or to announce changes to team-shared resources — USER.md and the team memory backend both propagate to every agent in the team automatically on their next turn, so messages like \"I updated USER.md\" or \"I wrote a new memory note\" are pure noise and will trigger an inbox-wake loop on the recipient.',\n parameters: {\n type: 'object',\n properties: {\n to: { type: 'string', description: 'Recipient agent id' },\n text: { type: 'string', description: 'Message text' },\n reply_to: {\n type: 'string',\n description: 'Optional: id of the message you are replying to',\n },\n },\n required: ['to', 'text'],\n },\n },\n async invoke(args) {\n const to = String(args.to ?? '')\n const text = String(args.text ?? '')\n if (!to) throw new Error('send_message: \"to\" is required')\n if (!text) throw new Error('send_message: \"text\" is required')\n if (!(await host.agentExists(to))) {\n throw new Error(`send_message: agent not found: ${to}`)\n }\n const replyTo = typeof args.reply_to === 'string' ? args.reply_to : null\n const { messageId } = await host.sendMessage({\n from: fromAgentId,\n to,\n payload: JSON.stringify({ text }),\n replyTo,\n })\n return `sent message ${messageId}`\n },\n },\n {\n def: {\n name: 'approval_status',\n description:\n 'Check the current status of a communication approval request that you created.',\n parameters: {\n type: 'object',\n properties: {\n approval_id: {\n type: 'string',\n description: 'Approval request id returned by a protected communication attempt',\n },\n },\n required: ['approval_id'],\n },\n },\n async invoke(args) {\n const approvalId = String(args.approval_id ?? '')\n if (!approvalId) throw new Error('approval_status: \"approval_id\" is required')\n const approval = await host.approvalStatus(fromAgentId, approvalId)\n if (!approval) throw new Error(`approval_status: approval not found: ${approvalId}`)\n const decision = approval.decidedBy\n ? `; decided by ${approval.decidedBy}${approval.decisionReason ? ` (${approval.decisionReason})` : ''}`\n : ''\n const failure = approval.deliveryError ? `; delivery error: ${approval.deliveryError}` : ''\n return `approval ${approval.id}: ${approval.status}; expires ${approval.expiresAt}${decision}${failure}`\n },\n },\n {\n def: {\n name: 'read_inbox',\n description: 'Read messages addressed to you. Marks unread messages as read by default.',\n parameters: {\n type: 'object',\n properties: {\n include_read: {\n type: 'boolean',\n description: 'Also include already-read messages (default false)',\n },\n },\n },\n },\n async invoke(args) {\n const includeRead = args.include_read === true\n const messages = await host.listInbox(fromAgentId, { unreadOnly: !includeRead })\n if (messages.length === 0) return '(no messages)'\n const lines: string[] = []\n for (const m of messages) {\n lines.push(`from ${m.fromAgentId} [${m.id}]: ${decodeText(m.payload)}`)\n if (!m.readAt) await host.markRead(m.id)\n }\n return lines.join('\\n')\n },\n },\n {\n def: {\n name: 'wait_for_reply',\n description:\n 'Wait for a reply only when the peer does not need your current workspace. For shared-workspace preparation, send_message and end this turn; the inbox reply resumes work after cleanup releases ownership.',\n parameters: {\n type: 'object',\n properties: {\n message_id: {\n type: 'string',\n description: 'id of the message you sent',\n },\n timeout_ms: {\n type: 'number',\n description: 'max wait in milliseconds (default 30000)',\n },\n poll_ms: {\n type: 'number',\n description: 'poll interval in milliseconds (default 200)',\n },\n },\n required: ['message_id'],\n },\n },\n async invoke(args) {\n const messageId = String(args.message_id ?? '')\n if (!messageId) throw new Error('wait_for_reply: \"message_id\" is required')\n const timeout = typeof args.timeout_ms === 'number' ? args.timeout_ms : 30000\n const poll = typeof args.poll_ms === 'number' ? args.poll_ms : 200\n const start = Date.now()\n while (Date.now() - start < timeout) {\n const replies = await host.findReplies(fromAgentId, messageId)\n if (replies.length > 0) {\n const r = replies[0]\n if (r) {\n if (!r.readAt) await host.markRead(r.id)\n return `reply from ${r.fromAgentId} [${r.id}]: ${decodeText(r.payload)}`\n }\n }\n await new Promise((r) => setTimeout(r, poll))\n }\n return `no reply within ${timeout}ms`\n },\n },\n ]\n}\n","// Team-shared USER.md read/write surface for agents.\n//\n// USER.md is inlined into every agent's system prompt every turn (capped at\n// 12 KB on the daemon-side host). Agents previously could only read it; now\n// they can also update it via read-modify-write so they can correct stale\n// facts, not just stack new ones. Concurrency between multiple agents in\n// the same team is handled via optimistic etag checks — see\n// `lib/user-md-host.ts` for the full rationale.\n//\n// Two tools:\n// - `user_md_get` — returns current content + an etag.\n// - `user_md_write` — replaces content; requires `if_match` to equal the\n// most recent etag, else returns a conflict.\n//\n// Project knowledge (codebase notes, decisions) still goes to `memory_write`.\n// Personal notes about the agent itself go to `home_write` on IDENTITY.md.\n\nimport type { UserMdHost } from '../worker/ipc-protocol.ts'\nimport type { ToolHandler } from './types.ts'\n\nexport function userMdTools(host: UserMdHost, teamId: string): ToolHandler[] {\n return [\n {\n def: {\n name: 'user_md_get',\n description:\n 'Read the team-shared USER.md (facts every agent in the team knows about the human). Returns the current content followed by an `etag:` line — you MUST pass that etag back as `if_match` on the next `user_md_write` so the daemon can detect concurrent edits by other agents in the team. Always call this immediately before any `user_md_write`.',\n parameters: {\n type: 'object',\n properties: {},\n },\n },\n async invoke() {\n const { content, etag } = await host.get(teamId)\n const body = content.length > 0 ? content : '(USER.md is empty)'\n return `${body}\\n\\n---\\netag: ${etag}`\n },\n },\n {\n def: {\n name: 'user_md_write',\n description:\n 'Replace the team-shared USER.md with new content. Use this for STABLE user-specific facts (preferences, role, working hours, how the human likes to be addressed). MANDATORY workflow: (1) call `user_md_get` first, (2) integrate your change into the full content preserving everything unrelated, (3) call `user_md_write` with the merged content and the etag you got from `user_md_get`. If another agent in the team wrote to USER.md between your get and write, this returns an etag-mismatch error — just call `user_md_get` again and retry the merge. The full result must fit under 12 KB. For project knowledge use `memory_write`; for notes about yourself use `home_write` on IDENTITY.md.',\n parameters: {\n type: 'object',\n properties: {\n content: {\n type: 'string',\n description:\n 'Full new contents of USER.md (this is a complete replacement, NOT an append). Include everything you want to keep.',\n },\n if_match: {\n type: 'string',\n description:\n 'The etag returned by your most recent `user_md_get`. The write fails if USER.md changed in the meantime.',\n },\n },\n required: ['content', 'if_match'],\n },\n },\n async invoke(args) {\n const content = String(args.content ?? '')\n const ifMatch = String(args.if_match ?? '')\n if (!ifMatch) {\n throw new Error(\n 'user_md_write: \"if_match\" is required — call user_md_get first to obtain the current etag.',\n )\n }\n const { etag, totalBytes } = await host.write(teamId, content, ifMatch)\n return `wrote USER.md (${totalBytes} bytes, new etag: ${etag})`\n },\n },\n ]\n}\n","import { fetch as undiciFetch } from 'undici'\nimport type { ToolHandler } from './types.ts'\nimport {\n type ExtractMode,\n type ExtractResult,\n extractReadable,\n markdownToPlain,\n} from './web-extract.ts'\nimport { guardedFetch, SsrFBlockedError } from './web-ssrf.ts'\n\nconst DEFAULT_USER_AGENT =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'\nconst DEFAULT_CACHE_TTL_MS = 15 * 60_000\nconst DEFAULT_CACHE_MAX = 100\nconst DEFAULT_MAX_LENGTH = 20_000\nconst DEFAULT_TIMEOUT_MS = 30_000\n// Raw body cap applied before parsing. Multi-megabyte HTML (Mintlify/Next.js\n// docs sites, ad-heavy pages) can stall or OOM Readability+linkedom's\n// synchronous DOM walk. 3 MB comfortably covers real articles; anything\n// larger gets truncated and the caller sees a note in the output.\nconst DEFAULT_MAX_BODY_BYTES = 3 * 1024 * 1024\n// If the primary Readability path returns fewer than this many characters\n// from a 2xx HTML response, and FIRECRAWL_API_KEY is configured, retry the\n// fetch via Firecrawl which renders JS server-side. Threshold is heuristic:\n// real articles routinely exceed 200 chars; pages that bottom out below it\n// are almost always JS-shell pages where extraction collapsed.\nconst FIRECRAWL_FALLBACK_THRESHOLD = 200\nconst FIRECRAWL_DEFAULT_URL = 'https://api.firecrawl.dev'\n\ninterface SearchResult {\n title: string\n url: string\n snippet: string\n}\n\nfunction stripHtml(s: string): string {\n return s\n .replace(/<[^>]*>/g, '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/ /g, ' ')\n .trim()\n}\n\n// --- search backends ---\n\nasync function braveSearch(\n query: string,\n limit: number,\n apiKey: string,\n fetchFn: typeof fetch,\n): Promise<SearchResult[]> {\n const params = new URLSearchParams({ q: query, count: String(limit) })\n const res = await fetchFn(`https://api.search.brave.com/res/v1/web/search?${params}`, {\n headers: {\n accept: 'application/json',\n 'accept-encoding': 'gzip',\n 'x-subscription-token': apiKey,\n },\n })\n if (!res.ok) throw new Error(`Brave Search: ${res.status} ${await res.text()}`)\n const data = (await res.json()) as {\n web?: { results?: { title?: string; url?: string; description?: string }[] }\n }\n return (data.web?.results ?? []).slice(0, limit).map((r) => ({\n title: r.title ?? '',\n url: r.url ?? '',\n snippet: r.description ? stripHtml(r.description) : '',\n }))\n}\n\nasync function searxngSearch(\n query: string,\n limit: number,\n baseURL: string,\n fetchFn: typeof fetch,\n): Promise<SearchResult[]> {\n const params = new URLSearchParams({ q: query, format: 'json' })\n const res = await fetchFn(`${baseURL}/search?${params}`, {\n headers: { accept: 'application/json' },\n })\n if (!res.ok) throw new Error(`SearXNG: ${res.status} ${await res.text()}`)\n const data = (await res.json()) as {\n results?: { title?: string; url?: string; content?: string }[]\n }\n return (data.results ?? []).slice(0, limit).map((r) => ({\n title: r.title ?? '',\n url: r.url ?? '',\n snippet: r.content ?? '',\n }))\n}\n\n// --- error formatting ---\n\n/**\n * Flatten an Error and its `cause` chain into a single readable string.\n * undici surfaces network failures as `TypeError: fetch failed` with the\n * real reason (UND_ERR_*, ECONNRESET, certificate errors, …) stashed in\n * `err.cause`; without unwrapping it the agent only ever sees \"fetch\n * failed\" and has no path to diagnose or work around the problem.\n */\nfunction describeError(err: unknown): string {\n if (!(err instanceof Error)) return String(err)\n const parts: string[] = []\n const seen = new Set<unknown>()\n let cur: unknown = err\n while (cur instanceof Error && !seen.has(cur)) {\n seen.add(cur)\n const code = (cur as { code?: string }).code\n parts.push(code ? `[${code}] ${cur.message}` : cur.message)\n cur = (cur as { cause?: unknown }).cause\n }\n return parts.join(' — cause: ')\n}\n\n// --- bounded body reader ---\n\n/**\n * Stream `res.body` and accumulate up to `maxBytes`, then truncate. Returns\n * the decoded text (using the charset from content-type, falling back to\n * UTF-8) and a flag the caller can surface to the agent so it knows the\n * page was larger than the cap.\n */\nasync function readBodyCapped(\n res: Response,\n maxBytes: number,\n): Promise<{ text: string; truncated: boolean }> {\n const ct = res.headers.get('content-type') ?? ''\n const charset = /charset=([^;]+)/i.exec(ct)?.[1]?.trim().toLowerCase() || 'utf-8'\n const decoder = new TextDecoder(charset, { fatal: false })\n const reader = res.body?.getReader()\n if (!reader) {\n const text = await res.text()\n if (text.length > maxBytes) return { text: text.slice(0, maxBytes), truncated: true }\n return { text, truncated: false }\n }\n const chunks: Uint8Array[] = []\n let total = 0\n let truncated = false\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (!value) continue\n if (total + value.byteLength > maxBytes) {\n const keep = maxBytes - total\n if (keep > 0) chunks.push(value.subarray(0, keep))\n truncated = true\n try {\n await reader.cancel()\n } catch {\n // body already settled\n }\n break\n }\n chunks.push(value)\n total += value.byteLength\n }\n const sum = chunks.reduce((s, c) => s + c.byteLength, 0)\n const merged = new Uint8Array(sum)\n let off = 0\n for (const c of chunks) {\n merged.set(c, off)\n off += c.byteLength\n }\n return { text: decoder.decode(merged), truncated }\n}\n\n// --- Firecrawl fallback ---\n\ninterface FirecrawlResponse {\n success?: boolean\n data?: {\n markdown?: string\n metadata?: { title?: string }\n }\n error?: string\n}\n\n/**\n * Last-resort HTML rendering via Firecrawl's `/v1/scrape` endpoint. Used\n * when the primary Readability path returns near-empty content from a 2xx\n * HTML response (JS-only shells, anti-bot walls, login redirects). Returns\n * `null` when Firecrawl is not configured or the call fails so the caller\n * falls back to the primary extraction unchanged — Firecrawl is best-effort,\n * never an error source.\n */\nasync function firecrawlScrape(\n url: string,\n mode: ExtractMode,\n env: NodeJS.ProcessEnv,\n fetchFn: typeof fetch,\n timeoutMs: number,\n): Promise<ExtractResult | null> {\n const apiKey = env.FIRECRAWL_API_KEY\n if (!apiKey) return null\n const base = (env.FIRECRAWL_URL ?? FIRECRAWL_DEFAULT_URL).replace(/\\/$/, '')\n const ac = new AbortController()\n const t = setTimeout(() => ac.abort(new Error('firecrawl timeout')), timeoutMs)\n try {\n const res = await fetchFn(`${base}/v1/scrape`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${apiKey}`,\n accept: 'application/json',\n },\n body: JSON.stringify({\n url,\n formats: ['markdown'],\n onlyMainContent: true,\n }),\n signal: ac.signal,\n })\n if (!res.ok) return null\n const body = (await res.json()) as FirecrawlResponse\n if (!body.success || !body.data?.markdown) return null\n const md = body.data.markdown\n const title = body.data.metadata?.title\n const text = mode === 'text' ? markdownToPlain(md) : md\n return title ? { text, title } : { text }\n } catch {\n return null\n } finally {\n clearTimeout(t)\n }\n}\n\n// --- per-URL cache ---\n\ninterface CacheEntry {\n value: ExtractResult\n expiresAt: number\n}\n\nfunction cacheGet(cache: Map<string, CacheEntry>, key: string): ExtractResult | null {\n const entry = cache.get(key)\n if (!entry) return null\n if (entry.expiresAt < Date.now()) {\n cache.delete(key)\n return null\n }\n // Refresh LRU position\n cache.delete(key)\n cache.set(key, entry)\n return entry.value\n}\n\nfunction cacheSet(\n cache: Map<string, CacheEntry>,\n key: string,\n value: ExtractResult,\n ttlMs: number,\n maxEntries: number,\n): void {\n cache.set(key, { value, expiresAt: Date.now() + ttlMs })\n while (cache.size > maxEntries) {\n const oldest = cache.keys().next().value\n if (!oldest) break\n cache.delete(oldest)\n }\n}\n\n// --- tool factory ---\n\nexport interface WebToolsOpts {\n fetchImpl?: typeof fetch\n env?: NodeJS.ProcessEnv\n /** Disable SSRF checks (tests against 127.0.0.1 mock servers). Default false. */\n allowPrivate?: boolean\n /** Cache TTL in ms. Default 15 min. */\n cacheTtlMs?: number\n /** Max cache entries. Default 100. */\n cacheMax?: number\n /** Fetch timeout in ms. Default 30s. */\n timeoutMs?: number\n /** Cap on raw response body bytes before parsing. Default 3 MB. */\n maxBodyBytes?: number\n /** Disable the Firecrawl fallback even when FIRECRAWL_API_KEY is set. */\n firecrawlDisabled?: boolean\n}\n\n/**\n * web_search backends (tried in order):\n * 1. Brave Search (env: BRAVE_API_KEY) — free tier, 2000 req/month\n * 2. SearXNG (env: SEARXNG_URL) — self-hosted, unlimited\n * 3. Error with setup instructions if neither is configured\n *\n * web_fetch: guarded fetch (SSRF-blocked private IPs) + Readability + markdown,\n * with per-URL in-memory cache (15 min TTL).\n */\nexport function webTools(opts?: WebToolsOpts): ToolHandler[] {\n return buildWebTools(opts, true)\n}\n\nexport type ProtectedWebFetchOpts = Omit<WebToolsOpts, 'env' | 'allowPrivate' | 'firecrawlDisabled'>\n\n/** The credential-free, public-address-only web capability for protected turns. */\nexport function protectedWebFetchTool(opts?: ProtectedWebFetchOpts): ToolHandler {\n const tool = buildWebTools(\n { ...opts, env: {}, allowPrivate: false, firecrawlDisabled: true },\n false,\n )[0]\n if (!tool) throw new Error('protected web_fetch tool construction failed')\n return tool\n}\n\nfunction buildWebTools(opts: WebToolsOpts | undefined, includeSearch: boolean): ToolHandler[] {\n // Default to undici 8's fetch so search-backend HTTP shares the same\n // stack as guardedFetch (and stays out of Node 24's bundled undici 7).\n const fetchFn = opts?.fetchImpl ?? (undiciFetch as unknown as typeof fetch)\n const env = opts?.env ?? process.env\n const allowPrivate = opts?.allowPrivate ?? false\n const cacheTtlMs = opts?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS\n const cacheMax = opts?.cacheMax ?? DEFAULT_CACHE_MAX\n const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const maxBodyBytes = opts?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n const firecrawlDisabled = opts?.firecrawlDisabled ?? false\n const cache = new Map<string, CacheEntry>()\n\n const tools: ToolHandler[] = []\n if (includeSearch) {\n tools.push({\n def: {\n name: 'web_search',\n description:\n 'Search the internet. Returns a list of titles, URLs, and snippets. Requires BRAVE_API_KEY or SEARXNG_URL to be configured.',\n parameters: {\n type: 'object',\n properties: {\n query: { type: 'string', description: 'Search query' },\n limit: { type: 'number', description: 'Max results to return (default 5)' },\n },\n required: ['query'],\n },\n },\n async invoke(args) {\n const query = String(args.query ?? '')\n if (!query) throw new Error('web_search: query is required')\n const limit = typeof args.limit === 'number' ? args.limit : 5\n\n const braveKey = env.BRAVE_API_KEY\n if (braveKey) {\n const results = await braveSearch(query, limit, braveKey, fetchFn)\n return results.length === 0 ? 'no results' : formatResults(results)\n }\n\n const searxngUrl = env.SEARXNG_URL\n if (searxngUrl) {\n const results = await searxngSearch(query, limit, searxngUrl, fetchFn)\n return results.length === 0 ? 'no results' : formatResults(results)\n }\n\n throw new Error(\n 'web_search: no search backend configured. Set BRAVE_API_KEY (free at https://brave.com/search/api/) or SEARXNG_URL.',\n )\n },\n })\n }\n tools.push({\n def: {\n name: 'web_fetch',\n description: firecrawlDisabled\n ? 'Fetch a public HTTP or HTTPS URL and return its readable content. Private, loopback, link-local, metadata, and DNS-rebinding targets are blocked. HTML is extracted via Readability and converted to markdown. Results are cached for 15 minutes.'\n : 'Fetch a URL and return its readable content. HTML is extracted via Readability and converted to markdown. When the primary extraction returns near-empty content from a 2xx HTML page (typical of JS-only shells), the tool automatically retries via Firecrawl if FIRECRAWL_API_KEY is configured. Results are cached for 15 minutes.',\n parameters: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The URL to fetch (http or https)' },\n max_length: {\n type: 'number',\n description: 'Max characters to return (default 20000)',\n },\n extract_mode: {\n type: 'string',\n enum: ['markdown', 'text'],\n description: 'Output format for HTML pages. Default \"markdown\".',\n },\n },\n required: ['url'],\n },\n },\n async invoke(args) {\n const url = String(args.url ?? '')\n if (!url) throw new Error('web_fetch: url is required')\n const maxLen = typeof args.max_length === 'number' ? args.max_length : DEFAULT_MAX_LENGTH\n const mode: ExtractMode = args.extract_mode === 'text' ? 'text' : 'markdown'\n const cacheKey = `${mode}|${url}`\n\n const cached = cacheGet(cache, cacheKey)\n if (cached) return formatOutput(cached, maxLen)\n\n let result: GuardedFetchResultShape | null = null\n try {\n result = await guardedFetch({\n url,\n fetchImpl: opts?.fetchImpl,\n allowPrivate,\n timeoutMs,\n init: {\n headers: {\n 'user-agent': DEFAULT_USER_AGENT,\n accept: 'text/html,application/xhtml+xml,application/json,text/plain,*/*',\n 'accept-language': 'en-US,en;q=0.9',\n },\n },\n })\n if (!result.response.ok) {\n throw new Error(`${result.response.status} ${result.response.statusText}`)\n }\n const ct = result.response.headers.get('content-type') ?? ''\n const isHtml = ct.includes('text/html') || ct.includes('xhtml')\n const { text: body, truncated } = await readBodyCapped(result.response, maxBodyBytes)\n let extracted: ExtractResult\n if (isHtml) {\n extracted = extractReadable(body, result.finalUrl, mode)\n } else if (ct.includes('application/json')) {\n try {\n extracted = { text: JSON.stringify(JSON.parse(body), null, 2) }\n } catch {\n extracted = { text: body }\n }\n } else {\n extracted = { text: body }\n }\n // Firecrawl fallback: only meaningful for HTML pages where the\n // primary extractor collapsed. Skip JSON / plain-text bodies and\n // skip when extraction already produced something substantial.\n if (isHtml && !firecrawlDisabled && extracted.text.length < FIRECRAWL_FALLBACK_THRESHOLD) {\n const rescued = await firecrawlScrape(result.finalUrl, mode, env, fetchFn, timeoutMs)\n if (rescued) {\n extracted = {\n ...rescued,\n text: `${rescued.text}\\n\\n[content rendered via Firecrawl fallback — primary extraction returned ${extracted.text.length} chars]`,\n }\n }\n }\n if (truncated) {\n extracted = {\n ...extracted,\n text: `${extracted.text}\\n\\n[raw body truncated at ${maxBodyBytes} bytes before extraction — page exceeded the size cap]`,\n }\n }\n cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax)\n return formatOutput(extracted, maxLen)\n } catch (err) {\n if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`)\n throw new Error(`web_fetch: ${describeError(err)}`)\n } finally {\n if (result) await result.release()\n }\n },\n })\n return tools\n}\n\ntype GuardedFetchResultShape = Awaited<ReturnType<typeof guardedFetch>>\n\nfunction formatOutput(r: ExtractResult, maxLen: number): string {\n const body = r.title ? `# ${r.title}\\n\\n${r.text}` : r.text\n if (body.length > maxLen) return `${body.slice(0, maxLen)}\\n\\n[truncated at ${maxLen} chars]`\n return body\n}\n\nfunction formatResults(results: SearchResult[]): string {\n return results.map((r, i) => `${i + 1}. ${r.title}\\n ${r.url}\\n ${r.snippet}`).join('\\n\\n')\n}\n","import { Readability } from '@mozilla/readability'\nimport { parseHTML } from 'linkedom'\n\nexport type ExtractMode = 'markdown' | 'text'\n\nexport interface ExtractResult {\n text: string\n title?: string\n}\n\nfunction decodeEntities(value: string): string {\n return value\n .replace(/ /gi, ' ')\n .replace(/&/gi, '&')\n .replace(/"/gi, '\"')\n .replace(/'/gi, \"'\")\n .replace(/</gi, '<')\n .replace(/>/gi, '>')\n .replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)))\n .replace(/&#(\\d+);/gi, (_, dec) => String.fromCharCode(Number.parseInt(dec, 10)))\n}\n\nfunction stripTags(value: string): string {\n return decodeEntities(value.replace(/<[^>]+>/g, ''))\n}\n\nfunction normalizeWhitespace(value: string): string {\n return value\n .replace(/\\r/g, '')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/[ \\t]{2,}/g, ' ')\n .trim()\n}\n\nfunction htmlToMarkdown(html: string): { text: string; title?: string } {\n const titleMatch = html.match(/<title[^>]*>([\\s\\S]*?)<\\/title>/i)\n const title = titleMatch ? normalizeWhitespace(stripTags(titleMatch[1] ?? '')) : undefined\n let text = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, '')\n .replace(/<nav[\\s\\S]*?<\\/nav>/gi, '')\n .replace(/<header[\\s\\S]*?<\\/header>/gi, '')\n .replace(/<footer[\\s\\S]*?<\\/footer>/gi, '')\n text = text.replace(/<a\\s+[^>]*href=[\"']([^\"']+)[\"'][^>]*>([\\s\\S]*?)<\\/a>/gi, (_, href, body) => {\n const label = normalizeWhitespace(stripTags(body))\n return label ? `[${label}](${href})` : href\n })\n text = text.replace(/<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi, (_, level, body) => {\n const n = Math.max(1, Math.min(6, Number.parseInt(level, 10)))\n return `\\n${'#'.repeat(n)} ${normalizeWhitespace(stripTags(body))}\\n`\n })\n text = text.replace(/<li[^>]*>([\\s\\S]*?)<\\/li>/gi, (_, body) => {\n const label = normalizeWhitespace(stripTags(body))\n return label ? `\\n- ${label}` : ''\n })\n text = text\n .replace(/<(br|hr)\\s*\\/?>/gi, '\\n')\n .replace(/<\\/(p|div|section|article|tr|ul|ol|table|blockquote)>/gi, '\\n')\n text = stripTags(text)\n return { text: normalizeWhitespace(text), title }\n}\n\nexport function markdownToPlain(md: string): string {\n let t = md\n t = t.replace(/!\\[[^\\]]*]\\([^)]+\\)/g, '')\n t = t.replace(/\\[([^\\]]+)]\\([^)]+\\)/g, '$1')\n t = t.replace(/```[\\s\\S]*?```/g, (block) =>\n block.replace(/```[^\\n]*\\n?/g, '').replace(/```/g, ''),\n )\n t = t.replace(/`([^`]+)`/g, '$1')\n t = t.replace(/^#{1,6}\\s+/gm, '')\n t = t.replace(/^\\s*[-*+]\\s+/gm, '')\n t = t.replace(/^\\s*\\d+\\.\\s+/gm, '')\n return normalizeWhitespace(t)\n}\n\n/**\n * Extract readable content from HTML using Readability, with a regex-based\n * markdown fallback when Readability can't identify an article.\n */\nexport function extractReadable(html: string, url: string, mode: ExtractMode): ExtractResult {\n const fallback = (): ExtractResult => {\n const r = htmlToMarkdown(html)\n return mode === 'text' ? { text: markdownToPlain(r.text), title: r.title } : r\n }\n try {\n const { document } = parseHTML(html)\n try {\n ;(document as unknown as { baseURI?: string }).baseURI = url\n } catch {\n // best-effort\n }\n type ReadabilityArg = ConstructorParameters<typeof Readability>[0]\n const parsed = new Readability(document as unknown as ReadabilityArg, {\n charThreshold: 0,\n }).parse()\n if (!parsed?.content) return fallback()\n const title = parsed.title || undefined\n if (mode === 'text') {\n const text = normalizeWhitespace(parsed.textContent ?? '')\n return text ? { text, title } : fallback()\n }\n const rendered = htmlToMarkdown(parsed.content)\n return { text: rendered.text, title: title ?? rendered.title }\n } catch {\n return fallback()\n }\n}\n","import { lookup as dnsLookupCb, type LookupAddress } from 'node:dns'\nimport { lookup as dnsLookup } from 'node:dns/promises'\nimport { isIP } from 'node:net'\nimport { Agent, type Dispatcher, fetch as undiciFetch } from 'undici'\n\nexport class SsrFBlockedError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SsrFBlockedError'\n }\n}\n\nconst BLOCKED_HOSTNAMES = new Set(['localhost', 'metadata.google.internal'])\n\nfunction normalizeHostname(hostname: string): string {\n let h = hostname.trim().toLowerCase().replace(/\\.$/, '')\n if (h.startsWith('[') && h.endsWith(']')) h = h.slice(1, -1)\n return h\n}\n\nfunction parseIpv4(address: string): number[] | null {\n const parts = address.split('.')\n if (parts.length !== 4 || parts.some((part) => !/^\\d{1,3}$/.test(part))) return null\n const nums = parts.map(Number)\n if (nums.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return null\n return nums\n}\n\nfunction parseIpv6(address: string): number[] | null {\n const zoneIndex = address.indexOf('%')\n let source = zoneIndex === -1 ? address : address.slice(0, zoneIndex)\n if (isIP(source) !== 6) return null\n\n if (source.includes('.')) {\n const separator = source.lastIndexOf(':')\n const ipv4 = parseIpv4(source.slice(separator + 1))\n if (separator === -1 || !ipv4) return null\n const [a, b, c, d] = ipv4 as [number, number, number, number]\n source = `${source.slice(0, separator)}:${((a << 8) | b).toString(16)}:${((c << 8) | d).toString(16)}`\n }\n\n const halves = source.split('::')\n if (halves.length > 2) return null\n const left = halves[0] ? halves[0].split(':') : []\n const right = halves[1] ? halves[1].split(':') : []\n const omitted = 8 - left.length - right.length\n if ((halves.length === 1 && omitted !== 0) || (halves.length === 2 && omitted < 1)) return null\n return [...left, ...Array.from({ length: omitted }, () => '0'), ...right].map((word) =>\n Number.parseInt(word, 16),\n )\n}\n\nfunction isPrivateIpv4(parts: number[]): boolean {\n const [a, b] = parts as [number, number, number, number]\n if (a === 0 || a === 10 || a === 127) return true\n if (a === 169 && b === 254) return true\n if (a === 172 && b >= 16 && b <= 31) return true\n if (a === 192 && b === 168) return true\n if (a === 100 && b >= 64 && b <= 127) return true\n return false\n}\n\nfunction embeddedIpv4(words: number[]): number[] | null {\n const [a, b, c, d, e, f, g, h] = words as [\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n number,\n ]\n const low32 = [g >> 8, g & 0xff, h >> 8, h & 0xff]\n\n // IPv4-compatible (::/96), IPv4-mapped (::ffff:0:0/96), and the\n // historical IPv4-translatable (::ffff:0:0:0/96) representations.\n if (\n (a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 0) ||\n (a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 0xffff) ||\n (a === 0 && b === 0 && c === 0 && d === 0 && e === 0xffff && f === 0)\n ) {\n return low32\n }\n\n // RFC 6052's well-known /96 NAT64 prefix.\n if (a === 0x64 && b === 0xff9b && c === 0 && d === 0 && e === 0 && f === 0) {\n return low32\n }\n\n // RFC 8215's local-use /48 translation prefix. With a /48 prefix, the\n // IPv4 octets occupy bytes 6, 7, 9, and 10; byte 8 is the reserved u byte.\n if (a === 0x64 && b === 0xff9b && c === 1 && e >> 8 === 0) {\n return [d >> 8, d & 0xff, e & 0xff, f >> 8]\n }\n\n // 6to4 embeds its IPv4 relay address immediately after 2002::/16.\n if (a === 0x2002) return [b >> 8, b & 0xff, c >> 8, c & 0xff]\n return null\n}\n\nexport function isPrivateIpAddress(address: string): boolean {\n let norm = address.trim().toLowerCase()\n if (norm.startsWith('[') && norm.endsWith(']')) norm = norm.slice(1, -1)\n if (!norm) return false\n const ipv4 = parseIpv4(norm)\n if (ipv4) return isPrivateIpv4(ipv4)\n const words = parseIpv6(norm)\n if (!words) return false\n const [first] = words as [number, ...number[]]\n if (words.every((word) => word === 0)) return true\n if (words.slice(0, 7).every((word) => word === 0) && words[7] === 1) return true\n if ((first & 0xfe00) === 0xfc00) return true // fc00::/7 unique-local\n if ((first & 0xffc0) === 0xfe80) return true // fe80::/10 link-local\n if ((first & 0xffc0) === 0xfec0) return true // fec0::/10 deprecated site-local\n if ((first & 0xff00) === 0xff00) return true // ff00::/8 multicast\n const embedded = embeddedIpv4(words)\n return embedded ? isPrivateIpv4(embedded) : false\n}\n\nexport function isBlockedHostname(hostname: string): boolean {\n const h = normalizeHostname(hostname)\n if (!h) return false\n if (BLOCKED_HOSTNAMES.has(h)) return true\n return h.endsWith('.localhost') || h.endsWith('.local') || h.endsWith('.internal')\n}\n\ntype LookupCallback = (\n err: NodeJS.ErrnoException | null,\n address: string | LookupAddress[],\n family?: number,\n) => void\n\nfunction createPinnedLookup(hostname: string, addresses: string[]): typeof dnsLookupCb {\n const normalized = normalizeHostname(hostname)\n const records = addresses.map((address) => ({\n address,\n family: (address.includes(':') ? 6 : 4) as 4 | 6,\n }))\n let index = 0\n return ((host: string, options?: unknown, callback?: unknown) => {\n const cb: LookupCallback =\n typeof options === 'function' ? (options as LookupCallback) : (callback as LookupCallback)\n if (!cb) return\n if (normalizeHostname(host) !== normalized) {\n if (typeof options === 'function' || options === undefined) {\n return (dnsLookupCb as unknown as (h: string, cb: LookupCallback) => void)(host, cb)\n }\n return (dnsLookupCb as unknown as (h: string, o: unknown, cb: LookupCallback) => void)(\n host,\n options,\n cb,\n )\n }\n const opts =\n typeof options === 'object' && options !== null\n ? (options as { all?: boolean; family?: number })\n : {}\n const family = typeof options === 'number' ? options : (opts.family ?? 0)\n const candidates =\n family === 4 || family === 6 ? records.filter((r) => r.family === family) : records\n const usable = candidates.length > 0 ? candidates : records\n if (opts.all) {\n cb(null, usable as LookupAddress[])\n return\n }\n const chosen = usable[index % usable.length]\n if (!chosen) return\n index += 1\n cb(null, chosen.address, chosen.family)\n }) as typeof dnsLookupCb\n}\n\nasync function resolveAndCheck(hostname: string): Promise<string[]> {\n const norm = normalizeHostname(hostname)\n if (!norm) throw new SsrFBlockedError('Invalid hostname')\n if (isBlockedHostname(norm)) throw new SsrFBlockedError(`Blocked hostname: ${hostname}`)\n if (isPrivateIpAddress(norm)) throw new SsrFBlockedError('Blocked: private IP literal')\n const results = await dnsLookup(norm, { all: true })\n if (results.length === 0) throw new SsrFBlockedError(`Cannot resolve: ${hostname}`)\n for (const r of results) {\n if (isPrivateIpAddress(r.address)) throw new SsrFBlockedError('Blocked: resolves to private IP')\n }\n return Array.from(new Set(results.map((r) => r.address)))\n}\n\ntype FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>\n\nexport interface GuardedFetchOptions {\n url: string\n fetchImpl?: FetchLike\n init?: RequestInit\n maxRedirects?: number\n timeoutMs?: number\n signal?: AbortSignal\n /** If true, skip all SSRF checks (for tests hitting 127.0.0.1 mocks). */\n allowPrivate?: boolean\n}\n\nexport interface GuardedFetchResult {\n response: Response\n finalUrl: string\n release: () => Promise<void>\n}\n\nfunction isRedirectStatus(s: number): boolean {\n return s === 301 || s === 302 || s === 303 || s === 307 || s === 308\n}\n\nfunction withoutCrossOriginCredentials(\n headersInit: ConstructorParameters<typeof Headers>[0] | undefined,\n): Headers | undefined {\n if (!headersInit) return undefined\n const headers = new Headers(headersInit)\n headers.delete('authorization')\n headers.delete('cookie')\n headers.delete('proxy-authorization')\n return headers\n}\n\nasync function closeDispatcher(d: Dispatcher | null): Promise<void> {\n if (!d) return\n try {\n await d.close()\n } catch {\n // ignore\n }\n}\n\nexport async function guardedFetch(opts: GuardedFetchOptions): Promise<GuardedFetchResult> {\n // Use undici's own fetch (not globalThis.fetch) so the `dispatcher` Agent\n // we attach below is the same undici major version as the fetch impl\n // consuming it. Node 24 ships undici 7.x as its built-in fetch; mixing\n // a standalone undici 8.x Agent with the 7.x dispatcher fails with\n // `UND_ERR_INVALID_ARG: invalid onRequestStart method` because the\n // diagnostics-channel handler signatures changed between majors.\n const fetcher: FetchLike = opts.fetchImpl ?? (undiciFetch as unknown as FetchLike)\n const maxRedirects = opts.maxRedirects ?? 3\n const abortController = new AbortController()\n const timeoutId = opts.timeoutMs\n ? setTimeout(() => abortController.abort(new Error('timeout')), opts.timeoutMs)\n : null\n if (opts.signal) {\n if (opts.signal.aborted) abortController.abort(opts.signal.reason)\n else\n opts.signal.addEventListener('abort', () => abortController.abort(opts.signal?.reason), {\n once: true,\n })\n }\n\n let current = opts.url\n let requestInit = { ...(opts.init ?? {}) }\n const visited = new Set<string>()\n let redirects = 0\n let dispatcher: Dispatcher | null = null\n\n const release = async (): Promise<void> => {\n if (timeoutId) clearTimeout(timeoutId)\n await closeDispatcher(dispatcher)\n dispatcher = null\n }\n\n while (true) {\n let parsed: URL\n try {\n parsed = new URL(current)\n } catch {\n await release()\n throw new Error('Invalid URL')\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n await release()\n throw new Error('Invalid URL: must be http or https')\n }\n\n // Pin DNS only when we control the dispatcher (native fetch path).\n const canPin = !opts.fetchImpl\n if (!opts.allowPrivate) {\n const addrs = await resolveAndCheck(parsed.hostname)\n await closeDispatcher(dispatcher)\n dispatcher = canPin\n ? new Agent({ connect: { lookup: createPinnedLookup(parsed.hostname, addrs) } })\n : null\n }\n\n const init: RequestInit = {\n ...requestInit,\n redirect: 'manual',\n signal: abortController.signal,\n }\n // `dispatcher` is a Node fetch extension not reflected in the DOM\n // RequestInit type — and the undici Dispatcher type brand can diverge\n // from the one @types/node bundles. Stamp it on via a cast.\n if (dispatcher) (init as unknown as { dispatcher: Dispatcher }).dispatcher = dispatcher\n\n let res: Response\n try {\n res = await fetcher(parsed.toString(), init)\n } catch (err) {\n await release()\n throw err\n }\n\n if (isRedirectStatus(res.status)) {\n const loc = res.headers.get('location')\n if (!loc) {\n await release()\n throw new Error(`Redirect ${res.status} missing Location header`)\n }\n redirects += 1\n if (redirects > maxRedirects) {\n await release()\n throw new Error(`Too many redirects (> ${maxRedirects})`)\n }\n const next = new URL(loc, parsed).toString()\n if (visited.has(next)) {\n await release()\n throw new Error('Redirect loop')\n }\n visited.add(next)\n void res.body?.cancel()\n if (new URL(next).origin !== parsed.origin) {\n requestInit = {\n ...requestInit,\n headers: withoutCrossOriginCredentials(requestInit.headers),\n }\n }\n current = next\n continue\n }\n\n return { response: res, finalUrl: parsed.toString(), release }\n }\n}\n","import type { WorkerIpcCall } from './ipc-client.ts'\nimport type { ApiKeyRefreshArgs, ApiKeyRefreshHost } from './ipc-protocol.ts'\n\nexport interface ApiKeyRefreshTurnContext {\n providerName: string\n agentId: string\n turnId: string\n}\n\n/** Build the worker-side callback passed to pi's `createBazilionSession`. */\nexport function createIpcApiKeyRefresher(\n call: WorkerIpcCall,\n context: ApiKeyRefreshTurnContext,\n): (providerName: string) => Promise<string> {\n return async (providerName) => {\n assertSelectedProvider(providerName, context.providerName)\n return call<string>('refreshApiKey', {\n providerName,\n agentId: context.agentId,\n turnId: context.turnId,\n } satisfies ApiKeyRefreshArgs)\n }\n}\n\n/**\n * Validate and execute one daemon-side refresh request. This is deliberately\n * separate from the generic IPC dispatcher so the credential-bearing result\n * cannot accidentally enter the stdout `ChatFrame` queue.\n */\nexport async function refreshApiKeyForTurn(\n args: ApiKeyRefreshArgs,\n context: ApiKeyRefreshTurnContext,\n host: ApiKeyRefreshHost | undefined,\n signal?: AbortSignal,\n): Promise<string> {\n if (args.agentId !== context.agentId || args.turnId !== context.turnId) {\n throw new Error('API key refresh request does not belong to this worker turn')\n }\n assertSelectedProvider(args.providerName, context.providerName)\n if (!host) throw new Error('provider credential refresh is unavailable for this worker turn')\n if (signal?.aborted) throw refreshCancelledError()\n\n let token: string\n try {\n const pending = host.refresh(args.providerName, signal)\n token = await waitForRefresh(pending, signal)\n } catch (error) {\n if (error instanceof ApiKeyRefreshCancelledError) throw error\n // OAuth libraries and HTTP clients can include request context in their\n // errors. Never forward that detail across IPC where it could become a\n // model- or user-visible authentication error.\n throw new Error('provider credential refresh failed — reconnect on /config and retry')\n }\n if (typeof token !== 'string' || token.length === 0) {\n throw new Error('API key refresh returned an invalid token')\n }\n return token\n}\n\nfunction assertSelectedProvider(requested: string, expected: string): void {\n if (!expected || requested !== expected) {\n throw new Error('unexpected API key refresh provider')\n }\n}\n\nfunction waitForRefresh(pending: Promise<string>, signal?: AbortSignal): Promise<string> {\n if (!signal) return pending\n return new Promise((resolve, reject) => {\n let settled = false\n const finish = (callback: () => void): void => {\n if (settled) return\n settled = true\n signal.removeEventListener('abort', onAbort)\n callback()\n }\n const onAbort = (): void => finish(() => reject(refreshCancelledError()))\n\n signal.addEventListener('abort', onAbort, { once: true })\n if (signal.aborted) {\n onAbort()\n return\n }\n pending.then(\n (value) => finish(() => resolve(value)),\n (error: unknown) => finish(() => reject(error)),\n )\n })\n}\n\nclass ApiKeyRefreshCancelledError extends Error {}\n\nfunction refreshCancelledError(): ApiKeyRefreshCancelledError {\n return new ApiKeyRefreshCancelledError('API key refresh cancelled with the worker turn')\n}\n","import { randomUUID } from 'node:crypto'\nimport type { IpcReply, IpcRequest, RpcMethod } from './ipc-protocol.ts'\n\nexport type WorkerIpcCall = <T>(method: RpcMethod, args: unknown) => Promise<T>\n\nexport interface WorkerIpcChannel {\n send?: (message: IpcRequest, done: (error: Error | null) => void) => void\n onMessage(listener: (message: unknown) => void): void\n onDisconnect(listener: () => void): void\n}\n\n/**\n * Correlated request client for the worker's private Node IPC channel. A\n * channel disconnect rejects and clears every pending request, so an OAuth\n * refresh cannot pin a worker after its parent or turn has gone away.\n */\nexport function createIpcClient(channel: WorkerIpcChannel): WorkerIpcCall {\n type Pending = { resolve: (value: unknown) => void; reject: (error: Error) => void }\n const pending = new Map<string, Pending>()\n let disconnected = false\n\n channel.onMessage((message) => {\n if (!isIpcReply(message)) return\n const entry = pending.get(message.id)\n if (!entry) return\n pending.delete(message.id)\n if (message.ok) entry.resolve(message.result)\n else entry.reject(new Error(message.error))\n })\n\n channel.onDisconnect(() => {\n disconnected = true\n for (const entry of pending.values()) {\n entry.reject(new Error('worker IPC channel disconnected'))\n }\n pending.clear()\n })\n\n return <T>(method: RpcMethod, args: unknown): Promise<T> => {\n if (disconnected) {\n return Promise.reject(new Error('worker IPC channel disconnected'))\n }\n const send = channel.send\n if (!send) {\n return Promise.reject(new Error('worker: no IPC channel — daemon must spawn with stdio:ipc'))\n }\n const id = randomUUID()\n const message = { type: 'rpc', id, method, args } as unknown as IpcRequest\n return new Promise<T>((resolve, reject) => {\n pending.set(id, { resolve: (value) => resolve(value as T), reject })\n try {\n send(message, (error) => {\n if (!error) return\n pending.delete(id)\n reject(error)\n })\n } catch (error) {\n pending.delete(id)\n reject(error instanceof Error ? error : new Error(String(error)))\n }\n })\n }\n}\n\nfunction isIpcReply(message: unknown): message is IpcReply {\n if (!message || typeof message !== 'object') return false\n const candidate = message as Record<string, unknown>\n return (\n candidate.type === 'rpc-reply' &&\n typeof candidate.id === 'string' &&\n typeof candidate.ok === 'boolean'\n )\n}\n"],"mappings":"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","names":["randomUUID","Type","join","readFileSync","randomUUID","randomUUID","mkdirSync","rmSync","writeFileSync","join","existsSync","readFileSync","join","existsSync","readFileSync","writeFileSync","join","existsSync","rmSync","mkdirSync","writeFileSync","join","existsSync","join","readdirSync","readFileSync","join","join","join","existsSync","rmSync","randomUUID","writeFileSync","join","randomUUID","randomUUID","randomUUID","randomUUID","createHash","randomBytes","randomUUID","randomUUID","createHash","randomBytes","randomUUID","createHash","randomBytes","randomUUID","existsSync","readFileSync","existsSync","readdirSync","rmSync","statSync","join","resolve","readFileSync","randomUUID","existsSync","renameSync","rmSync","randomUUID","createHash","randomUUID","randomUUID","randomUUID","readdirSync","rmSync","rmSync","mkdirSync","readdirSync","readFileSync","rmSync","writeFileSync","dirname","join","resolve","sep","join","sep","mkdirSync","resolve","dirname","readFileSync","writeFileSync","readdirSync","rmSync","text","closeSync","constants","fstatSync","openSync","realpathSync","basename","join","basename","realpathSync","openSync","join","constants","stat","fstatSync","count","closeSync","keys","realpathSync","join","createHash","closeSync","constants","fstatSync","lstatSync","openSync","readSync","realpathSync","isAbsolute","join","stat","count","realpathSync","join","text","keys","randomUUID","closeSync","constants","mkdtempSync","openSync","rmSync","writeFileSync","tmpdir","isAbsolute","join","relative","sep","DOCKER_CLIENT_CONTROL_ENV","SANDBOX_CONTROL_ENV","randomUUID","resolve","closeSync","isAbsolute","join","constants","identity","randomUUID","resolve","closeSync","mkdtempSync","tmpdir","writeFileSync","openSync","rmSync","DOCKER_CLIENT_CONTROL_ENV","SANDBOX_CONTROL_ENV","detail","relative","sep","resolve","detail","Type","object","list","value","issue","Type","existsSync","mkdirSync","statSync","writeFileSync","basename","join","Type","existsSync","readdirSync","join","closeSync","constants","fstatSync","lstatSync","mkdirSync","openSync","readFileSync","realpathSync","dirname","isAbsolute","relative","resolve","lstatSync","openSync","constants","fstatSync","readFileSync","closeSync","existsSync","readdirSync","join","existsSync","lstatSync","realpathSync","dirname","isAbsolute","join","relative","sep","createLocalBashOperations","defineTool","defineTool","base","createLocalBashOperations","customBash","existsSync","join","dirname","lstatSync","realpathSync","relative","sep","isAbsolute","constants","existsSync","lstatSync","mkdirSync","mkdtempSync","realpathSync","rmSync","statSync","access","tmpdir","isAbsolute","join","relative","sep","keys","text","isAbsolute","lstatSync","realpathSync","statSync","existsSync","relative","sep","text","Type","existsSync","rmSync","join","closeSync","constants","fstatSync","openSync","readSync","realpathSync","statSync","basename","isAbsolute","relative","resolve","sep","isWithin","stat","count","closeSync","constants","fstatSync","lstatSync","openSync","readdirSync","writeFileSync","join","openSync","constants","fstatSync","writeFileSync","closeSync","lstatSync","readdirSync","join","text","undiciFetch","text","text","undiciFetch","Type","existsSync","mkdirSync","join","writeFileSync","join","randomUUID","resolve","randomUUID","identity","Type","text","memory","messagingHost","userMdHost","bashApprovalHost","refreshApiKey","handle","resolve"]}
|