@agimon-ai/doompi-sandbox 0.0.1-alpha.37

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.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/adapters/brokerHost.cjs +2 -0
  5. package/dist/adapters/brokerHost.cjs.map +1 -0
  6. package/dist/adapters/brokerHost.d.cts +16 -0
  7. package/dist/adapters/brokerHost.d.cts.map +1 -0
  8. package/dist/adapters/brokerHost.d.mts +16 -0
  9. package/dist/adapters/brokerHost.d.mts.map +1 -0
  10. package/dist/adapters/brokerHost.mjs +2 -0
  11. package/dist/adapters/brokerHost.mjs.map +1 -0
  12. package/dist/adapters/brokerServer.cjs +2 -0
  13. package/dist/adapters/brokerServer.cjs.map +1 -0
  14. package/dist/adapters/brokerServer.mjs +2 -0
  15. package/dist/adapters/brokerServer.mjs.map +1 -0
  16. package/dist/adapters/devcontainer.cjs +2 -0
  17. package/dist/adapters/devcontainer.cjs.map +1 -0
  18. package/dist/adapters/devcontainer.mjs +2 -0
  19. package/dist/adapters/devcontainer.mjs.map +1 -0
  20. package/dist/adapters/engineProcess.cjs +2 -0
  21. package/dist/adapters/engineProcess.cjs.map +1 -0
  22. package/dist/adapters/engineProcess.mjs +2 -0
  23. package/dist/adapters/engineProcess.mjs.map +1 -0
  24. package/dist/adapters/harness.cjs +2 -0
  25. package/dist/adapters/harness.cjs.map +1 -0
  26. package/dist/adapters/harness.d.cts +26 -0
  27. package/dist/adapters/harness.d.cts.map +1 -0
  28. package/dist/adapters/harness.d.mts +26 -0
  29. package/dist/adapters/harness.d.mts.map +1 -0
  30. package/dist/adapters/harness.mjs +2 -0
  31. package/dist/adapters/harness.mjs.map +1 -0
  32. package/dist/adapters/loginPorts.cjs +2 -0
  33. package/dist/adapters/loginPorts.cjs.map +1 -0
  34. package/dist/adapters/loginPorts.mjs +2 -0
  35. package/dist/adapters/loginPorts.mjs.map +1 -0
  36. package/dist/adapters/pi/extension.cjs +2 -0
  37. package/dist/adapters/pi/extension.cjs.map +1 -0
  38. package/dist/adapters/pi/extension.d.cts +9 -0
  39. package/dist/adapters/pi/extension.d.cts.map +1 -0
  40. package/dist/adapters/pi/extension.d.mts +9 -0
  41. package/dist/adapters/pi/extension.d.mts.map +1 -0
  42. package/dist/adapters/pi/extension.mjs +2 -0
  43. package/dist/adapters/pi/extension.mjs.map +1 -0
  44. package/dist/adapters/sandboxImageTag.cjs +2 -0
  45. package/dist/adapters/sandboxImageTag.cjs.map +1 -0
  46. package/dist/adapters/sandboxImageTag.mjs +2 -0
  47. package/dist/adapters/sandboxImageTag.mjs.map +1 -0
  48. package/dist/commands/doomSandboxCommand.cjs +2 -0
  49. package/dist/commands/doomSandboxCommand.cjs.map +1 -0
  50. package/dist/commands/doomSandboxCommand.mjs +2 -0
  51. package/dist/commands/doomSandboxCommand.mjs.map +1 -0
  52. package/dist/container/index.cjs +2 -0
  53. package/dist/container/index.cjs.map +1 -0
  54. package/dist/container/index.d.cts +6 -0
  55. package/dist/container/index.d.cts.map +1 -0
  56. package/dist/container/index.d.mts +6 -0
  57. package/dist/container/index.d.mts.map +1 -0
  58. package/dist/container/index.mjs +2 -0
  59. package/dist/container/index.mjs.map +1 -0
  60. package/dist/extensions/pi.cjs +1 -0
  61. package/dist/extensions/pi.d.cts +2 -0
  62. package/dist/extensions/pi.d.mts +2 -0
  63. package/dist/extensions/pi.mjs +1 -0
  64. package/dist/index.cjs +1 -0
  65. package/dist/index.d.cts +5 -0
  66. package/dist/index.d.mts +5 -0
  67. package/dist/index.mjs +1 -0
  68. package/dist/sandboxHarness.cjs +1 -0
  69. package/dist/sandboxHarness.d.cts +2 -0
  70. package/dist/sandboxHarness.d.mts +2 -0
  71. package/dist/sandboxHarness.mjs +1 -0
  72. package/dist/services/brokerProviders.cjs +2 -0
  73. package/dist/services/brokerProviders.cjs.map +1 -0
  74. package/dist/services/brokerProviders.mjs +2 -0
  75. package/dist/services/brokerProviders.mjs.map +1 -0
  76. package/dist/services/brokerRoutes.cjs +2 -0
  77. package/dist/services/brokerRoutes.cjs.map +1 -0
  78. package/dist/services/brokerRoutes.mjs +2 -0
  79. package/dist/services/brokerRoutes.mjs.map +1 -0
  80. package/dist/services/devcontainer.cjs +3 -0
  81. package/dist/services/devcontainer.cjs.map +1 -0
  82. package/dist/services/devcontainer.mjs +3 -0
  83. package/dist/services/devcontainer.mjs.map +1 -0
  84. package/dist/services/extensionService.cjs +2 -0
  85. package/dist/services/extensionService.cjs.map +1 -0
  86. package/dist/services/extensionService.d.cts +10 -0
  87. package/dist/services/extensionService.d.cts.map +1 -0
  88. package/dist/services/extensionService.d.mts +10 -0
  89. package/dist/services/extensionService.d.mts.map +1 -0
  90. package/dist/services/extensionService.mjs +2 -0
  91. package/dist/services/extensionService.mjs.map +1 -0
  92. package/dist/services/oauthCallback.cjs +2 -0
  93. package/dist/services/oauthCallback.cjs.map +1 -0
  94. package/dist/services/oauthCallback.mjs +2 -0
  95. package/dist/services/oauthCallback.mjs.map +1 -0
  96. package/dist/services/runFlags.cjs +2 -0
  97. package/dist/services/runFlags.cjs.map +1 -0
  98. package/dist/services/runFlags.mjs +2 -0
  99. package/dist/services/runFlags.mjs.map +1 -0
  100. package/dist/services/sandboxBridge.cjs +3 -0
  101. package/dist/services/sandboxBridge.cjs.map +1 -0
  102. package/dist/services/sandboxBridge.mjs +3 -0
  103. package/dist/services/sandboxBridge.mjs.map +1 -0
  104. package/dist/services/sandboxEnvironment.cjs +2 -0
  105. package/dist/services/sandboxEnvironment.cjs.map +1 -0
  106. package/dist/services/sandboxEnvironment.mjs +2 -0
  107. package/dist/services/sandboxEnvironment.mjs.map +1 -0
  108. package/dist/services/sandboxImage.cjs +3 -0
  109. package/dist/services/sandboxImage.cjs.map +1 -0
  110. package/dist/services/sandboxImage.mjs +3 -0
  111. package/dist/services/sandboxImage.mjs.map +1 -0
  112. package/dist/services/sandboxPlan.cjs +2 -0
  113. package/dist/services/sandboxPlan.cjs.map +1 -0
  114. package/dist/services/sandboxPlan.mjs +2 -0
  115. package/dist/services/sandboxPlan.mjs.map +1 -0
  116. package/dist/types/extension.d.cts +15 -0
  117. package/dist/types/extension.d.cts.map +1 -0
  118. package/dist/types/extension.d.mts +15 -0
  119. package/dist/types/extension.d.mts.map +1 -0
  120. package/dist/types/sandboxHarness.d.cts +38 -0
  121. package/dist/types/sandboxHarness.d.cts.map +1 -0
  122. package/dist/types/sandboxHarness.d.mts +38 -0
  123. package/dist/types/sandboxHarness.d.mts.map +1 -0
  124. package/llms.txt +11 -0
  125. package/package.json +94 -0
  126. package/src/prompts/doompi-use-sandbox/SKILL.md +14 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.mjs","names":[],"sources":["../../src/adapters/harness.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { SandboxLaunchRequest } from '@agimon-ai/doompi-extension-contracts/sandbox-harness';\nimport { findDevcontainerConfig, runDevcontainerSession } from './devcontainer.ts';\nimport { DEVCONTAINER_DISABLED_ENV } from '../services/devcontainer.ts';\nimport { availableLoginPorts } from './loginPorts.ts';\nimport { startBroker, type RunningBroker } from './brokerHost.ts';\nimport { BRIDGE_FILE_NAME, sandboxBridgeSource } from '../services/sandboxBridge.ts';\nimport { buildSandboxPlan, containerEnvironment } from '../services/sandboxPlan.ts';\nimport { OAUTH_CALLBACK_PORTS } from '../services/oauthCallback.ts';\nimport { assertRunFlags, parseRunFlags } from '../services/runFlags.ts';\nimport { sandboxDockerfile } from '../services/sandboxImage.ts';\nimport { sandboxImageTag } from './sandboxImageTag.ts';\nimport type { EngineProcessRunner, SandboxEngine, SandboxHostFacts } from '../types/sandboxHarness.ts';\nimport { SpawnEngineProcessRunner } from './engineProcess.ts';\n\nconst ENGINE_ENV = 'DOOMPI_SANDBOX_ENGINE';\nconst BROKER_DISABLED_ENV = 'DOOMPI_SANDBOX_BROKER';\nconst RUN_FLAGS_ENV = 'DOOMPI_SANDBOX_RUN_FLAGS';\nconst DISABLED_VALUE = '0';\n// Every entry takes docker's run syntax, which is what the plan emits.\nconst ENGINES: SandboxEngine[] = ['docker', 'podman', 'nerdctl', 'finch'];\nconst DOCKERFILE_NAME = 'Dockerfile';\nconst MODES_FILE = path.join('.doom', 'modes.yaml');\nconst LOCAL_PACKAGE_PATTERN = /^\\s*(?:-|name:)\\s*[\"']?\\.{1,2}\\//m;\nconst REPO_KEY_LENGTH = 12;\n\n// Self-reference instead of a relative path: the file depth differs between\n// running from src and from the built dist tree.\nconst require = createRequire(import.meta.url);\nconst SELF_MANIFEST = '@agimon-ai/doompi-sandbox/package.json';\n\nexport interface SandboxLauncherDependencies {\n runner?: EngineProcessRunner;\n /** Seam for tests; defaults to probing the real OAuth callback ports. */\n loginPorts?: () => Promise<number[]>;\n /** Seam for tests; defaults to the real host broker. */\n startBroker?: (options: {\n environment: Readonly<Record<string, string | undefined>>;\n onDenied?: (reason: string) => void;\n }) => Promise<RunningBroker | undefined>;\n /** Distribution version override; defaults to this package's own version. */\n version?: string;\n hostFacts?: Partial<SandboxHostFacts>;\n}\n\nfunction distributionVersion(): string {\n return (require(SELF_MANIFEST) as { version: string }).version;\n}\n\n/** Fixed per-repository directory a reused dev container can keep mounted. */\nfunction brokerDirectory(repoRoot: string): string {\n return path.join(os.tmpdir(), `${repoKey(repoRoot)}-broker`);\n}\n\nfunction repoKey(repoRoot: string): string {\n return `doompi-sandbox-${createHash('sha256').update(repoRoot).digest('hex').slice(0, REPO_KEY_LENGTH)}`;\n}\n\nasync function detectEngine(\n runner: EngineProcessRunner,\n environment: Readonly<Record<string, string | undefined>>,\n): Promise<SandboxEngine> {\n const configured = environment[ENGINE_ENV]?.trim();\n if (configured) {\n if ((ENGINES as string[]).includes(configured)) return configured as SandboxEngine;\n throw new Error(`${ENGINE_ENV} must be one of: ${ENGINES.join(', ')}.`);\n }\n for (const engine of ENGINES) {\n const probe = await runner.capture(engine, ['--version']);\n if (probe?.exitCode === 0) return engine;\n }\n throw new Error(`No container engine found. Install one of ${ENGINES.join(', ')}, or set ${ENGINE_ENV}.`);\n}\n\nasync function ensureImage(\n runner: EngineProcessRunner,\n engine: SandboxEngine,\n version: string,\n onProgress: ((message: string) => void) | undefined,\n): Promise<string> {\n const tag = sandboxImageTag(version);\n const inspected = await runner.capture(engine, ['image', 'inspect', tag]);\n if (inspected?.exitCode === 0) return tag;\n\n onProgress?.(`building ${tag} (first run for this DoomPi version)`);\n const context = fs.mkdtempSync(path.join(os.tmpdir(), 'doompi-sandbox-build-'));\n try {\n fs.writeFileSync(path.join(context, DOCKERFILE_NAME), sandboxDockerfile());\n fs.writeFileSync(path.join(context, BRIDGE_FILE_NAME), sandboxBridgeSource());\n const exitCode = await runner.run(engine, [\n 'build',\n '-t',\n tag,\n '--build-arg',\n `DOOMPI_VERSION=${version}`,\n context,\n ]);\n if (exitCode !== 0) throw new Error(`Building the sandbox image failed with exit code ${exitCode}.`);\n } finally {\n fs.rmSync(context, { recursive: true, force: true });\n }\n return tag;\n}\n\nfunction warnAboutLocalPackages(repoRoot: string, onProgress: ((message: string) => void) | undefined): void {\n let modes: string;\n try {\n modes = fs.readFileSync(path.join(repoRoot, MODES_FILE), 'utf8');\n } catch {\n return;\n }\n if (LOCAL_PACKAGE_PATTERN.test(modes)) {\n onProgress?.(\n 'modes.yaml declares local workspace packages; their platform-specific dependencies ' +\n 'must be installed inside the container before they can load',\n );\n }\n}\n\n/** Builds the launcher with injectable seams for tests. */\nexport function createSandboxLauncher(dependencies: SandboxLauncherDependencies = {}) {\n const runner = dependencies.runner ?? new SpawnEngineProcessRunner();\n const beginBroker = dependencies.startBroker ?? startBroker;\n const resolveLoginPorts = dependencies.loginPorts ?? availableLoginPorts;\n return {\n async launchSandbox(request: SandboxLaunchRequest): Promise<number> {\n const engine = await detectEngine(runner, request.environment);\n const runFlags = parseRunFlags(request.environment[RUN_FLAGS_ENV]);\n assertRunFlags(runFlags, RUN_FLAGS_ENV);\n request.onProgress?.(`using ${engine}${runFlags.length > 0 ? ` with ${runFlags.join(' ')}` : ''}`);\n\n const version = dependencies.version ?? distributionVersion();\n // A workspace that describes its own container is describing the\n // toolchain its agent needs, so that container wins over the built-in\n // image. Nothing here is built or pulled until that choice is made.\n const devcontainer =\n request.environment[DEVCONTAINER_DISABLED_ENV] === DISABLED_VALUE\n ? undefined\n : findDevcontainerConfig(request.repoRoot);\n const imageTag = devcontainer ? undefined : await ensureImage(runner, engine, version, request.onProgress);\n if (devcontainer) {\n request.onProgress?.(`using the workspace dev container from ${path.relative(request.repoRoot, devcontainer)}`);\n request.onProgress?.(\n 'its configuration owns the mounts and run arguments, so this is not an isolation boundary',\n );\n } else {\n warnAboutLocalPackages(request.repoRoot, request.onProgress);\n }\n\n const broker =\n request.environment[BROKER_DISABLED_ENV] === DISABLED_VALUE\n ? undefined\n : await beginBroker({\n environment: request.environment,\n onDenied: (reason) => request.onProgress?.(`broker refused a call: ${reason}`),\n // A reused dev container keeps the mounts it was created with,\n // so the socket has to be at the same path on every launch.\n ...(devcontainer ? { socketDirectory: brokerDirectory(request.repoRoot) } : {}),\n });\n if (broker) {\n request.onProgress?.(`brokering ${broker.providers.join(', ')}; provider keys stay on the host`);\n }\n\n const loginPorts = devcontainer ? [] : await resolveLoginPorts();\n if (!devcontainer && loginPorts.length < OAUTH_CALLBACK_PORTS.length) {\n request.onProgress?.(\n 'some OAuth callback ports are already taken; /login inside this sandbox may not complete',\n );\n }\n\n try {\n const hasTty = process.stdin.isTTY === true && process.stdout.isTTY === true;\n if (devcontainer) {\n return await runDevcontainerSession({\n repoRoot: request.repoRoot,\n cwd: request.cwd,\n forwardArgs: request.forwardArgs,\n environment: containerEnvironment(request.environment, broker),\n engine,\n runner,\n version,\n hasTty,\n ...(broker?.endpoint.transport === 'unix' ? { socketDirectory: broker.endpoint.socketDirectory } : {}),\n ...(request.onProgress ? { onProgress: request.onProgress } : {}),\n });\n }\n\n const host: SandboxHostFacts = {\n hasTty,\n platform: process.platform,\n userId: process.getuid?.(),\n groupId: process.getgid?.(),\n repoKey: repoKey(request.repoRoot),\n version,\n ...dependencies.hostFacts,\n };\n const plan = buildSandboxPlan({\n repoRoot: request.repoRoot,\n cwd: request.cwd,\n forwardArgs: request.forwardArgs,\n environment: request.environment,\n engine,\n host,\n imageTag: imageTag as string,\n runFlags,\n loginPorts,\n broker,\n });\n request.onProgress?.(`starting contained session in ${imageTag}`);\n return await runner.run(engine, plan.runArgs);\n } finally {\n await broker?.stop();\n }\n },\n };\n}\n\n/** Contract entry the DoomPi harness resolves for `--sandbox`. */\nexport function launchSandbox(request: SandboxLaunchRequest): Promise<number> {\n return createSandboxLauncher().launchSandbox(request);\n}\n"],"mappings":"43BAmBA,MAAM,EAAa,wBAEb,EAAgB,2BAGhB,EAA2B,CAAC,SAAU,SAAU,UAAW,OAAO,EAElE,EAAa,EAAK,KAAK,QAAS,YAAY,EAC5C,EAAwB,oCAKxB,EAAU,EAAc,YAAY,GAAG,EAiB7C,SAAS,GAA8B,CACrC,OAAQ,EAAQ,wCAAa,CAAC,CAAyB,OACzD,CAGA,SAAS,EAAgB,EAA0B,CACjD,OAAO,EAAK,KAAK,EAAG,OAAO,EAAG,GAAG,EAAQ,CAAQ,EAAE,QAAQ,CAC7D,CAEA,SAAS,EAAQ,EAA0B,CACzC,MAAO,kBAAkB,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAe,GACvG,CAEA,eAAe,EACb,EACA,EACwB,CACxB,IAAM,EAAa,EAAY,EAAW,EAAE,KAAK,EACjD,GAAI,EAAY,CACd,GAAK,EAAqB,SAAS,CAAU,EAAG,OAAO,EACvD,MAAU,MAAM,GAAG,EAAW,mBAAmB,EAAQ,KAAK,IAAI,EAAE,EAAE,CACxE,CACA,IAAK,IAAM,KAAU,EAEnB,IAAI,MADgB,EAAO,QAAQ,EAAQ,CAAC,WAAW,CAAC,EAAA,EAC7C,WAAa,EAAG,OAAO,EAEpC,MAAU,MAAM,6CAA6C,EAAQ,KAAK,IAAI,EAAE,WAAW,EAAW,EAAE,CAC1G,CAEA,eAAe,EACb,EACA,EACA,EACA,EACiB,CACjB,IAAM,EAAM,EAAgB,CAAO,EAEnC,IAAI,MADoB,EAAO,QAAQ,EAAQ,CAAC,QAAS,UAAW,CAAG,CAAC,EAAA,EACzD,WAAa,EAAG,OAAO,EAEtC,IAAa,YAAY,EAAI,qCAAqC,EAClE,IAAM,EAAU,EAAG,YAAY,EAAK,KAAK,EAAG,OAAO,EAAG,uBAAuB,CAAC,EAC9E,GAAI,CACF,EAAG,cAAc,EAAK,KAAK,EAAS,YAAe,EAAG,EAAkB,CAAC,EACzE,EAAG,cAAc,EAAK,KAAK,EAAS,CAAgB,EAAG,EAAoB,CAAC,EAC5E,IAAM,EAAW,MAAM,EAAO,IAAI,EAAQ,CACxC,QACA,KACA,EACA,cACA,kBAAkB,IAClB,CACF,CAAC,EACD,GAAI,IAAa,EAAG,MAAU,MAAM,oDAAoD,EAAS,EAAE,CACrG,QAAU,CACR,EAAG,OAAO,EAAS,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CACrD,CACA,OAAO,CACT,CAEA,SAAS,EAAuB,EAAkB,EAA2D,CAC3G,IAAI,EACJ,GAAI,CACF,EAAQ,EAAG,aAAa,EAAK,KAAK,EAAU,CAAU,EAAG,MAAM,CACjE,MAAQ,CACN,MACF,CACI,EAAsB,KAAK,CAAK,GAClC,IACE,gJAEF,CAEJ,CAGA,SAAgB,EAAsB,EAA4C,CAAC,EAAG,CACpF,IAAM,EAAS,EAAa,QAAU,IAAI,EACpC,EAAc,EAAa,aAAe,EAC1C,EAAoB,EAAa,YAAc,EACrD,MAAO,CACL,MAAM,cAAc,EAAgD,CAClE,IAAM,EAAS,MAAM,EAAa,EAAQ,EAAQ,WAAW,EACvD,EAAW,EAAc,EAAQ,YAAY,EAAc,EACjE,EAAe,EAAU,CAAa,EACtC,EAAQ,aAAa,SAAS,IAAS,EAAS,OAAS,EAAI,SAAS,EAAS,KAAK,GAAG,IAAM,IAAI,EAEjG,IAAM,EAAU,EAAa,SAAW,EAAoB,EAItD,EACJ,EAAQ,YAAA,8BAA2C,IAC/C,IAAA,GACA,EAAuB,EAAQ,QAAQ,EACvC,EAAW,EAAe,IAAA,GAAY,MAAM,EAAY,EAAQ,EAAQ,EAAS,EAAQ,UAAU,EACrG,GACF,EAAQ,aAAa,0CAA0C,EAAK,SAAS,EAAQ,SAAU,CAAY,GAAG,EAC9G,EAAQ,aACN,2FACF,GAEA,EAAuB,EAAQ,SAAU,EAAQ,UAAU,EAG7D,IAAM,EACJ,EAAQ,YAAY,wBAAyB,IACzC,IAAA,GACA,MAAM,EAAY,CAChB,YAAa,EAAQ,YACrB,SAAW,GAAW,EAAQ,aAAa,0BAA0B,GAAQ,EAG7E,GAAI,EAAe,CAAE,gBAAiB,EAAgB,EAAQ,QAAQ,CAAE,EAAI,CAAC,CAC/E,CAAC,EACH,GACF,EAAQ,aAAa,aAAa,EAAO,UAAU,KAAK,IAAI,EAAE,iCAAiC,EAGjG,IAAM,EAAa,EAAe,CAAC,EAAI,MAAM,EAAkB,EAC3D,CAAC,GAAgB,EAAW,OAAS,EAAqB,QAC5D,EAAQ,aACN,0FACF,EAGF,GAAI,CACF,IAAM,EAAS,QAAQ,MAAM,QAAU,IAAQ,QAAQ,OAAO,QAAU,GACxE,GAAI,EACF,OAAO,MAAM,EAAuB,CAClC,SAAU,EAAQ,SAClB,IAAK,EAAQ,IACb,YAAa,EAAQ,YACrB,YAAa,EAAqB,EAAQ,YAAa,CAAM,EAC7D,SACA,SACA,UACA,SACA,GAAI,GAAQ,SAAS,YAAc,OAAS,CAAE,gBAAiB,EAAO,SAAS,eAAgB,EAAI,CAAC,EACpG,GAAI,EAAQ,WAAa,CAAE,WAAY,EAAQ,UAAW,EAAI,CAAC,CACjE,CAAC,EAGH,IAAM,EAAyB,CAC7B,SACA,SAAU,QAAQ,SAClB,OAAQ,QAAQ,SAAS,EACzB,QAAS,QAAQ,SAAS,EAC1B,QAAS,EAAQ,EAAQ,QAAQ,EACjC,UACA,GAAG,EAAa,SAClB,EACM,EAAO,EAAiB,CAC5B,SAAU,EAAQ,SAClB,IAAK,EAAQ,IACb,YAAa,EAAQ,YACrB,YAAa,EAAQ,YACrB,SACA,OACU,WACV,WACA,aACA,QACF,CAAC,EAED,OADA,EAAQ,aAAa,iCAAiC,GAAU,EACzD,MAAM,EAAO,IAAI,EAAQ,EAAK,OAAO,CAC9C,QAAU,CACR,MAAM,GAAQ,KAAK,CACrB,CACF,CACF,CACF,CAGA,SAAgB,EAAc,EAAgD,CAC5E,OAAO,EAAsB,CAAC,CAAC,cAAc,CAAO,CACtD"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/oauthCallback.cjs");let n=require("node:net");n=e.__toESM(n,1);function r(e){return new Promise(t=>{let r=n.default.createServer();r.once(`error`,()=>t(!1)),r.listen(e,`127.0.0.1`,()=>{r.close(()=>t(!0))})})}async function i(e=t.OAUTH_CALLBACK_PORTS){let n=[];for(let t of e)await r(t)&&n.push(t);return n}exports.availableLoginPorts=i;
2
+ //# sourceMappingURL=loginPorts.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loginPorts.cjs","names":["net","OAUTH_CALLBACK_PORTS"],"sources":["../../src/adapters/loginPorts.ts"],"sourcesContent":["import net from 'node:net';\nimport { OAUTH_CALLBACK_PORTS } from '../services/oauthCallback.ts';\n\nconst LOOPBACK = '127.0.0.1';\n\nfunction portIsFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = net.createServer();\n probe.once('error', () => resolve(false));\n probe.listen(port, LOOPBACK, () => {\n probe.close(() => resolve(true));\n });\n });\n}\n\n/**\n * Reports which OAuth callback ports this launch may publish.\n *\n * A taken port means another sandbox already holds it, so publishing would\n * fail the whole launch over a login the session may never attempt. Skipping\n * it costs only the ability to log in from inside this particular container.\n */\nexport async function availableLoginPorts(ports: readonly number[] = OAUTH_CALLBACK_PORTS): Promise<number[]> {\n const free: number[] = [];\n for (const port of ports) {\n if (await portIsFree(port)) free.push(port);\n }\n return free;\n}\n"],"mappings":"2IAKA,SAAS,EAAW,EAAgC,CAClD,OAAO,IAAI,QAAS,GAAY,CAC9B,IAAM,EAAQA,EAAAA,QAAI,aAAa,EAC/B,EAAM,KAAK,YAAe,EAAQ,EAAK,CAAC,EACxC,EAAM,OAAO,EAAM,gBAAgB,CACjC,EAAM,UAAY,EAAQ,EAAI,CAAC,CACjC,CAAC,CACH,CAAC,CACH,CASA,eAAsB,EAAoB,EAA2BC,EAAAA,qBAAyC,CAC5G,IAAM,EAAiB,CAAC,EACxB,IAAK,IAAM,KAAQ,EACb,MAAM,EAAW,CAAI,GAAG,EAAK,KAAK,CAAI,EAE5C,OAAO,CACT"}
@@ -0,0 +1,2 @@
1
+ import{OAUTH_CALLBACK_PORTS as e}from"../services/oauthCallback.mjs";import t from"node:net";function n(e){return new Promise(n=>{let r=t.createServer();r.once(`error`,()=>n(!1)),r.listen(e,`127.0.0.1`,()=>{r.close(()=>n(!0))})})}async function r(t=e){let r=[];for(let e of t)await n(e)&&r.push(e);return r}export{r as availableLoginPorts};
2
+ //# sourceMappingURL=loginPorts.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loginPorts.mjs","names":[],"sources":["../../src/adapters/loginPorts.ts"],"sourcesContent":["import net from 'node:net';\nimport { OAUTH_CALLBACK_PORTS } from '../services/oauthCallback.ts';\n\nconst LOOPBACK = '127.0.0.1';\n\nfunction portIsFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = net.createServer();\n probe.once('error', () => resolve(false));\n probe.listen(port, LOOPBACK, () => {\n probe.close(() => resolve(true));\n });\n });\n}\n\n/**\n * Reports which OAuth callback ports this launch may publish.\n *\n * A taken port means another sandbox already holds it, so publishing would\n * fail the whole launch over a login the session may never attempt. Skipping\n * it costs only the ability to log in from inside this particular container.\n */\nexport async function availableLoginPorts(ports: readonly number[] = OAUTH_CALLBACK_PORTS): Promise<number[]> {\n const free: number[] = [];\n for (const port of ports) {\n if (await portIsFree(port)) free.push(port);\n }\n return free;\n}\n"],"mappings":"6FAKA,SAAS,EAAW,EAAgC,CAClD,OAAO,IAAI,QAAS,GAAY,CAC9B,IAAM,EAAQ,EAAI,aAAa,EAC/B,EAAM,KAAK,YAAe,EAAQ,EAAK,CAAC,EACxC,EAAM,OAAO,EAAM,gBAAgB,CACjC,EAAM,UAAY,EAAQ,EAAI,CAAC,CACjC,CAAC,CACH,CAAC,CACH,CASA,eAAsB,EAAoB,EAA2B,EAAyC,CAC5G,IAAM,EAAiB,CAAC,EACxB,IAAK,IAAM,KAAQ,EACb,MAAM,EAAW,CAAI,GAAG,EAAK,KAAK,CAAI,EAE5C,OAAO,CACT"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../commands/doomSandboxCommand.cjs"),t=require("../../services/brokerProviders.cjs"),n=require("../../container/index.cjs");let r=require("@agimon-ai/doompi-extension-contracts/cordis-host"),i=require("@agimon-ai/doompi-extension-contracts/help");const a=`@agimon-ai/doompi-sandbox`;function o(t,r,o=n.createSandboxContainer()){t.inject([i.DOOM_HELP_SERVICE],e=>{let t=(0,i.requireDoomHelpService)(e).register({source:a,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-use-sandbox`,description:`Use @agimon-ai/doompi-sandbox: Container sandbox for DoomPi launches: the agent, extensions, and tools run inside Docker or Podman while the terminal stays on the host`}]});return()=>t.dispose()}),e.registerSandboxCommand(r,o.service),s(r,process.env)}function s(e,n){for(let r of t.brokeredProviderOverrides(n))e.registerProvider(r.provider,{baseUrl:r.baseUrl})}function c(e,t){o(e,t.pi,t.dependencies)}async function l(e,t=n.createSandboxContainer()){let i=await(0,r.connectDoomCordisHost)(e,a),o=i.root.plugin(c,{pi:e,dependencies:t});try{await o}catch(e){try{await o.dispose()}finally{await i.dispose()}throw e}let s;e.on(`session_shutdown`,()=>s??=(async()=>{try{await o.dispose()}finally{await i.dispose()}})())}exports.activateSandboxExtension=l,exports.default=l,exports.installSandboxRuntime=o,exports.registerBrokeredProviders=s;
2
+ //# sourceMappingURL=extension.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.cjs","names":["createSandboxContainer","DOOM_HELP_SERVICE","requireDoomHelpService","brokeredProviderOverrides","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["// @scaffold-generated\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerSandboxCommand } from '../../commands/doomSandboxCommand.ts';\nimport { brokeredProviderOverrides } from '../../services/brokerProviders.ts';\nimport { createSandboxContainer } from '../../container';\nimport type { SandboxExtensionDependencies } from '../../types/extension';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-sandbox';\n\n/**\n * Installs the package runtime inside its host-owned Cordis plugin fiber.\n *\n * Everything registered inside the effect is owned by the cordis fiber, so one\n * dispose on session shutdown unwinds it in reverse order. Yield a disposer for\n * each registration rather than tracking cleanup by hand; Pi can reload an\n * extension in-process, and a leaked listener duplicates work against stale\n * session state.\n */\nexport function installSandboxRuntime(\n cordis: Context,\n pi: ExtensionAPI,\n dependencies: SandboxExtensionDependencies = createSandboxContainer(),\n): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-sandbox',\n description:\n 'Use @agimon-ai/doompi-sandbox: Container sandbox for DoomPi launches: the agent, extensions, and tools run inside Docker or Podman while the terminal stays on the host',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n registerSandboxCommand(pi, dependencies.service);\n registerBrokeredProviders(pi, process.env);\n}\n\n/**\n * Redirects brokered providers at the loopback bridge inside the sandbox.\n *\n * Silent by design when the launch set no broker variables: an unbrokered or\n * host session must keep Pi's own provider endpoints.\n */\nexport function registerBrokeredProviders(\n pi: Pick<ExtensionAPI, 'registerProvider'>,\n environment: Readonly<Record<string, string | undefined>>,\n): void {\n for (const override of brokeredProviderOverrides(environment)) {\n pi.registerProvider(override.provider, { baseUrl: override.baseUrl });\n }\n}\n\ninterface SandboxPluginConfig {\n readonly pi: ExtensionAPI;\n readonly dependencies: SandboxExtensionDependencies;\n}\n\nfunction sandboxPlugin(cordis: Context, config: SandboxPluginConfig): void {\n installSandboxRuntime(cordis, config.pi, config.dependencies);\n}\n\n/** Connects the package's standard Pi entry to DoomPi's shared Cordis host. */\nexport async function activateSandboxExtension(\n pi: ExtensionAPI,\n dependencies: SandboxExtensionDependencies = createSandboxContainer(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(sandboxPlugin, { pi, dependencies });\n\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default activateSandboxExtension;\n"],"mappings":"2QAUA,MAAM,EAAiB,4BAWvB,SAAgB,EACd,EACA,EACA,EAA6CA,EAAAA,uBAAuB,EAC9D,CACN,EAAO,OAAO,CAACC,EAAAA,iBAAiB,EAAI,GAAgB,CAClD,IAAM,GAAA,EAAeC,EAAAA,uBAAAA,CAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,KACA,OAAQ,CACN,CACE,KAAM,qBACN,YACE,yKACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,EAAA,uBAAuB,EAAI,EAAa,OAAO,EAC/C,EAA0B,EAAI,QAAQ,GAAG,CAC3C,CAQA,SAAgB,EACd,EACA,EACM,CACN,IAAK,IAAM,KAAYC,EAAAA,0BAA0B,CAAW,EAC1D,EAAG,iBAAiB,EAAS,SAAU,CAAE,QAAS,EAAS,OAAQ,CAAC,CAExE,CAOA,SAAS,EAAc,EAAiB,EAAmC,CACzE,EAAsB,EAAQ,EAAO,GAAI,EAAO,YAAY,CAC9D,CAGA,eAAsB,EACpB,EACA,EAA6CH,EAAAA,uBAAuB,EACrD,CACf,IAAM,EAAa,MAAA,EAAMI,EAAAA,sBAAAA,CAAsB,EAAI,CAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAe,CAAE,KAAI,cAAa,CAAC,EAExE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CAEA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF"}
@@ -0,0 +1,9 @@
1
+ import { SandboxExtensionDependencies } from "../../types/extension.cjs";
2
+ import "@deepseek-ai/cordis";
3
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ //#region src/adapters/pi/extension.d.ts
5
+ /** Connects the package's standard Pi entry to DoomPi's shared Cordis host. */
6
+ declare function activateSandboxExtension(pi: ExtensionAPI, dependencies?: SandboxExtensionDependencies): Promise<void>;
7
+ //#endregion
8
+ export { activateSandboxExtension, activateSandboxExtension as default };
9
+ //# sourceMappingURL=extension.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;iBAsEsB,yBACpB,IAAI,cACJ,eAAc,+BACb"}
@@ -0,0 +1,9 @@
1
+ import { SandboxExtensionDependencies } from "../../types/extension.mjs";
2
+ import "@deepseek-ai/cordis";
3
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ //#region src/adapters/pi/extension.d.ts
5
+ /** Connects the package's standard Pi entry to DoomPi's shared Cordis host. */
6
+ declare function activateSandboxExtension(pi: ExtensionAPI, dependencies?: SandboxExtensionDependencies): Promise<void>;
7
+ //#endregion
8
+ export { activateSandboxExtension, activateSandboxExtension as default };
9
+ //# sourceMappingURL=extension.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;iBAsEsB,yBACpB,IAAI,cACJ,eAAc,+BACb"}
@@ -0,0 +1,2 @@
1
+ import{registerSandboxCommand as e}from"../../commands/doomSandboxCommand.mjs";import{brokeredProviderOverrides as t}from"../../services/brokerProviders.mjs";import{createSandboxContainer as n}from"../../container/index.mjs";import{connectDoomCordisHost as r}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as i,requireDoomHelpService as a}from"@agimon-ai/doompi-extension-contracts/help";const o=`@agimon-ai/doompi-sandbox`;function s(t,r,s=n()){t.inject([i],e=>{let t=a(e).register({source:o,moduleUrl:import.meta.url,skills:[{name:`doompi-use-sandbox`,description:`Use @agimon-ai/doompi-sandbox: Container sandbox for DoomPi launches: the agent, extensions, and tools run inside Docker or Podman while the terminal stays on the host`}]});return()=>t.dispose()}),e(r,s.service),c(r,process.env)}function c(e,n){for(let r of t(n))e.registerProvider(r.provider,{baseUrl:r.baseUrl})}function l(e,t){s(e,t.pi,t.dependencies)}async function u(e,t=n()){let i=await r(e,o),a=i.root.plugin(l,{pi:e,dependencies:t});try{await a}catch(e){try{await a.dispose()}finally{await i.dispose()}throw e}let s;e.on(`session_shutdown`,()=>s??=(async()=>{try{await a.dispose()}finally{await i.dispose()}})())}export{u as activateSandboxExtension,u as default,s as installSandboxRuntime,c as registerBrokeredProviders};
2
+ //# sourceMappingURL=extension.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["// @scaffold-generated\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerSandboxCommand } from '../../commands/doomSandboxCommand.ts';\nimport { brokeredProviderOverrides } from '../../services/brokerProviders.ts';\nimport { createSandboxContainer } from '../../container';\nimport type { SandboxExtensionDependencies } from '../../types/extension';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-sandbox';\n\n/**\n * Installs the package runtime inside its host-owned Cordis plugin fiber.\n *\n * Everything registered inside the effect is owned by the cordis fiber, so one\n * dispose on session shutdown unwinds it in reverse order. Yield a disposer for\n * each registration rather than tracking cleanup by hand; Pi can reload an\n * extension in-process, and a leaked listener duplicates work against stale\n * session state.\n */\nexport function installSandboxRuntime(\n cordis: Context,\n pi: ExtensionAPI,\n dependencies: SandboxExtensionDependencies = createSandboxContainer(),\n): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-sandbox',\n description:\n 'Use @agimon-ai/doompi-sandbox: Container sandbox for DoomPi launches: the agent, extensions, and tools run inside Docker or Podman while the terminal stays on the host',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n registerSandboxCommand(pi, dependencies.service);\n registerBrokeredProviders(pi, process.env);\n}\n\n/**\n * Redirects brokered providers at the loopback bridge inside the sandbox.\n *\n * Silent by design when the launch set no broker variables: an unbrokered or\n * host session must keep Pi's own provider endpoints.\n */\nexport function registerBrokeredProviders(\n pi: Pick<ExtensionAPI, 'registerProvider'>,\n environment: Readonly<Record<string, string | undefined>>,\n): void {\n for (const override of brokeredProviderOverrides(environment)) {\n pi.registerProvider(override.provider, { baseUrl: override.baseUrl });\n }\n}\n\ninterface SandboxPluginConfig {\n readonly pi: ExtensionAPI;\n readonly dependencies: SandboxExtensionDependencies;\n}\n\nfunction sandboxPlugin(cordis: Context, config: SandboxPluginConfig): void {\n installSandboxRuntime(cordis, config.pi, config.dependencies);\n}\n\n/** Connects the package's standard Pi entry to DoomPi's shared Cordis host. */\nexport async function activateSandboxExtension(\n pi: ExtensionAPI,\n dependencies: SandboxExtensionDependencies = createSandboxContainer(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(sandboxPlugin, { pi, dependencies });\n\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default activateSandboxExtension;\n"],"mappings":"saAUA,MAAM,EAAiB,4BAWvB,SAAgB,EACd,EACA,EACA,EAA6C,EAAuB,EAC9D,CACN,EAAO,OAAO,CAAC,CAAiB,EAAI,GAAgB,CAClD,IAAM,EAAe,EAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAW,YAAY,IACvB,OAAQ,CACN,CACE,KAAM,qBACN,YACE,yKACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,EAAuB,EAAI,EAAa,OAAO,EAC/C,EAA0B,EAAI,QAAQ,GAAG,CAC3C,CAQA,SAAgB,EACd,EACA,EACM,CACN,IAAK,IAAM,KAAY,EAA0B,CAAW,EAC1D,EAAG,iBAAiB,EAAS,SAAU,CAAE,QAAS,EAAS,OAAQ,CAAC,CAExE,CAOA,SAAS,EAAc,EAAiB,EAAmC,CACzE,EAAsB,EAAQ,EAAO,GAAI,EAAO,YAAY,CAC9D,CAGA,eAAsB,EACpB,EACA,EAA6C,EAAuB,EACrD,CACf,IAAM,EAAa,MAAM,EAAsB,EAAI,CAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAe,CAAE,KAAI,cAAa,CAAC,EAExE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CAEA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../services/sandboxBridge.cjs"),t=require("../services/sandboxImage.cjs");let n=require("node:crypto");function r(r){let i=(0,n.createHash)(`sha256`).update(t.sandboxDockerfile()).update(e.sandboxBridgeSource()).digest(`hex`).slice(0,8);return t.formatImageTag(r,i)}exports.sandboxImageTag=r;
2
+ //# sourceMappingURL=sandboxImageTag.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandboxImageTag.cjs","names":["createHash","sandboxDockerfile","sandboxBridgeSource","formatImageTag"],"sources":["../../src/adapters/sandboxImageTag.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { sandboxBridgeSource } from '../services/sandboxBridge.ts';\nimport { formatImageTag, sandboxDockerfile } from '../services/sandboxImage.ts';\n\nconst DIGEST_LENGTH = 8;\n\n/** Tags the image with the distribution version and a digest of its definition. */\nexport function sandboxImageTag(version: string): string {\n const digest = createHash('sha256')\n .update(sandboxDockerfile())\n .update(sandboxBridgeSource())\n .digest('hex')\n .slice(0, DIGEST_LENGTH);\n return formatImageTag(version, digest);\n}\n"],"mappings":"wHAOA,SAAgB,EAAgB,EAAyB,CACvD,IAAM,GAAA,EAASA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAChC,OAAOC,EAAAA,kBAAkB,CAAC,CAAC,CAC3B,OAAOC,EAAAA,oBAAoB,CAAC,CAAC,CAC7B,OAAO,KAAK,CAAC,CACb,MAAM,EAAG,CAAa,EACzB,OAAOC,EAAAA,eAAe,EAAS,CAAM,CACvC"}
@@ -0,0 +1,2 @@
1
+ import{sandboxBridgeSource as e}from"../services/sandboxBridge.mjs";import{formatImageTag as t,sandboxDockerfile as n}from"../services/sandboxImage.mjs";import{createHash as r}from"node:crypto";function i(i){let a=r(`sha256`).update(n()).update(e()).digest(`hex`).slice(0,8);return t(i,a)}export{i as sandboxImageTag};
2
+ //# sourceMappingURL=sandboxImageTag.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandboxImageTag.mjs","names":[],"sources":["../../src/adapters/sandboxImageTag.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { sandboxBridgeSource } from '../services/sandboxBridge.ts';\nimport { formatImageTag, sandboxDockerfile } from '../services/sandboxImage.ts';\n\nconst DIGEST_LENGTH = 8;\n\n/** Tags the image with the distribution version and a digest of its definition. */\nexport function sandboxImageTag(version: string): string {\n const digest = createHash('sha256')\n .update(sandboxDockerfile())\n .update(sandboxBridgeSource())\n .digest('hex')\n .slice(0, DIGEST_LENGTH);\n return formatImageTag(version, digest);\n}\n"],"mappings":"kMAOA,SAAgB,EAAgB,EAAyB,CACvD,IAAM,EAAS,EAAW,QAAQ,CAAC,CAChC,OAAO,EAAkB,CAAC,CAAC,CAC3B,OAAO,EAAoB,CAAC,CAAC,CAC7B,OAAO,KAAK,CAAC,CACb,MAAM,EAAG,CAAa,EACzB,OAAO,EAAe,EAAS,CAAM,CACvC"}
@@ -0,0 +1,2 @@
1
+ const e=`doom-sandbox`,t=`Show sandbox container status for this session`;function n(n,r){n.registerCommand(e,{description:t,handler:async(e,t)=>{let n=await r.execute();t.hasUI&&t.ui.notify(n.message,n.level)}})}exports.COMMAND_DESCRIPTION=t,exports.COMMAND_NAME=e,exports.registerSandboxCommand=n;
2
+ //# sourceMappingURL=doomSandboxCommand.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomSandboxCommand.cjs","names":[],"sources":["../../src/commands/doomSandboxCommand.ts"],"sourcesContent":["// @scaffold-generated\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { SandboxExtensionService } from '../types/extension.ts';\n\nexport const COMMAND_NAME = 'doom-sandbox';\nexport const COMMAND_DESCRIPTION = 'Show sandbox container status for this session';\n\nexport function registerSandboxCommand(\n pi: Pick<ExtensionAPI, 'registerCommand'>,\n service: SandboxExtensionService,\n): void {\n pi.registerCommand(COMMAND_NAME, {\n description: COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => {\n const result = await service.execute();\n if (ctx.hasUI) ctx.ui.notify(result.message, result.level);\n },\n });\n}\n"],"mappings":"AAIA,MAAa,EAAe,eACf,EAAsB,iDAEnC,SAAgB,EACd,EACA,EACM,CACN,EAAG,gBAAgB,EAAc,CAC/B,YAAa,EACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,IAAM,EAAS,MAAM,EAAQ,QAAQ,EACjC,EAAI,OAAO,EAAI,GAAG,OAAO,EAAO,QAAS,EAAO,KAAK,CAC3D,CACF,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ const e=`doom-sandbox`,t=`Show sandbox container status for this session`;function n(n,r){n.registerCommand(e,{description:t,handler:async(e,t)=>{let n=await r.execute();t.hasUI&&t.ui.notify(n.message,n.level)}})}export{t as COMMAND_DESCRIPTION,e as COMMAND_NAME,n as registerSandboxCommand};
2
+ //# sourceMappingURL=doomSandboxCommand.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomSandboxCommand.mjs","names":[],"sources":["../../src/commands/doomSandboxCommand.ts"],"sourcesContent":["// @scaffold-generated\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { SandboxExtensionService } from '../types/extension.ts';\n\nexport const COMMAND_NAME = 'doom-sandbox';\nexport const COMMAND_DESCRIPTION = 'Show sandbox container status for this session';\n\nexport function registerSandboxCommand(\n pi: Pick<ExtensionAPI, 'registerCommand'>,\n service: SandboxExtensionService,\n): void {\n pi.registerCommand(COMMAND_NAME, {\n description: COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => {\n const result = await service.execute();\n if (ctx.hasUI) ctx.ui.notify(result.message, result.level);\n },\n });\n}\n"],"mappings":"AAIA,MAAa,EAAe,eACf,EAAsB,iDAEnC,SAAgB,EACd,EACA,EACM,CACN,EAAG,gBAAgB,EAAc,CAC/B,YAAa,EACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,IAAM,EAAS,MAAM,EAAQ,QAAQ,EACjC,EAAI,OAAO,EAAI,GAAG,OAAO,EAAO,QAAS,EAAO,KAAK,CAC3D,CACF,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ const e=require("../services/extensionService.cjs");function t(t={}){return{service:t.service??new e.DefaultSandboxExtensionService(process.env)}}exports.createSandboxContainer=t;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["DefaultSandboxExtensionService"],"sources":["../../src/container/index.ts"],"sourcesContent":["import { DefaultSandboxExtensionService } from '../services/extensionService.ts';\nimport type { SandboxExtensionDependencies } from '../types/extension.ts';\n\nexport function createSandboxContainer(\n overrides: Partial<SandboxExtensionDependencies> = {},\n): SandboxExtensionDependencies {\n return {\n service: overrides.service ?? new DefaultSandboxExtensionService(process.env),\n };\n}\n"],"mappings":"oDAGA,SAAgB,EACd,EAAmD,CAAC,EACtB,CAC9B,MAAO,CACL,QAAS,EAAU,SAAW,IAAIA,EAAAA,+BAA+B,QAAQ,GAAG,CAC9E,CACF"}
@@ -0,0 +1,6 @@
1
+ import { SandboxExtensionDependencies } from "../types/extension.cjs";
2
+ //#region src/container/index.d.ts
3
+ declare function createSandboxContainer(overrides?: Partial<SandboxExtensionDependencies>): SandboxExtensionDependencies;
4
+ //#endregion
5
+ export { createSandboxContainer };
6
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/container/index.ts"],"mappings":";;iBAGgB,uBACd,YAAW,QAAQ,gCAClB"}
@@ -0,0 +1,6 @@
1
+ import { SandboxExtensionDependencies } from "../types/extension.mjs";
2
+ //#region src/container/index.d.ts
3
+ declare function createSandboxContainer(overrides?: Partial<SandboxExtensionDependencies>): SandboxExtensionDependencies;
4
+ //#endregion
5
+ export { createSandboxContainer };
6
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/container/index.ts"],"mappings":";;iBAGgB,uBACd,YAAW,QAAQ,gCAClB"}
@@ -0,0 +1,2 @@
1
+ import{DefaultSandboxExtensionService as e}from"../services/extensionService.mjs";function t(t={}){return{service:t.service??new e(process.env)}}export{t as createSandboxContainer};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/container/index.ts"],"sourcesContent":["import { DefaultSandboxExtensionService } from '../services/extensionService.ts';\nimport type { SandboxExtensionDependencies } from '../types/extension.ts';\n\nexport function createSandboxContainer(\n overrides: Partial<SandboxExtensionDependencies> = {},\n): SandboxExtensionDependencies {\n return {\n service: overrides.service ?? new DefaultSandboxExtensionService(process.env),\n };\n}\n"],"mappings":"kFAGA,SAAgB,EACd,EAAmD,CAAC,EACtB,CAC9B,MAAO,CACL,QAAS,EAAU,SAAW,IAAI,EAA+B,QAAQ,GAAG,CAC9E,CACF"}
@@ -0,0 +1 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../adapters/pi/extension.cjs");exports.activateSandboxExtension=e.activateSandboxExtension,exports.default=e.activateSandboxExtension;
@@ -0,0 +1,2 @@
1
+ import { activateSandboxExtension } from "../adapters/pi/extension.cjs";
2
+ export { activateSandboxExtension, activateSandboxExtension as default };
@@ -0,0 +1,2 @@
1
+ import { activateSandboxExtension } from "../adapters/pi/extension.mjs";
2
+ export { activateSandboxExtension, activateSandboxExtension as default };
@@ -0,0 +1 @@
1
+ import{activateSandboxExtension as e}from"../adapters/pi/extension.mjs";export{e as activateSandboxExtension,e as default};
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/extensionService.cjs"),t=require("./container/index.cjs"),n=require("./adapters/pi/extension.cjs");exports.DefaultSandboxExtensionService=e.DefaultSandboxExtensionService,exports.activateSandboxExtension=n.activateSandboxExtension,exports.createSandboxContainer=t.createSandboxContainer;
@@ -0,0 +1,5 @@
1
+ import { SandboxExtensionDependencies, SandboxExtensionResult, SandboxExtensionService } from "./types/extension.cjs";
2
+ import { activateSandboxExtension } from "./adapters/pi/extension.cjs";
3
+ import { DefaultSandboxExtensionService } from "./services/extensionService.cjs";
4
+ import { createSandboxContainer } from "./container/index.cjs";
5
+ export { DefaultSandboxExtensionService, type SandboxExtensionDependencies, type SandboxExtensionResult, type SandboxExtensionService, activateSandboxExtension, createSandboxContainer };
@@ -0,0 +1,5 @@
1
+ import { SandboxExtensionDependencies, SandboxExtensionResult, SandboxExtensionService } from "./types/extension.mjs";
2
+ import { activateSandboxExtension } from "./adapters/pi/extension.mjs";
3
+ import { DefaultSandboxExtensionService } from "./services/extensionService.mjs";
4
+ import { createSandboxContainer } from "./container/index.mjs";
5
+ export { DefaultSandboxExtensionService, type SandboxExtensionDependencies, type SandboxExtensionResult, type SandboxExtensionService, activateSandboxExtension, createSandboxContainer };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{DefaultSandboxExtensionService as e}from"./services/extensionService.mjs";import{createSandboxContainer as t}from"./container/index.mjs";import{activateSandboxExtension as n}from"./adapters/pi/extension.mjs";export{e as DefaultSandboxExtensionService,n as activateSandboxExtension,t as createSandboxContainer};
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/harness.cjs");exports.createSandboxLauncher=e.createSandboxLauncher,exports.launchSandbox=e.launchSandbox;
@@ -0,0 +1,2 @@
1
+ import { SandboxLauncherDependencies, createSandboxLauncher, launchSandbox } from "./adapters/harness.cjs";
2
+ export { type SandboxLauncherDependencies, createSandboxLauncher, launchSandbox };
@@ -0,0 +1,2 @@
1
+ import { SandboxLauncherDependencies, createSandboxLauncher, launchSandbox } from "./adapters/harness.mjs";
2
+ export { type SandboxLauncherDependencies, createSandboxLauncher, launchSandbox };
@@ -0,0 +1 @@
1
+ import{createSandboxLauncher as e,launchSandbox as t}from"./adapters/harness.mjs";export{e as createSandboxLauncher,t as launchSandbox};
@@ -0,0 +1,2 @@
1
+ const e=require("./sandboxBridge.cjs");function t(t){let n=t[e.BROKER_PROVIDERS_ENV]?.trim();if(!n)return[];let r=t.DOOMPI_BROKER_SOCKET?.trim()?`127.0.0.1:${e.BROKER_CONTAINER_PORT}`:t[e.BROKER_ADDRESS_ENV]?.trim();return r?n.split(`,`).map(e=>e.trim()).filter(e=>e.length>0).map(e=>({provider:e,baseUrl:`http://${r}/${e}`})):[]}exports.brokeredProviderOverrides=t;
2
+ //# sourceMappingURL=brokerProviders.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brokerProviders.cjs","names":["BROKER_PROVIDERS_ENV","BROKER_CONTAINER_PORT","BROKER_ADDRESS_ENV"],"sources":["../../src/services/brokerProviders.ts"],"sourcesContent":["import { BROKER_ADDRESS_ENV, BROKER_CONTAINER_PORT, BROKER_PROVIDERS_ENV, BROKER_SOCKET_ENV } from './sandboxBridge.ts';\n\nexport interface BrokeredProviderOverride {\n provider: string;\n baseUrl: string;\n}\n\n/**\n * Points brokered providers at whichever route the launch granted.\n *\n * Pi reads base URLs from package-internal provider data, so redirecting a\n * session means overriding the provider rather than setting an environment\n * variable. The credential is left alone: the launch already replaced it with\n * the session token the broker expects.\n *\n * A broker on a host port is addressed directly. Only a mounted socket needs\n * the loopback bridge, because no SDK can dial a socket path.\n */\nexport function brokeredProviderOverrides(\n environment: Readonly<Record<string, string | undefined>>,\n): BrokeredProviderOverride[] {\n const configured = environment[BROKER_PROVIDERS_ENV]?.trim();\n if (!configured) return [];\n const authority = environment[BROKER_SOCKET_ENV]?.trim()\n ? `127.0.0.1:${BROKER_CONTAINER_PORT}`\n : environment[BROKER_ADDRESS_ENV]?.trim();\n if (!authority) return [];\n return configured\n .split(',')\n .map((provider) => provider.trim())\n .filter((provider) => provider.length > 0)\n .map((provider) => ({ provider, baseUrl: `http://${authority}/${provider}` }));\n}\n"],"mappings":"uCAkBA,SAAgB,EACd,EAC4B,CAC5B,IAAM,EAAa,EAAYA,EAAAA,qBAAqB,EAAE,KAAK,EAC3D,GAAI,CAAC,EAAY,MAAO,CAAC,EACzB,IAAM,EAAY,EAAA,sBAAgC,KAAK,EACnD,aAAaC,EAAAA,wBACb,EAAYC,EAAAA,mBAAmB,EAAE,KAAK,EAE1C,OADK,EACE,EACJ,MAAM,GAAG,CAAC,CACV,IAAK,GAAa,EAAS,KAAK,CAAC,CAAC,CAClC,OAAQ,GAAa,EAAS,OAAS,CAAC,CAAC,CACzC,IAAK,IAAc,CAAE,WAAU,QAAS,UAAU,EAAU,GAAG,GAAW,EAAE,EALxD,CAAC,CAM1B"}
@@ -0,0 +1,2 @@
1
+ import{BROKER_ADDRESS_ENV as e,BROKER_CONTAINER_PORT as t,BROKER_PROVIDERS_ENV as n}from"./sandboxBridge.mjs";function r(r){let i=r[n]?.trim();if(!i)return[];let a=r.DOOMPI_BROKER_SOCKET?.trim()?`127.0.0.1:${t}`:r[e]?.trim();return a?i.split(`,`).map(e=>e.trim()).filter(e=>e.length>0).map(e=>({provider:e,baseUrl:`http://${a}/${e}`})):[]}export{r as brokeredProviderOverrides};
2
+ //# sourceMappingURL=brokerProviders.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brokerProviders.mjs","names":[],"sources":["../../src/services/brokerProviders.ts"],"sourcesContent":["import { BROKER_ADDRESS_ENV, BROKER_CONTAINER_PORT, BROKER_PROVIDERS_ENV, BROKER_SOCKET_ENV } from './sandboxBridge.ts';\n\nexport interface BrokeredProviderOverride {\n provider: string;\n baseUrl: string;\n}\n\n/**\n * Points brokered providers at whichever route the launch granted.\n *\n * Pi reads base URLs from package-internal provider data, so redirecting a\n * session means overriding the provider rather than setting an environment\n * variable. The credential is left alone: the launch already replaced it with\n * the session token the broker expects.\n *\n * A broker on a host port is addressed directly. Only a mounted socket needs\n * the loopback bridge, because no SDK can dial a socket path.\n */\nexport function brokeredProviderOverrides(\n environment: Readonly<Record<string, string | undefined>>,\n): BrokeredProviderOverride[] {\n const configured = environment[BROKER_PROVIDERS_ENV]?.trim();\n if (!configured) return [];\n const authority = environment[BROKER_SOCKET_ENV]?.trim()\n ? `127.0.0.1:${BROKER_CONTAINER_PORT}`\n : environment[BROKER_ADDRESS_ENV]?.trim();\n if (!authority) return [];\n return configured\n .split(',')\n .map((provider) => provider.trim())\n .filter((provider) => provider.length > 0)\n .map((provider) => ({ provider, baseUrl: `http://${authority}/${provider}` }));\n}\n"],"mappings":"8GAkBA,SAAgB,EACd,EAC4B,CAC5B,IAAM,EAAa,EAAY,EAAqB,EAAE,KAAK,EAC3D,GAAI,CAAC,EAAY,MAAO,CAAC,EACzB,IAAM,EAAY,EAAA,sBAAgC,KAAK,EACnD,aAAa,IACb,EAAY,EAAmB,EAAE,KAAK,EAE1C,OADK,EACE,EACJ,MAAM,GAAG,CAAC,CACV,IAAK,GAAa,EAAS,KAAK,CAAC,CAAC,CAClC,OAAQ,GAAa,EAAS,OAAS,CAAC,CAAC,CACzC,IAAK,IAAc,CAAE,WAAU,QAAS,UAAU,EAAU,GAAG,GAAW,EAAE,EALxD,CAAC,CAM1B"}
@@ -0,0 +1,2 @@
1
+ const e=[{provider:`anthropic`,upstream:`https://api.anthropic.com`,hostKeyEnv:[`ANTHROPIC_API_KEY`]},{provider:`openai`,upstream:`https://api.openai.com/v1`,hostKeyEnv:[`OPENAI_API_KEY`]},{provider:`google`,upstream:`https://generativelanguage.googleapis.com/v1beta`,hostKeyEnv:[`GEMINI_API_KEY`]},{provider:`groq`,upstream:`https://api.groq.com/openai/v1`,hostKeyEnv:[`GROQ_API_KEY`]},{provider:`xai`,upstream:`https://api.x.ai/v1`,hostKeyEnv:[`XAI_API_KEY`]},{provider:`openrouter`,upstream:`https://openrouter.ai/api/v1`,hostKeyEnv:[`OPENROUTER_API_KEY`]},{provider:`deepseek`,upstream:`https://api.deepseek.com`,hostKeyEnv:[`DEEPSEEK_API_KEY`]},{provider:`mistral`,upstream:`https://api.mistral.ai`,hostKeyEnv:[`MISTRAL_API_KEY`]},{provider:`together`,upstream:`https://api.together.ai/v1`,hostKeyEnv:[`TOGETHER_API_KEY`]},{provider:`cerebras`,upstream:`https://api.cerebras.ai/v1`,hostKeyEnv:[`CEREBRAS_API_KEY`]},{provider:`moonshotai`,upstream:`https://api.moonshot.ai/v1`,hostKeyEnv:[`MOONSHOT_API_KEY`]},{provider:`zai`,upstream:`https://api.z.ai/api/coding/paas/v4`,hostKeyEnv:[`ZAI_API_KEY`]}];function t(t){let n=[];for(let r of e)for(let e of r.hostKeyEnv){let i=t[e]?.trim();if(i){n.push({route:r,envName:e,value:i});break}}return n}exports.resolveBrokeredCredentials=t;
2
+ //# sourceMappingURL=brokerRoutes.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brokerRoutes.cjs","names":[],"sources":["../../src/services/brokerRoutes.ts"],"sourcesContent":["export interface BrokerRoute {\n /** Pi provider name, which is also the broker's first path segment. */\n provider: string;\n /** Upstream origin plus any base path Pi's own provider data carries. */\n upstream: string;\n /** Host environment variables that may hold the real credential, in priority order. */\n hostKeyEnv: readonly string[];\n}\n\n/**\n * Providers the broker can terminate, mirroring Pi's own provider data.\n *\n * Curated rather than derived: Pi keeps base URLs in package-internal JSON that\n * carries no stability promise. A provider missing here is simply not brokered,\n * and its credential stays on the host.\n */\nconst ROUTES: readonly BrokerRoute[] = [\n { provider: 'anthropic', upstream: 'https://api.anthropic.com', hostKeyEnv: ['ANTHROPIC_API_KEY'] },\n { provider: 'openai', upstream: 'https://api.openai.com/v1', hostKeyEnv: ['OPENAI_API_KEY'] },\n { provider: 'google', upstream: 'https://generativelanguage.googleapis.com/v1beta', hostKeyEnv: ['GEMINI_API_KEY'] },\n { provider: 'groq', upstream: 'https://api.groq.com/openai/v1', hostKeyEnv: ['GROQ_API_KEY'] },\n { provider: 'xai', upstream: 'https://api.x.ai/v1', hostKeyEnv: ['XAI_API_KEY'] },\n { provider: 'openrouter', upstream: 'https://openrouter.ai/api/v1', hostKeyEnv: ['OPENROUTER_API_KEY'] },\n { provider: 'deepseek', upstream: 'https://api.deepseek.com', hostKeyEnv: ['DEEPSEEK_API_KEY'] },\n { provider: 'mistral', upstream: 'https://api.mistral.ai', hostKeyEnv: ['MISTRAL_API_KEY'] },\n { provider: 'together', upstream: 'https://api.together.ai/v1', hostKeyEnv: ['TOGETHER_API_KEY'] },\n { provider: 'cerebras', upstream: 'https://api.cerebras.ai/v1', hostKeyEnv: ['CEREBRAS_API_KEY'] },\n { provider: 'moonshotai', upstream: 'https://api.moonshot.ai/v1', hostKeyEnv: ['MOONSHOT_API_KEY'] },\n { provider: 'zai', upstream: 'https://api.z.ai/api/coding/paas/v4', hostKeyEnv: ['ZAI_API_KEY'] },\n];\n\nexport function brokerRoutes(): readonly BrokerRoute[] {\n return ROUTES;\n}\n\nexport function findBrokerRoute(provider: string): BrokerRoute | undefined {\n return ROUTES.find((route) => route.provider === provider);\n}\n\nexport interface ResolvedCredential {\n route: BrokerRoute;\n /** Environment variable the container would otherwise have received. */\n envName: string;\n value: string;\n}\n\n/** Selects the providers this host can actually broker for a session. */\nexport function resolveBrokeredCredentials(\n environment: Readonly<Record<string, string | undefined>>,\n): ResolvedCredential[] {\n const resolved: ResolvedCredential[] = [];\n for (const route of ROUTES) {\n for (const envName of route.hostKeyEnv) {\n const value = environment[envName]?.trim();\n if (value) {\n resolved.push({ route, envName, value });\n break;\n }\n }\n }\n return resolved;\n}\n"],"mappings":"AAgBA,MAAM,EAAiC,CACrC,CAAE,SAAU,YAAa,SAAU,4BAA6B,WAAY,CAAC,mBAAmB,CAAE,EAClG,CAAE,SAAU,SAAU,SAAU,4BAA6B,WAAY,CAAC,gBAAgB,CAAE,EAC5F,CAAE,SAAU,SAAU,SAAU,mDAAoD,WAAY,CAAC,gBAAgB,CAAE,EACnH,CAAE,SAAU,OAAQ,SAAU,iCAAkC,WAAY,CAAC,cAAc,CAAE,EAC7F,CAAE,SAAU,MAAO,SAAU,sBAAuB,WAAY,CAAC,aAAa,CAAE,EAChF,CAAE,SAAU,aAAc,SAAU,+BAAgC,WAAY,CAAC,oBAAoB,CAAE,EACvG,CAAE,SAAU,WAAY,SAAU,2BAA4B,WAAY,CAAC,kBAAkB,CAAE,EAC/F,CAAE,SAAU,UAAW,SAAU,yBAA0B,WAAY,CAAC,iBAAiB,CAAE,EAC3F,CAAE,SAAU,WAAY,SAAU,6BAA8B,WAAY,CAAC,kBAAkB,CAAE,EACjG,CAAE,SAAU,WAAY,SAAU,6BAA8B,WAAY,CAAC,kBAAkB,CAAE,EACjG,CAAE,SAAU,aAAc,SAAU,6BAA8B,WAAY,CAAC,kBAAkB,CAAE,EACnG,CAAE,SAAU,MAAO,SAAU,sCAAuC,WAAY,CAAC,aAAa,CAAE,CAClG,EAkBA,SAAgB,EACd,EACsB,CACtB,IAAM,EAAiC,CAAC,EACxC,IAAK,IAAM,KAAS,EAClB,IAAK,IAAM,KAAW,EAAM,WAAY,CACtC,IAAM,EAAQ,EAAY,EAAQ,EAAE,KAAK,EACzC,GAAI,EAAO,CACT,EAAS,KAAK,CAAE,QAAO,UAAS,OAAM,CAAC,EACvC,KACF,CACF,CAEF,OAAO,CACT"}
@@ -0,0 +1,2 @@
1
+ const e=[{provider:`anthropic`,upstream:`https://api.anthropic.com`,hostKeyEnv:[`ANTHROPIC_API_KEY`]},{provider:`openai`,upstream:`https://api.openai.com/v1`,hostKeyEnv:[`OPENAI_API_KEY`]},{provider:`google`,upstream:`https://generativelanguage.googleapis.com/v1beta`,hostKeyEnv:[`GEMINI_API_KEY`]},{provider:`groq`,upstream:`https://api.groq.com/openai/v1`,hostKeyEnv:[`GROQ_API_KEY`]},{provider:`xai`,upstream:`https://api.x.ai/v1`,hostKeyEnv:[`XAI_API_KEY`]},{provider:`openrouter`,upstream:`https://openrouter.ai/api/v1`,hostKeyEnv:[`OPENROUTER_API_KEY`]},{provider:`deepseek`,upstream:`https://api.deepseek.com`,hostKeyEnv:[`DEEPSEEK_API_KEY`]},{provider:`mistral`,upstream:`https://api.mistral.ai`,hostKeyEnv:[`MISTRAL_API_KEY`]},{provider:`together`,upstream:`https://api.together.ai/v1`,hostKeyEnv:[`TOGETHER_API_KEY`]},{provider:`cerebras`,upstream:`https://api.cerebras.ai/v1`,hostKeyEnv:[`CEREBRAS_API_KEY`]},{provider:`moonshotai`,upstream:`https://api.moonshot.ai/v1`,hostKeyEnv:[`MOONSHOT_API_KEY`]},{provider:`zai`,upstream:`https://api.z.ai/api/coding/paas/v4`,hostKeyEnv:[`ZAI_API_KEY`]}];function t(t){let n=[];for(let r of e)for(let e of r.hostKeyEnv){let i=t[e]?.trim();if(i){n.push({route:r,envName:e,value:i});break}}return n}export{t as resolveBrokeredCredentials};
2
+ //# sourceMappingURL=brokerRoutes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brokerRoutes.mjs","names":[],"sources":["../../src/services/brokerRoutes.ts"],"sourcesContent":["export interface BrokerRoute {\n /** Pi provider name, which is also the broker's first path segment. */\n provider: string;\n /** Upstream origin plus any base path Pi's own provider data carries. */\n upstream: string;\n /** Host environment variables that may hold the real credential, in priority order. */\n hostKeyEnv: readonly string[];\n}\n\n/**\n * Providers the broker can terminate, mirroring Pi's own provider data.\n *\n * Curated rather than derived: Pi keeps base URLs in package-internal JSON that\n * carries no stability promise. A provider missing here is simply not brokered,\n * and its credential stays on the host.\n */\nconst ROUTES: readonly BrokerRoute[] = [\n { provider: 'anthropic', upstream: 'https://api.anthropic.com', hostKeyEnv: ['ANTHROPIC_API_KEY'] },\n { provider: 'openai', upstream: 'https://api.openai.com/v1', hostKeyEnv: ['OPENAI_API_KEY'] },\n { provider: 'google', upstream: 'https://generativelanguage.googleapis.com/v1beta', hostKeyEnv: ['GEMINI_API_KEY'] },\n { provider: 'groq', upstream: 'https://api.groq.com/openai/v1', hostKeyEnv: ['GROQ_API_KEY'] },\n { provider: 'xai', upstream: 'https://api.x.ai/v1', hostKeyEnv: ['XAI_API_KEY'] },\n { provider: 'openrouter', upstream: 'https://openrouter.ai/api/v1', hostKeyEnv: ['OPENROUTER_API_KEY'] },\n { provider: 'deepseek', upstream: 'https://api.deepseek.com', hostKeyEnv: ['DEEPSEEK_API_KEY'] },\n { provider: 'mistral', upstream: 'https://api.mistral.ai', hostKeyEnv: ['MISTRAL_API_KEY'] },\n { provider: 'together', upstream: 'https://api.together.ai/v1', hostKeyEnv: ['TOGETHER_API_KEY'] },\n { provider: 'cerebras', upstream: 'https://api.cerebras.ai/v1', hostKeyEnv: ['CEREBRAS_API_KEY'] },\n { provider: 'moonshotai', upstream: 'https://api.moonshot.ai/v1', hostKeyEnv: ['MOONSHOT_API_KEY'] },\n { provider: 'zai', upstream: 'https://api.z.ai/api/coding/paas/v4', hostKeyEnv: ['ZAI_API_KEY'] },\n];\n\nexport function brokerRoutes(): readonly BrokerRoute[] {\n return ROUTES;\n}\n\nexport function findBrokerRoute(provider: string): BrokerRoute | undefined {\n return ROUTES.find((route) => route.provider === provider);\n}\n\nexport interface ResolvedCredential {\n route: BrokerRoute;\n /** Environment variable the container would otherwise have received. */\n envName: string;\n value: string;\n}\n\n/** Selects the providers this host can actually broker for a session. */\nexport function resolveBrokeredCredentials(\n environment: Readonly<Record<string, string | undefined>>,\n): ResolvedCredential[] {\n const resolved: ResolvedCredential[] = [];\n for (const route of ROUTES) {\n for (const envName of route.hostKeyEnv) {\n const value = environment[envName]?.trim();\n if (value) {\n resolved.push({ route, envName, value });\n break;\n }\n }\n }\n return resolved;\n}\n"],"mappings":"AAgBA,MAAM,EAAiC,CACrC,CAAE,SAAU,YAAa,SAAU,4BAA6B,WAAY,CAAC,mBAAmB,CAAE,EAClG,CAAE,SAAU,SAAU,SAAU,4BAA6B,WAAY,CAAC,gBAAgB,CAAE,EAC5F,CAAE,SAAU,SAAU,SAAU,mDAAoD,WAAY,CAAC,gBAAgB,CAAE,EACnH,CAAE,SAAU,OAAQ,SAAU,iCAAkC,WAAY,CAAC,cAAc,CAAE,EAC7F,CAAE,SAAU,MAAO,SAAU,sBAAuB,WAAY,CAAC,aAAa,CAAE,EAChF,CAAE,SAAU,aAAc,SAAU,+BAAgC,WAAY,CAAC,oBAAoB,CAAE,EACvG,CAAE,SAAU,WAAY,SAAU,2BAA4B,WAAY,CAAC,kBAAkB,CAAE,EAC/F,CAAE,SAAU,UAAW,SAAU,yBAA0B,WAAY,CAAC,iBAAiB,CAAE,EAC3F,CAAE,SAAU,WAAY,SAAU,6BAA8B,WAAY,CAAC,kBAAkB,CAAE,EACjG,CAAE,SAAU,WAAY,SAAU,6BAA8B,WAAY,CAAC,kBAAkB,CAAE,EACjG,CAAE,SAAU,aAAc,SAAU,6BAA8B,WAAY,CAAC,kBAAkB,CAAE,EACnG,CAAE,SAAU,MAAO,SAAU,sCAAuC,WAAY,CAAC,aAAa,CAAE,CAClG,EAkBA,SAAgB,EACd,EACsB,CACtB,IAAM,EAAiC,CAAC,EACxC,IAAK,IAAM,KAAS,EAClB,IAAK,IAAM,KAAW,EAAM,WAAY,CACtC,IAAM,EAAQ,EAAY,EAAQ,EAAE,KAAK,EACzC,GAAI,EAAO,CACT,EAAS,KAAK,CAAE,QAAO,UAAS,OAAM,CAAC,EACvC,KACF,CACF,CAEF,OAAO,CACT"}
@@ -0,0 +1,3 @@
1
+ const e=[`.devcontainer/devcontainer.json`,`.devcontainer.json`];function t(e){return[`up`,`--workspace-folder`,e.repoRoot,`--log-format`,`json`,...e.socketDirectory&&e.socketTarget?[`--mount`,`type=bind,source=${e.socketDirectory},target=${e.socketTarget}`]:[]]}function n(e){let t;for(let n of e.split(`
2
+ `)){let e=n.trim();if(!e.startsWith(`{`))continue;let r;try{r=JSON.parse(e)}catch{continue}typeof r==`object`&&r&&`outcome`in r&&(t=r)}return t?t.outcome===`success`?t.containerId?{containerId:t.containerId,remoteWorkspaceFolder:t.remoteWorkspaceFolder}:{error:`The dev container started but reported no container id.`}:{error:t.description||t.message||`Dev container start reported ${t.outcome}.`}:{error:`The dev container CLI reported no outcome.`}}function r(e,t,n){let r=t.startsWith(e)?t.slice(e.length).replace(/^[\\/]+/,``):``;return r?`${n}/${r.split(`\\`).join(`/`)}`:n}function i(e){return[`sh`,`-c`,`command -v doompi >/dev/null 2>&1 && exit 0; command -v npm >/dev/null 2>&1 || { echo "doompi-sandbox: this dev container has no npm to install DoomPi with" >&2; exit 127; }; npm install -g @agimon-ai/doompi@${e} >/dev/null`]}function a(e,t,n){return e.map(e=>e===t||e.startsWith(`${t}/`)?r(t,e,n):e)}function o(e){let t=Object.entries(e.environment).sort(([e],[t])=>e.localeCompare(t));return[`exec`,`-i`,...e.hasTty?[`-t`]:[],`-w`,e.cwd,...t.flatMap(([e,t])=>[`-e`,`${e}=${t}`]),e.containerId,...e.command]}exports.DEVCONTAINER_CLI_PACKAGE=`@devcontainers/cli@0.80.0`,exports.DEVCONTAINER_CONFIG_PATHS=e,exports.DEVCONTAINER_DISABLED_ENV=`DOOMPI_SANDBOX_DEVCONTAINER`,exports.bootstrapCommand=i,exports.containerWorkspacePath=r,exports.devcontainerExecArgs=o,exports.devcontainerUpArgs=t,exports.mapForwardArgs=a,exports.parseDevcontainerUp=n;
3
+ //# sourceMappingURL=devcontainer.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devcontainer.cjs","names":[],"sources":["../../src/services/devcontainer.ts"],"sourcesContent":["/**\n * Relative locations the Dev Containers spec looks for a configuration.\n *\n * Ordered as the spec resolves them, so the first hit on disk wins.\n */\nexport const DEVCONTAINER_CONFIG_PATHS: readonly string[] = ['.devcontainer/devcontainer.json', '.devcontainer.json'];\n\nexport const DEVCONTAINER_DISABLED_ENV = 'DOOMPI_SANDBOX_DEVCONTAINER';\nexport const DEVCONTAINER_CLI_PACKAGE = '@devcontainers/cli@0.80.0';\n/** Marks containers this layer brought up, so a stale one can be told apart. */\nexport const DEVCONTAINER_ID_LABEL = 'doompi.sandbox.devcontainer';\n\nexport interface DevcontainerUpResult {\n containerId?: string;\n /** Where the CLI mounted the workspace inside the container. */\n remoteWorkspaceFolder?: string;\n error?: string;\n}\n\nexport interface DevcontainerUpOptions {\n repoRoot: string;\n /** Host directory holding the broker socket, bind-mounted when one is used. */\n socketDirectory?: string;\n /** Container path the socket directory is mounted at. */\n socketTarget?: string;\n}\n\n/** Arguments for bringing the workspace's own dev container up. */\nexport function devcontainerUpArgs(options: DevcontainerUpOptions): string[] {\n return [\n 'up',\n '--workspace-folder',\n options.repoRoot,\n '--log-format',\n 'json',\n ...(options.socketDirectory && options.socketTarget\n ? ['--mount', `type=bind,source=${options.socketDirectory},target=${options.socketTarget}`]\n : []),\n ];\n}\n\n/**\n * Reads the container id out of the CLI's JSON log stream.\n *\n * The stream carries progress records too, so the result is the last record\n * that reports an outcome rather than the first line parsed.\n */\nexport function parseDevcontainerUp(stdout: string): DevcontainerUpResult {\n let outcome:\n | { outcome?: string; containerId?: string; remoteWorkspaceFolder?: string; message?: string; description?: string }\n | undefined;\n for (const line of stdout.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed.startsWith('{')) continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n continue;\n }\n if (typeof parsed === 'object' && parsed !== null && 'outcome' in parsed) {\n outcome = parsed as typeof outcome;\n }\n }\n if (!outcome) return { error: 'The dev container CLI reported no outcome.' };\n if (outcome.outcome !== 'success') {\n return { error: outcome.description || outcome.message || `Dev container start reported ${outcome.outcome}.` };\n }\n if (!outcome.containerId) return { error: 'The dev container started but reported no container id.' };\n return { containerId: outcome.containerId, remoteWorkspaceFolder: outcome.remoteWorkspaceFolder };\n}\n\n/**\n * Maps a host path into the dev container's own workspace.\n *\n * A dev container mounts the workspace where its configuration says, commonly\n * /workspaces/<name>, so the host path the launch was given does not exist\n * inside and would fail the exec before the agent ever starts.\n */\nexport function containerWorkspacePath(repoRoot: string, cwd: string, remoteWorkspaceFolder: string): string {\n const relative = cwd.startsWith(repoRoot) ? cwd.slice(repoRoot.length).replace(/^[\\\\/]+/, '') : '';\n return relative ? `${remoteWorkspaceFolder}/${relative.split('\\\\').join('/')}` : remoteWorkspaceFolder;\n}\n\n/**\n * Installs the distribution inside a dev container that lacks it.\n *\n * The container is the workspace's own, so it carries the project toolchain\n * rather than this layer's, and nothing guarantees DoomPi is present. `up`\n * reuses an existing container, so this is paid once for its lifetime.\n */\nexport function bootstrapCommand(version: string): string[] {\n return [\n 'sh',\n '-c',\n `command -v doompi >/dev/null 2>&1 && exit 0; ` +\n `command -v npm >/dev/null 2>&1 || { echo \"doompi-sandbox: this dev container has no npm to install DoomPi with\" >&2; exit 127; }; ` +\n `npm install -g @agimon-ai/doompi@${version} >/dev/null`,\n ];\n}\n\n/**\n * Rewrites forwarded host paths into the dev container's workspace.\n *\n * The launcher replays its resolved options, and several carry absolute host\n * paths: --cwd always, plugin and additional directories when set. Those\n * resolve in the built-in image, which mounts the repository at the path it\n * has on the host, but a dev container mounts it wherever its configuration\n * says, and the session would fail looking for a repository that is not there.\n */\nexport function mapForwardArgs(args: readonly string[], repoRoot: string, remoteWorkspaceFolder: string): string[] {\n return args.map((argument) =>\n argument === repoRoot || argument.startsWith(`${repoRoot}/`)\n ? containerWorkspacePath(repoRoot, argument, remoteWorkspaceFolder)\n : argument,\n );\n}\n\nexport interface DevcontainerExecOptions {\n containerId: string;\n cwd: string;\n environment: Readonly<Record<string, string>>;\n hasTty: boolean;\n command: readonly string[];\n}\n\n/**\n * Runs a command in the dev container through the engine rather than the CLI.\n *\n * `devcontainer exec` allocates no terminal, which a full screen TUI needs, so\n * the session is attached to the container the CLI already started.\n */\nexport function devcontainerExecArgs(options: DevcontainerExecOptions): string[] {\n const environmentPairs = Object.entries(options.environment).sort(([left], [right]) => left.localeCompare(right));\n return [\n 'exec',\n '-i',\n ...(options.hasTty ? ['-t'] : []),\n '-w',\n options.cwd,\n ...environmentPairs.flatMap(([name, value]) => ['-e', `${name}=${value}`]),\n options.containerId,\n ...options.command,\n ];\n}\n"],"mappings":"AAKA,MAAa,EAA+C,CAAC,kCAAmC,oBAAoB,EAuBpH,SAAgB,EAAmB,EAA0C,CAC3E,MAAO,CACL,KACA,qBACA,EAAQ,SACR,eACA,OACA,GAAI,EAAQ,iBAAmB,EAAQ,aACnC,CAAC,UAAW,oBAAoB,EAAQ,gBAAgB,UAAU,EAAQ,cAAc,EACxF,CAAC,CACP,CACF,CAQA,SAAgB,EAAoB,EAAsC,CACxE,IAAI,EAGJ,IAAK,IAAM,KAAQ,EAAO,MAAM;CAAI,EAAG,CACrC,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,CAAC,EAAQ,WAAW,GAAG,EAAG,SAC9B,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAO,CAC7B,MAAQ,CACN,QACF,CACI,OAAO,GAAW,UAAY,GAAmB,YAAa,IAChE,EAAU,EAEd,CAMA,OALK,EACD,EAAQ,UAAY,UAGnB,EAAQ,YACN,CAAE,YAAa,EAAQ,YAAa,sBAAuB,EAAQ,qBAAsB,EAD/D,CAAE,MAAO,yDAA0D,EAF3F,CAAE,MAAO,EAAQ,aAAe,EAAQ,SAAW,gCAAgC,EAAQ,QAAQ,EAAG,EAF1F,CAAE,MAAO,4CAA6C,CAM7E,CASA,SAAgB,EAAuB,EAAkB,EAAa,EAAuC,CAC3G,IAAM,EAAW,EAAI,WAAW,CAAQ,EAAI,EAAI,MAAM,EAAS,MAAM,CAAC,CAAC,QAAQ,UAAW,EAAE,EAAI,GAChG,OAAO,EAAW,GAAG,EAAsB,GAAG,EAAS,MAAM,IAAI,CAAC,CAAC,KAAK,GAAG,IAAM,CACnF,CASA,SAAgB,EAAiB,EAA2B,CAC1D,MAAO,CACL,KACA,KACA,mNAEsC,EAAQ,YAChD,CACF,CAWA,SAAgB,EAAe,EAAyB,EAAkB,EAAyC,CACjH,OAAO,EAAK,IAAK,GACf,IAAa,GAAY,EAAS,WAAW,GAAG,EAAS,EAAE,EACvD,EAAuB,EAAU,EAAU,CAAqB,EAChE,CACN,CACF,CAgBA,SAAgB,EAAqB,EAA4C,CAC/E,IAAM,EAAmB,OAAO,QAAQ,EAAQ,WAAW,CAAC,CAAC,MAAM,CAAC,GAAO,CAAC,KAAW,EAAK,cAAc,CAAK,CAAC,EAChH,MAAO,CACL,OACA,KACA,GAAI,EAAQ,OAAS,CAAC,IAAI,EAAI,CAAC,EAC/B,KACA,EAAQ,IACR,GAAG,EAAiB,SAAS,CAAC,EAAM,KAAW,CAAC,KAAM,GAAG,EAAK,GAAG,GAAO,CAAC,EACzE,EAAQ,YACR,GAAG,EAAQ,OACb,CACF"}
@@ -0,0 +1,3 @@
1
+ const e=[`.devcontainer/devcontainer.json`,`.devcontainer.json`],t=`DOOMPI_SANDBOX_DEVCONTAINER`,n=`@devcontainers/cli@0.80.0`;function r(e){return[`up`,`--workspace-folder`,e.repoRoot,`--log-format`,`json`,...e.socketDirectory&&e.socketTarget?[`--mount`,`type=bind,source=${e.socketDirectory},target=${e.socketTarget}`]:[]]}function i(e){let t;for(let n of e.split(`
2
+ `)){let e=n.trim();if(!e.startsWith(`{`))continue;let r;try{r=JSON.parse(e)}catch{continue}typeof r==`object`&&r&&`outcome`in r&&(t=r)}return t?t.outcome===`success`?t.containerId?{containerId:t.containerId,remoteWorkspaceFolder:t.remoteWorkspaceFolder}:{error:`The dev container started but reported no container id.`}:{error:t.description||t.message||`Dev container start reported ${t.outcome}.`}:{error:`The dev container CLI reported no outcome.`}}function a(e,t,n){let r=t.startsWith(e)?t.slice(e.length).replace(/^[\\/]+/,``):``;return r?`${n}/${r.split(`\\`).join(`/`)}`:n}function o(e){return[`sh`,`-c`,`command -v doompi >/dev/null 2>&1 && exit 0; command -v npm >/dev/null 2>&1 || { echo "doompi-sandbox: this dev container has no npm to install DoomPi with" >&2; exit 127; }; npm install -g @agimon-ai/doompi@${e} >/dev/null`]}function s(e,t,n){return e.map(e=>e===t||e.startsWith(`${t}/`)?a(t,e,n):e)}function c(e){let t=Object.entries(e.environment).sort(([e],[t])=>e.localeCompare(t));return[`exec`,`-i`,...e.hasTty?[`-t`]:[],`-w`,e.cwd,...t.flatMap(([e,t])=>[`-e`,`${e}=${t}`]),e.containerId,...e.command]}export{n as DEVCONTAINER_CLI_PACKAGE,e as DEVCONTAINER_CONFIG_PATHS,t as DEVCONTAINER_DISABLED_ENV,o as bootstrapCommand,a as containerWorkspacePath,c as devcontainerExecArgs,r as devcontainerUpArgs,s as mapForwardArgs,i as parseDevcontainerUp};
3
+ //# sourceMappingURL=devcontainer.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devcontainer.mjs","names":[],"sources":["../../src/services/devcontainer.ts"],"sourcesContent":["/**\n * Relative locations the Dev Containers spec looks for a configuration.\n *\n * Ordered as the spec resolves them, so the first hit on disk wins.\n */\nexport const DEVCONTAINER_CONFIG_PATHS: readonly string[] = ['.devcontainer/devcontainer.json', '.devcontainer.json'];\n\nexport const DEVCONTAINER_DISABLED_ENV = 'DOOMPI_SANDBOX_DEVCONTAINER';\nexport const DEVCONTAINER_CLI_PACKAGE = '@devcontainers/cli@0.80.0';\n/** Marks containers this layer brought up, so a stale one can be told apart. */\nexport const DEVCONTAINER_ID_LABEL = 'doompi.sandbox.devcontainer';\n\nexport interface DevcontainerUpResult {\n containerId?: string;\n /** Where the CLI mounted the workspace inside the container. */\n remoteWorkspaceFolder?: string;\n error?: string;\n}\n\nexport interface DevcontainerUpOptions {\n repoRoot: string;\n /** Host directory holding the broker socket, bind-mounted when one is used. */\n socketDirectory?: string;\n /** Container path the socket directory is mounted at. */\n socketTarget?: string;\n}\n\n/** Arguments for bringing the workspace's own dev container up. */\nexport function devcontainerUpArgs(options: DevcontainerUpOptions): string[] {\n return [\n 'up',\n '--workspace-folder',\n options.repoRoot,\n '--log-format',\n 'json',\n ...(options.socketDirectory && options.socketTarget\n ? ['--mount', `type=bind,source=${options.socketDirectory},target=${options.socketTarget}`]\n : []),\n ];\n}\n\n/**\n * Reads the container id out of the CLI's JSON log stream.\n *\n * The stream carries progress records too, so the result is the last record\n * that reports an outcome rather than the first line parsed.\n */\nexport function parseDevcontainerUp(stdout: string): DevcontainerUpResult {\n let outcome:\n | { outcome?: string; containerId?: string; remoteWorkspaceFolder?: string; message?: string; description?: string }\n | undefined;\n for (const line of stdout.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed.startsWith('{')) continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n continue;\n }\n if (typeof parsed === 'object' && parsed !== null && 'outcome' in parsed) {\n outcome = parsed as typeof outcome;\n }\n }\n if (!outcome) return { error: 'The dev container CLI reported no outcome.' };\n if (outcome.outcome !== 'success') {\n return { error: outcome.description || outcome.message || `Dev container start reported ${outcome.outcome}.` };\n }\n if (!outcome.containerId) return { error: 'The dev container started but reported no container id.' };\n return { containerId: outcome.containerId, remoteWorkspaceFolder: outcome.remoteWorkspaceFolder };\n}\n\n/**\n * Maps a host path into the dev container's own workspace.\n *\n * A dev container mounts the workspace where its configuration says, commonly\n * /workspaces/<name>, so the host path the launch was given does not exist\n * inside and would fail the exec before the agent ever starts.\n */\nexport function containerWorkspacePath(repoRoot: string, cwd: string, remoteWorkspaceFolder: string): string {\n const relative = cwd.startsWith(repoRoot) ? cwd.slice(repoRoot.length).replace(/^[\\\\/]+/, '') : '';\n return relative ? `${remoteWorkspaceFolder}/${relative.split('\\\\').join('/')}` : remoteWorkspaceFolder;\n}\n\n/**\n * Installs the distribution inside a dev container that lacks it.\n *\n * The container is the workspace's own, so it carries the project toolchain\n * rather than this layer's, and nothing guarantees DoomPi is present. `up`\n * reuses an existing container, so this is paid once for its lifetime.\n */\nexport function bootstrapCommand(version: string): string[] {\n return [\n 'sh',\n '-c',\n `command -v doompi >/dev/null 2>&1 && exit 0; ` +\n `command -v npm >/dev/null 2>&1 || { echo \"doompi-sandbox: this dev container has no npm to install DoomPi with\" >&2; exit 127; }; ` +\n `npm install -g @agimon-ai/doompi@${version} >/dev/null`,\n ];\n}\n\n/**\n * Rewrites forwarded host paths into the dev container's workspace.\n *\n * The launcher replays its resolved options, and several carry absolute host\n * paths: --cwd always, plugin and additional directories when set. Those\n * resolve in the built-in image, which mounts the repository at the path it\n * has on the host, but a dev container mounts it wherever its configuration\n * says, and the session would fail looking for a repository that is not there.\n */\nexport function mapForwardArgs(args: readonly string[], repoRoot: string, remoteWorkspaceFolder: string): string[] {\n return args.map((argument) =>\n argument === repoRoot || argument.startsWith(`${repoRoot}/`)\n ? containerWorkspacePath(repoRoot, argument, remoteWorkspaceFolder)\n : argument,\n );\n}\n\nexport interface DevcontainerExecOptions {\n containerId: string;\n cwd: string;\n environment: Readonly<Record<string, string>>;\n hasTty: boolean;\n command: readonly string[];\n}\n\n/**\n * Runs a command in the dev container through the engine rather than the CLI.\n *\n * `devcontainer exec` allocates no terminal, which a full screen TUI needs, so\n * the session is attached to the container the CLI already started.\n */\nexport function devcontainerExecArgs(options: DevcontainerExecOptions): string[] {\n const environmentPairs = Object.entries(options.environment).sort(([left], [right]) => left.localeCompare(right));\n return [\n 'exec',\n '-i',\n ...(options.hasTty ? ['-t'] : []),\n '-w',\n options.cwd,\n ...environmentPairs.flatMap(([name, value]) => ['-e', `${name}=${value}`]),\n options.containerId,\n ...options.command,\n ];\n}\n"],"mappings":"AAKA,MAAa,EAA+C,CAAC,kCAAmC,oBAAoB,EAEvG,EAA4B,8BAC5B,EAA2B,4BAoBxC,SAAgB,EAAmB,EAA0C,CAC3E,MAAO,CACL,KACA,qBACA,EAAQ,SACR,eACA,OACA,GAAI,EAAQ,iBAAmB,EAAQ,aACnC,CAAC,UAAW,oBAAoB,EAAQ,gBAAgB,UAAU,EAAQ,cAAc,EACxF,CAAC,CACP,CACF,CAQA,SAAgB,EAAoB,EAAsC,CACxE,IAAI,EAGJ,IAAK,IAAM,KAAQ,EAAO,MAAM;CAAI,EAAG,CACrC,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,CAAC,EAAQ,WAAW,GAAG,EAAG,SAC9B,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAO,CAC7B,MAAQ,CACN,QACF,CACI,OAAO,GAAW,UAAY,GAAmB,YAAa,IAChE,EAAU,EAEd,CAMA,OALK,EACD,EAAQ,UAAY,UAGnB,EAAQ,YACN,CAAE,YAAa,EAAQ,YAAa,sBAAuB,EAAQ,qBAAsB,EAD/D,CAAE,MAAO,yDAA0D,EAF3F,CAAE,MAAO,EAAQ,aAAe,EAAQ,SAAW,gCAAgC,EAAQ,QAAQ,EAAG,EAF1F,CAAE,MAAO,4CAA6C,CAM7E,CASA,SAAgB,EAAuB,EAAkB,EAAa,EAAuC,CAC3G,IAAM,EAAW,EAAI,WAAW,CAAQ,EAAI,EAAI,MAAM,EAAS,MAAM,CAAC,CAAC,QAAQ,UAAW,EAAE,EAAI,GAChG,OAAO,EAAW,GAAG,EAAsB,GAAG,EAAS,MAAM,IAAI,CAAC,CAAC,KAAK,GAAG,IAAM,CACnF,CASA,SAAgB,EAAiB,EAA2B,CAC1D,MAAO,CACL,KACA,KACA,mNAEsC,EAAQ,YAChD,CACF,CAWA,SAAgB,EAAe,EAAyB,EAAkB,EAAyC,CACjH,OAAO,EAAK,IAAK,GACf,IAAa,GAAY,EAAS,WAAW,GAAG,EAAS,EAAE,EACvD,EAAuB,EAAU,EAAU,CAAqB,EAChE,CACN,CACF,CAgBA,SAAgB,EAAqB,EAA4C,CAC/E,IAAM,EAAmB,OAAO,QAAQ,EAAQ,WAAW,CAAC,CAAC,MAAM,CAAC,GAAO,CAAC,KAAW,EAAK,cAAc,CAAK,CAAC,EAChH,MAAO,CACL,OACA,KACA,GAAI,EAAQ,OAAS,CAAC,IAAI,EAAI,CAAC,EAC/B,KACA,EAAQ,IACR,GAAG,EAAiB,SAAS,CAAC,EAAM,KAAW,CAAC,KAAM,GAAG,EAAK,GAAG,GAAO,CAAC,EACzE,EAAQ,YACR,GAAG,EAAQ,OACb,CACF"}
@@ -0,0 +1,2 @@
1
+ let e=require("@agimon-ai/doompi-extension-contracts/sandbox-harness");var t=class{environment;constructor(e){this.environment=e}async execute(){return{message:(0,e.insideSandbox)(this.environment)?`Sandboxed session: the agent, extensions, and tools run inside the container.`:`Host session: relaunch with dpi --sandbox to contain the agent in a container.`,level:`info`}}};exports.DefaultSandboxExtensionService=t;
2
+ //# sourceMappingURL=extensionService.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensionService.cjs","names":["insideSandbox"],"sources":["../../src/services/extensionService.ts"],"sourcesContent":["import { insideSandbox } from '@agimon-ai/doompi-extension-contracts/sandbox-harness';\nimport type { SandboxExtensionResult, SandboxExtensionService } from '../types/extension.ts';\n\nconst SANDBOXED_MESSAGE = 'Sandboxed session: the agent, extensions, and tools run inside the container.';\nconst HOST_MESSAGE = 'Host session: relaunch with dpi --sandbox to contain the agent in a container.';\n\nexport class DefaultSandboxExtensionService implements SandboxExtensionService {\n private readonly environment: Readonly<Record<string, string | undefined>>;\n\n constructor(environment: Readonly<Record<string, string | undefined>>) {\n this.environment = environment;\n }\n\n async execute(): Promise<SandboxExtensionResult> {\n return {\n message: insideSandbox(this.environment) ? SANDBOXED_MESSAGE : HOST_MESSAGE,\n level: 'info',\n };\n }\n}\n"],"mappings":"uEAMA,IAAa,EAAb,KAA+E,CAC7E,YAEA,YAAY,EAA2D,CACrE,KAAK,YAAc,CACrB,CAEA,MAAM,SAA2C,CAC/C,MAAO,CACL,SAAA,EAASA,EAAAA,cAAAA,CAAc,KAAK,WAAW,EAAI,gFAAoB,iFAC/D,MAAO,MACT,CACF,CACF"}
@@ -0,0 +1,10 @@
1
+ import { SandboxExtensionResult, SandboxExtensionService } from "../types/extension.cjs";
2
+ //#region src/services/extensionService.d.ts
3
+ declare class DefaultSandboxExtensionService implements SandboxExtensionService {
4
+ private readonly environment;
5
+ constructor(environment: Readonly<Record<string, string | undefined>>);
6
+ execute(): Promise<SandboxExtensionResult>;
7
+ }
8
+ //#endregion
9
+ export { DefaultSandboxExtensionService };
10
+ //# sourceMappingURL=extensionService.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensionService.d.cts","names":[],"sources":["../../src/services/extensionService.ts"],"mappings":";;cAMa,0CAA0C;mBACpC;EAEjB,YAAY,aAAa,SAAS;EAI5B,WAAW,QAAQ"}
@@ -0,0 +1,10 @@
1
+ import { SandboxExtensionResult, SandboxExtensionService } from "../types/extension.mjs";
2
+ //#region src/services/extensionService.d.ts
3
+ declare class DefaultSandboxExtensionService implements SandboxExtensionService {
4
+ private readonly environment;
5
+ constructor(environment: Readonly<Record<string, string | undefined>>);
6
+ execute(): Promise<SandboxExtensionResult>;
7
+ }
8
+ //#endregion
9
+ export { DefaultSandboxExtensionService };
10
+ //# sourceMappingURL=extensionService.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensionService.d.mts","names":[],"sources":["../../src/services/extensionService.ts"],"mappings":";;cAMa,0CAA0C;mBACpC;EAEjB,YAAY,aAAa,SAAS;EAI5B,WAAW,QAAQ"}
@@ -0,0 +1,2 @@
1
+ import{insideSandbox as e}from"@agimon-ai/doompi-extension-contracts/sandbox-harness";var t=class{environment;constructor(e){this.environment=e}async execute(){return{message:e(this.environment)?`Sandboxed session: the agent, extensions, and tools run inside the container.`:`Host session: relaunch with dpi --sandbox to contain the agent in a container.`,level:`info`}}};export{t as DefaultSandboxExtensionService};
2
+ //# sourceMappingURL=extensionService.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensionService.mjs","names":[],"sources":["../../src/services/extensionService.ts"],"sourcesContent":["import { insideSandbox } from '@agimon-ai/doompi-extension-contracts/sandbox-harness';\nimport type { SandboxExtensionResult, SandboxExtensionService } from '../types/extension.ts';\n\nconst SANDBOXED_MESSAGE = 'Sandboxed session: the agent, extensions, and tools run inside the container.';\nconst HOST_MESSAGE = 'Host session: relaunch with dpi --sandbox to contain the agent in a container.';\n\nexport class DefaultSandboxExtensionService implements SandboxExtensionService {\n private readonly environment: Readonly<Record<string, string | undefined>>;\n\n constructor(environment: Readonly<Record<string, string | undefined>>) {\n this.environment = environment;\n }\n\n async execute(): Promise<SandboxExtensionResult> {\n return {\n message: insideSandbox(this.environment) ? SANDBOXED_MESSAGE : HOST_MESSAGE,\n level: 'info',\n };\n }\n}\n"],"mappings":"sFAMA,IAAa,EAAb,KAA+E,CAC7E,YAEA,YAAY,EAA2D,CACrE,KAAK,YAAc,CACrB,CAEA,MAAM,SAA2C,CAC/C,MAAO,CACL,QAAS,EAAc,KAAK,WAAW,EAAI,gFAAoB,iFAC/D,MAAO,MACT,CACF,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=[1455,1456,53692];function t(e){return e.flatMap(e=>[`-p`,`127.0.0.1:${e}:${e}`])}exports.OAUTH_CALLBACK_HOST_ENV=`PI_OAUTH_CALLBACK_HOST`,exports.OAUTH_CALLBACK_PORTS=e,exports.OAUTH_CONTAINER_BIND=`0.0.0.0`,exports.oauthPublishArgs=t;
2
+ //# sourceMappingURL=oauthCallback.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthCallback.cjs","names":[],"sources":["../../src/services/oauthCallback.ts"],"sourcesContent":["/**\n * Fixed loopback ports Pi's OAuth flows listen on.\n *\n * A provider redirects the browser to `http://localhost:<port>`, so the host\n * port has to match exactly and cannot be remapped. Providers that bind an\n * ephemeral port instead, such as OpenRouter, cannot be published ahead of the\n * flow and are not covered.\n */\nexport const OAUTH_CALLBACK_PORTS: readonly number[] = [\n 1455, // openai-codex\n 1456, // radius\n 53692, // anthropic\n];\n\nexport const OAUTH_CALLBACK_HOST_ENV = 'PI_OAUTH_CALLBACK_HOST';\n\n/**\n * Address the in-container callback server binds.\n *\n * Pi binds loopback by default, which a published port cannot reach: the\n * engine forwards to the container's external interface, not its loopback.\n * Only the bind address changes; the redirect the provider sees stays\n * `localhost`.\n */\nexport const OAUTH_CONTAINER_BIND = '0.0.0.0';\n\n/** Publishes each reachable callback port back onto the host's loopback. */\nexport function oauthPublishArgs(ports: readonly number[]): string[] {\n return ports.flatMap((port) => ['-p', `127.0.0.1:${port}:${port}`]);\n}\n"],"mappings":"AAQA,MAAa,EAA0C,CACrD,KACA,KACA,KACF,EAeA,SAAgB,EAAiB,EAAoC,CACnE,OAAO,EAAM,QAAS,GAAS,CAAC,KAAM,aAAa,EAAK,GAAG,GAAM,CAAC,CACpE"}
@@ -0,0 +1,2 @@
1
+ const e=[1455,1456,53692],t=`PI_OAUTH_CALLBACK_HOST`,n=`0.0.0.0`;function r(e){return e.flatMap(e=>[`-p`,`127.0.0.1:${e}:${e}`])}export{t as OAUTH_CALLBACK_HOST_ENV,e as OAUTH_CALLBACK_PORTS,n as OAUTH_CONTAINER_BIND,r as oauthPublishArgs};
2
+ //# sourceMappingURL=oauthCallback.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthCallback.mjs","names":[],"sources":["../../src/services/oauthCallback.ts"],"sourcesContent":["/**\n * Fixed loopback ports Pi's OAuth flows listen on.\n *\n * A provider redirects the browser to `http://localhost:<port>`, so the host\n * port has to match exactly and cannot be remapped. Providers that bind an\n * ephemeral port instead, such as OpenRouter, cannot be published ahead of the\n * flow and are not covered.\n */\nexport const OAUTH_CALLBACK_PORTS: readonly number[] = [\n 1455, // openai-codex\n 1456, // radius\n 53692, // anthropic\n];\n\nexport const OAUTH_CALLBACK_HOST_ENV = 'PI_OAUTH_CALLBACK_HOST';\n\n/**\n * Address the in-container callback server binds.\n *\n * Pi binds loopback by default, which a published port cannot reach: the\n * engine forwards to the container's external interface, not its loopback.\n * Only the bind address changes; the redirect the provider sees stays\n * `localhost`.\n */\nexport const OAUTH_CONTAINER_BIND = '0.0.0.0';\n\n/** Publishes each reachable callback port back onto the host's loopback. */\nexport function oauthPublishArgs(ports: readonly number[]): string[] {\n return ports.flatMap((port) => ['-p', `127.0.0.1:${port}:${port}`]);\n}\n"],"mappings":"AAQA,MAAa,EAA0C,CACrD,KACA,KACA,KACF,EAEa,EAA0B,yBAU1B,EAAuB,UAGpC,SAAgB,EAAiB,EAAoC,CACnE,OAAO,EAAM,QAAS,GAAS,CAAC,KAAM,aAAa,EAAK,GAAG,GAAM,CAAC,CACpE"}
@@ -0,0 +1,2 @@
1
+ function e(e){return(e??``).split(/\s+/).filter(e=>e.length>0)}const t=/^--?[^\s=]+(=.*)?$/s;function n(e,n){let r=e.find(e=>!t.test(e));if(r!==void 0)throw Error(`${n} accepts engine options in --flag or --flag=value form; "${r}" is neither. Write a value as part of its option, for example --runtime=runsc.`)}exports.assertRunFlags=n,exports.parseRunFlags=e;
2
+ //# sourceMappingURL=runFlags.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runFlags.cjs","names":[],"sources":["../../src/services/runFlags.ts"],"sourcesContent":["/**\n * Splits configured engine options into argv entries.\n *\n * Whitespace separated rather than shell parsed: the engine is spawned without\n * a shell, so quoting rules would only invent a syntax the caller does not\n * actually get.\n */\nexport function parseRunFlags(configured: string | undefined): string[] {\n return (configured ?? '').split(/\\s+/).filter((flag) => flag.length > 0);\n}\n\nconst OPTION_FORM = /^--?[^\\s=]+(=.*)?$/s;\n\n/**\n * Requires every configured entry to be a self-contained option.\n *\n * Separated values cannot be told from positional arguments without knowing\n * each option's arity, and a stray positional would silently replace the image\n * or its command. Demanding `--flag=value` removes the ambiguity instead of\n * guessing at it.\n */\nexport function assertRunFlags(flags: readonly string[], optionName: string): void {\n const invalid = flags.find((flag) => !OPTION_FORM.test(flag));\n if (invalid !== undefined) {\n throw new Error(\n `${optionName} accepts engine options in --flag or --flag=value form; \"${invalid}\" is neither. ` +\n 'Write a value as part of its option, for example --runtime=runsc.',\n );\n }\n}\n"],"mappings":"AAOA,SAAgB,EAAc,EAA0C,CACtE,OAAQ,GAAc,GAAA,CAAI,MAAM,KAAK,CAAC,CAAC,OAAQ,GAAS,EAAK,OAAS,CAAC,CACzE,CAEA,MAAM,EAAc,sBAUpB,SAAgB,EAAe,EAA0B,EAA0B,CACjF,IAAM,EAAU,EAAM,KAAM,GAAS,CAAC,EAAY,KAAK,CAAI,CAAC,EAC5D,GAAI,IAAY,IAAA,GACd,MAAU,MACR,GAAG,EAAW,2DAA2D,EAAQ,gFAEnF,CAEJ"}
@@ -0,0 +1,2 @@
1
+ function e(e){return(e??``).split(/\s+/).filter(e=>e.length>0)}const t=/^--?[^\s=]+(=.*)?$/s;function n(e,n){let r=e.find(e=>!t.test(e));if(r!==void 0)throw Error(`${n} accepts engine options in --flag or --flag=value form; "${r}" is neither. Write a value as part of its option, for example --runtime=runsc.`)}export{n as assertRunFlags,e as parseRunFlags};
2
+ //# sourceMappingURL=runFlags.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runFlags.mjs","names":[],"sources":["../../src/services/runFlags.ts"],"sourcesContent":["/**\n * Splits configured engine options into argv entries.\n *\n * Whitespace separated rather than shell parsed: the engine is spawned without\n * a shell, so quoting rules would only invent a syntax the caller does not\n * actually get.\n */\nexport function parseRunFlags(configured: string | undefined): string[] {\n return (configured ?? '').split(/\\s+/).filter((flag) => flag.length > 0);\n}\n\nconst OPTION_FORM = /^--?[^\\s=]+(=.*)?$/s;\n\n/**\n * Requires every configured entry to be a self-contained option.\n *\n * Separated values cannot be told from positional arguments without knowing\n * each option's arity, and a stray positional would silently replace the image\n * or its command. Demanding `--flag=value` removes the ambiguity instead of\n * guessing at it.\n */\nexport function assertRunFlags(flags: readonly string[], optionName: string): void {\n const invalid = flags.find((flag) => !OPTION_FORM.test(flag));\n if (invalid !== undefined) {\n throw new Error(\n `${optionName} accepts engine options in --flag or --flag=value form; \"${invalid}\" is neither. ` +\n 'Write a value as part of its option, for example --runtime=runsc.',\n );\n }\n}\n"],"mappings":"AAOA,SAAgB,EAAc,EAA0C,CACtE,OAAQ,GAAc,GAAA,CAAI,MAAM,KAAK,CAAC,CAAC,OAAQ,GAAS,EAAK,OAAS,CAAC,CACzE,CAEA,MAAM,EAAc,sBAUpB,SAAgB,EAAe,EAA0B,EAA0B,CACjF,IAAM,EAAU,EAAM,KAAM,GAAS,CAAC,EAAY,KAAK,CAAI,CAAC,EAC5D,GAAI,IAAY,IAAA,GACd,MAAU,MACR,GAAG,EAAW,2DAA2D,EAAQ,gFAEnF,CAEJ"}
@@ -0,0 +1,3 @@
1
+ const e=`sandbox-bridge.mjs`,t=`/opt/doompi/${e}`,n=`DOOMPI_BROKER_SOCKET`,r=`DOOMPI_BROKER_ADDRESS`,i=`DOOMPI_BROKER_PORT`;function a(){return[`import { spawn } from 'node:child_process';`,`import net from 'node:net';`,``,`const socketPath = process.env.${n};`,`const address = process.env.${r};`,`const port = Number(process.env.${i});`,`const [command, ...args] = process.argv.slice(2);`,``,`function startChild(whenDone) {`,` const child = spawn(command, args, { stdio: 'inherit' });`,` child.on('exit', (code, signal) => whenDone(code ?? (signal ? 1 : 0)));`,` child.on('error', (error) => {`," process.stderr.write(`[doompi] sandbox bridge: ${error.message}\\n`);",` whenDone(127);`,` });`,`}`,``,`function dialBroker() {`,` if (socketPath) return net.connect(socketPath);`,` const separator = address.lastIndexOf(':');`,` return net.connect(Number(address.slice(separator + 1)), address.slice(0, separator));`,`}`,``,`if ((!socketPath && !address) || !Number.isInteger(port)) {`,` startChild((code) => {`,` process.exitCode = code;`,` });`,`} else {`,` const server = net.createServer((client) => {`,` const upstream = dialBroker();`,` client.on('error', () => upstream.destroy());`,` upstream.on('error', () => client.destroy());`,` client.pipe(upstream);`,` upstream.pipe(client);`,` });`,` server.listen(port, '127.0.0.1', () => {`,` startChild((code) => {`,` server.close();`,` process.exitCode = code;`,` });`,` });`,`}`,``].join(`
2
+ `)}exports.BRIDGE_CONTAINER_PATH=t,exports.BRIDGE_FILE_NAME=e,exports.BROKER_ADDRESS_ENV=r,exports.BROKER_CONTAINER_PORT=8317,exports.BROKER_HOST_GATEWAY=`host.docker.internal`,exports.BROKER_PORT_ENV=i,exports.BROKER_PROVIDERS_ENV=`DOOMPI_BROKER_PROVIDERS`,exports.BROKER_SOCKET_CONTAINER_PATH=`/run/doompi/broker.sock`,exports.BROKER_SOCKET_ENV=n,exports.sandboxBridgeSource=a;
3
+ //# sourceMappingURL=sandboxBridge.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandboxBridge.cjs","names":[],"sources":["../../src/services/sandboxBridge.ts"],"sourcesContent":["export const BRIDGE_FILE_NAME = 'sandbox-bridge.mjs';\nexport const BRIDGE_CONTAINER_PATH = `/opt/doompi/${BRIDGE_FILE_NAME}`;\nexport const BROKER_SOCKET_ENV = 'DOOMPI_BROKER_SOCKET';\nexport const BROKER_ADDRESS_ENV = 'DOOMPI_BROKER_ADDRESS';\n/** Hostname the engine maps to the host, used when the broker listens on TCP. */\nexport const BROKER_HOST_GATEWAY = 'host.docker.internal';\nexport const BROKER_PORT_ENV = 'DOOMPI_BROKER_PORT';\nexport const BROKER_PROVIDERS_ENV = 'DOOMPI_BROKER_PROVIDERS';\n/** Fixed inside the container, which has its own loopback namespace. */\nexport const BROKER_CONTAINER_PORT = 8317;\nexport const BROKER_SOCKET_CONTAINER_PATH = '/run/doompi/broker.sock';\n\n/**\n * Container-side forwarder from loopback TCP to the mounted broker socket.\n *\n * Provider SDKs issue ordinary HTTP through Node's global fetch, which cannot\n * address a unix socket, and on a virtual machine backed engine cannot reach\n * one at all. Forwarding raw bytes leaves every SDK unmodified whichever way\n * the broker listens. Wrapping the launcher rather than running as a separate\n * service ties the listener's lifetime to the session without an init process.\n */\nexport function sandboxBridgeSource(): string {\n return [\n \"import { spawn } from 'node:child_process';\",\n \"import net from 'node:net';\",\n '',\n `const socketPath = process.env.${BROKER_SOCKET_ENV};`,\n `const address = process.env.${BROKER_ADDRESS_ENV};`,\n `const port = Number(process.env.${BROKER_PORT_ENV});`,\n 'const [command, ...args] = process.argv.slice(2);',\n '',\n 'function startChild(whenDone) {',\n \" const child = spawn(command, args, { stdio: 'inherit' });\",\n \" child.on('exit', (code, signal) => whenDone(code ?? (signal ? 1 : 0)));\",\n \" child.on('error', (error) => {\",\n ' process.stderr.write(`[doompi] sandbox bridge: ${error.message}\\\\n`);',\n ' whenDone(127);',\n ' });',\n '}',\n '',\n 'function dialBroker() {',\n ' if (socketPath) return net.connect(socketPath);',\n \" const separator = address.lastIndexOf(':');\",\n ' return net.connect(Number(address.slice(separator + 1)), address.slice(0, separator));',\n '}',\n '',\n 'if ((!socketPath && !address) || !Number.isInteger(port)) {',\n ' startChild((code) => {',\n ' process.exitCode = code;',\n ' });',\n '} else {',\n ' const server = net.createServer((client) => {',\n ' const upstream = dialBroker();',\n \" client.on('error', () => upstream.destroy());\",\n \" upstream.on('error', () => client.destroy());\",\n ' client.pipe(upstream);',\n ' upstream.pipe(client);',\n ' });',\n \" server.listen(port, '127.0.0.1', () => {\",\n ' startChild((code) => {',\n ' server.close();',\n ' process.exitCode = code;',\n ' });',\n ' });',\n '}',\n '',\n ].join('\\n');\n}\n"],"mappings":"AAAA,MAAa,EAAmB,qBACnB,EAAwB,eAAe,IACvC,EAAoB,uBACpB,EAAqB,wBAGrB,EAAkB,qBAe/B,SAAgB,GAA8B,CAC5C,MAAO,CACL,8CACA,8BACA,GACA,kCAAkC,EAAkB,GACpD,+BAA+B,EAAmB,GAClD,mCAAmC,EAAgB,IACnD,oDACA,GACA,kCACA,8DACA,4EACA,mCACA,4EACA,qBACA,QACA,IACA,GACA,0BACA,oDACA,gDACA,2FACA,IACA,GACA,8DACA,2BACA,+BACA,QACA,WACA,kDACA,qCACA,oDACA,oDACA,6BACA,6BACA,QACA,6CACA,6BACA,wBACA,iCACA,UACA,QACA,IACA,EACF,CAAC,CAAC,KAAK;CAAI,CACb"}
@@ -0,0 +1,3 @@
1
+ const e=`sandbox-bridge.mjs`,t=`/opt/doompi/${e}`,n=`DOOMPI_BROKER_SOCKET`,r=`DOOMPI_BROKER_ADDRESS`,i=`host.docker.internal`,a=`DOOMPI_BROKER_PORT`,o=`DOOMPI_BROKER_PROVIDERS`,s=8317,c=`/run/doompi/broker.sock`;function l(){return[`import { spawn } from 'node:child_process';`,`import net from 'node:net';`,``,`const socketPath = process.env.${n};`,`const address = process.env.${r};`,`const port = Number(process.env.${a});`,`const [command, ...args] = process.argv.slice(2);`,``,`function startChild(whenDone) {`,` const child = spawn(command, args, { stdio: 'inherit' });`,` child.on('exit', (code, signal) => whenDone(code ?? (signal ? 1 : 0)));`,` child.on('error', (error) => {`," process.stderr.write(`[doompi] sandbox bridge: ${error.message}\\n`);",` whenDone(127);`,` });`,`}`,``,`function dialBroker() {`,` if (socketPath) return net.connect(socketPath);`,` const separator = address.lastIndexOf(':');`,` return net.connect(Number(address.slice(separator + 1)), address.slice(0, separator));`,`}`,``,`if ((!socketPath && !address) || !Number.isInteger(port)) {`,` startChild((code) => {`,` process.exitCode = code;`,` });`,`} else {`,` const server = net.createServer((client) => {`,` const upstream = dialBroker();`,` client.on('error', () => upstream.destroy());`,` upstream.on('error', () => client.destroy());`,` client.pipe(upstream);`,` upstream.pipe(client);`,` });`,` server.listen(port, '127.0.0.1', () => {`,` startChild((code) => {`,` server.close();`,` process.exitCode = code;`,` });`,` });`,`}`,``].join(`
2
+ `)}export{t as BRIDGE_CONTAINER_PATH,e as BRIDGE_FILE_NAME,r as BROKER_ADDRESS_ENV,s as BROKER_CONTAINER_PORT,i as BROKER_HOST_GATEWAY,a as BROKER_PORT_ENV,o as BROKER_PROVIDERS_ENV,c as BROKER_SOCKET_CONTAINER_PATH,n as BROKER_SOCKET_ENV,l as sandboxBridgeSource};
3
+ //# sourceMappingURL=sandboxBridge.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandboxBridge.mjs","names":[],"sources":["../../src/services/sandboxBridge.ts"],"sourcesContent":["export const BRIDGE_FILE_NAME = 'sandbox-bridge.mjs';\nexport const BRIDGE_CONTAINER_PATH = `/opt/doompi/${BRIDGE_FILE_NAME}`;\nexport const BROKER_SOCKET_ENV = 'DOOMPI_BROKER_SOCKET';\nexport const BROKER_ADDRESS_ENV = 'DOOMPI_BROKER_ADDRESS';\n/** Hostname the engine maps to the host, used when the broker listens on TCP. */\nexport const BROKER_HOST_GATEWAY = 'host.docker.internal';\nexport const BROKER_PORT_ENV = 'DOOMPI_BROKER_PORT';\nexport const BROKER_PROVIDERS_ENV = 'DOOMPI_BROKER_PROVIDERS';\n/** Fixed inside the container, which has its own loopback namespace. */\nexport const BROKER_CONTAINER_PORT = 8317;\nexport const BROKER_SOCKET_CONTAINER_PATH = '/run/doompi/broker.sock';\n\n/**\n * Container-side forwarder from loopback TCP to the mounted broker socket.\n *\n * Provider SDKs issue ordinary HTTP through Node's global fetch, which cannot\n * address a unix socket, and on a virtual machine backed engine cannot reach\n * one at all. Forwarding raw bytes leaves every SDK unmodified whichever way\n * the broker listens. Wrapping the launcher rather than running as a separate\n * service ties the listener's lifetime to the session without an init process.\n */\nexport function sandboxBridgeSource(): string {\n return [\n \"import { spawn } from 'node:child_process';\",\n \"import net from 'node:net';\",\n '',\n `const socketPath = process.env.${BROKER_SOCKET_ENV};`,\n `const address = process.env.${BROKER_ADDRESS_ENV};`,\n `const port = Number(process.env.${BROKER_PORT_ENV});`,\n 'const [command, ...args] = process.argv.slice(2);',\n '',\n 'function startChild(whenDone) {',\n \" const child = spawn(command, args, { stdio: 'inherit' });\",\n \" child.on('exit', (code, signal) => whenDone(code ?? (signal ? 1 : 0)));\",\n \" child.on('error', (error) => {\",\n ' process.stderr.write(`[doompi] sandbox bridge: ${error.message}\\\\n`);',\n ' whenDone(127);',\n ' });',\n '}',\n '',\n 'function dialBroker() {',\n ' if (socketPath) return net.connect(socketPath);',\n \" const separator = address.lastIndexOf(':');\",\n ' return net.connect(Number(address.slice(separator + 1)), address.slice(0, separator));',\n '}',\n '',\n 'if ((!socketPath && !address) || !Number.isInteger(port)) {',\n ' startChild((code) => {',\n ' process.exitCode = code;',\n ' });',\n '} else {',\n ' const server = net.createServer((client) => {',\n ' const upstream = dialBroker();',\n \" client.on('error', () => upstream.destroy());\",\n \" upstream.on('error', () => client.destroy());\",\n ' client.pipe(upstream);',\n ' upstream.pipe(client);',\n ' });',\n \" server.listen(port, '127.0.0.1', () => {\",\n ' startChild((code) => {',\n ' server.close();',\n ' process.exitCode = code;',\n ' });',\n ' });',\n '}',\n '',\n ].join('\\n');\n}\n"],"mappings":"AAAA,MAAa,EAAmB,qBACnB,EAAwB,eAAe,IACvC,EAAoB,uBACpB,EAAqB,wBAErB,EAAsB,uBACtB,EAAkB,qBAClB,EAAuB,0BAEvB,EAAwB,KACxB,EAA+B,0BAW5C,SAAgB,GAA8B,CAC5C,MAAO,CACL,8CACA,8BACA,GACA,kCAAkC,EAAkB,GACpD,+BAA+B,EAAmB,GAClD,mCAAmC,EAAgB,IACnD,oDACA,GACA,kCACA,8DACA,4EACA,mCACA,4EACA,qBACA,QACA,IACA,GACA,0BACA,oDACA,gDACA,2FACA,IACA,GACA,8DACA,2BACA,+BACA,QACA,WACA,kDACA,qCACA,oDACA,oDACA,6BACA,6BACA,QACA,6CACA,6BACA,wBACA,iCACA,UACA,QACA,IACA,EACF,CAAC,CAAC,KAAK;CAAI,CACb"}
@@ -0,0 +1,2 @@
1
+ const e=new Set([`TERM`,`COLORTERM`,`LANG`,`TZ`,`DOOMPI_PRESET`,`ELICITATION_SESSION_ID`,`HTTP_PROXY`,`HTTPS_PROXY`,`NO_PROXY`,`http_proxy`,`https_proxy`,`no_proxy`]),t=[`LC_`],n=[`_API_KEY`,`_AUTH_TOKEN`,`_BASE_URL`];function r(e){return n.some(t=>e.endsWith(t))}function i(r){return e.has(r)||t.some(e=>r.startsWith(e))||n.some(e=>r.endsWith(e))}function a(e){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&i(n)&&(t[n]=r);return t}exports.filterSandboxEnvironment=a,exports.isCredentialEnvName=r;
2
+ //# sourceMappingURL=sandboxEnvironment.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandboxEnvironment.cjs","names":[],"sources":["../../src/services/sandboxEnvironment.ts"],"sourcesContent":["const EXACT_NAMES = new Set([\n 'TERM',\n 'COLORTERM',\n 'LANG',\n 'TZ',\n 'DOOMPI_PRESET',\n 'ELICITATION_SESSION_ID',\n 'HTTP_PROXY',\n 'HTTPS_PROXY',\n 'NO_PROXY',\n 'http_proxy',\n 'https_proxy',\n 'no_proxy',\n]);\nconst NAME_PREFIXES = ['LC_'];\nconst NAME_SUFFIXES = ['_API_KEY', '_AUTH_TOKEN', '_BASE_URL'];\n\n/** True for variables shaped like a provider credential or endpoint override. */\nexport function isCredentialEnvName(name: string): boolean {\n return NAME_SUFFIXES.some((suffix) => name.endsWith(suffix));\n}\n\nfunction allowed(name: string): boolean {\n return (\n EXACT_NAMES.has(name) ||\n NAME_PREFIXES.some((prefix) => name.startsWith(prefix)) ||\n NAME_SUFFIXES.some((suffix) => name.endsWith(suffix))\n );\n}\n\n/**\n * Projects the host environment down to what a sandboxed session may see.\n *\n * The list is an allowlist on purpose: everything a terminal accumulates\n * (agents, sockets, tokens, paths) stays on the host unless a rule names it.\n * Provider credentials pass through for now; the credential broker planned for\n * the client-server phase removes even those from the container.\n */\nexport function filterSandboxEnvironment(\n environment: Readonly<Record<string, string | undefined>>,\n): Record<string, string> {\n const filtered: Record<string, string> = {};\n for (const [name, value] of Object.entries(environment)) {\n if (value !== undefined && allowed(name)) filtered[name] = value;\n }\n return filtered;\n}\n"],"mappings":"AAAA,MAAM,EAAc,IAAI,IAAI,CAC1B,OACA,YACA,OACA,KACA,gBACA,yBACA,aACA,cACA,WACA,aACA,cACA,UACF,CAAC,EACK,EAAgB,CAAC,KAAK,EACtB,EAAgB,CAAC,WAAY,cAAe,WAAW,EAG7D,SAAgB,EAAoB,EAAuB,CACzD,OAAO,EAAc,KAAM,GAAW,EAAK,SAAS,CAAM,CAAC,CAC7D,CAEA,SAAS,EAAQ,EAAuB,CACtC,OACE,EAAY,IAAI,CAAI,GACpB,EAAc,KAAM,GAAW,EAAK,WAAW,CAAM,CAAC,GACtD,EAAc,KAAM,GAAW,EAAK,SAAS,CAAM,CAAC,CAExD,CAUA,SAAgB,EACd,EACwB,CACxB,IAAM,EAAmC,CAAC,EAC1C,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAW,EAChD,IAAU,IAAA,IAAa,EAAQ,CAAI,IAAG,EAAS,GAAQ,GAE7D,OAAO,CACT"}
@@ -0,0 +1,2 @@
1
+ const e=new Set([`TERM`,`COLORTERM`,`LANG`,`TZ`,`DOOMPI_PRESET`,`ELICITATION_SESSION_ID`,`HTTP_PROXY`,`HTTPS_PROXY`,`NO_PROXY`,`http_proxy`,`https_proxy`,`no_proxy`]),t=[`LC_`],n=[`_API_KEY`,`_AUTH_TOKEN`,`_BASE_URL`];function r(e){return n.some(t=>e.endsWith(t))}function i(r){return e.has(r)||t.some(e=>r.startsWith(e))||n.some(e=>r.endsWith(e))}function a(e){let t={};for(let[n,r]of Object.entries(e))r!==void 0&&i(n)&&(t[n]=r);return t}export{a as filterSandboxEnvironment,r as isCredentialEnvName};
2
+ //# sourceMappingURL=sandboxEnvironment.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandboxEnvironment.mjs","names":[],"sources":["../../src/services/sandboxEnvironment.ts"],"sourcesContent":["const EXACT_NAMES = new Set([\n 'TERM',\n 'COLORTERM',\n 'LANG',\n 'TZ',\n 'DOOMPI_PRESET',\n 'ELICITATION_SESSION_ID',\n 'HTTP_PROXY',\n 'HTTPS_PROXY',\n 'NO_PROXY',\n 'http_proxy',\n 'https_proxy',\n 'no_proxy',\n]);\nconst NAME_PREFIXES = ['LC_'];\nconst NAME_SUFFIXES = ['_API_KEY', '_AUTH_TOKEN', '_BASE_URL'];\n\n/** True for variables shaped like a provider credential or endpoint override. */\nexport function isCredentialEnvName(name: string): boolean {\n return NAME_SUFFIXES.some((suffix) => name.endsWith(suffix));\n}\n\nfunction allowed(name: string): boolean {\n return (\n EXACT_NAMES.has(name) ||\n NAME_PREFIXES.some((prefix) => name.startsWith(prefix)) ||\n NAME_SUFFIXES.some((suffix) => name.endsWith(suffix))\n );\n}\n\n/**\n * Projects the host environment down to what a sandboxed session may see.\n *\n * The list is an allowlist on purpose: everything a terminal accumulates\n * (agents, sockets, tokens, paths) stays on the host unless a rule names it.\n * Provider credentials pass through for now; the credential broker planned for\n * the client-server phase removes even those from the container.\n */\nexport function filterSandboxEnvironment(\n environment: Readonly<Record<string, string | undefined>>,\n): Record<string, string> {\n const filtered: Record<string, string> = {};\n for (const [name, value] of Object.entries(environment)) {\n if (value !== undefined && allowed(name)) filtered[name] = value;\n }\n return filtered;\n}\n"],"mappings":"AAAA,MAAM,EAAc,IAAI,IAAI,CAC1B,OACA,YACA,OACA,KACA,gBACA,yBACA,aACA,cACA,WACA,aACA,cACA,UACF,CAAC,EACK,EAAgB,CAAC,KAAK,EACtB,EAAgB,CAAC,WAAY,cAAe,WAAW,EAG7D,SAAgB,EAAoB,EAAuB,CACzD,OAAO,EAAc,KAAM,GAAW,EAAK,SAAS,CAAM,CAAC,CAC7D,CAEA,SAAS,EAAQ,EAAuB,CACtC,OACE,EAAY,IAAI,CAAI,GACpB,EAAc,KAAM,GAAW,EAAK,WAAW,CAAM,CAAC,GACtD,EAAc,KAAM,GAAW,EAAK,SAAS,CAAM,CAAC,CAExD,CAUA,SAAgB,EACd,EACwB,CACxB,IAAM,EAAmC,CAAC,EAC1C,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAW,EAChD,IAAU,IAAA,IAAa,EAAQ,CAAI,IAAG,EAAS,GAAQ,GAE7D,OAAO,CACT"}